"""Pydantic schemas for receipts API.""" import json from datetime import datetime, date from decimal import Decimal from typing import Optional, List, Any, Union from pydantic import BaseModel, Field, ConfigDict, field_validator from backend.modules.data_entry.db.models.receipt import ReceiptType, ReceiptDirection, ReceiptStatus from backend.modules.data_entry.db.models.accounting_entry import EntryType # ============ Accounting Entry Schemas ============ class AccountingEntryBase(BaseModel): """Base schema for accounting entry.""" entry_type: EntryType account_code: str = Field(max_length=20) account_name: Optional[str] = Field(default=None, max_length=200) amount: Decimal partner_id: Optional[int] = None cost_center_id: Optional[int] = None class AccountingEntryCreate(AccountingEntryBase): """Schema for creating an accounting entry.""" pass class AccountingEntryUpdate(BaseModel): """Schema for updating an accounting entry.""" entry_type: Optional[EntryType] = None account_code: Optional[str] = Field(default=None, max_length=20) account_name: Optional[str] = Field(default=None, max_length=200) amount: Optional[Decimal] = None partner_id: Optional[int] = None cost_center_id: Optional[int] = None class AccountingEntryResponse(AccountingEntryBase): """Schema for accounting entry response.""" model_config = ConfigDict(from_attributes=True) id: int receipt_id: int is_auto_generated: bool modified_by: Optional[str] = None modified_at: Optional[datetime] = None sort_order: int # ============ Attachment Schemas ============ class AttachmentResponse(BaseModel): """Schema for attachment response.""" model_config = ConfigDict(from_attributes=True) id: int receipt_id: int filename: str stored_filename: str file_path: str file_size: int mime_type: str uploaded_at: datetime # ============ TVA Schema ============ class TvaEntrySchema(BaseModel): """Single TVA entry with code, percentage and amount.""" code: Optional[str] = Field(default=None, description="TVA code: A, B, C, D") percent: int = Field(description="TVA percentage: 0, 5, 9, 19, 21") amount: Decimal = Field(description="TVA amount for this rate") class PaymentMethodSchema(BaseModel): """Payment method entry (CARD/NUMERAR).""" method: str = Field(description="Payment method: CARD or NUMERAR") amount: Decimal = Field(description="Amount paid with this method") # ============ Receipt Schemas ============ class ReceiptBase(BaseModel): """Base schema for receipt.""" receipt_type: ReceiptType = ReceiptType.BON_FISCAL direction: ReceiptDirection = ReceiptDirection.CHELTUIALA receipt_number: Optional[str] = Field(default=None, max_length=50) receipt_series: Optional[str] = Field(default=None, max_length=20) receipt_date: date amount: Decimal = Field(gt=0) description: Optional[str] = Field(default=None, max_length=500) # TVA info (multiple entries support) tva_breakdown: Optional[List[TvaEntrySchema]] = Field(default=None, description="List of TVA entries") tva_total: Optional[Decimal] = Field(default=None, description="Total TVA amount") items_count: Optional[int] = Field(default=None, description="Number of items") vendor_address: Optional[str] = Field(default=None, max_length=500, description="Vendor address") # Other fields expense_type_code: Optional[str] = Field(default=None, max_length=20) company_id: int # partner_id removed - supplier data is text-only (partner_name, cui) partner_name: Optional[str] = Field(default=None, max_length=200) cui: Optional[str] = Field(default=None, max_length=20, description="Fiscal code (CUI) from OCR") ocr_raw_text: Optional[str] = Field(default=None, description="Raw OCR text for debugging") payment_methods: Optional[List[PaymentMethodSchema]] = Field(default=None, description="Payment methods from OCR") cash_register_id: Optional[int] = None cash_register_name: Optional[str] = Field(default=None, max_length=100) cash_register_account: Optional[str] = Field(default=None, max_length=20) payment_mode: Optional[str] = Field(default=None, description="Payment mode: casa/banca/avans_decontare") class ReceiptCreate(ReceiptBase): """Schema for creating a receipt.""" pass class ReceiptUpdate(BaseModel): """Schema for updating a receipt (DRAFT only).""" receipt_type: Optional[ReceiptType] = None direction: Optional[ReceiptDirection] = None receipt_number: Optional[str] = Field(default=None, max_length=50) receipt_series: Optional[str] = Field(default=None, max_length=20) receipt_date: Optional[date] = None amount: Optional[Decimal] = Field(default=None, gt=0) description: Optional[str] = Field(default=None, max_length=500) # TVA info (multiple entries support) tva_breakdown: Optional[List[TvaEntrySchema]] = Field(default=None, description="List of TVA entries") tva_total: Optional[Decimal] = Field(default=None, description="Total TVA amount") items_count: Optional[int] = Field(default=None, description="Number of items") vendor_address: Optional[str] = Field(default=None, max_length=500, description="Vendor address") # Other fields expense_type_code: Optional[str] = Field(default=None, max_length=20) # partner_id removed - supplier data is text-only (partner_name, cui) partner_name: Optional[str] = Field(default=None, max_length=200) cui: Optional[str] = Field(default=None, max_length=20, description="Fiscal code (CUI) from OCR") ocr_raw_text: Optional[str] = Field(default=None, description="Raw OCR text for debugging") payment_methods: Optional[List[PaymentMethodSchema]] = Field(default=None, description="Payment methods from OCR") cash_register_id: Optional[int] = None cash_register_name: Optional[str] = Field(default=None, max_length=100) cash_register_account: Optional[str] = Field(default=None, max_length=20) payment_mode: Optional[str] = Field(default=None, description="Payment mode: casa/banca/avans_decontare") class ReceiptResponse(ReceiptBase): """Schema for receipt response with all fields.""" model_config = ConfigDict(from_attributes=True) id: int # Override amount to allow zero values in response (validation is on input, not output) amount: Decimal status: ReceiptStatus created_by: str created_at: datetime updated_at: datetime submitted_at: Optional[datetime] = None reviewed_by: Optional[str] = None reviewed_at: Optional[datetime] = None rejection_reason: Optional[str] = None oracle_synced_at: Optional[datetime] = None oracle_act_id: Optional[int] = None oracle_error: Optional[str] = None # Relationships (optional, loaded when needed) attachments: List[AttachmentResponse] = [] entries: List[AccountingEntryResponse] = [] @field_validator('tva_breakdown', mode='before') @classmethod def parse_tva_breakdown(cls, v: Any) -> Optional[List[dict]]: """Deserialize tva_breakdown from JSON string if needed.""" if v is None: return None if isinstance(v, str): try: return json.loads(v) except (json.JSONDecodeError, TypeError): return None if isinstance(v, list): return v return None @field_validator('payment_methods', mode='before') @classmethod def parse_payment_methods(cls, v: Any) -> Optional[List[dict]]: """Deserialize payment_methods from JSON string if needed.""" if v is None: return None if isinstance(v, str): try: return json.loads(v) except (json.JSONDecodeError, TypeError): return None if isinstance(v, list): return v return None class ReceiptListResponse(BaseModel): """Schema for paginated receipt list response.""" items: List[ReceiptResponse] total: int page: int page_size: int pages: int class ReceiptFilter(BaseModel): """Schema for filtering receipts.""" status: Optional[ReceiptStatus] = None direction: Optional[ReceiptDirection] = None company_id: Optional[int] = None created_by: Optional[str] = None date_from: Optional[date] = None date_to: Optional[date] = None search: Optional[str] = None # Search in description, partner_name page: int = Field(default=1, ge=1) page_size: int = Field(default=20, ge=1, le=100) # ============ Workflow Schemas ============ class WorkflowAction(BaseModel): """Schema for workflow action response.""" success: bool message: str receipt: Optional[ReceiptResponse] = None class RejectRequest(BaseModel): """Schema for rejection request.""" reason: str = Field(min_length=5, max_length=500) class EntriesUpdateRequest(BaseModel): """Schema for bulk updating accounting entries.""" entries: List[AccountingEntryCreate] # ============ Nomenclature Schemas ============ class PartnerOption(BaseModel): """Schema for partner dropdown option (used for autocomplete assistance).""" name: str fiscal_code: Optional[str] = None address: Optional[str] = None source: str = "oracle" # 'oracle' (synced) or 'local' class AccountOption(BaseModel): """Schema for account dropdown option.""" code: str name: str class CashRegisterOption(BaseModel): """Schema for cash register dropdown option.""" id: int name: str account_code: str # 5311, 5121, etc. class ExpenseTypeOption(BaseModel): """Schema for expense type dropdown option.""" code: str name: str account_code: str has_vat: bool vat_percent: Decimal = Decimal("19")