20251215滴修正
This commit is contained in:
232
backend/app/routers/journal_entries.py
Normal file
232
backend/app/routers/journal_entries.py
Normal file
@@ -0,0 +1,232 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user