Files
autocrm/app/Filament/Tenant/Resources/WorkOrderResource/RelationManagers/WorksRelationManager.php
T
Vasyka 3c7e5fffbf improve WO form: inline client/vehicle create, mechanic filter, category translation, PDF emoji fix
- client_id field: switched to relationship() with createOptionForm so new clients
  can be created inline (name, phone, email); search also matches by phone
- vehicle_id field: added createOptionForm (make, model, plate, year, VIN); newly
  created vehicle is automatically linked to the currently selected client
- master_id field: now shows only active users with role mechanic/master
- WorksRelationManager: labor catalog select marked optional; added createOptionForm
  so new catalog entries can be added without leaving the WO form
- SubcontractJobsRelationManager: category table column now translates value via __()
  and shows explicit label(__('Categorie'))
- PDF work-order: replaced 📞 / ✉ / ⚠ emojis with plain-text equivalents so
  DomPDF/DejaVu Sans renders them correctly instead of showing "????"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-04 19:56:40 +00:00

139 lines
7.1 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ă') . ' (' . __('opțional') . ')')
->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);
}
})
->createOptionForm([
Forms\Components\Select::make('category')->label(__('Categorie'))
->options(\App\Support\I18n::opts(array_combine(Labor::CATEGORIES, Labor::CATEGORIES)))
->required(),
Forms\Components\TextInput::make('name_ro')->label(__('Denumire'))->required(),
Forms\Components\TextInput::make('hours')->label(__('Ore normate'))->numeric()->default(1)->required(),
Forms\Components\TextInput::make('price')->label(__('Preț total (MDL)'))->numeric()->default(0)->required(),
])
->createOptionUsing(function (array $data) {
$data['is_active'] = true;
return Labor::create($data)->id;
})
->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');
}
}