TorbatYar/backend/services/accounting/app/api/v1/ledger.py
Mortezakoohjani 12c8615615 Ship enterprise Accounting FE/API with CRUD parity and production wiring.
Adds accounting-service PATCH/archive, fiscal helpers, COA templates and setup status, plus SuperApp Accounting UI (DS, scoreboard, masters, vouchers, ledger, ops modules) with session refresh and HTTPS public API URLs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 15:26:43 +03:30

94 lines
2.9 KiB
Python

"""General Ledger & Trial Balance API — Phase 5.3."""
from __future__ import annotations
from decimal import Decimal
from uuid import UUID
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.deps import get_db, require_tenant
from app.core.security import get_current_user
from app.repositories.ledger import LedgerBalanceRepository
from app.services.balance_engine import BalanceEngine
from shared.security import CurrentUser
router = APIRouter()
class BalanceRead(BaseModel):
account_id: UUID
fiscal_period_id: UUID
opening_balance: Decimal
debit_total: Decimal
credit_total: Decimal
closing_balance: Decimal
current_balance: Decimal
model_config = {"from_attributes": True}
class TrialBalanceRead(BaseModel):
fiscal_period_id: UUID
total_debit: Decimal
total_credit: Decimal
difference: Decimal
is_balanced: bool
@router.get("/balances/{account_id}", response_model=BalanceRead | None)
async def get_account_balance(
account_id: UUID,
fiscal_period_id: UUID,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
repo = LedgerBalanceRepository(db)
balance = await repo.get_by_account_period(tenant_id, account_id, fiscal_period_id)
return balance
@router.get("/balances", response_model=list[BalanceRead])
async def list_period_balances(
fiscal_period_id: UUID,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
repo = LedgerBalanceRepository(db)
return await repo.list_by_period(tenant_id, fiscal_period_id)
@router.post("/trial-balance", response_model=TrialBalanceRead)
async def generate_trial_balance(
fiscal_period_id: UUID,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
engine = BalanceEngine(db)
snapshot = await engine.generate_trial_balance(tenant_id, fiscal_period_id)
await db.commit()
return TrialBalanceRead(
fiscal_period_id=snapshot.fiscal_period_id,
total_debit=snapshot.total_debit,
total_credit=snapshot.total_credit,
difference=snapshot.difference,
is_balanced=snapshot.is_balanced,
)
@router.post("/recalculate")
async def recalculate_balances(
fiscal_period_id: UUID,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
engine = BalanceEngine(db)
count = await engine.recalculate_period(tenant_id, fiscal_period_id)
await db.commit()
return {"recalculated_accounts": count}