This commit is contained in:
admin
2026-02-20 15:47:27 +09:00
parent c60cbf2d9a
commit 584530937b
108 changed files with 12112 additions and 416 deletions

View File

@@ -0,0 +1,174 @@
"""
修正版本追踪管理器
記錄日記條目的修正歷史,追踪原始交易和修正版本
"""
from decimal import Decimal
from datetime import date
from typing import Optional, Dict, Any
def mark_old_version_as_outdated(conn, original_entry_id: int) -> None:
"""
將舊版本標記為非最新版本 (is_latest = false)
"""
with conn.cursor() as cur:
cur.execute("""
UPDATE journal_entries
SET is_latest = false
WHERE original_entry_id = %s OR journal_entry_id = %s
ORDER BY created_at DESC
OFFSET 1
""", (original_entry_id, original_entry_id))
def create_new_revision(
conn,
entry_date: date,
description: str,
fiscal_year: int,
lines: list,
created_by: str = "system",
original_entry_id: Optional[int] = None
) -> int:
"""
創建新的修正版本
返回: 新創建的 journal_entry_id
"""
with conn.cursor() as cur:
# ① 如果是修正交易,獲取最新版本的 revision_count
revision_count = 1
if original_entry_id:
# 获取原始交易 ID可能已有多個版本
cur.execute("""
SELECT revision_count FROM journal_entries
WHERE (journal_entry_id = %s OR original_entry_id = %s)
AND is_latest = true
ORDER BY created_at DESC
LIMIT 1
""", (original_entry_id, original_entry_id))
row = cur.fetchone()
if row:
revision_count = row["revision_count"] + 1
else:
# 如果找不到最新版本,說明 original_entry_id 是第一個版本
revision_count = 2
# ② 如果是修正,標記舊版本
if original_entry_id:
cur.execute("""
UPDATE journal_entries
SET is_latest = false
WHERE (journal_entry_id = %s OR original_entry_id = %s)
AND is_latest = true
""", (original_entry_id, original_entry_id))
# ③ 創建新版本
normalized_original_id = original_entry_id if original_entry_id else None
cur.execute("""
INSERT INTO journal_entries (
entry_date,
description,
fiscal_year,
is_latest,
revision_count,
original_entry_id,
is_deleted,
created_by
)
VALUES (%s, %s, %s, true, %s, %s, false, %s)
RETURNING journal_entry_id
""", (
entry_date,
description,
fiscal_year,
revision_count,
normalized_original_id,
created_by
))
entry_id = cur.fetchone()["journal_entry_id"]
# ④ 插入交易明細
for line in lines:
# 轉換 tax_direction
tax_dir_db = None
if line.get("tax_direction"):
if line["tax_direction"] == 'paid':
tax_dir_db = 'INPUT'
elif line["tax_direction"] == 'received':
tax_dir_db = 'OUTPUT'
else:
tax_dir_db = line["tax_direction"].upper()
cur.execute("""
INSERT INTO journal_lines (
journal_entry_id,
account_id,
debit,
credit,
tax_rate,
tax_direction
)
VALUES (%s, %s, %s, %s, %s, %s)
""", (
entry_id,
line.get("account_id"),
line.get("debit", Decimal("0")),
line.get("credit", Decimal("0")),
line.get("tax_rate"),
tax_dir_db
))
return entry_id
def get_current_version(conn, journal_entry_id: int) -> Optional[Dict[str, Any]]:
"""
取得指定交易的最新版本資訊
"""
with conn.cursor() as cur:
cur.execute("""
SELECT
journal_entry_id,
entry_date,
description,
revision_count,
original_entry_id,
is_latest,
created_at,
created_by
FROM journal_entries
WHERE journal_entry_id = %s
""", (journal_entry_id,))
row = cur.fetchone()
if row:
return dict(row)
return None
def get_revision_history(conn, original_entry_id: int) -> list:
"""
取得某個交易的所有修正版本歷史
"""
with conn.cursor() as cur:
cur.execute("""
SELECT
journal_entry_id,
entry_date,
description,
revision_count,
is_latest,
created_at,
created_by
FROM journal_entries
WHERE original_entry_id = %s OR journal_entry_id = %s
ORDER BY created_at ASC
""", (original_entry_id, original_entry_id))
rows = cur.fetchall()
return [dict(row) for row in rows]

View File

