"""Receipt and ReceiptAttachment SQLModel models.""" from datetime import datetime, date from decimal import Decimal from enum import Enum from typing import Optional, List, TYPE_CHECKING from sqlmodel import SQLModel, Field, Relationship class ReceiptType(str, Enum): """Type of receipt document.""" BON_FISCAL = "bon_fiscal" CHITANTA = "chitanta" class ReceiptDirection(str, Enum): """Direction of receipt - expense or income.""" CHELTUIALA = "cheltuiala" # Expense (receipt from supplier) INCASARE = "incasare" # Income (receipt issued to client) class ReceiptStatus(str, Enum): """Workflow status of receipt.""" DRAFT = "draft" # User is filling in data PENDING_REVIEW = "pending_review" # Awaiting accountant approval APPROVED = "approved" # Approved by accountant REJECTED = "rejected" # Rejected by accountant SYNCED = "synced" # Synced to Oracle (Phase 2) class PaymentMode(str, Enum): """Payment mode - how the expense was paid.""" CASA = "casa" # Numerar firma (5311) BANCA = "banca" # Virament/POS (5121) AVANS_DECONTARE = "avans_decontare" # Decont angajat (542) class ProcessingStatus(str, Enum): """Processing status for bulk uploaded receipts.""" PENDING = "pending" # Waiting in queue PROCESSING = "processing" # Currently being processed by OCR COMPLETED = "completed" # Successfully processed FAILED = "failed" # Processing failed with error if TYPE_CHECKING: from .accounting_entry import AccountingEntry class Receipt(SQLModel, table=True): """Receipt (Bon Fiscal / Chitanta) with approval workflow.""" __tablename__ = "receipts" id: Optional[int] = Field(default=None, primary_key=True) # Document identification receipt_type: ReceiptType = Field(default=ReceiptType.BON_FISCAL) direction: ReceiptDirection = Field(default=ReceiptDirection.CHELTUIALA) receipt_number: Optional[str] = Field(default=None, max_length=50) receipt_series: Optional[str] = Field(default=None, max_length=20) # Main data receipt_date: date amount: Decimal = Field(decimal_places=2, max_digits=15) description: Optional[str] = Field(default=None, max_length=500) # TVA info (extracted from OCR) - stored as JSON for multiple entries tva_breakdown: Optional[str] = Field(default=None, max_length=1000) # JSON: [{"code":"A","percent":19,"amount":"15.20"}] tva_total: Optional[Decimal] = Field(default=None, decimal_places=2, max_digits=15) items_count: Optional[int] = Field(default=None) vendor_address: Optional[str] = Field(default=None, max_length=500) # Expense type (for auto-generating accounting entries) expense_type_code: Optional[str] = Field(default=None, max_length=20) # Oracle references (nomenclatures) company_id: int # partner_id removed - supplier data is text-only (partner_name, cui) partner_name: Optional[str] = Field(default=None, max_length=200) # Supplier name from OCR/selection cui: Optional[str] = Field(default=None, max_length=20) # Fiscal code from OCR ocr_raw_text: Optional[str] = Field(default=None) # Raw OCR text for debugging payment_methods: Optional[str] = Field(default=None, max_length=500) # JSON: [{"method":"CARD","amount":"50.00"}] cash_register_id: Optional[int] = Field(default=None) # Cash/Bank ID from Oracle cash_register_name: Optional[str] = Field(default=None, max_length=100) # Cache for display cash_register_account: Optional[str] = Field(default=None, max_length=20) # Account code (5311, 5121) payment_mode: Optional[str] = Field(default=None, max_length=20) # PaymentMode value: casa/banca/avans_decontare # Workflow status: ReceiptStatus = Field(default=ReceiptStatus.DRAFT) created_by: str = Field(max_length=100) # Username of creator created_at: datetime = Field(default_factory=datetime.utcnow) updated_at: datetime = Field(default_factory=datetime.utcnow) submitted_at: Optional[datetime] = Field(default=None) # When submitted for approval # Approval reviewed_by: Optional[str] = Field(default=None, max_length=100) # Accountant username reviewed_at: Optional[datetime] = Field(default=None) rejection_reason: Optional[str] = Field(default=None, max_length=500) # Reason for rejection # Phase 2 - Oracle sync oracle_synced_at: Optional[datetime] = Field(default=None) oracle_act_id: Optional[int] = Field(default=None) oracle_error: Optional[str] = Field(default=None, max_length=500) # Bulk upload batch tracking batch_id: Optional[str] = Field(default=None, max_length=50, index=True) processing_status: Optional[str] = Field(default=None, max_length=20, index=True) # ProcessingStatus enum value processing_error: Optional[str] = Field(default=None) # Full error message text file_hash: Optional[str] = Field(default=None, max_length=64, index=True) # SHA-256 hash for duplicate detection processing_started_at: Optional[datetime] = Field(default=None) processing_completed_at: Optional[datetime] = Field(default=None) # Relationships attachments: List["ReceiptAttachment"] = Relationship( back_populates="receipt", sa_relationship_kwargs={"cascade": "all, delete-orphan"} ) entries: List["AccountingEntry"] = Relationship( back_populates="receipt", sa_relationship_kwargs={"cascade": "all, delete-orphan"} ) class ReceiptAttachment(SQLModel, table=True): """Attachment (photo or PDF) for a receipt.""" __tablename__ = "receipt_attachments" id: Optional[int] = Field(default=None, primary_key=True) receipt_id: int = Field(foreign_key="receipts.id", index=True) # File info filename: str = Field(max_length=255) # Original filename stored_filename: str = Field(max_length=255) # Filename on disk (UUID) file_path: str = Field(max_length=500) # Relative path file_size: int # Size in bytes mime_type: str = Field(max_length=100) # MIME type (image/jpeg, application/pdf) uploaded_at: datetime = Field(default_factory=datetime.utcnow) # Relationship receipt: Optional[Receipt] = Relationship(back_populates="attachments")