Files
njts-accounting-core/MIGRATION_SUMMARY.md
2026-02-20 15:47:27 +09:00

370 lines
9.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 【新版本追踪系統 - 完整變更摘要】
**實施日期**: 2025 年初
**系統**: 日記條目修正追踪
**狀態**: 準備就緒,等待部署
---
## 📊 高层视图
這次更新實現了一個完整的修正版本追踪系統,用來替代舊的複雜逆仕訳邏輯。
### Before舊系統
```
原始交易 (ID: 123) ──→ 逆仕訳 (ID: 456) ──→ 修正交易 (ID: 789)
[複雜 parent_entry_id 追踪]
```
### After新系統
```
原始交易 (ID: 123, revision_count: 1, is_latest: false, original_entry_id: NULL)
修正版本 (ID: 234, revision_count: 2, is_latest: false, original_entry_id: 123)
最終版本 (ID: 345, revision_count: 3, is_latest: true, original_entry_id: 123)
```
---
## 🔧 技術變更詳情
### 1. 数据库表结构 (`journal_entries`)
#### 新增三個欄位
| 欄位名 | 類型 | 預設值 | 用途 |
| ------------------- | ------- | ------ | --------------------------- |
| `is_latest` | BOOLEAN | true | 標記是否為最新版本 |
| `revision_count` | INTEGER | 1 | 版本編號1=原始2+=修正) |
| `original_entry_id` | INTEGER | NULL | 指向原始交易的ID |
#### 新增索引
```sql
-- 用於快速查詢最新交易(試算表經常使用)
CREATE INDEX idx_journal_entries_is_latest ON journal_entries(is_latest, entry_date DESC);
-- 用於快速查詢修正歷史
CREATE INDEX idx_journal_entries_original_id ON journal_entries(original_entry_id);
```
---
### 2. 後端代碼變更
#### 文件結構
```
backend/app/
├── db_auto_migration.py [新增] 自動遷移腳本
├── core/
│ └── revision_manager.py [新增] 版本管理模塊
├── routers/
│ └── journal_entries.py [修改] 簡化為使用新系統
└── main.py [修改] 添加啟動事件運行遷移
```
#### 核心函數
**revision_manager.py:**
```python
# 創建新修正版本(自動標記舊版本過時)
create_new_revision(
conn,
entry_date,
description,
fiscal_year,
lines,
created_by="system",
original_entry_id=None
) int # 返回新交易的 ID
# 獲取修正歷史
get_revision_history(conn, original_entry_id) list
# 獲取當前版本
get_current_version(conn, journal_entry_id) dict
```
**journal_entries.py 路由變更:**
```python
# POST /journal-entries 簡化
@router.post("")
def create_journal_entry(req: JournalEntryRequest):
# 使用 revision_manager.create_new_revision()
# 自動處理版本遞增和標記
# GET /journal-entries 優化
@router.get("")
def get_journal_entries(...):
# 新方式: WHERE is_latest = true
# 舊方式: 複雜的 parent_entry_id 檢查和 NOT IN 子查詢
# 新增修正歷史端點
@router.get("/{journal_id}/revision-history")
def get_revision_history(journal_id: int):
# 返回指定交易的所有版本
```
---
### 3. SQL 查詢優化
#### 試算表查詢
**舊方式(複雜,低效):**
```sql
SELECT je.* FROM journal_entries je
LEFT JOIN journal_entries child ON child.parent_entry_id = je.journal_entry_id
WHERE je.is_deleted = false
AND child.journal_entry_id IS NULL -- 排除已被修正的交易
AND je.description NOT LIKE '%[逆仕訳]%' -- 排除逆仕訳
```
- ❌ 消耗資源LEFT JOIN + NOT IN 子查詢
- ❌ 性能:需要掃描所有記錄查找修正
- ❌ 複雜:邏輯散布在多個條件中
**新方式(簡潔,高效):**
```sql
SELECT je.* FROM journal_entries je
WHERE je.is_deleted = false
AND je.is_latest = true
```
- ✅ 高效:直接使用索引 `idx_journal_entries_is_latest`
- ✅ 簡潔:單一條件
- ✅ 明確:語意清晰
**性能提升預估:**
- 查詢時間: -60% -80%
- 資源消耗: -70%
- 索引使用效率: +100%(直接索引掃描)
---
## 🔄 工作流程 - 修正一筆交易
### 場景:需要修正交易 ID 123
#### 1. 前端調用
```javascript
POST /journal-entries
{
entry_date: "2025-01-15",
description: "【修正】銷售收入",
lines: [
{ account_id: 101, debit: 10000, credit: 0 },
{ account_id: 202, debit: 0, credit: 10000 }
],
original_entry_id: 123 // ← 新參數
}
```
#### 2. 後端處理
```python
# POST /journal-entries 端點
entry_id = create_new_revision(
conn=conn,
entry_date=date(2025, 1, 15),
description="【修正】銷售收入",
fiscal_year=2025,
lines=[...],
original_entry_id=123
)
# create_new_revision() 內部操作:
# 1. 查詢交易 123 的當前 revision_count假設為 1
# 2. 標記交易 123: is_latest = false
# 3. 創建新交易:
# - journal_entry_id = 456
# - revision_count = 2
# - is_latest = true
# - original_entry_id = 123
# 4. 返回 456
```
#### 3. 前端收到回應
```json
{
"status": "ok",
"journal_entry_id": 456,
"revision_created": true
}
```
#### 4. 試算表查詢
```python
# 舊行為:只看交易 123找不到因為已被標記為過時
# 新行為:只看交易 456因為 is_latest = true
SELECT * FROM journal_entries
WHERE is_deleted = false AND is_latest = true
# 結果:包括交易 456不包括交易 123
```
#### 5. 修正歷史查詢
```python
GET /journal-entries/456/revision-history
# 結果:
# [
# {journal_entry_id: 123, revision_count: 1, is_latest: false, ...},
# {journal_entry_id: 456, revision_count: 2, is_latest: true, ...}
# ]
```
---
## 📁 文件清單
### 新增文件
| 文件 | 用途 | 大小 |
| ---------------------------------------------------- | ---------------- | ------ |
| `backend/app/db_auto_migration.py` | 自動遷移腳本 | ~2.5KB |
| `backend/app/core/revision_manager.py` | 版本管理核心模塊 | ~4KB |
| `backend/sql/migration_revision_tracking_manual.sql` | 手動 SQL 遷移 | ~2KB |
| `test_environment.py` | 環境驗證工具 | ~3KB |
| `IMPLEMENTATION_GUIDE.md` | 實施指南 | ~6KB |
### 修改文件
| 文件 | 變更內容 | 影響範圍 |
| ---------------------------------------- | ------------------------ | ---------------- |
| `backend/app/main.py` | 添加啟動事件運行遷移 | 應用初始化 |
| `backend/app/routers/journal_entries.py` | 簡化後端邏輯,整合新系統 | 所有日記條目操作 |
### 未修改文件
(其他所有文件保持不變)
---
## ✅ 遷移驗收清單
### 前置條件
- [ ] 已備份生產數據庫
- [ ] 已準備回滾計劃
- [ ] 已通知相關用户
### 技術驗收
- [ ] 自動遷移腳本無語法錯誤
- [ ] 新字段已添加到 `journal_entries`
- [ ] 索引已創建並可用
- [ ] 後端代碼編譯無誤
### 功能驗收
- [ ] 應用啟動時自動運行遷移
- [ ] 可成功創建新交易
- [ ] 可成功創建修正版本original_entry_id 有效)
- [ ] 試算表查詢僅顯示 `is_latest = true` 的交易
- [ ] 修正歷史端點返回完整版本列表
### 性能驗收
- [ ] 試算表查詢響應時間 < 500ms50000+ 記錄)
- [ ] 索引命中率 > 95%
- [ ] 無慢查詢告警
### 用户驗收
- [ ] 前端修正流程正常工作
- [ ] 已修正的交易不再出現在主列表中
- [ ] 用户可查看修正歷史記錄
---
## 🎯 已解決的問題
### ❌ 舊系統問題
1. **複雜的逆仕訳邏輯**
- 需要生成對沖交易
- 難以追踪修正歷史
- 用户容易混淆
2. **低效的試算表查詢**
- 使用 LEFT JOIN + NOT IN 子查詢
- 需要掃描大量記錄
- 性能隨着數據增長而惡化
3. **修正版本管理困難**
- 沒有清晰的版本編號
- 無法區分原始交易與修正版本
- 修正歷史散亂,難以查詢
### ✅ 新系統優勢
1. **清晰的版本追踪**
- 每個版本都有唯一的 revision_count
- is_latest 標誌明確標記當前版本
- original_entry_id 清楚指向原始交易
2. **高效的查詢性能**
- 直接使用索引查詢
- 簡單明需的 SQL 條件
- 性能與數據量無關
3. **更好的用户體驗**
- 修正流程更直觀
- 可查看完整的修正歷史
- 不需要理解複雜的逆仕訳概念
---
## 🚀 部署計劃
### 第 1 階段:準備(立即)
```
1. 備份生產數據庫
2. 在測試環境驗證遷移
3. 准備回滾計劃
```
### 第 2 階段:部署(確認後)
```
1. 更新應用代碼
2. 重啟應用(自動運行遷移)
3. 驗證遷移成功
4. 運行集成測試
```
### 第 3 階段:驗證(部署後)
```
1. 檢查應用日誌
2. 測試核心功能
3. 驗證性能指標
4. 通知相關團隊
```
---
## 📞 支持與聯系
如有疑問或遇到問題,請參考:
- 📖 [實施指南](IMPLEMENTATION_GUIDE.md)
- 📋 [手動 SQL 遷移](backend/sql/migration_revision_tracking_manual.sql)
- 🧪 [環境驗證工具](test_environment.py)
**所有文件已準備就緒,可隨時部署。**