'Necesară', 'ordered' => 'Comandată', 'delivered' => 'Sosită', 'installed' => 'Montată', ]; protected $fillable = [ 'company_id', 'work_order_id', 'part_id', 'name', 'article', 'brand', 'qty', 'unit', 'buy_price', 'sell_price', 'discount_pct', 'total', 'status', 'notes', 'requires_approval', 'approved_at', 'approval_token', 'declined_at', ]; protected $casts = [ 'qty' => 'decimal:2', 'buy_price' => 'decimal:2', 'sell_price' => 'decimal:2', 'discount_pct' => 'decimal:2', 'total' => 'decimal:2', 'requires_approval' => 'boolean', 'approved_at' => 'datetime', 'declined_at' => 'datetime', ]; public function isPendingApproval(): bool { return $this->requires_approval && $this->approved_at === null && $this->declined_at === null; } public function workOrder(): BelongsTo { return $this->belongsTo(WorkOrder::class); } public function part(): BelongsTo { return $this->belongsTo(Part::class); } protected static function booted(): void { static::saving(function (self $row) { $sub = (float) $row->qty * (float) $row->sell_price; $disc = (float) $row->discount_pct; $row->total = round($sub * (1 - $disc / 100), 2); if ($row->requires_approval && empty($row->approval_token)) { $row->approval_token = \Illuminate\Support\Str::random(24); } }); // Reserve batches as soon as a catalog-linked part line is created. // Reservations don't reduce on-hand qty, only block other reservations. static::created(function (self $row) { if ($row->part_id) { try { app(\App\Services\Warehouse\WarehouseService::class)->reserve($row); } catch (\App\Services\Warehouse\InsufficientStockException $e) { \Illuminate\Support\Facades\Log::warning('WO part reservation skipped: ' . $e->getMessage()); } } }); // If qty / part link changes, release old reservation and re-reserve. static::updated(function (self $row) { if ($row->wasChanged(['qty', 'part_id'])) { $svc = app(\App\Services\Warehouse\WarehouseService::class); $svc->release($row); if ($row->part_id) { try { $svc->reserve($row); } catch (\App\Services\Warehouse\InsufficientStockException $e) { \Illuminate\Support\Facades\Log::warning('WO part re-reservation skipped: ' . $e->getMessage()); } } } }); static::deleted(function (self $row) { app(\App\Services\Warehouse\WarehouseService::class)->release($row); }); static::saved(fn (self $row) => $row->workOrder?->recalcTotal()); static::deleted(fn (self $row) => $row->workOrder?->recalcTotal()); } }