from pydantic import BaseModel from typing import Optional, List from datetime import datetime from enum import Enum # ==================== ENUMS ==================== class OrderStatus(str, Enum): DRAFT = "draft" CONFIRMED = "confirmed" PARTIALLY_RECEIVED = "partially_received" RECEIVED = "received" CANCELLED = "cancelled" class OrderType(str, Enum): SALES = "sales" PURCHASE = "purchase" class DocumentType(str, Enum): """Tipi documento di vendita (Mago4: MA_SaleDoc.DocumentType)""" IMMEDIATE_INVOICE = "immediate_invoice" # Fattura Immediata DELIVERY_NOTE = "delivery_note" # Documento di Trasporto (DDT) ACCOMPANYING_INVOICE = "accompanying_invoice" # Fattura Accompagnatoria PROFORMA_INVOICE = "proforma_invoice" # Fattura ProForma CREDIT_NOTE = "credit_note" # Nota di Credito DEBIT_NOTE = "debit_note" # Nota di Debito DOCUMENT_TYPE_LABELS = { DocumentType.IMMEDIATE_INVOICE: "Fattura Immediata", DocumentType.DELIVERY_NOTE: "Documento di Trasporto", DocumentType.ACCOMPANYING_INVOICE: "Fattura Accompagnatoria", DocumentType.PROFORMA_INVOICE: "Fattura ProForma", DocumentType.CREDIT_NOTE: "Nota di Credito", DocumentType.DEBIT_NOTE: "Nota di Debito", } class DocumentStatus(str, Enum): """Stato documento (Mago4: Printed / Issued / PostedToInventory)""" DRAFT = "draft" # Bozza CONFIRMED = "confirmed" # Confermato PRINTED = "printed" # Stampato ISSUED = "issued" # Emesso (definitivo) POSTED = "posted" # Contabilizzato DOCUMENT_STATUS_LABELS = { DocumentStatus.DRAFT: "Bozza", DocumentStatus.CONFIRMED: "Confermato", DocumentStatus.PRINTED: "Stampato", DocumentStatus.ISSUED: "Emesso", DocumentStatus.POSTED: "Contabilizzato", } class MovementSign(str, Enum): """Segno movimento di magazzino (Mago4: InventoryReasons.DebitCreditSign)""" LOAD = "load" # Carico (+) UNLOAD = "unload" # Scarico (-) # ==================== ANAGRAFICHE ==================== class Customer(BaseModel): id: int code: str name: str email: str phone: str address: str city: str country: str vat_number: str = "" # Partita IVA fiscal_code: str = "" # Codice Fiscale payment_terms: str = "RB 30 gg" # Pagamento credit_limit: float = 0.0 balance: float = 0.0 class Supplier(BaseModel): id: int code: str name: str email: str phone: str address: str city: str country: str vat_number: str = "" payment_terms_days: int = 30 balance: float = 0.0 class Product(BaseModel): id: int code: str description: str category: str uom: str = "NR" # Unità di misura unit_price: float = 0.0 # Prezzo di vendita purchase_price: float = 0.0 # Prezzo di acquisto / costo tax_rate: float = 22.0 # Aliquota IVA % quantity_in_stock: int = 0 # Giacenza (On-hand) reorder_level: int = 0 # Scorta minima (Min. Stock) max_stock: int = 0 # Scorta massima supplier_id: Optional[int] = None # ==================== ORDINI ==================== class OrderLine(BaseModel): id: int product_id: int product_code: str product_description: str quantity: float unit_price: float total_amount: float class SalesOrder(BaseModel): id: int order_number: str order_date: datetime customer_id: int customer_name: str status: OrderStatus lines: List[OrderLine] total_amount: float delivery_date: Optional[datetime] = None class PurchaseOrder(BaseModel): id: int order_number: str order_date: datetime supplier_id: int supplier_name: str status: OrderStatus lines: List[OrderLine] total_amount: float expected_delivery_date: Optional[datetime] = None # ==================== FATTURAZIONE (InvoiceMng) ==================== class InvoiceLine(BaseModel): """Riga documento (Mago4: MA_SaleDocDetail)""" line_no: int item_code: str description: str uom: str = "NR" quantity: float = 1.0 unit_value: float = 0.0 # Valore unitario discount_pct: float = 0.0 # Sconto % net_price: float = 0.0 # Prezzo netto unitario taxable_amount: float = 0.0 # Imponibile riga tax_rate: float = 22.0 # Aliquota IVA tax_amount: float = 0.0 # Imposta riga class Invoice(BaseModel): """Documento di vendita (Mago4: MA_SaleDoc)""" id: int doc_type: DocumentType doc_no: str # Numero documento doc_date: datetime # Data documento posting_date: datetime # Data registrazione customer_id: int customer_name: str customer_vat: str = "" our_reference: str = "" # Nostro riferimento your_reference: str = "" # Vostro riferimento payment_terms: str = "RB 30 gg" price_list: str = "Listino Base" currency: str = "EUR" salesperson: str = "" # Agente net_of_tax: bool = False # Prezzi IVA inclusa lines: List[InvoiceLine] = [] taxable_total: float = 0.0 # Totale imponibile tax_total: float = 0.0 # Totale imposta total: float = 0.0 # Totale documento status: DocumentStatus = DocumentStatus.DRAFT printed: bool = False issued: bool = False posted_to_inventory: bool = False notes: str = "" # ==================== MAGAZZINO (Inventory) ==================== class InventoryReason(BaseModel): """Causale di magazzino (Mago4: MA_InventoryReasons)""" code: str # Causale (es. CAR, SCA) description: str sign: MovementSign # Carico / Scarico fiscal: bool = False # Numerazione fiscale updates_balance: bool = True # Aggiorna giacenze class InventoryMovementLine(BaseModel): """Riga movimento (Mago4: MA_InventoryEntriesDetail)""" line_no: int item_code: str description: str uom: str = "NR" quantity: float = 0.0 unit_value: float = 0.0 line_amount: float = 0.0 lot: str = "" # Lotto location: str = "" # Ubicazione class InventoryMovement(BaseModel): """Movimento di magazzino (Mago4: MA_InventoryEntries)""" id: int reason_code: str # Causale reason_description: str sign: MovementSign doc_no: str doc_date: datetime posting_date: datetime storage: str = "MAG01" # Deposito custsupp_name: str = "" # Cliente/Fornitore lines: List[InventoryMovementLine] = [] total_amount: float = 0.0 posted: bool = False # Movimento confermato (aggiorna giacenze) notes: str = "" # ==================== SCADENZARIO (AP_AR — Partite/Effetti) ==================== class PaymentScheduleType(str, Enum): """Tipo partita (Mago4: DebitCreditSign)""" RECEIVABLE = "receivable" # Credito / Incasso da ricevere PAYABLE = "payable" # Debito / Pagamento da effettuare PAYMENT_SCHEDULE_TYPE_LABELS = { PaymentScheduleType.RECEIVABLE: "Incasso", PaymentScheduleType.PAYABLE: "Pagamento", } class PaymentSchedule(BaseModel): """Partita aperta / scadenza (Mago4: MA_PyblsRcvbls + Details)""" id: int type: PaymentScheduleType custsupp_name: str # Cliente o Fornitore doc_no: str # Numero documento di origine doc_date: datetime due_date: datetime # Data scadenza (InstallmentDate) installment_no: int = 1 # Numero rata payment_method: str = "RB" # Modalità (RB, Bonifico, RID...) amount: float = 0.0 # Importo rata is_credit_note: bool = False # Nota di credito (segno opposto) closed: bool = False # Saldata / Incassata (Closed/Collected) bank: str = "" # Banca di presentazione notes: str = "" @property def signed_amount(self) -> float: return -self.amount if self.is_credit_note else self.amount # ==================== UI / DASHBOARD ==================== class DashboardWidget(BaseModel): title: str subtitle: Optional[str] = None style: str size: str = "small" icon: Optional[str] = None color: Optional[str] = None value: Optional[str] = None format: Optional[str] = None data: Optional[dict] = None