2026-01-21 11:46:16 +08:00
|
|
|
|
---
|
|
|
|
|
|
name: 操作紀錄實作規範
|
2026-02-04 15:39:05 +08:00
|
|
|
|
description: 規範系統內 Activity Log 的實作標準,包含自動名稱解析、複雜單據合併記錄、與前端顯示優化。
|
2026-01-21 11:46:16 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
# 操作紀錄實作規範 (Activity Logging Skill)
|
2026-01-21 11:46:16 +08:00
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
本文件定義了 Star ERP 系統中操作紀錄的最高實作標準,旨在確保每筆日誌都具有「高度可讀性」與「單一性」。
|
2026-01-21 11:46:16 +08:00
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
---
|
2026-01-21 11:46:16 +08:00
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
## 1. 後端實作核心 (Backend)
|
2026-01-21 11:46:16 +08:00
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
### 1.1 全域 ID 轉名稱邏輯 (Global ID Resolution)
|
|
|
|
|
|
為了讓管理者能直覺看懂日誌,所有的 ID(如 `warehouse_id`, `created_by`)在記錄時都應自動解析為名稱。此邏輯應統一在 Model 的 `tapActivity` 中實作。
|
2026-01-21 11:46:16 +08:00
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
#### 關鍵實作參考:
|
2026-01-21 11:46:16 +08:00
|
|
|
|
```php
|
|
|
|
|
|
public function tapActivity(\Spatie\Activitylog\Contracts\Activity $activity, string $eventName)
|
|
|
|
|
|
{
|
2026-02-04 15:39:05 +08:00
|
|
|
|
// 🚩 核心:轉換為陣列以避免 Indirect modification error
|
|
|
|
|
|
$properties = $activity->properties instanceof \Illuminate\Support\Collection
|
|
|
|
|
|
? $activity->properties->toArray()
|
|
|
|
|
|
: $activity->properties;
|
|
|
|
|
|
|
|
|
|
|
|
// 1. Snapshot 快照:用於主描述的上下文(例如:單號、名稱)
|
2026-01-21 11:46:16 +08:00
|
|
|
|
$snapshot = $properties['snapshot'] ?? [];
|
2026-02-04 15:39:05 +08:00
|
|
|
|
$snapshot['doc_no'] = $this->doc_no;
|
|
|
|
|
|
$snapshot['warehouse_name'] = $this->warehouse?->name;
|
|
|
|
|
|
$properties['snapshot'] = $snapshot;
|
2026-01-21 11:46:16 +08:00
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
// 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']);
|
2026-01-21 11:46:16 +08:00
|
|
|
|
|
|
|
|
|
|
$activity->properties = $properties;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
### 1.2 複雜操作的日誌合併 (Log Consolidation)
|
|
|
|
|
|
當一個操作同時涉及「多個品項異動」與「單據狀態變更」時,**嚴禁**產生多筆重複日誌。
|
2026-01-21 11:46:16 +08:00
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
* **策略**:在 Service 層手動發送主日誌,並使用 `saveQuietly()` 更新單據屬性以抑止 Trait 的自動日誌。
|
|
|
|
|
|
* **格式**:主日誌應包含 `items_diff` (品項差異) 與 `attributes/old` (單據狀態變更)。
|
2026-01-21 11:46:16 +08:00
|
|
|
|
|
|
|
|
|
|
```php
|
2026-02-04 15:39:05 +08:00
|
|
|
|
// 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');
|
|
|
|
|
|
});
|
2026-01-21 11:46:16 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
---
|
2026-01-21 11:46:16 +08:00
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
## 2. 前端介面規範 (Frontend)
|
2026-01-21 11:46:16 +08:00
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
### 2.1 標籤命名規範 (Field Labels)
|
|
|
|
|
|
前端顯示應完全移除「ID」字眼,提供最友善的閱讀體驗。
|
2026-01-21 11:46:16 +08:00
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
**檔案位置**: `resources/js/Components/ActivityLog/ActivityDetailDialog.tsx`
|
2026-01-21 11:46:16 +08:00
|
|
|
|
```typescript
|
|
|
|
|
|
const fieldLabels: Record<string, string> = {
|
2026-02-04 15:39:05 +08:00
|
|
|
|
warehouse_id: '倉庫', // ❌ 禁用「倉庫 ID」
|
|
|
|
|
|
created_by: '建立者', // ❌ 禁用「建立者 ID」
|
|
|
|
|
|
completed_by: '完成者',
|
|
|
|
|
|
status: '狀態',
|
2026-01-21 11:46:16 +08:00
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
### 2.2 特殊結構顯示
|
|
|
|
|
|
* **品項異動**:前端應能渲染 `items_diff` 結構,以「品項名稱 + 數值變動」的方式呈現表格(已在 `ActivityDetailDialog` 實作)。
|
2026-01-21 11:46:16 +08:00
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
---
|
2026-01-21 11:46:16 +08:00
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
## 3. 開發檢核清單 (Checklist)
|
2026-01-21 11:46:16 +08:00
|
|
|
|
|
2026-02-04 15:39:05 +08:00
|
|
|
|
- [ ] **Model**: `tapActivity` 是否已處理 Collection 快照?
|
|
|
|
|
|
- [ ] **Model**: 是否已實作全域 ID 至名稱的自動解析?
|
|
|
|
|
|
- [ ] **Service**: 是否使用 `saveQuietly()` 避免產生重複的「單據已更新」日誌?
|
|
|
|
|
|
- [ ] **UI**: `fieldLabels` 是否已移除所有「ID」字樣?
|
|
|
|
|
|
- [ ] **UI**: 若有品項異動,是否已正確格式化傳入 `items_diff`?
|