Files
njts-accounting-core/backend/app/routers/journal_entries.py
FNOS-WIN11ENT\choshoukaku b6751ced18 20251215滴修正
2025-12-15 14:49:59 +09:00

233 lines
7.1 KiB
Python
Raw 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.
from fastapi import APIRouter, HTTPException, Query
from typing import Optional
from decimal import Decimal
from datetime import date
from app.core.database import get_connection
from app.models.journal_entry import JournalEntryRequest
router = APIRouter(prefix="/journal-entries", tags=["仕訳"])
# -------------------------------------------------
# 仕訳登録
# -------------------------------------------------
@router.post("", summary="仕訳登録")
def create_journal_entry(req: JournalEntryRequest):
if len(req.lines) < 2:
raise HTTPException(status_code=400, detail="仕訳行は2行以上必要です")
if req.entry_date < date(2025, 6, 1):
raise HTTPException(status_code=400, detail="仕訳日は2025-06-01以降である必要があります")
debit_total = Decimal("0")
credit_total = Decimal("0")
for line in req.lines:
if line.debit < 0 or line.credit < 0:
raise HTTPException(status_code=400, detail="金額は正数で入力してください")
if line.debit > 0 and line.credit > 0:
raise HTTPException(status_code=400, detail="同一行で借方・貸方の同時入力は不可です")
debit_total += line.debit
credit_total += line.credit
if debit_total != credit_total:
raise HTTPException(
status_code=400,
detail=f"借方合計({debit_total})と貸方合計({credit_total})が一致しません"
)
with get_connection() as conn, conn.cursor() as cur:
# 期首ロック確認
cur.execute("""
SELECT is_locked
FROM opening_balance_locks
WHERE fiscal_year = 2025
""")
row = cur.fetchone()
if not row or not row[0]:
raise HTTPException(status_code=400, detail="期首残高が確定lockされていません")
# ヘッダ登録
cur.execute("""
INSERT INTO journal_entries (entry_date, description)
VALUES (%s, %s)
RETURNING journal_entry_id
""", (req.entry_date, req.description))
journal_entry_id = cur.fetchone()[0]
# 明細登録
for line in req.lines:
cur.execute("""
INSERT INTO journal_lines
(journal_entry_id, account_id, debit, credit)
VALUES (%s, %s, %s, %s)
""", (
journal_entry_id,
line.account_id,
line.debit,
line.credit
))
return {
"message": "仕訳を登録しました",
"journal_entry_id": journal_entry_id
}
# -------------------------------------------------
# 仕訳一覧取得
# -------------------------------------------------
@router.get("", summary="仕訳一覧取得")
def list_journal_entries(
from_date: Optional[str] = Query(None),
to_date: Optional[str] = Query(None),
keyword: Optional[str] = Query(None)
):
sql = """
SELECT
je.journal_entry_id,
je.entry_date,
je.description,
SUM(jl.debit) AS debit_total,
SUM(jl.credit) AS credit_total
FROM journal_entries je
JOIN journal_lines jl
ON jl.journal_entry_id = je.journal_entry_id
WHERE 1=1
"""
params = []
if from_date:
sql += " AND je.entry_date >= %s"
params.append(from_date)
if to_date:
sql += " AND je.entry_date <= %s"
params.append(to_date)
if keyword:
sql += " AND je.description ILIKE %s"
params.append(f"%{keyword}%")
sql += """
GROUP BY je.journal_entry_id, je.entry_date, je.description
ORDER BY je.entry_date DESC, je.journal_entry_id DESC
"""
with get_connection() as conn, conn.cursor() as cur:
cur.execute(sql, params)
rows = cur.fetchall()
return [
{
"journal_entry_id": r[0],
"entry_date": r[1],
"description": r[2],
"debit_total": r[3],
"credit_total": r[4],
}
for r in rows
]
# -------------------------------------------------
# 仕訳明細取得
# -------------------------------------------------
@router.get("/{journal_entry_id}", summary="仕訳明細取得")
def get_journal_entry(journal_entry_id: int):
with get_connection() as conn, conn.cursor() as cur:
cur.execute("""
SELECT entry_date, description
FROM journal_entries
WHERE journal_entry_id = %s
""", (journal_entry_id,))
header = cur.fetchone()
if not header:
raise HTTPException(status_code=404, detail="仕訳が存在しません")
cur.execute("""
SELECT
jl.account_id,
a.account_code,
a.account_name,
jl.debit,
jl.credit
FROM journal_lines jl
JOIN accounts a ON a.account_id = jl.account_id
WHERE jl.journal_entry_id = %s
""", (journal_entry_id,))
lines = cur.fetchall()
return {
"entry_date": header[0],
"description": header[1],
"lines": [
{
"account_id": l[0],
"account_code": l[1],
"account_name": l[2],
"debit": l[3],
"credit": l[4]
} for l in lines
]
}
# -------------------------------------------------
# 逆仕訳生成(修正仕訳)
# -------------------------------------------------
@router.post("/{journal_entry_id}/reverse", summary="逆仕訳生成")
def reverse_journal_entry(journal_entry_id: int):
with get_connection() as conn, conn.cursor() as cur:
# 元仕訳取得
cur.execute("""
SELECT entry_date, description
FROM journal_entries
WHERE journal_entry_id = %s
""", (journal_entry_id,))
header = cur.fetchone()
if not header:
raise HTTPException(status_code=404, detail="仕訳が存在しません")
entry_date, description = header
cur.execute("""
SELECT account_id, debit, credit
FROM journal_lines
WHERE journal_entry_id = %s
""", (journal_entry_id,))
lines = cur.fetchall()
if not lines:
raise HTTPException(status_code=400, detail="仕訳明細が存在しません")
# 逆仕訳ヘッダ
cur.execute("""
INSERT INTO journal_entries (entry_date, description)
VALUES (%s, %s)
RETURNING journal_entry_id
""", (entry_date, f"【修正】{description}"))
new_entry_id = cur.fetchone()[0]
# 逆仕訳明細(借贷反転)
for account_id, debit, credit in lines:
cur.execute("""
INSERT INTO journal_lines
(journal_entry_id, account_id, debit, credit)
VALUES (%s, %s, %s, %s)
""", (
new_entry_id,
account_id,
credit,
debit
))
return {
"message": "逆仕訳を作成しました",
"reversed_journal_entry_id": new_entry_id
}