logFillable() ->dontSubmitEmptyLogs(); } /** * @var array 暫存的活動紀錄屬性 (不會存入資料庫) */ public $activityProperties = []; /** * 自定義日誌屬性名稱解析 */ public function tapActivity(\Spatie\Activitylog\Models\Activity $activity, string $eventName) { $properties = $activity->properties->toArray(); // 處置日誌事件說明 if ($eventName === 'created') { $activity->description = 'created'; } elseif ($eventName === 'updated') { // 如果屬性中有 status 且變更為 completed,將描述改為 posted if (isset($properties['attributes']['status']) && $properties['attributes']['status'] === 'completed') { $activity->description = 'posted'; $eventName = 'posted'; // 供後續快照邏輯判定 } else { $activity->description = 'updated'; } } // 處理倉庫 ID 轉名稱 $idToNameFields = [ 'from_warehouse_id' => 'fromWarehouse', 'to_warehouse_id' => 'toWarehouse', 'created_by' => 'createdBy', 'posted_by' => 'postedBy', ]; foreach (['attributes', 'old'] as $part) { if (isset($properties[$part])) { foreach ($idToNameFields as $idField => $relation) { if (isset($properties[$part][$idField])) { $id = $properties[$part][$idField]; $nameField = str_replace('_id', '_name', $idField); $name = null; if ($this->relationLoaded($relation) && $this->$relation && $this->$relation->id == $id) { $name = $this->$relation->name; } else { $model = $this->$relation()->getRelated()->find($id); $name = $model ? $model->name : "ID: $id"; } $properties[$part][$nameField] = $name; } } } } // 基本單據資訊快照 (包含單號、來源、目的地) if (in_array($eventName, ['created', 'updated', 'posted', 'deleted'])) { $properties['snapshot'] = [ 'doc_no' => $this->doc_no, 'from_warehouse_name' => $this->fromWarehouse?->name, 'to_warehouse_name' => $this->toWarehouse?->name, 'status' => $this->status, ]; } // 移除輔助欄位與雜訊 if (isset($properties['attributes'])) { unset($properties['attributes']['from_warehouse_name']); unset($properties['attributes']['to_warehouse_name']); unset($properties['attributes']['activityProperties']); unset($properties['attributes']['updated_at']); } if (isset($properties['old'])) { unset($properties['old']['updated_at']); } // 合併暫存屬性 (例如 items_diff) if (!empty($this->activityProperties)) { $properties = array_merge($properties, $this->activityProperties); } $activity->properties = collect($properties); } protected $fillable = [ 'doc_no', 'from_warehouse_id', 'to_warehouse_id', 'transit_warehouse_id', 'status', 'remarks', 'posted_at', 'created_by', 'updated_by', 'posted_by', 'dispatched_at', 'dispatched_by', 'received_at', 'received_by', ]; protected $casts = [ 'posted_at' => 'datetime', 'dispatched_at' => 'datetime', 'received_at' => 'datetime', ]; protected static function boot() { parent::boot(); static::creating(function ($model) { if (empty($model->doc_no)) { $today = date('Ymd'); $prefix = 'TRF-' . $today . '-'; $lastDoc = static::where('doc_no', 'like', $prefix . '%') ->orderBy('doc_no', 'desc') ->first(); if ($lastDoc) { $lastNumber = substr($lastDoc->doc_no, -2); $nextNumber = str_pad((int)$lastNumber + 1, 2, '0', STR_PAD_LEFT); } else { $nextNumber = '01'; } $model->doc_no = $prefix . $nextNumber; } }); } public function fromWarehouse(): BelongsTo { return $this->belongsTo(Warehouse::class, 'from_warehouse_id'); } public function toWarehouse(): BelongsTo { return $this->belongsTo(Warehouse::class, 'to_warehouse_id'); } public function items(): HasMany { return $this->hasMany(InventoryTransferItem::class, 'transfer_order_id'); } public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } public function storeRequisition(): \Illuminate\Database\Eloquent\Relations\HasOne { return $this->hasOne(StoreRequisition::class, 'transfer_order_id'); } public function postedBy(): BelongsTo { return $this->belongsTo(User::class, 'posted_by'); } public function transitWarehouse(): BelongsTo { return $this->belongsTo(Warehouse::class, 'transit_warehouse_id'); } public function dispatchedBy(): BelongsTo { return $this->belongsTo(User::class, 'dispatched_by'); } public function receivedBy(): BelongsTo { return $this->belongsTo(User::class, 'received_by'); } }