feat(labors): trilingual name — add name_en column + Labor::label() helper

- Migration: add nullable `labors.name_en` alongside existing name_ro/
  name_ru.
- Labor model: label(?locale) accessor returns name_{locale} with
  name_ro fallback.
- LaborResource form: expose 3rd 'Nume (EN)' field.
- Table + Select displays now go through label() so options show the
  locale-appropriate name:
    /app/labors table column,
    WorkOrderResource works Select ('[category] name (Nh)'),
    ServiceTemplate items Select,
    ServiceComposer WorkOrderWork snapshot on create.
- Snapshot name saved on wo_works.name at creation reflects the
  current locale; existing rows keep their stored snapshot untouched.

All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-26 10:54:00 +00:00
parent 64a170b7f9
commit 9609295a9e
8 changed files with 50 additions and 7 deletions
@@ -59,6 +59,7 @@ class LaborResource extends Resource
Forms\Components\TextInput::make('code')->label(__('Cod'))->maxLength(32),
Forms\Components\TextInput::make('name_ro')->label(__('Nume (RO)'))->required()->maxLength(160),
Forms\Components\TextInput::make('name_ru')->label(__('Nume (RU)'))->maxLength(160),
Forms\Components\TextInput::make('name_en')->label(__('Nume (EN)'))->maxLength(160),
Forms\Components\Select::make('pricing_mode')
->label(__('Mod tarifare'))
->options(\App\Support\I18n::opts(Labor::PRICING_MODES))
@@ -81,7 +82,11 @@ class LaborResource extends Resource
return $table
->columns([
Tables\Columns\TextColumn::make('category')->label(__('Categorie'))->badge()->sortable(),
Tables\Columns\TextColumn::make('name_ro')->label(__('Manoperă'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('name_ro')
->label(__('Manoperă'))
->searchable()
->sortable()
->getStateUsing(fn ($record) => $record->label()),
Tables\Columns\TextColumn::make('pricing_mode')
->label(__('Tarifare'))
->formatStateUsing(fn ($s) => $s === 'fixed' ? __('Fix') : __('Pe oră'))
@@ -36,13 +36,15 @@ class ItemsRelationManager extends RelationManager
->required(),
Forms\Components\Select::make('labor_id')
->label(__('Manoperă'))
->options(fn () => Labor::where('is_active', true)->pluck('name_ro', 'id'))
->options(fn () => Labor::where('is_active', true)->get()
->mapWithKeys(fn ($l) => [$l->id => $l->label()])
->all())
->searchable()
->visible(fn (Get $get) => $get('kind') === 'labor')
->live()
->afterStateUpdated(function ($state, Set $set) {
if ($state && $l = Labor::find($state)) {
$set('name', $l->name_ro);
$set('name', $l->label());
$set('hours', $l->hours);
}
}),
@@ -31,13 +31,13 @@ class WorksRelationManager extends RelationManager
->label(__('Catalog manoperă'))
->options(fn () => Labor::where('is_active', true)
->get()
->mapWithKeys(fn ($l) => [$l->id => '[' . __($l->category) . '] ' . $l->name_ro . ' (' . $l->hours . __('h') . ')'])
->mapWithKeys(fn ($l) => [$l->id => '[' . __($l->category) . '] ' . $l->label() . ' (' . $l->hours . __('h') . ')'])
->toArray())
->searchable()
->live()
->afterStateUpdated(function ($state, Set $set) {
if ($state && $labor = Labor::find($state)) {
$set('name', $labor->name_ro);
$set('name', $labor->label());
$set('hours', $labor->hours);
$set('price_per_hour', $labor->hours > 0 ? round($labor->price / max((float) $labor->hours, 1), 2) : 0);
}
+9 -1
View File
@@ -22,10 +22,18 @@ class Labor extends Model
];
protected $fillable = [
'company_id', 'category', 'name_ro', 'name_ru', 'code',
'company_id', 'category', 'name_ro', 'name_ru', 'name_en', 'code',
'hours', 'pricing_mode', 'fixed_price', 'price', 'is_active', 'notes',
];
/** Localised name — prefers name_{locale}, falls back to name_ro. */
public function label(?string $locale = null): string
{
$locale = $locale ?: app()->getLocale();
$col = 'name_' . $locale;
return $this->{$col} ?: ($this->name_ro ?: '');
}
protected $casts = [
'hours' => 'decimal:2',
'fixed_price' => 'decimal:2',
+1 -1
View File
@@ -44,7 +44,7 @@ class ServiceComposer
$work = WorkOrderWork::create([
'work_order_id' => $wo->id,
'labor_id' => $labor->id,
'name' => $labor->name_ro,
'name' => $labor->label(),
'hours' => $hours,
'price_per_hour' => $pricePerHour,
'status' => 'todo',