From 649af40919228c35cb15d7b55d824c663d080c15 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Mon, 2 Mar 2026 10:47:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=A6=E4=BD=9C=20InventoryService=20?= =?UTF-8?q?=E7=9A=84=E6=89=B9=E9=87=8F=E5=85=A5=E5=BA=AB=20(processIncomin?= =?UTF-8?q?gInventory)=20=E8=88=87=E5=BA=AB=E5=AD=98=E8=AA=BF=E6=95=B4=20(?= =?UTF-8?q?adjustInventory)=20=E9=82=8F=E8=BC=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contracts/InventoryServiceInterface.php | 19 ++ .../Controllers/InventoryController.php | 180 ++---------------- .../Inventory/Services/InventoryService.php | 164 ++++++++++++++++ .../ActivityLog/ActivityDetailDialog.tsx | 143 ++++++++------ .../js/Components/ActivityLog/LogTable.tsx | 50 ++++- 5 files changed, 330 insertions(+), 226 deletions(-) diff --git a/app/Modules/Inventory/Contracts/InventoryServiceInterface.php b/app/Modules/Inventory/Contracts/InventoryServiceInterface.php index 4fa2433..9f20b39 100644 --- a/app/Modules/Inventory/Contracts/InventoryServiceInterface.php +++ b/app/Modules/Inventory/Contracts/InventoryServiceInterface.php @@ -165,4 +165,23 @@ interface InventoryServiceInterface * @return \Illuminate\Support\Collection|null */ public function getPosInventoryByWarehouseCode(string $code); + + /** + * 處理批量入庫邏輯 (含批號產生與現有批號累加)。 + * + * @param \App\Modules\Inventory\Models\Warehouse $warehouse + * @param array $items 入庫品項清單 + * @param array $meta 資料包含 inboundDate, reason, notes + * @return void + */ + public function processIncomingInventory(\App\Modules\Inventory\Models\Warehouse $warehouse, array $items, array $meta): void; + + /** + * 處理單一庫存項目的調整。 + * + * @param \App\Modules\Inventory\Models\Inventory $inventory + * @param array $data 包含 quantity, operation, type, reason, unit_cost 等 + * @return void + */ + public function adjustInventory(\App\Modules\Inventory\Models\Inventory $inventory, array $data): void; } \ No newline at end of file diff --git a/app/Modules/Inventory/Controllers/InventoryController.php b/app/Modules/Inventory/Controllers/InventoryController.php index bc8f615..ab7311e 100644 --- a/app/Modules/Inventory/Controllers/InventoryController.php +++ b/app/Modules/Inventory/Controllers/InventoryController.php @@ -22,10 +22,14 @@ use App\Modules\Core\Contracts\CoreServiceInterface; class InventoryController extends Controller { protected $coreService; + protected $inventoryService; - public function __construct(CoreServiceInterface $coreService) - { + public function __construct( + CoreServiceInterface $coreService, + \App\Modules\Inventory\Contracts\InventoryServiceInterface $inventoryService + ) { $this->coreService = $coreService; + $this->inventoryService = $inventoryService; } public function index(Request $request, Warehouse $warehouse) @@ -182,97 +186,11 @@ class InventoryController extends Controller ]); return DB::transaction(function () use ($validated, $warehouse) { - foreach ($validated['items'] as $item) { - // ... (略,傳遞 unit_cost 交給 Service 處理) ... - // 這裡需要修改呼叫 Service 的地方或直接更新邏輯 - // 為求快速,我將在此更新邏輯 - - $inventory = null; - - if ($item['batchMode'] === 'existing') { - // 模式 A:選擇現有批號 (包含已刪除的也要能找回來累加) - $inventory = Inventory::withTrashed()->findOrFail($item['inventoryId']); - if ($inventory->trashed()) { - $inventory->restore(); - } - - // 更新成本 (若有傳入) - if (isset($item['unit_cost'])) { - $inventory->unit_cost = $item['unit_cost']; - } - } elseif ($item['batchMode'] === 'none') { - // 模式 C:不使用批號 (自動累加至 NO-BATCH) - $inventory = $warehouse->inventories()->withTrashed()->firstOrNew( - [ - 'product_id' => $item['productId'], - 'batch_number' => 'NO-BATCH' - ], - [ - 'quantity' => 0, - 'unit_cost' => $item['unit_cost'] ?? 0, - 'total_value' => 0, - 'arrival_date' => $validated['inboundDate'], - 'expiry_date' => null, - 'origin_country' => 'TW', - ] - ); - - if ($inventory->trashed()) { - $inventory->restore(); - } - } else { - // 模式 B:建立新批號 - $originCountry = $item['originCountry'] ?? 'TW'; - $product = Product::find($item['productId']); - - $batchNumber = Inventory::generateBatchNumber( - $product->code ?? 'UNK', - $originCountry, - $validated['inboundDate'] - ); - - // 檢查是否存在 - $inventory = $warehouse->inventories()->withTrashed()->firstOrNew( - [ - 'product_id' => $item['productId'], - 'batch_number' => $batchNumber - ], - [ - 'quantity' => 0, - 'unit_cost' => $item['unit_cost'] ?? 0, // 新增 - 'total_value' => 0, // 稍後計算 - 'location' => $item['location'] ?? null, - 'arrival_date' => $validated['inboundDate'], - 'expiry_date' => $item['expiryDate'] ?? null, - 'origin_country' => $originCountry, - ] - ); - - if ($inventory->trashed()) { - $inventory->restore(); - } - } - - $currentQty = $inventory->quantity; - $newQty = $currentQty + $item['quantity']; - - $inventory->quantity = $newQty; - // 更新總價值 - $inventory->total_value = $inventory->quantity * $inventory->unit_cost; - $inventory->saveQuietly(); // 使用 saveQuietly() 避免產生冗餘的「單據已更新」日誌 - - // 寫入異動紀錄 - $inventory->transactions()->create([ - 'type' => '手動入庫', - 'quantity' => $item['quantity'], - 'unit_cost' => $inventory->unit_cost, // 記錄成本 - 'balance_before' => $currentQty, - 'balance_after' => $newQty, - 'reason' => $validated['reason'] . ($validated['notes'] ? ' - ' . $validated['notes'] : ''), - 'actual_time' => $validated['inboundDate'], - 'user_id' => auth()->id(), - ]); - } + $this->inventoryService->processIncomingInventory($warehouse, $validated['items'], [ + 'inboundDate' => $validated['inboundDate'], + 'reason' => $validated['reason'], + 'notes' => $validated['notes'] ?? '', + ]); return redirect()->route('warehouses.inventory.index', $warehouse->id) ->with('success', '庫存記錄已儲存成功'); @@ -401,81 +319,7 @@ class InventoryController extends Controller ]); return DB::transaction(function () use ($validated, $inventory) { - $currentQty = (float) $inventory->quantity; - $newQty = (float) $validated['quantity']; - - // 判斷是否來自調整彈窗 (包含 operation 參數) - $isAdjustment = isset($validated['operation']); - $changeQty = 0; - - if ($isAdjustment) { - switch ($validated['operation']) { - case 'add': - $changeQty = (float) $validated['quantity']; - $newQty = $currentQty + $changeQty; - break; - case 'subtract': - $changeQty = -(float) $validated['quantity']; - $newQty = $currentQty + $changeQty; - break; - case 'set': - $changeQty = $newQty - $currentQty; - break; - } - } else { - // 來自編輯頁面,直接 Set - $changeQty = $newQty - $currentQty; - } - - // 更新成本 (若有傳) - if (isset($validated['unit_cost'])) { - $inventory->unit_cost = $validated['unit_cost']; - } - - // 更新庫存 - $inventory->quantity = $newQty; - // 更新總值 - $inventory->total_value = $inventory->quantity * $inventory->unit_cost; - $inventory->saveQuietly(); // 使用 saveQuietly() 避免與下方的 transaction 紀錄重複 - - // 異動類型映射 - $type = $validated['type'] ?? ($isAdjustment ? 'manual_adjustment' : 'adjustment'); - $typeMapping = [ - 'manual_adjustment' => '手動調整庫存', - 'adjustment' => '盤點調整', - 'purchase_in' => '採購進貨', - 'sales_out' => '銷售出庫', - 'return_in' => '退貨入庫', - 'return_out' => '退貨出庫', - 'transfer_in' => '撥補入庫', - 'transfer_out' => '撥補出庫', - ]; - $chineseType = $typeMapping[$type] ?? $type; - - // 如果是編輯頁面來的,且沒傳 type,設為手動編輯 - if (!$isAdjustment && !isset($validated['type'])) { - $chineseType = '手動編輯'; - } - - // 整理原因 - $reason = $validated['reason'] ?? ($isAdjustment ? '手動庫存調整' : '編輯頁面更新'); - if (isset($validated['notes'])) { - $reason .= ' - ' . $validated['notes']; - } - - // 寫入異動紀錄 - if (abs($changeQty) > 0.0001) { - $inventory->transactions()->create([ - 'type' => $chineseType, - 'quantity' => $changeQty, - 'unit_cost' => $inventory->unit_cost, // 記錄 - 'balance_before' => $currentQty, - 'balance_after' => $newQty, - 'reason' => $reason, - 'actual_time' => now(), - 'user_id' => auth()->id(), - ]); - } + $this->inventoryService->adjustInventory($inventory, $validated); return redirect()->route('warehouses.inventory.index', $inventory->warehouse_id) ->with('success', '庫存資料已更新'); diff --git a/app/Modules/Inventory/Services/InventoryService.php b/app/Modules/Inventory/Services/InventoryService.php index a8e8746..edc9931 100644 --- a/app/Modules/Inventory/Services/InventoryService.php +++ b/app/Modules/Inventory/Services/InventoryService.php @@ -674,4 +674,168 @@ class InventoryService implements InventoryServiceInterface ->groupBy('inventories.product_id', 'products.external_pos_id', 'products.code', 'products.name') ->get(); } + + public function processIncomingInventory(Warehouse $warehouse, array $items, array $meta): void + { + DB::transaction(function () use ($warehouse, $items, $meta) { + foreach ($items as $item) { + $inventory = null; + + if ($item['batchMode'] === 'existing') { + // 模式 A:選擇現有批號 (包含已刪除的也要能找回來累加) + $inventory = Inventory::withTrashed()->findOrFail($item['inventoryId']); + if ($inventory->trashed()) { + $inventory->restore(); + } + + // 更新成本 (若有傳入) + if (isset($item['unit_cost'])) { + $inventory->unit_cost = $item['unit_cost']; + } + } elseif ($item['batchMode'] === 'none') { + // 模式 C:不使用批號 (自動累加至 NO-BATCH) + $inventory = $warehouse->inventories()->withTrashed()->firstOrNew( + [ + 'product_id' => $item['productId'], + 'batch_number' => 'NO-BATCH' + ], + [ + 'quantity' => 0, + 'unit_cost' => $item['unit_cost'] ?? 0, + 'total_value' => 0, + 'arrival_date' => $meta['inboundDate'], + 'origin_country' => 'TW', + ] + ); + + if ($inventory->trashed()) { + $inventory->restore(); + } + } else { + // 模式 B:建立新批號 + $originCountry = $item['originCountry'] ?? 'TW'; + $product = Product::find($item['productId']); + + $batchNumber = Inventory::generateBatchNumber( + $product->code ?? 'UNK', + $originCountry, + $meta['inboundDate'] + ); + + // 檢查是否存在 + $inventory = $warehouse->inventories()->withTrashed()->firstOrNew( + [ + 'product_id' => $item['productId'], + 'batch_number' => $batchNumber + ], + [ + 'quantity' => 0, + 'unit_cost' => $item['unit_cost'] ?? 0, + 'total_value' => 0, + 'location' => $item['location'] ?? null, + 'arrival_date' => $meta['inboundDate'], + 'expiry_date' => $item['expiryDate'] ?? null, + 'origin_country' => $originCountry, + ] + ); + + if ($inventory->trashed()) { + $inventory->restore(); + } + } + + $currentQty = $inventory->quantity; + $newQty = $currentQty + $item['quantity']; + + $inventory->quantity = $newQty; + // 更新總價值 + $inventory->total_value = $inventory->quantity * $inventory->unit_cost; + $inventory->saveQuietly(); + + // 寫入異動紀錄 + $inventory->transactions()->create([ + 'type' => '手動入庫', + 'quantity' => $item['quantity'], + 'unit_cost' => $inventory->unit_cost, + 'balance_before' => $currentQty, + 'balance_after' => $newQty, + 'reason' => $meta['reason'] . (!empty($meta['notes']) ? ' - ' . $meta['notes'] : ''), + 'actual_time' => $meta['inboundDate'], + 'user_id' => auth()->id(), + ]); + } + }); + } + + public function adjustInventory(Inventory $inventory, array $data): void + { + DB::transaction(function () use ($inventory, $data) { + $currentQty = (float) $inventory->quantity; + $newQty = (float) $data['quantity']; + + $isAdjustment = isset($data['operation']); + $changeQty = 0; + + if ($isAdjustment) { + switch ($data['operation']) { + case 'add': + $changeQty = (float) $data['quantity']; + $newQty = $currentQty + $changeQty; + break; + case 'subtract': + $changeQty = -(float) $data['quantity']; + $newQty = $currentQty + $changeQty; + break; + case 'set': + $changeQty = $newQty - $currentQty; + break; + } + } else { + $changeQty = $newQty - $currentQty; + } + + if (isset($data['unit_cost'])) { + $inventory->unit_cost = $data['unit_cost']; + } + + $inventory->quantity = $newQty; + $inventory->total_value = $inventory->quantity * $inventory->unit_cost; + $inventory->saveQuietly(); + + $type = $data['type'] ?? ($isAdjustment ? 'manual_adjustment' : 'adjustment'); + $typeMapping = [ + 'manual_adjustment' => '手動調整庫存', + 'adjustment' => '盤點調整', + 'purchase_in' => '採購進貨', + 'sales_out' => '銷售出庫', + 'return_in' => '退貨入庫', + 'return_out' => '退貨出庫', + 'transfer_in' => '撥補入庫', + 'transfer_out' => '撥補出庫', + ]; + $chineseType = $typeMapping[$type] ?? $type; + + if (!$isAdjustment && !isset($data['type'])) { + $chineseType = '手動編輯'; + } + + $reason = $data['reason'] ?? ($isAdjustment ? '手動庫存調整' : '編輯頁面更新'); + if (!empty($data['notes'])) { + $reason .= ' - ' . $data['notes']; + } + + if (abs($changeQty) > 0.0001) { + $inventory->transactions()->create([ + 'type' => $chineseType, + 'quantity' => $changeQty, + 'unit_cost' => $inventory->unit_cost, + 'balance_before' => $currentQty, + 'balance_after' => $newQty, + 'reason' => $reason, + 'actual_time' => now(), + 'user_id' => auth()->id(), + ]); + } + }); + } } diff --git a/resources/js/Components/ActivityLog/ActivityDetailDialog.tsx b/resources/js/Components/ActivityLog/ActivityDetailDialog.tsx index e7b0e4c..9fc49ab 100644 --- a/resources/js/Components/ActivityLog/ActivityDetailDialog.tsx +++ b/resources/js/Components/ActivityLog/ActivityDetailDialog.tsx @@ -43,10 +43,15 @@ interface Props { // 欄位翻譯對照表 const fieldLabels: Record = { + id: 'ID', + created_at: '建立時間', + updated_at: '更新時間', + deleted_at: '刪除時間', name: '名稱', code: '商品代號', username: '登入帳號', description: '描述', + // ... (保持原有翻譯) price: '價格', cost: '成本', stock: '庫存', @@ -66,6 +71,11 @@ const fieldLabels: Record = { role_id: '角色', email_verified_at: '電子郵件驗證時間', remember_token: '登入權杖', + barcode: '條碼', + external_pos_id: '外部 POS ID', + cost_price: '成本價', + member_price: '會員價', + wholesale_price: '批發價', // 快照欄位 category_name: '分類名稱', base_unit_name: '基本單位名稱', @@ -78,6 +88,9 @@ const fieldLabels: Record = { contact_name: '聯絡人', tel: '電話', remark: '備註', + license_plate: '車牌號碼', + driver_name: '司機姓名', + default_transit_warehouse_id: '預設在途倉庫', // 倉庫與庫存欄位 warehouse_name: '倉庫名稱', product_name: '商品名稱', @@ -98,6 +111,12 @@ const fieldLabels: Record = { quality_status: '品質狀態', quality_remark: '品質備註', purchase_order_id: '來源採購單', + inventory_id: '庫存 ID', + balance_before: '異動前餘額', + balance_after: '異動後餘額', + reference_type: '參考單據類型', + reference_id: '參考單據 ID', + actual_time: '實際時點', // 採購單欄位 po_number: '採購單號', vendor_id: '廠商', @@ -173,7 +192,50 @@ const statusMap: Record = { in_progress: '生產中', // 調撥單狀態 voided: '已作廢', - // completed 已定義 +}; + +// 主體類型解析 (Model 類名轉中文) +const subjectTypeMap: Record = { + // 完整路徑映射 + 'App\\Modules\\Inventory\\Models\\Product': '商品資料', + 'App\\Modules\\Inventory\\Models\\Warehouse': '倉庫資料', + 'App\\Modules\\Inventory\\Models\\Inventory': '庫存異動', + 'App\\Modules\\Inventory\\Models\\Category': '商品分類', + 'App\\Modules\\Inventory\\Models\\Unit': '單位', + 'App\\Modules\\Inventory\\Models\\InventoryTransaction': '庫存異動紀錄', + 'App\\Modules\\Inventory\\Models\\GoodsReceipt': '進貨單', + 'App\\Modules\\Inventory\\Models\\InventoryCountDoc': '庫存盤點單', + 'App\\Modules\\Inventory\\Models\\InventoryAdjustDoc': '庫存盤調單', + 'App\\Modules\\Inventory\\Models\\InventoryTransferOrder': '庫存調撥單', + 'App\\Modules\\Inventory\\Models\\StockMovementDoc': '庫存單據', + 'App\\Modules\\Procurement\\Models\\Vendor': '廠商資料', + 'App\\Modules\\Procurement\\Models\\PurchaseOrder': '採購單', + 'App\\Modules\\Production\\Models\\ProductionOrder': '生產工單', + 'App\\Modules\\Production\\Models\\Recipe': '生產配方', + 'App\\Modules\\Production\\Models\\RecipeItem': '配方品項', + 'App\\Modules\\Production\\Models\\ProductionOrderItem': '工單品項', + 'App\\Modules\\Finance\\Models\\UtilityFee': '公共事業費', + 'App\\Modules\\Core\\Models\\User': '使用者帳號', + 'App\\Modules\\Core\\Models\\Role': '角色權限', + // 簡寫映射 (應對後端回傳 class_basename 的情況) + 'Product': '商品資料', + 'Warehouse': '倉庫資料', + 'Inventory': '庫存異動', + 'InventoryTransaction': '庫存異動紀錄', + 'Category': '商品分類', + 'Unit': '單位', + 'Vendor': '廠商資料', + 'PurchaseOrder': '採購單', + 'GoodsReceipt': '進貨單', + 'ProductionOrder': '生產工單', + 'Recipe': '生產配方', + 'InventoryCountDoc': '庫存盤點單', + 'InventoryAdjustDoc': '庫存盤調單', + 'InventoryTransferOrder': '庫存調撥單', + 'StockMovementDoc': '庫存單據', + 'User': '使用者帳號', + 'Role': '角色權限', + 'UtilityFee': '公共事業費', }; // 庫存品質狀態對照表 @@ -202,9 +264,10 @@ export default function ActivityDetailDialog({ open, onOpenChange, activity }: P // 自訂欄位排序順序 const sortOrder = [ - 'po_number', 'vendor_name', 'warehouse_name', 'order_date', 'expected_delivery_date', 'status', 'remark', + 'doc_no', 'po_number', 'gr_number', 'production_number', + 'vendor_name', 'warehouse_name', 'order_date', 'expected_delivery_date', 'status', 'remark', 'invoice_number', 'invoice_date', 'invoice_amount', - 'total_amount', 'tax_amount', 'grand_total' // 確保金額的特定順序 + 'total_amount', 'tax_amount', 'grand_total' ]; // 過濾掉通常會記錄但對使用者無用的內部鍵 @@ -215,30 +278,22 @@ export default function ActivityDetailDialog({ open, onOpenChange, activity }: P .sort((a, b) => { const indexA = sortOrder.indexOf(a); const indexB = sortOrder.indexOf(b); - - // 如果兩者都在排序順序中,比較索引 if (indexA !== -1 && indexB !== -1) return indexA - indexB; - // 如果只有 A 在排序順序中,它排在前面(或根據邏輯,通常將已知欄位排在前面) if (indexA !== -1) return -1; if (indexB !== -1) return 1; - // 否則按字母順序或預設 return a.localeCompare(b); }); // 檢查鍵是否為快照名稱欄位或輔助名稱欄位的輔助函式 const isSnapshotField = (key: string) => { - // 隱藏快照欄位 const snapshotFields = [ 'category_name', 'base_unit_name', 'large_unit_name', 'purchase_unit_name', 'warehouse_name', 'user_name', 'from_warehouse_name', 'to_warehouse_name', - 'created_by_name', 'updated_by_name', 'completed_by_name', 'posted_by_name' + 'created_by_name', 'updated_by_name', 'completed_by_name', 'posted_by_name', + 'vendor_name', 'product_name', 'recipe_name' ]; - if (snapshotFields.includes(key)) return true; - - // 隱藏所有以 _name 結尾的欄位(因為它們通常是 ID 欄位的文字補充) if (key.endsWith('_name')) return true; - return false; }; @@ -262,36 +317,26 @@ export default function ActivityDetailDialog({ open, onOpenChange, activity }: P }; const formatValue = (key: string, value: any) => { - // 遮蔽密碼 if (key === 'password') return '******'; - if (value === null || value === undefined) return '-'; if (typeof value === 'boolean') return value ? '是' : '否'; if (key === 'is_active') return value ? '啟用' : '停用'; - // 處理採購單狀態 if (key === 'status' && typeof value === 'string' && statusMap[value]) { return statusMap[value]; } - - // 處理庫存品質狀態 if (key === 'quality_status' && typeof value === 'string' && qualityStatusMap[value]) { return qualityStatusMap[value]; } - - // 處理入庫類型 if (key === 'type' && typeof value === 'string' && typeMap[value]) { return typeMap[value]; } - // 處理日期欄位 (YYYY-MM-DD) - if ((key === 'order_date' || key === 'expected_delivery_date' || key === 'invoice_date' || key === 'arrival_date' || key === 'expiry_date') && typeof value === 'string') { - // 僅取日期部分 (YYYY-MM-DD) + // 處理日期與時間 + if ((key === 'order_date' || key === 'expected_delivery_date' || key === 'invoice_date' || key === 'arrival_date' || key === 'expiry_date' || key === 'received_date' || key === 'production_date') && typeof value === 'string') { return value.split('T')[0].split(' ')[0]; } - - // 處理日期時間欄位 (YYYY-MM-DD HH:mm:ss) - if ((key === 'snapshot_date' || key === 'completed_at' || key === 'posted_at') && typeof value === 'string') { + if ((key === 'snapshot_date' || key === 'completed_at' || key === 'posted_at' || key === 'created_at' || key === 'updated_at') && typeof value === 'string') { try { const date = new Date(value); return date.toLocaleString('zh-TW', { @@ -308,44 +353,31 @@ export default function ActivityDetailDialog({ open, onOpenChange, activity }: P return value; } } - return String(value); }; const getFormattedValue = (key: string, value: any) => { - // 如果是 ID 欄位,嘗試在快照或屬性中尋找對應名稱 if (key.endsWith('_id')) { const nameKey = key.replace('_id', '_name'); - // 先檢查快照,然後檢查屬性 const nameValue = snapshot[nameKey] || attributes[nameKey]; - if (nameValue) { - return `${nameValue}`; - } + if (nameValue) return `${nameValue}`; } return formatValue(key, value); }; - // 取得翻譯欄位標籤的輔助函式 - const getFieldLabel = (key: string) => { - return fieldLabels[key] || key; - }; + const getFieldLabel = (key: string) => fieldLabels[key] || key; + const getSubjectTypeLabel = (type: string) => subjectTypeMap[type] || type; - // 取得標題的主題名稱 const getSubjectName = () => { - // 庫存的特殊處理:顯示 "倉庫 - 商品" if ((snapshot.warehouse_name || attributes.warehouse_name) && (snapshot.product_name || attributes.product_name)) { - const wName = snapshot.warehouse_name || attributes.warehouse_name; - const pName = snapshot.product_name || attributes.product_name; - return `${wName} - ${pName}`; + return `${snapshot.warehouse_name || attributes.warehouse_name} - ${snapshot.product_name || attributes.product_name}`; } - - const nameParams = ['doc_no', 'po_number', 'name', 'code', 'product_name', 'warehouse_name', 'category_name', 'base_unit_name', 'title']; + const nameParams = ['doc_no', 'po_number', 'gr_number', 'production_number', 'name', 'code', 'product_name', 'warehouse_name', 'category_name', 'title']; for (const param of nameParams) { if (snapshot[param]) return snapshot[param]; if (attributes[param]) return attributes[param]; if (old[param]) return old[param]; } - if (attributes.id || old.id) return `#${attributes.id || old.id}`; return ''; }; @@ -365,7 +397,6 @@ export default function ActivityDetailDialog({ open, onOpenChange, activity }: P - {/* 現代化元數據條 */}
@@ -379,19 +410,19 @@ export default function ActivityDetailDialog({ open, onOpenChange, activity }: P {subjectName ? `${subjectName} ` : ''} - {activity.properties?.sub_subject || activity.subject_type} + ({getSubjectTypeLabel(activity.properties?.sub_subject || activity.subject_type)})
- {/* 僅在描述與事件名稱不同時顯示(不太可能發生但為了安全起見) */} - {activity.description !== getEventLabel(activity.event) && - activity.description !== 'created' && activity.description !== 'updated' && ( -
- - {activity.description} -
- )}
+ {/* 僅在描述與事件名稱不同時顯示(不太可能發生但為了安全起見) */} + {activity.description !== getEventLabel(activity.event) && + activity.description !== 'created' && activity.description !== 'updated' && ( +
+ + {activity.description} +
+ )}
{activity.event === 'created' ? ( @@ -571,7 +602,7 @@ export default function ActivityDetailDialog({ open, onOpenChange, activity }: P
)} - - + + ); } diff --git a/resources/js/Components/ActivityLog/LogTable.tsx b/resources/js/Components/ActivityLog/LogTable.tsx index 1bfc191..00bc9f2 100644 --- a/resources/js/Components/ActivityLog/LogTable.tsx +++ b/resources/js/Components/ActivityLog/LogTable.tsx @@ -29,6 +29,50 @@ interface LogTableProps { from?: number; // 起始索引編號 (paginator.from) } +// 主體類型解析 (Model 類名轉中文) +const subjectTypeMap: Record = { + // 完整路徑映射 + 'App\\Modules\\Inventory\\Models\\Product': '商品資料', + 'App\\Modules\\Inventory\\Models\\Warehouse': '倉庫資料', + 'App\\Modules\\Inventory\\Models\\Inventory': '庫存異動', + 'App\\Modules\\Inventory\\Models\\Category': '商品分類', + 'App\\Modules\\Inventory\\Models\\Unit': '單位', + 'App\\Modules\\Inventory\\Models\\InventoryTransaction': '庫存異動紀錄', + 'App\\Modules\\Inventory\\Models\\GoodsReceipt': '進貨單', + 'App\\Modules\\Inventory\\Models\\InventoryCountDoc': '庫存盤點單', + 'App\\Modules\\Inventory\\Models\\InventoryAdjustDoc': '庫存盤調單', + 'App\\Modules\\Inventory\\Models\\InventoryTransferOrder': '庫存調撥單', + 'App\\Modules\\Inventory\\Models\\StockMovementDoc': '庫存單據', + 'App\\Modules\\Procurement\\Models\\Vendor': '廠商資料', + 'App\\Modules\\Procurement\\Models\\PurchaseOrder': '採購單', + 'App\\Modules\\Production\\Models\\ProductionOrder': '生產工單', + 'App\\Modules\\Production\\Models\\Recipe': '生產配方', + 'App\\Modules\\Production\\Models\\RecipeItem': '配方品項', + 'App\\Modules\\Production\\Models\\ProductionOrderItem': '工單品項', + 'App\\Modules\\Finance\\Models\\UtilityFee': '公共事業費', + 'App\\Modules\\Core\\Models\\User': '使用者帳號', + 'App\\Modules\\Core\\Models\\Role': '角色權限', + // 簡寫映射 + 'Product': '商品資料', + 'Warehouse': '倉庫資料', + 'Inventory': '庫存異動', + 'InventoryTransaction': '庫存異動紀錄', + 'Category': '商品分類', + 'Unit': '單位', + 'Vendor': '廠商資料', + 'PurchaseOrder': '採購單', + 'GoodsReceipt': '進貨單', + 'ProductionOrder': '生產工單', + 'Recipe': '生產配方', + 'InventoryCountDoc': '庫存盤點單', + 'InventoryAdjustDoc': '庫存盤調單', + 'InventoryTransferOrder': '庫存調撥單', + 'StockMovementDoc': '庫存單據', + 'User': '使用者帳號', + 'Role': '角色權限', + 'UtilityFee': '公共事業費', +}; + export default function LogTable({ activities, sortField, @@ -37,6 +81,8 @@ export default function LogTable({ onViewDetail, from = 1 }: LogTableProps) { + const getSubjectTypeLabel = (type: string) => subjectTypeMap[type] || type; + const getEventBadgeClass = (event: string) => { switch (event) { case 'created': return 'bg-green-50 text-green-700 border-green-200 hover:bg-green-100'; @@ -111,7 +157,7 @@ export default function LogTable({ {props.sub_subject ? ( {props.sub_subject} ) : ( - {activity.subject_type} + {getSubjectTypeLabel(activity.subject_type)} )} {/* 如果有原因/來源則顯示(例如:來自補貨) */} @@ -185,7 +231,7 @@ export default function LogTable({ - {activity.subject_type} + {getSubjectTypeLabel(activity.subject_type)}