*/ use HasFactory; use \Spatie\Activitylog\Traits\LogsActivity; protected $fillable = [ 'code', 'vendor_id', 'warehouse_id', 'user_id', 'order_date', 'expected_delivery_date', 'status', 'total_amount', 'tax_amount', 'grand_total', 'remark', 'invoice_number', 'invoice_date', 'invoice_amount', ]; protected $casts = [ 'order_date' => 'date', 'expected_delivery_date' => 'date', 'total_amount' => 'decimal:2', ]; public function getActivitylogOptions(): \Spatie\Activitylog\LogOptions { return \Spatie\Activitylog\LogOptions::defaults() ->logAll() ->logOnlyDirty() ->dontSubmitEmptyLogs(); } public function tapActivity(\Spatie\Activitylog\Contracts\Activity $activity, string $eventName) { // 🚩 核心:轉換為陣列以避免 Indirect modification error $properties = $activity->properties instanceof \Illuminate\Support\Collection ? $activity->properties->toArray() : $activity->properties; // 1. Snapshot 快照 $snapshot = $properties['snapshot'] ?? []; $snapshot['po_number'] = $this->code; $snapshot['vendor_name'] = $this->vendor?->name; // 倉庫名稱需透過服務取得(跨模組),若已在 snapshot 中則保留 if (!isset($snapshot['warehouse_name']) && $this->warehouse_id) { $warehouse = app(\App\Modules\Inventory\Contracts\InventoryServiceInterface::class)->getWarehouse($this->warehouse_id); $snapshot['warehouse_name'] = $warehouse?->name ?? null; } $properties['snapshot'] = $snapshot; // 2. 名稱解析:自動將 attributes 與 old 中的 ID 換成人名/物名 $resolver = function (&$data) { if (empty($data) || !is_array($data)) return; // 使用者 ID 轉換 foreach (['user_id', 'created_by', 'updated_by'] as $f) { if (isset($data[$f]) && is_numeric($data[$f])) { $data[$f] = \App\Modules\Core\Models\User::find($data[$f])?->name ?? $data[$f]; } } // 廠商 ID 轉換 if (isset($data['vendor_id']) && is_numeric($data['vendor_id'])) { $data['vendor_id'] = Vendor::find($data['vendor_id'])?->name ?? $data['vendor_id']; } // 倉庫 ID 轉換(跨模組,透過服務) if (isset($data['warehouse_id']) && is_numeric($data['warehouse_id'])) { $warehouse = app(\App\Modules\Inventory\Contracts\InventoryServiceInterface::class)->getWarehouse($data['warehouse_id']); $data['warehouse_id'] = $warehouse?->name ?? $data['warehouse_id']; } }; if (isset($properties['attributes'])) $resolver($properties['attributes']); if (isset($properties['old'])) $resolver($properties['old']); // 3. 合併 activityProperties (手動傳入的 items_diff 等) if (!empty($this->activityProperties)) { $properties = array_merge($properties, $this->activityProperties); } $activity->properties = $properties; } public function vendor(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Vendor::class); } public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(\App\Modules\Core\Models\User::class); } public function items(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(PurchaseOrderItem::class); } /** * 檢查是否可以轉移至新狀態,並驗證權限。 */ public function canTransitionTo(string $newStatus, $user = null): bool { $user = $user ?? auth()->user(); if (!$user) return false; if ($user->hasRole('super-admin')) return true; $currentStatus = $this->status; // 定義合法的狀態轉移路徑與所需權限 $transitions = [ 'draft' => [ 'pending' => 'purchase_orders.view', // 基本檢視者即可送審 'cancelled' => 'purchase_orders.cancel', ], 'pending' => [ 'approved' => 'purchase_orders.approve', 'draft' => 'purchase_orders.approve', // 退回草稿 'cancelled' => 'purchase_orders.cancel', ], 'approved' => [ 'cancelled' => 'purchase_orders.cancel', 'partial' => null, // 系統自動轉移,不需手動權限點 ], 'partial' => [ 'completed' => null, // 系統自動轉移 'closed' => 'purchase_orders.approve', // 手動結案通常需要核准權限 'cancelled' => 'purchase_orders.cancel', ], ]; if (!isset($transitions[$currentStatus])) { return false; } if (!array_key_exists($newStatus, $transitions[$currentStatus])) { return false; } $requiredPermission = $transitions[$currentStatus][$newStatus]; return $requiredPermission ? $user->can($requiredPermission) : true; } }