This commit is contained in:
HotSwapp
2025-08-16 10:05:42 -05:00
parent 0347284556
commit ae4484381f
15 changed files with 3966 additions and 77 deletions

View File

@@ -1,5 +1,8 @@
from sqlalchemy import Column, Integer, String, DateTime, Float, Text, Index
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):
@@ -45,3 +48,174 @@ class BillingBatchFile(BaseModel):
)
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"<StatementTemplate(name='{self.name}', default={self.is_default})>"
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"<BillingStatement(number='{self.statement_number}', file_no='{self.file_no}', total={self.total_due})>"
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"<BillingStatementItem(statement_id={self.statement_id}, amount={self.amount})>"
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"<StatementPayment(statement_id={self.statement_id}, amount={self.payment_amount})>"