de2964c854
- WorksRelationManager Name column: getStateUsing(labor?->label()) so the row shows the labor's localized name (not the raw snapshot); fallback to stored snapshot when labor is missing. - 4 explicit empty states with helper description + icon on Works, Parts, SubcontractJobs, Payments — replaces Filament's auto-generated 'Не найдено X / Создать X для старта.' - 4 CreateAction titles / modal headings for all four relation managers. - Notification 'Piesa returnată în stoc' / 'Nimic de restituit' wrapped. - +11 translation keys. All 306 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
127 lines
6.2 KiB
PHP
127 lines
6.2 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Tenant\Resources\WorkOrderResource\RelationManagers;
|
|
|
|
use App\Models\Tenant\Labor;
|
|
use App\Models\Tenant\User;
|
|
use App\Models\Tenant\WorkOrderWork;
|
|
use Filament\Actions;
|
|
use Filament\Forms;
|
|
use Filament\Resources\RelationManagers\RelationManager;
|
|
use Filament\Schemas\Components\Utilities\Set;
|
|
use Filament\Schemas\Schema;
|
|
use Filament\Tables;
|
|
use Filament\Tables\Table;
|
|
|
|
class WorksRelationManager extends RelationManager
|
|
{
|
|
protected static string $relationship = 'works';
|
|
|
|
protected static ?string $title = null;
|
|
|
|
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
|
{
|
|
return __('Manopere');
|
|
}
|
|
|
|
public function form(Schema $schema): Schema
|
|
{
|
|
return $schema->components([
|
|
Forms\Components\Select::make('labor_id')
|
|
->label(__('Catalog manoperă'))
|
|
->options(fn () => Labor::where('is_active', true)
|
|
->get()
|
|
->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->label());
|
|
$set('hours', $labor->hours);
|
|
$set('price_per_hour', $labor->hours > 0 ? round($labor->price / max((float) $labor->hours, 1), 2) : 0);
|
|
}
|
|
})
|
|
->columnSpanFull(),
|
|
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->columnSpanFull(),
|
|
Forms\Components\TextInput::make('hours')->label(__('Ore'))->numeric()->default(1)->required(),
|
|
Forms\Components\TextInput::make('price_per_hour')->label(__('Preț/h'))->numeric()->required(),
|
|
Forms\Components\Select::make('master_id')
|
|
->label(__('Maistru'))
|
|
->options(fn () => User::pluck('name', 'id'))
|
|
->searchable(),
|
|
Forms\Components\Select::make('status')
|
|
->options(\App\Support\I18n::opts(WorkOrderWork::STATUSES))
|
|
->default('todo')
|
|
->required(),
|
|
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
|
|
]);
|
|
}
|
|
|
|
/** Global visibility flag from tenant Settings — user can hide margin details in-session. */
|
|
private static function marginDetailsVisible(): bool
|
|
{
|
|
if (! auth()->user()?->canDo(\App\Auth\Permissions::FINANCE_VIEW_INTERNAL_MARGIN)) {
|
|
return false;
|
|
}
|
|
$tenant = app(\App\Tenancy\TenantManager::class)->current();
|
|
return (bool) data_get($tenant?->settings, 'show_internal_margin_details', true);
|
|
}
|
|
|
|
public function table(Table $table): Table
|
|
{
|
|
return $table
|
|
->recordTitleAttribute('name')
|
|
->columns([
|
|
Tables\Columns\TextColumn::make('name')->label(__('Manoperă'))->wrap()
|
|
->getStateUsing(fn ($record) => $record->labor?->label() ?: $record->name),
|
|
Tables\Columns\TextColumn::make('hours')->label(__('Ore'))->numeric(decimalPlaces: 2)->alignRight(),
|
|
Tables\Columns\TextColumn::make('price_per_hour')->label(__('Preț/h'))->money('MDL')->alignRight(),
|
|
Tables\Columns\TextColumn::make('total')
|
|
->label(__('Total'))
|
|
->money('MDL')
|
|
->alignRight()
|
|
->description(function ($record) {
|
|
if (! self::marginDetailsVisible() || $record->salary_base === null) return null;
|
|
// WO-level toggle off → hide margin text complet
|
|
if ($record->workOrder && $record->workOrder->apply_margin === false) return null;
|
|
if ((float) $record->applied_margin_pct === 0.0) return null;
|
|
return 'Bază salariu: ' . number_format((float) $record->salary_base, 2) . ' MDL · marjă ' . rtrim(rtrim(number_format((float) $record->applied_margin_pct, 2), '0'), '.') . '%';
|
|
}),
|
|
Tables\Columns\TextColumn::make('master.name')->label(__('Maistru'))->placeholder('—'),
|
|
Tables\Columns\TextColumn::make('status')
|
|
->formatStateUsing(fn ($s) => __(WorkOrderWork::STATUSES[$s] ?? $s))
|
|
->badge()
|
|
->colors(['gray' => ['todo'], 'warning' => ['in_progress'], 'success' => ['done']]),
|
|
])
|
|
->headerActions([
|
|
Actions\CreateAction::make()
|
|
->label(__('Adaugă manoperă'))
|
|
->modalHeading(__('Adaugă manoperă în fișă'))
|
|
->after(function (WorkOrderWork $record) {
|
|
// Auto-add the labor's default parts to the parent WO.
|
|
if (! $record->labor_id) return;
|
|
$labor = Labor::with('laborParts.part')->find($record->labor_id);
|
|
$wo = $record->workOrder;
|
|
if (! $labor || ! $wo || $labor->laborParts->isEmpty()) return;
|
|
$composer = app(\App\Services\ServiceComposer::class);
|
|
foreach ($labor->laborParts as $lp) {
|
|
if ($lp->part) {
|
|
$composer->addPart($wo, $lp->part, (float) $lp->qty, $lp->unit);
|
|
}
|
|
}
|
|
\Filament\Notifications\Notification::make()
|
|
->title(__('Piese implicite adăugate (:n)', ['n' => $labor->laborParts->count()]))
|
|
->success()->send();
|
|
}),
|
|
])
|
|
->actions([
|
|
Actions\EditAction::make(),
|
|
Actions\DeleteAction::make(),
|
|
])
|
|
->emptyStateHeading(__('Nicio manoperă efectuată.'))
|
|
->emptyStateDescription(__('Adaugă manopere din catalog — piesele implicite se adaugă automat în tab-ul Piese.'))
|
|
->emptyStateIcon('heroicon-o-wrench');
|
|
}
|
|
}
|