@@ -0,0 +1,157 @@
"""
自動數據庫遷移 - 添加修正追踪欄位
在應用啟動時執行
"""
import os
from psycopg import connect
def run_auto_migration():
"""自動遷移:添加修正追踪欄位"""
try:
conn = connect(
host=os.getenv("DB_HOST"),
port=int(os.getenv("DB_PORT")),
dbname=os.getenv("DB_NAME"),
user=os.getenv("DB_USER"),
password=os.getenv("DB_PASSWORD"),
)
with conn.cursor() as cur:
print("\n" + "=" * 80)
print("【自動數據庫遷移】")
print("=" * 80)
# 1. 添加 is_latest 欄位
print("\n【步驟 1】檢查 is_latest 欄位...")
try:
cur.execute("""
SELECT COUNT(*)
FROM information_schema.columns
WHERE table_name = 'journal_entries' AND column_name = 'is_latest'
""")
if cur.fetchone()[0] == 0:
# 欄位不存在,添加
try:
cur.execute("""
ALTER TABLE journal_entries
ADD COLUMN is_latest BOOLEAN DEFAULT true
""")
conn.commit()
print("✓ 欄位 is_latest 已添加")
except Exception as e:
conn.rollback()
print(f"⚠️ 無法添加 is_latest可能由於權限: {e}")
else:
print("✓ 欄位 is_latest 已存在")
except Exception as e:
print(f"⚠️ 檢查 is_latest 失敗: {e}")
# 2. 添加 revision_count 欄位
print("\n【步驟 2】檢查 revision_count 欄位...")
try:
cur.execute("""
SELECT COUNT(*)
FROM information_schema.columns
WHERE table_name = 'journal_entries' AND column_name = 'revision_count'
""")
if cur.fetchone()[0] == 0:
try:
cur.execute("""
ALTER TABLE journal_entries
ADD COLUMN revision_count INTEGER DEFAULT 1
""")
conn.commit()
print("✓ 欄位 revision_count 已添加")
except Exception as e:
conn.rollback()
print(f"⚠️ 無法添加 revision_count可能由於權限: {e}")
else:
print("✓ 欄位 revision_count 已存在")
except Exception as e:
print(f"⚠️ 檢查 revision_count 失敗: {e}")
# 3. 添加 original_entry_id 欄位
print("\n【步驟 3】檢查 original_entry_id 欄位...")
try:
cur.execute("""
SELECT COUNT(*)
FROM information_schema.columns
WHERE table_name = 'journal_entries' AND column_name = 'original_entry_id'
""")
if cur.fetchone()[0] == 0:
try:
cur.execute("""
ALTER TABLE journal_entries
ADD COLUMN original_entry_id INTEGER
""")
conn.commit()
print("✓ 欄位 original_entry_id 已添加")
except Exception as e:
conn.rollback()
print(f"⚠️ 無法添加 original_entry_id可能由於權限: {e}")
else:
print("✓ 欄位 original_entry_id 已存在")
except Exception as e:
print(f"⚠️ 檢查 original_entry_id 失敗: {e}")
# 4. 創建索引
print("\n【步驟 4】創建索引...")
try:
cur.execute("""
CREATE INDEX IF NOT EXISTS idx_journal_entries_is_latest
ON journal_entries(is_latest, entry_date)
""")
conn.commit()
print("✓ 索引 idx_journal_entries_is_latest 已創建")
except Exception as e:
print(f"⚠️ 無法創建索引: {e}")
# 5. 驗證
print("\n【步驟 5】驗證新欄位...")
try:
cur.execute("""
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'journal_entries'
AND column_name IN ('is_latest', 'revision_count', 'original_entry_id')
ORDER BY ordinal_position
""")
rows = cur.fetchall()
if rows:
for row in rows:
col_name = row[0]
data_type = row[1]
print(f"{col_name}: {data_type}")
else:
print(" 新欄位尚未存在(需要管理員執行遷移 SQL")
except Exception as e:
print(f"⚠️ 驗證失敗: {e}")
print("\n" + "=" * 80)
print("✓ 自動遷移檢查完成!")
print("=" * 80 + "\n")
conn.close()
except Exception as e:
print(f"\n❌ 無法連接到數據庫或執行遷移: {e}")
print("\n提示:")
print("1. 確認數據庫連接信息正確DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD")
print("2. 如果字段仍不存在,請使用管理員帳戶執行以下 SQL 命令:")
print("""
ALTER TABLE journal_entries ADD COLUMN IF NOT EXISTS is_latest BOOLEAN DEFAULT true;
ALTER TABLE journal_entries ADD COLUMN IF NOT EXISTS revision_count INTEGER DEFAULT 1;
ALTER TABLE journal_entries ADD COLUMN IF NOT EXISTS original_entry_id INTEGER;
CREATE INDEX IF NOT EXISTS idx_journal_entries_is_latest ON journal_entries(is_latest, entry_date);
""")
if __name__ == "__main__":
run_auto_migration()

