"""Setup progress + document number sequences API.""" from __future__ import annotations from uuid import UUID from fastapi import APIRouter, Depends, Query from pydantic import BaseModel, Field 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.foundation import ( AccountRepository, ChartOfAccountsRepository, CurrencyRepository, FiscalPeriodRepository, FiscalYearRepository, ) from app.repositories.posting import VoucherRepository from app.schemas.foundation import SetupStatusRead from app.services.document_number_service import DocumentNumberService from shared.security import CurrentUser router = APIRouter() class AllocateRequest(BaseModel): document_type: str = Field(min_length=1, max_length=50) class SequencePreview(BaseModel): document_type: str prefix: str next_number: int padding: int preview: str @router.get("/status", response_model=SetupStatusRead) async def setup_status( tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), _user: CurrentUser = Depends(get_current_user), ): charts = await ChartOfAccountsRepository(db).list_by_tenant(tenant_id, limit=1) accounts = await AccountRepository(db).list_by_tenant(tenant_id, limit=1) currencies = await CurrencyRepository(db).list_by_tenant(tenant_id, limit=100) years = await FiscalYearRepository(db).list_by_tenant(tenant_id, limit=1) periods = await FiscalPeriodRepository(db).list_by_tenant(tenant_id, limit=1) has_cash = False has_customer = False try: from app.api.v1.treasury import CashBoxRepo from app.api.v1.receivable_payable import CustomerRepo has_cash = bool(await CashBoxRepo(db).list_by_tenant(tenant_id, limit=1)) has_customer = bool(await CustomerRepo(db).list_by_tenant(tenant_id, limit=1)) except Exception: pass has_voucher = bool(await VoucherRepository(db).list_by_tenant(tenant_id, limit=1)) flags = { "has_chart": bool(charts), "has_accounts": bool(accounts), "has_currency": bool(currencies), "has_base_currency": any(c.is_base for c in currencies), "has_fiscal_year": bool(years), "has_fiscal_period": bool(periods), "has_cash_box": has_cash, "has_customer": has_customer, "has_voucher": has_voucher, } completed = sum(1 for v in flags.values() if v) total = len(flags) return SetupStatusRead( **flags, completed_steps=completed, total_steps=total, percent=round(100 * completed / total) if total else 0, ) @router.get("/sequences/next", response_model=SequencePreview) async def peek_next_number( document_type: str = Query(min_length=1, max_length=50), tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), _user: CurrentUser = Depends(get_current_user), ): """Preview next number without consuming it (for form autofill).""" svc = DocumentNumberService(db) data = await svc.peek(tenant_id, document_type) await db.commit() return SequencePreview(**data) @router.post("/sequences/allocate") async def allocate_number( body: AllocateRequest, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), _user: CurrentUser = Depends(get_current_user), ): """Consume and return the next number for a document type.""" svc = DocumentNumberService(db) number = await svc.allocate(tenant_id, body.document_type) await db.commit() return {"document_type": body.document_type, "number": number} class PostingPolicyUpdate(BaseModel): auto_post: dict[str, bool] | None = None require_balanced: bool | None = None require_source_reference: bool | None = None lock_after_post: bool | None = None sequential_numbers: bool | None = None idempotent_source: bool | None = None posting_mode: str | None = None @router.get("/posting-policy") async def get_posting_policy( tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), _user: CurrentUser = Depends(get_current_user), ): from app.services.posting_policy_service import PostingPolicyService return await PostingPolicyService(db).get_policy(tenant_id) @router.put("/posting-policy") async def update_posting_policy( body: PostingPolicyUpdate, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), _user: CurrentUser = Depends(get_current_user), ): from app.services.posting_policy_service import PostingPolicyService policy = await PostingPolicyService(db).update_policy( tenant_id, body.model_dump(exclude_none=True) ) await db.commit() return policy