from sqlalchemy import Column, Integer, String, DateTime, Date, Float, Boolean, Text, Index, ForeignKey, Enum from sqlalchemy.orm import relationship from sqlalchemy.sql import func from app.models.base import BaseModel import enum class BillingBatch(BaseModel): __tablename__ = "billing_batches" id = Column(Integer, primary_key=True, index=True) batch_id = Column(String(100), unique=True, nullable=False, index=True) status = Column(String(32), nullable=False) total_files = Column(Integer, nullable=False, default=0) successful_files = Column(Integer, nullable=False, default=0) failed_files = Column(Integer, nullable=False, default=0) started_at = Column(DateTime(timezone=True), nullable=False) updated_at = Column(DateTime(timezone=True)) completed_at = Column(DateTime(timezone=True)) processing_time_seconds = Column(Float) success_rate = Column(Float) error_message = Column(Text) __table_args__ = ( Index("ix_billing_batches_started_at", "started_at"), Index("ix_billing_batches_updated_at", "updated_at"), Index("ix_billing_batches_completed_at", "completed_at"), {}, ) class BillingBatchFile(BaseModel): __tablename__ = "billing_batch_files" id = Column(Integer, primary_key=True, index=True) batch_id = Column(String(100), nullable=False, index=True) file_no = Column(String(50), nullable=False, index=True) status = Column(String(32), nullable=False) error_message = Column(Text) filename = Column(String(255)) size = Column(Integer) started_at = Column(DateTime(timezone=True)) completed_at = Column(DateTime(timezone=True)) __table_args__ = ( Index("ix_billing_batch_files_batch_file", "batch_id", "file_no"), {}, ) class StatementStatus(str, enum.Enum): """Statement status enumeration""" DRAFT = "draft" PENDING_APPROVAL = "pending_approval" APPROVED = "approved" SENT = "sent" PAID = "paid" CANCELLED = "cancelled" class StatementTemplate(BaseModel): """ Templates for billing statement generation Allows customization of statement format, footer text, etc. """ __tablename__ = "statement_templates" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(100), nullable=False, unique=True) description = Column(Text) # Template content header_template = Column(Text) # HTML/Jinja2 template for header footer_template = Column(Text) # HTML/Jinja2 template for footer css_styles = Column(Text) # Custom CSS for statement styling # Template settings is_default = Column(Boolean, default=False) is_active = Column(Boolean, default=True) # Metadata created_at = Column(DateTime, default=func.now()) updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) created_by = Column(String(50)) # Relationships statements = relationship("BillingStatement", back_populates="template") def __repr__(self): return f"" class BillingStatement(BaseModel): """ Generated billing statements for files/clients Tracks statement metadata, status, and generation details """ __tablename__ = "billing_statements" id = Column(Integer, primary_key=True, autoincrement=True) statement_number = Column(String(50), unique=True, nullable=False) # Unique statement identifier # File/Client reference file_no = Column(String(45), ForeignKey("files.file_no"), nullable=False) customer_id = Column(String(45), ForeignKey("rolodex.id")) # Optional direct customer reference # Statement period period_start = Column(Date, nullable=False) period_end = Column(Date, nullable=False) statement_date = Column(Date, nullable=False, default=func.current_date()) due_date = Column(Date) # Financial totals previous_balance = Column(Float, default=0.0) current_charges = Column(Float, default=0.0) payments_credits = Column(Float, default=0.0) total_due = Column(Float, nullable=False) # Trust account information trust_balance = Column(Float, default=0.0) trust_applied = Column(Float, default=0.0) # Statement details status = Column(Enum(StatementStatus), default=StatementStatus.DRAFT) template_id = Column(Integer, ForeignKey("statement_templates.id")) # Generated content html_content = Column(Text) # Generated HTML content pdf_path = Column(String(500)) # Path to generated PDF file # Billing metadata billed_transaction_count = Column(Integer, default=0) approved_by = Column(String(50)) # User who approved the statement approved_at = Column(DateTime) sent_by = Column(String(50)) # User who sent the statement sent_at = Column(DateTime) # Metadata created_at = Column(DateTime, default=func.now()) updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) created_by = Column(String(50)) # Notes and customization custom_footer = Column(Text) # Override template footer for this statement internal_notes = Column(Text) # Internal notes not shown to client # Relationships file = relationship("File", back_populates="billing_statements") customer = relationship("Rolodex", back_populates="billing_statements") template = relationship("StatementTemplate", back_populates="statements") statement_items = relationship("BillingStatementItem", back_populates="statement", cascade="all, delete-orphan") def __repr__(self): return f"" class BillingStatementItem(BaseModel): """ Individual line items on a billing statement Links to ledger entries that were included in the statement """ __tablename__ = "billing_statement_items" id = Column(Integer, primary_key=True, autoincrement=True) statement_id = Column(Integer, ForeignKey("billing_statements.id"), nullable=False) ledger_id = Column(Integer, ForeignKey("ledger.id"), nullable=False) # Item display details (cached from ledger for statement consistency) date = Column(Date, nullable=False) description = Column(Text) quantity = Column(Float, default=0.0) rate = Column(Float, default=0.0) amount = Column(Float, nullable=False) # Item categorization item_category = Column(String(50)) # fees, costs, payments, adjustments # Metadata created_at = Column(DateTime, default=func.now()) # Relationships statement = relationship("BillingStatement", back_populates="statement_items") ledger_entry = relationship("Ledger") def __repr__(self): return f"" class StatementPayment(BaseModel): """ Payments applied to billing statements Tracks payment history and application """ __tablename__ = "statement_payments" id = Column(Integer, primary_key=True, autoincrement=True) statement_id = Column(Integer, ForeignKey("billing_statements.id"), nullable=False) # Payment details payment_date = Column(Date, nullable=False) payment_amount = Column(Float, nullable=False) payment_method = Column(String(50)) # check, credit_card, trust_transfer, etc. reference_number = Column(String(100)) # check number, transaction ID, etc. # Application details applied_to_fees = Column(Float, default=0.0) applied_to_costs = Column(Float, default=0.0) applied_to_trust = Column(Float, default=0.0) # Metadata created_at = Column(DateTime, default=func.now()) created_by = Column(String(50)) notes = Column(Text) # Relationships statement = relationship("BillingStatement") def __repr__(self): return f""