Users now fill legal name, national ID, economic code, address and contacts instead of generic setting key/value ops documents. Co-authored-by: Cursor <cursoragent@cursor.com>
193 lines
6.3 KiB
Python
193 lines
6.3 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}
|
|
|
|
|
|
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
|
|
|
|
|
|
class CompanyProfileUpdate(BaseModel):
|
|
legal_name: str | None = None
|
|
trade_name: str | None = None
|
|
national_id: str | None = None
|
|
economic_code: str | None = None
|
|
registration_number: str | None = None
|
|
postal_code: str | None = None
|
|
province: str | None = None
|
|
city: str | None = None
|
|
address: str | None = None
|
|
phone: str | None = None
|
|
mobile: str | None = None
|
|
email: str | None = None
|
|
website: str | None = None
|
|
ceo_name: str | None = None
|
|
accountant_name: str | None = None
|
|
|
|
|
|
@router.get("/company-profile")
|
|
async def get_company_profile(
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
db: AsyncSession = Depends(get_db),
|
|
_user: CurrentUser = Depends(get_current_user),
|
|
):
|
|
from app.services.company_profile_service import CompanyProfileService
|
|
|
|
return await CompanyProfileService(db).get_profile(tenant_id)
|
|
|
|
|
|
@router.put("/company-profile")
|
|
async def update_company_profile(
|
|
body: CompanyProfileUpdate,
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
db: AsyncSession = Depends(get_db),
|
|
_user: CurrentUser = Depends(get_current_user),
|
|
):
|
|
from app.services.company_profile_service import CompanyProfileService
|
|
|
|
profile = await CompanyProfileService(db).update_profile(
|
|
tenant_id, body.model_dump(exclude_none=False)
|
|
)
|
|
await db.commit()
|
|
return profile
|