View File

@@ -21,6 +21,18 @@ app.add_middleware(
allow_headers=["*"],
)
# ─────────────────────────
# 自動データベース遷移
# ─────────────────────────
@app.on_event("startup")
async def run_startup_migration():
"""アプリケーション起動時にデータベス遷移を実行"""
try:
from app.db_auto_migration import run_auto_migration
run_auto_migration()
except Exception as e:
print(f"⚠️ 遷移エラー(無視): {e}")
@app.exception_handler(ValidationError)
async def validation_exception_handler(request: Request, exc: ValidationError):
# ここでは最初のエラーだけ簡潔に返す(必要に応じて整形)

View File

@@ -61,19 +61,16 @@ def fetch_trial_balance(date_from: str, date_to: str):
opening_balance_from_table = D(ob_row["ob"]) if ob_row else D(0)
# date_from より前の仕訳による増減
# 修正された仕訳(親となった仕訳)は除外
# 修正された仕訳(is_latest = false)は除外
cur.execute("""
SELECT COALESCE(SUM(l.debit - l.credit), 0) AS opening
FROM journal_lines l
JOIN journal_entries j
ON j.journal_entry_id = l.journal_entry_id
LEFT JOIN journal_entries child
ON child.parent_entry_id = j.journal_entry_id
WHERE l.account_id = %s
AND j.entry_date < %s
AND j.is_deleted = false
AND j.description NOT LIKE '[逆仕訳]%%'
AND child.journal_entry_id IS NULL
AND j.is_latest = true
""", (aid, date_from))
opening_from_journals = D(cur.fetchone()["opening"])
@@ -81,7 +78,7 @@ def fetch_trial_balance(date_from: str, date_to: str):
opening = opening_balance_from_table + opening_from_journals
# 当期増減date_from date_to
# 修正された仕訳(親となった仕訳)は除外
# 修正された仕訳(is_latest = false)は除外
cur.execute("""
SELECT
COALESCE(SUM(l.debit), 0) AS debit,
@@ -89,13 +86,10 @@ def fetch_trial_balance(date_from: str, date_to: str):
FROM journal_lines l
JOIN journal_entries j
ON j.journal_entry_id = l.journal_entry_id
LEFT JOIN journal_entries child
ON child.parent_entry_id = j.journal_entry_id
WHERE l.account_id = %s
AND j.entry_date BETWEEN %s AND %s
AND j.is_deleted = false
AND j.description NOT LIKE '[逆仕訳]%%'
AND child.journal_entry_id IS NULL
AND j.is_latest = true
""", (aid, date_from, date_to))
row = cur.fetchone()
debit = D(row["debit"])

View File

