Files
star-erp/.agents/rules/activity-logging.md
sky121113 deef3baacc
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 57s
refactor: 重構模組通訊與調整儀表板功能
- 依循跨模組通訊規範,將 Sales 與 Production 模組中對 Inventory 的直接模型關聯改為透過 InventoryServiceInterface 取得
- 於 InventoryService 實作獲取最高庫存價值、即將過期商品等方法,供儀表板使用
- 確保所有跨模組調用皆採用手動水和(Manual Hydration)方式組合資料
- 移除本地已歸檔的 .agent 規範檔案
2026-02-25 11:48:52 +08:00

4.3 KiB
Raw Blame History

trigger
trigger
always_on

name: 操作紀錄實作規範 description: 規範系統內 Activity Log 的實作標準,包含自動名稱解析、複雜單據合併記錄、與前端顯示優化。

操作紀錄實作規範 (Activity Logging Skill)

本文件定義了 Star ERP 系統中操作紀錄的最高實作標準,旨在確保每筆日誌都具有「高度可讀性」與「單一性」。


1. 後端實作核心 (Backend)

1.1 全域 ID 轉名稱邏輯 (Global ID Resolution)

為了讓管理者能直覺看懂日誌,所有的 IDwarehouse_id, created_by)在記錄時都應自動解析為名稱。此邏輯應統一在 Model 的 tapActivity 中實作。

關鍵實作參考:

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['doc_no'] = $this->doc_no;
    $snapshot['warehouse_name'] = $this->warehouse?->name;
    $properties['snapshot'] = $snapshot;

    // 2. 名稱解析:自動將 attributes 與 old 中的 ID 換成人名/物名
    $resolver = function (&$data) {
        if (empty($data) || !is_array($data)) return;
        
        // 使用者 ID 轉換
        foreach (['created_by', 'updated_by', 'completed_by'] as $f) {
            if (isset($data[$f]) && is_numeric($data[$f])) {
                $data[$f] = \App\Modules\Core\Models\User::find($data[$f])?->name;
            }
        }
        // 倉庫 ID 轉換
        if (isset($data['warehouse_id']) && is_numeric($data['warehouse_id'])) {
            $data['warehouse_id'] = \App\Modules\Inventory\Models\Warehouse::find($data['warehouse_id'])?->name;
        }
    };

    if (isset($properties['attributes'])) $resolver($properties['attributes']);
    if (isset($properties['old'])) $resolver($properties['old']);

    $activity->properties = $properties;
}

1.2 複雜操作的日誌合併 (Log Consolidation)

當一個操作同時涉及「多個品項異動」與「單據狀態變更」時,嚴禁產生多筆重複日誌。

  • 策略:在 Service 層手動發送主日誌,並使用 saveQuietly() 更新單據屬性以抑止 Trait 的自動日誌。
  • 格式:主日誌應包含 items_diff (品項差異) 與 attributes/old (單據狀態變更)。
// Service 中的實作方式
DB::transaction(function () use ($doc, $items) {
    // 1. 更新品項 (記錄變動細節)
    $updatedItems = $this->getUpdatedItems($doc, $items); 
    
    // 2. 靜默更新單據狀態 (避免 Trait 產生冗餘日誌)
    $doc->status = 'completed';
    $doc->saveQuietly(); 

    // 3. 手動觸發單一合併日誌
    activity()
        ->performedOn($doc)
        ->withProperties([
            'items_diff' => ['updated' => $updatedItems],
            'attributes' => ['status' => 'completed'],
            'old' => ['status' => 'counting']
        ])
        ->log('updated');
});

2. 前端介面規範 (Frontend)

2.1 標籤命名規範 (Field Labels)

前端顯示應完全移除「ID」字眼提供最友善的閱讀體驗。

檔案位置: resources/js/Components/ActivityLog/ActivityDetailDialog.tsx

const fieldLabels: Record<string, string> = {
    warehouse_id: '倉庫',   // ❌ 禁用「倉庫 ID」
    created_by: '建立者',   // ❌ 禁用「建立者 ID」
    completed_by: '完成者',
    status: '狀態',
};

2.2 特殊結構顯示

  • 品項異動:前端應能渲染 items_diff 結構,以「品項名稱 + 數值變動」的方式呈現表格(已在 ActivityDetailDialog 實作)。

3. 開發檢核清單 (Checklist)

  • Model: tapActivity 是否已處理 Collection 快照?
  • Model: 是否已實作全域 ID 至名稱的自動解析?
  • Service: 是否使用 saveQuietly() 避免產生重複的「單據已更新」日誌?
  • UI: fieldLabels 是否已移除所有「ID」字樣
  • UI: 若有品項異動,是否已正確格式化傳入 items_diff