""" ORM models per rendicontazione-api. Schema: gepafin_rendic (stesso DB del BE Gepafin sandbox). """ import uuid from datetime import datetime from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, UniqueConstraint, Numeric, Boolean, Date from sqlalchemy.dialects.postgresql import UUID, JSONB from sqlalchemy.sql import func from sqlalchemy.orm import relationship from .db import Base class CallRemissionSchema(Base): """ Schema di rendicontazione per un bando. Uno per call_id. status: DRAFT (modificabile) -> PUBLISHED (visibile ai beneficiari). """ __tablename__ = "call_remission_schema" __table_args__ = ( UniqueConstraint("call_id", name="uq_call_remission_schema_call_id"), {"schema": "gepafin_rendic"}, ) id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) call_id = Column(Integer, nullable=False, unique=True) schema_version = Column(Integer, nullable=False, default=1) status = Column(String(32), nullable=False, default="DRAFT") schema_json = Column(JSONB, nullable=False) created_by = Column(Integer, nullable=False) created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now()) updated_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()) published_at = Column(DateTime(timezone=True), nullable=True) published_by = Column(Integer, nullable=True) class RemissionPractice(Base): """ Pratica di rendicontazione di un beneficiario per una specifica application in CONTRACT_SIGNED. Uno schema_snapshot congelato alla creazione: se il superadmin modifica lo schema del bando dopo, la pratica continua a usare la versione snapshot. """ __tablename__ = "remission_practice" __table_args__ = ( UniqueConstraint("application_id", name="uq_remission_practice_application"), {"schema": "gepafin_rendic"}, ) id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) call_id = Column(Integer, nullable=False) application_id = Column(Integer, nullable=False, unique=True) company_id = Column(Integer, nullable=False) user_id = Column(Integer, nullable=False) # beneficiario che compila status = Column(String(32), nullable=False, default="DRAFT") # DRAFT -> SUBMITTED -> UNDER_REVIEW -> APPROVED | REJECTED | AWAITING_AMENDMENT schema_snapshot = Column(JSONB, nullable=False) # copia schema al momento start iva_regime = Column(String(32), nullable=True) # ORDINARIO | FORFETTARIO | ESENTE amount_erogato = Column(Numeric(14, 2), nullable=False) # copiato da application.amount_accepted notes_beneficiario = Column(Text, nullable=True) # colonne istruttoria assigned_instructor_id = Column(Integer, nullable=True) reviewed_at = Column(DateTime(timezone=True), nullable=True) reviewed_by = Column(Integer, nullable=True) rejection_reason = Column(Text, nullable=True) approved_remission = Column(Numeric(14, 2), nullable=True) created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now()) updated_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()) submitted_at = Column(DateTime(timezone=True), nullable=True) # relazioni invoices = relationship("RemissionInvoice", back_populates="practice", cascade="all, delete-orphan") ula_employees = relationship("RemissionUlaEmployee", back_populates="practice", cascade="all, delete-orphan") documents = relationship("RemissionDocument", back_populates="practice", cascade="all, delete-orphan") amendment_requests = relationship("RemissionAmendmentRequest", back_populates="practice", cascade="all, delete-orphan") class RemissionInvoice(Base): """Fattura rendicontata dentro una pratica, assegnata a una categoria.""" __tablename__ = "remission_invoice" __table_args__ = ({"schema": "gepafin_rendic"},) id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) practice_id = Column(UUID(as_uuid=True), ForeignKey("gepafin_rendic.remission_practice.id", ondelete="CASCADE"), nullable=False) category_code = Column(String(16), nullable=False) # B1 / B2 / B3 / custom invoice_number = Column(String(128), nullable=False) invoice_date = Column(Date, nullable=False) payment_date = Column(Date, nullable=False) supplier_name = Column(String(255), nullable=False) supplier_vat = Column(String(32), nullable=False) description = Column(Text, nullable=False) taxable = Column(Numeric(14, 2), nullable=False) # imponibile vat = Column(Numeric(14, 2), nullable=False, default=0) total = Column(Numeric(14, 2), nullable=False) pdf_filename = Column(String(512), nullable=True) # per ora solo nome, upload vero dopo created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now()) practice = relationship("RemissionPractice", back_populates="invoices") class RemissionUlaEmployee(Base): """Dipendente conteggiato nel calcolo ULA.""" __tablename__ = "remission_ula_employee" __table_args__ = ({"schema": "gepafin_rendic"},) id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) practice_id = Column(UUID(as_uuid=True), ForeignKey("gepafin_rendic.remission_practice.id", ondelete="CASCADE"), nullable=False) codice_fiscale = Column(String(16), nullable=False) full_name = Column(String(255), nullable=False) contract_type = Column(String(64), nullable=False) # T_IND / T_DET / APPR / ... role_description = Column(String(255), nullable=True) fte_pct = Column(Numeric(5, 4), nullable=False, default=1) # 0..1 period_start_date = Column(Date, nullable=False) period_end_date = Column(Date, nullable=False) supporting_doc_type = Column(String(64), nullable=True) supporting_doc_filename = Column(String(512), nullable=True) created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now()) practice = relationship("RemissionPractice", back_populates="ula_employees") class RemissionDocument(Base): """Documento associato alla pratica (DURC, visura, ecc.).""" __tablename__ = "remission_document" __table_args__ = ({"schema": "gepafin_rendic"},) id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) practice_id = Column(UUID(as_uuid=True), ForeignKey("gepafin_rendic.remission_practice.id", ondelete="CASCADE"), nullable=False) doc_code = Column(String(64), nullable=False) # DURC / VISURA_CAMERALE / ... filename = Column(String(512), nullable=True) uploaded_at = Column(DateTime(timezone=True), nullable=True) expires_at = Column(Date, nullable=True) notes = Column(Text, nullable=True) practice = relationship("RemissionPractice", back_populates="documents") class RemissionAmendmentRequest(Base): """Richiesta di soccorso istruttorio: istruttore chiede integrazioni al beneficiario.""" __tablename__ = "remission_amendment_request" __table_args__ = ({"schema": "gepafin_rendic"},) id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) practice_id = Column(UUID(as_uuid=True), ForeignKey("gepafin_rendic.remission_practice.id", ondelete="CASCADE"), nullable=False) requested_by = Column(Integer, nullable=False) request_text = Column(Text, nullable=False) scope = Column(JSONB, nullable=True, default=dict) deadline = Column(Date, nullable=False) status = Column(String(32), nullable=False, default="AWAITING") # AWAITING -> RESPONSE_RECEIVED -> CLOSED | EXPIRED | REJECTED response_text = Column(Text, nullable=True) response_at = Column(DateTime(timezone=True), nullable=True) closed_at = Column(DateTime(timezone=True), nullable=True) closed_by = Column(Integer, nullable=True) created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now()) updated_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()) practice = relationship("RemissionPractice", back_populates="amendment_requests")