@@ -23,7 +23,8 @@ class JournalEntryRequest(BaseModel):
tax_paid_account_id: Optional[int] = None # 仮払消費税等 勘定
tax_received_account_id: Optional[int] = None # 仮受消費税等 勘定
rounding_mode: str = "round" # "round", "floor", or "ceil"
parent_entry_id: Optional[int] = None # 修正元の仕訳ID
original_entry_id: Optional[int] = None # 修正元の仕訳ID(新版本追踪システム用)
parent_entry_id: Optional[int] = None # 互換性のため残す(非推奨)
class JournalEntryUpdateRequest(BaseModel):
description: str
@@ -41,23 +42,75 @@ def get_journal_entries(
to_date: date = None,
keyword: str = None,
account_id: int = None,
account_side: str = None, # "debit" or "credit"
include_history: bool = False
):
"""
仕訳一覧を検索して取得
- include_history=true: すべての版本を表示(修正歴含む)
- include_history=false (デフォルト): 最新版本のみを表示
"""
with get_connection() as conn:
with conn.cursor() as cur:
# クエリ構築
if include_history:
# 修正履歴を含むすべてのバージョンを表示
# 首先检查字段是否存在
cur.execute("""
SELECT column_name FROM information_schema.columns
WHERE table_name = 'journal_entries'
AND column_name IN ('is_latest', 'revision_count', 'original_entry_id')
""")
existing_fields = {row['column_name'] for row in cur.fetchall()}
# 根据字段是否存在构建不同的查询
if 'revision_count' in existing_fields and 'is_latest' in existing_fields:
# 使用新字段的完整查询
if include_history:
sql = """
SELECT
je.journal_entry_id,
je.entry_date,
je.description,
je.fiscal_year,
je.revision_count,
je.is_latest,
je.original_entry_id,
COALESCE(SUM(jl.debit), 0) as debit_total,
COALESCE(SUM(jl.credit), 0) as credit_total,
STRING_AGG(DISTINCT CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END, ', ' ORDER BY CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END) as tax_rates
FROM journal_entries je
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
WHERE je.is_deleted = false
"""
else:
sql = """
SELECT
je.journal_entry_id,
je.entry_date,
je.description,
je.fiscal_year,
je.revision_count,
je.is_latest,
je.original_entry_id,
COALESCE(SUM(jl.debit), 0) as debit_total,
COALESCE(SUM(jl.credit), 0) as credit_total,
STRING_AGG(DISTINCT CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END, ', ' ORDER BY CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END) as tax_rates
FROM journal_entries je
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
WHERE je.is_deleted = false
AND je.is_latest = true
"""
params = []
else:
# 使用不含新字段的查询(向后兼容)
sql = """
SELECT
je.journal_entry_id,
je.entry_date,
je.description,
je.fiscal_year,
je.version,
1 as revision_count,
true as is_latest,
NULL::INTEGER as original_entry_id,
COALESCE(SUM(jl.debit), 0) as debit_total,
COALESCE(SUM(jl.credit), 0) as credit_total,
STRING_AGG(DISTINCT CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END, ', ' ORDER BY CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END) as tax_rates
@@ -66,29 +119,6 @@ def get_journal_entries(
WHERE je.is_deleted = false
"""
params = []
else:
# 最新バージョンのみ表示parent_entry_idを持つ古い仕訳と逆仕訳を除外
sql = """
SELECT
je.journal_entry_id,
je.entry_date,
je.description,
je.fiscal_year,
je.version,
COALESCE(SUM(jl.debit), 0) as debit_total,
COALESCE(SUM(jl.credit), 0) as credit_total,
STRING_AGG(DISTINCT CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END, ', ' ORDER BY CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END) as tax_rates
FROM journal_entries je
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
WHERE je.is_deleted = false
AND je.description NOT LIKE %s
AND je.journal_entry_id NOT IN (
SELECT parent_entry_id
FROM journal_entries
WHERE parent_entry_id IS NOT NULL
)
"""
params = ['%[逆仕訳]%']
if from_date:
sql += " AND je.entry_date >= %s"
@@ -102,7 +132,24 @@ def get_journal_entries(
sql += " AND je.description LIKE %s"
params.append(f"%{keyword}%")
if account_id:
# 科目と方向で絞込SQ副查询で指定の科目・方向を持つ仕訳のみ
if account_id and account_side:
# 科目+方向の組み合わせで絞込
if account_side == "debit":
sql += """ AND je.journal_entry_id IN (
SELECT DISTINCT journal_entry_id
FROM journal_lines
WHERE account_id = %s AND debit > 0
)"""
elif account_side == "credit":
sql += """ AND je.journal_entry_id IN (
SELECT DISTINCT journal_entry_id
FROM journal_lines
WHERE account_id = %s AND credit > 0
)"""
params.append(account_id)
elif account_id:
# 方向指定がない場合は科目のみで絞込
sql += """ AND je.journal_entry_id IN (
SELECT DISTINCT journal_entry_id
FROM journal_lines
@@ -111,8 +158,18 @@ def get_journal_entries(
params.append(account_id)
sql += """
GROUP BY je.journal_entry_id, je.entry_date, je.description, je.fiscal_year, je.version
ORDER BY je.entry_date DESC, je.journal_entry_id DESC, je.version DESC
GROUP BY je.journal_entry_id, je.entry_date, je.description, je.fiscal_year """
# 只有当字段存在时才添加到 GROUP BY
if 'revision_count' in existing_fields:
sql += ", je.revision_count"
if 'is_latest' in existing_fields:
sql += ", je.is_latest"
if 'original_entry_id' in existing_fields:
sql += ", je.original_entry_id"
sql += """
ORDER BY je.entry_date DESC, je.journal_entry_id DESC
"""
cur.execute(sql, params)
@@ -135,7 +192,9 @@ def get_journal_entry(journal_id: int):
entry_date,
description,
fiscal_year,
version,
revision_count,
is_latest,
original_entry_id,
is_deleted
FROM journal_entries
WHERE journal_entry_id = %s
@@ -170,6 +229,54 @@ def get_journal_entry(journal_id: int):
}
@router.get("/{journal_id}/revision-history", summary="修正歴取得")
def get_revision_history(journal_id: int):
"""
指定された仕訳の修正歴史を取得(すべてのバージョン)
"""
with get_connection() as conn:
with conn.cursor() as cur:
# 最初に、指定されたIDが実際の元の交易か、修正版かを確認
cur.execute("""
SELECT original_entry_id FROM journal_entries WHERE journal_entry_id = %s
""", (journal_id,))
row = cur.fetchone()
if not row:
raise HTTPException(status_code=404, detail="仕訳が存在しません")
# 元の交易IDを取得
original_id = row["original_entry_id"] if row["original_entry_id"] else journal_id
# その交易とすべての修正版を取得
cur.execute("""
SELECT
journal_entry_id,
entry_date,
description,
revision_count,
is_latest,
created_at,
created_by,
COALESCE(SUM(jl.debit), 0) as debit_total,
COALESCE(SUM(jl.credit), 0) as credit_total
FROM journal_entries je
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
WHERE je.journal_entry_id = %s
OR (je.original_entry_id = %s)
GROUP BY je.journal_entry_id, je.entry_date, je.description, je.revision_count, je.is_latest, je.created_at, je.created_by
ORDER BY je.revision_count ASC
""", (journal_id, original_id))
versions = cur.fetchall()
return {
"original_entry_id": original_id,
"total_versions": len(versions),
"versions": [dict(v) for v in versions]
}
@router.post("/{journal_id}/reverse", summary="逆仕訳作成")
def create_reverse_entry(journal_id: int):
"""
@@ -260,6 +367,9 @@ def create_journal_entry(req: JournalEntryRequest):
if not req.lines:
raise HTTPException(status_code=400, detail="仕訳明細不能为空")
# 互換性処理:古い parent_entry_id が提供された場合、original_entry_id として処理
original_entry_id = req.original_entry_id or req.parent_entry_id
# 税額計算と税行生成
all_lines = []
@@ -318,41 +428,79 @@ def create_journal_entry(req: JournalEntryRequest):
fiscal_year = req.entry_date.year
with get_connection() as conn:
with conn.cursor() as cur:
# 仕訳ヘッダ
# 检查版本追踪字段是否存在
with conn.cursor() as check_cur:
check_cur.execute("""
SELECT column_name FROM information_schema.columns
WHERE table_name = 'journal_entries'
AND column_name IN ('is_latest', 'revision_count', 'original_entry_id')
""")
existing_fields = {row['column_name'] for row in check_cur.fetchall()}
# 如果版本追踪字段存在,使用新系统;否则使用旧系统
if len(existing_fields) == 3: # 3个字段都存在
# 新系统:使用版本追踪 - 内联实现
with conn.cursor() as cur:
# ① 如果是修正交易,獲取最新版本的 revision_count
revision_count = 1
if original_entry_id:
cur.execute("""
SELECT revision_count FROM journal_entries
WHERE (journal_entry_id = %s OR original_entry_id = %s)
AND is_latest = true
ORDER BY created_at DESC
LIMIT 1
""", (original_entry_id, original_entry_id))
row = cur.fetchone()
if row:
revision_count = row['revision_count'] + 1
else:
revision_count = 2
# ② 如果是修正,標記舊版本
if original_entry_id:
cur.execute("""
UPDATE journal_entries
SET is_latest = false
WHERE (journal_entry_id = %s OR original_entry_id = %s)
AND is_latest = true
""", (original_entry_id, original_entry_id))
# ③ 創建新版本
cur.execute("""
INSERT INTO journal_entries (
entry_date,
description,
fiscal_year,
version,
is_latest,
revision_count,
original_entry_id,
is_deleted,
created_by,
parent_entry_id
created_by
)
VALUES (%s, %s, %s, 1, false, %s, %s)
VALUES (%s, %s, %s, true, %s, %s, false, %s)
RETURNING journal_entry_id
""", (
req.entry_date,
req.description,
fiscal_year,
"system",
req.parent_entry_id
revision_count,
original_entry_id if original_entry_id else None,
"system"
))
entry_id = cur.fetchone()["journal_entry_id"]
# 明細(税行を含む)
# ④ 插入交易明細
for line in all_lines:
# tax_directionの値を変換: paid->INPUT, received->OUTPUT, none->NONE
# 轉換 tax_direction
tax_dir_db = None
if line.tax_direction:
if line.tax_direction == 'paid':
tax_dir_db = 'INPUT'
elif line.tax_direction == 'received':
tax_dir_db = 'OUTPUT'
elif line.tax_direction == 'none':
tax_dir_db = 'NONE'
else:
tax_dir_db = line.tax_direction.upper()
@@ -374,13 +522,60 @@ def create_journal_entry(req: JournalEntryRequest):
line.tax_rate,
tax_dir_db
))
conn.commit()
return {
"status": "ok",
"journal_entry_id": entry_id
}
conn.commit()
return {
"status": "ok",
"journal_entry_id": entry_id,
"revision_created": original_entry_id is not None
}
else:
# 旧系统:没有版本追踪,直接创建仕訳
with conn.cursor() as cur:
cur.execute("""
INSERT INTO journal_entries (
entry_date, description, fiscal_year, is_deleted, created_by
) VALUES (%s, %s, %s, false, %s)
RETURNING journal_entry_id
""", (req.entry_date, req.description, fiscal_year, "system"))
entry_id = cur.fetchone()["journal_entry_id"]
# 插入交易明細
for line in all_lines:
# 轉換 tax_direction
tax_dir_db = None
if line.tax_direction:
if line.tax_direction == 'paid':
tax_dir_db = 'INPUT'
elif line.tax_direction == 'received':
tax_dir_db = 'OUTPUT'
else:
tax_dir_db = line.tax_direction.upper()
cur.execute("""
INSERT INTO journal_lines (
journal_entry_id, account_id, debit, credit,
tax_rate, tax_direction
) VALUES (%s, %s, %s, %s, %s, %s)
""", (
entry_id,
line.account_id,
line.debit,
line.credit,
line.tax_rate,
tax_dir_db
))
conn.commit()
return {
"status": "ok",
"journal_entry_id": entry_id,
"revision_created": False,
"warning": "版本追踪字段不存在,使用降级模式"
}
@router.put("/{journal_id}")

