diff --git a/backend/app/__pycache__/main.cpython-312.pyc b/backend/app/__pycache__/main.cpython-312.pyc index 8c1b95b..bcc55dc 100644 Binary files a/backend/app/__pycache__/main.cpython-312.pyc and b/backend/app/__pycache__/main.cpython-312.pyc differ diff --git a/backend/app/main.py b/backend/app/main.py index 23ed1f8..0e5af8d 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -42,3 +42,6 @@ app.include_router(ledger_router) from app.modules.trial_balance.router import router as trial_balance_router app.include_router(trial_balance_router) +from app.modules.opening_balances.router import router as opening_router +app.include_router(opening_router) + diff --git a/backend/app/modules/journals/schemas.py b/backend/app/modules/journals/schemas.py new file mode 100644 index 0000000..f131588 --- /dev/null +++ b/backend/app/modules/journals/schemas.py @@ -0,0 +1,29 @@ +# app/modules/journals/schemas.py +from pydantic import BaseModel, field_validator +from typing import List +from decimal import Decimal +from datetime import date + +class JournalLineCreate(BaseModel): + account_id: int + debit: Decimal = 0 + credit: Decimal = 0 + + @field_validator("debit", "credit") + @classmethod + def non_negative(cls, v): + if v < 0: + raise ValueError("金額は0以上で入力してください") + return v + +class JournalCreate(BaseModel): + journal_date: date + description: str + lines: List[JournalLineCreate] + + @field_validator("lines") + @classmethod + def validate_lines(cls, v): + if len(v) < 2: + raise ValueError("仕訳明細は2行以上必要です") + return v diff --git a/backend/app/modules/opening_balances/__init__.py b/backend/app/modules/opening_balances/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/modules/opening_balances/__pycache__/__init__.cpython-312.pyc b/backend/app/modules/opening_balances/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..61681c5 Binary files /dev/null and b/backend/app/modules/opening_balances/__pycache__/__init__.cpython-312.pyc differ diff --git a/backend/app/modules/opening_balances/__pycache__/router.cpython-312.pyc b/backend/app/modules/opening_balances/__pycache__/router.cpython-312.pyc new file mode 100644 index 0000000..6a6fd76 Binary files /dev/null and b/backend/app/modules/opening_balances/__pycache__/router.cpython-312.pyc differ diff --git a/backend/app/modules/opening_balances/__pycache__/schema.cpython-312.pyc b/backend/app/modules/opening_balances/__pycache__/schema.cpython-312.pyc new file mode 100644 index 0000000..14d10b2 Binary files /dev/null and b/backend/app/modules/opening_balances/__pycache__/schema.cpython-312.pyc differ diff --git a/backend/app/modules/opening_balances/router.py b/backend/app/modules/opening_balances/router.py new file mode 100644 index 0000000..4b40f4a --- /dev/null +++ b/backend/app/modules/opening_balances/router.py @@ -0,0 +1,55 @@ +# backend/app/modules/opening_balances/router.py +from fastapi import APIRouter, HTTPException +from psycopg.errors import ForeignKeyViolation +from app.core.database import get_connection +from app.modules.opening_balances.schema import OpeningBalanceCreate + +router = APIRouter( + prefix="/opening-balances", + tags=["期首残高"] +) + +@router.post("/", summary="期首残高 登録/更新") +def upsert_opening_balance(data: OpeningBalanceCreate): + try: + with get_connection() as conn, conn.cursor() as cur: + cur.execute(""" + INSERT INTO opening_balances + (fiscal_year, account_id, opening_debit, opening_credit) + VALUES (%s, %s, %s, %s) + ON CONFLICT (fiscal_year, account_id) + DO UPDATE SET + opening_debit = EXCLUDED.opening_debit, + opening_credit = EXCLUDED.opening_credit + """, ( + data.fiscal_year, + data.account_id, + data.opening_debit, + data.opening_credit + )) + conn.commit() + return {"result": "ok"} + + except ForeignKeyViolation: + raise HTTPException( + status_code=400, + detail="存在しない科目が指定されています" + ) + + +@router.get("/{fiscal_year}", summary="期首残高一覧取得") +def list_opening_balances(fiscal_year: int): + with get_connection() as conn, conn.cursor() as cur: + cur.execute(""" + SELECT + a.account_code, + a.account_name, + a.account_type, + ob.opening_debit, + ob.opening_credit + FROM opening_balances ob + JOIN accounts a ON a.account_id = ob.account_id + WHERE ob.fiscal_year = %s + ORDER BY a.account_code + """, (fiscal_year,)) + return cur.fetchall() diff --git a/backend/app/modules/opening_balances/schema.py b/backend/app/modules/opening_balances/schema.py new file mode 100644 index 0000000..0133545 --- /dev/null +++ b/backend/app/modules/opening_balances/schema.py @@ -0,0 +1,25 @@ +# backend/app/modules/opening_balances/schema.py +from pydantic import BaseModel, field_validator + +class OpeningBalanceCreate(BaseModel): + fiscal_year: int + account_id: int + opening_debit: float = 0 + opening_credit: float = 0 + + @field_validator("fiscal_year") + @classmethod + def validate_year(cls, v: int): + if v < 2000 or v > 2100: + raise ValueError("会计年度不正确") + return v + + @field_validator("opening_credit") + @classmethod + def validate_balance(cls, v, info): + debit = info.data.get("opening_debit", 0) + if debit > 0 and v > 0: + raise ValueError("借方和贷方不能同时输入金额") + if debit < 0 or v < 0: + raise ValueError("金额不能为负数") + return v