修正
This commit is contained in:
@@ -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}")
|
||||
|
||||
@@ -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 += """
|
||||
|
||||
Reference in New Issue
Block a user