View File

@@ -14,19 +14,53 @@ def get_trial_balance(
):
print("### trial-balance OPTIONAL PARAMS ACTIVE ###")
sql = """
SELECT
a.account_id,
a.account_code,
a.account_name,
a.account_type,
COALESCE(SUM(l.debit), 0) AS debit,
COALESCE(SUM(l.credit), 0) AS credit
FROM journal_lines l
JOIN journal_entries e ON e.journal_entry_id = l.journal_entry_id
JOIN accounts a ON a.account_id = l.account_id
WHERE 1 = 1
"""
with get_connection() as conn:
# 检查 is_latest 字段是否存在(新的版本追踪系统)
with conn.cursor() as check_cur:
check_cur.execute("""
SELECT COUNT(*) as cnt
FROM information_schema.columns
WHERE table_name = 'journal_entries' AND column_name = 'is_latest'
""")
has_is_latest = check_cur.fetchone()[0] > 0
# 构建查询语句
if has_is_latest:
# 新系统只取最新版本is_latest = true
sql = """
SELECT
a.account_id,
a.account_code,
a.account_name,
a.account_type,
COALESCE(SUM(l.debit), 0) AS debit,
COALESCE(SUM(l.credit), 0) AS credit
FROM journal_lines l
JOIN journal_entries e ON e.journal_entry_id = l.journal_entry_id
JOIN accounts a ON a.account_id = l.account_id
WHERE e.is_deleted = false
AND e.is_latest = true
"""
else:
# 旧系统:排除已修正的记录(使用 parent_entry_id
sql = """
SELECT
a.account_id,
a.account_code,
a.account_name,
a.account_type,
COALESCE(SUM(l.debit), 0) AS debit,
COALESCE(SUM(l.credit), 0) AS credit
FROM journal_lines l
JOIN journal_entries e ON e.journal_entry_id = l.journal_entry_id
JOIN accounts a ON a.account_id = l.account_id
WHERE e.is_deleted = false
AND e.journal_entry_id NOT IN (
SELECT parent_entry_id
FROM journal_entries
WHERE parent_entry_id IS NOT NULL
)
"""
params = {}
@@ -35,11 +69,11 @@ def get_trial_balance(
params["fiscal_year"] = fiscal_year
if date_from is not None:
sql += " AND e.journal_date >= %(date_from)s"
sql += " AND e.entry_date >= %(date_from)s"
params["date_from"] = date_from
if date_to is not None:
sql += " AND e.journal_date <= %(date_to)s"
sql += " AND e.entry_date <= %(date_to)s"
params["date_to"] = date_to
sql += """