Wire DocumentNumberSequence allocator, ledger-based subsidiary/detail reports, and posted-voucher reopen flow without mock ops forms. Co-authored-by: Cursor <cursoragent@cursor.com>
111 lines
3.7 KiB
Python
111 lines
3.7 KiB
Python
"""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}
|