222 lines
8.0 KiB
Python
222 lines
8.0 KiB
Python
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"<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})>"
|
|
|
|
|