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

@@ -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}")