Files
Alby96andClaude Sonnet 4.6 b6b6759b9a Restyle UI to match Mago4 and add invoicing, warehouse, scadenzario
Rework the demo into a faithful Mago4 look-and-feel and expand it with
core ERP modules modelled on the real Mago4 database schema.

UI (ispirata a Mago4):
- Blue header with logo/search/operation date, desktop-style window tabs
- White sidebar with coloured module icons (Anagrafiche, Vendite, Acquisti,
  Amministrazione, Logistica)
- Card-based module landing pages with breadcrumb + tab strip
- Document detail forms with left tabs, line grid and totals box

Fatturazione (InvoiceMng / MA_SaleDoc):
- Document types (Fattura Immediata, DDT, Nota di Credito, ProForma)
- Lines with discount/IVA, automatic taxable/tax/total computation
- Document states (draft -> printed -> issued / posted_to_inventory)

Magazzino (Inventory / MA_InventoryEntries + MA_InventoryReasons):
- Inventory movements with load/unload reasons (DebitCreditSign)
- Posting a movement actually updates item balances
- Causali and stock valuation pages

Amministrazione / Scadenzario (AP_AR / MA_PyblsRcvbls):
- Open items (receivables/payables) with due date, method, state
- Settle action closes the item and recomputes the cash forecast

Dashboard riprogettata (focus su scadenze e to-do):
- Overdue alert banner, KPI for overdue payables/receivables
- Upcoming deadlines table, prioritised "Cose da fare" action list
- 30-day cash-flow forecast and customer credit-limit check

Adds REST endpoints for invoices, movements, schedules and dashboard data.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 11:47:32 +02:00

275 lines
8.4 KiB
Python

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