- Extend Transaction with ledger fields (item_no, employee_number, t_code, t_type_l, quantity, rate, billed) - Startup SQLite migration to add missing columns on transactions - Ledger create/update/delete endpoints with validations and auto-compute Amount = Quantity × Rate - Uniqueness: ensure (transaction_date, item_no) per case by auto-incrementing - Compute case totals (billed/unbilled/overall) and display in case view - Update case.html for master-detail ledger UI; add client-side auto-compute JS - Enhance import_ledger_data to populate extended fields - Close/Reopen actions retained; case detail sorting by date/item - Auth: switch to pbkdf2_sha256 default (bcrypt fallback) and seed admin robustness Tested in Docker: health OK, login OK, import ROLODEX/FILES OK, ledger create persisted and totals displayed.
221 lines
7.4 KiB
Python
221 lines
7.4 KiB
Python
"""
|
|
SQLAlchemy models for the Delphi database.
|
|
|
|
All models inherit from Base which is configured in the database module.
|
|
"""
|
|
|
|
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Float, Text, Boolean
|
|
from sqlalchemy.orm import relationship
|
|
from sqlalchemy.ext.declarative import declarative_base
|
|
from sqlalchemy.sql import func
|
|
|
|
# Create Base for SQLAlchemy 1.x compatibility
|
|
Base = declarative_base()
|
|
|
|
|
|
class User(Base):
|
|
"""
|
|
User model for authentication.
|
|
|
|
Stores user credentials and basic information for login functionality.
|
|
"""
|
|
__tablename__ = "users"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
username = Column(String(50), unique=True, index=True, nullable=False)
|
|
password_hash = Column(String(255), nullable=False)
|
|
is_active = Column(Boolean, default=True)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
|
|
|
def __repr__(self):
|
|
return f"<User(id={self.id}, username='{self.username}')>"
|
|
|
|
|
|
class Client(Base):
|
|
"""
|
|
Client model representing individuals or entities.
|
|
|
|
Core client information imported from ROLODEX data.
|
|
"""
|
|
__tablename__ = "clients"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
rolodex_id = Column(String(20), unique=True, index=True)
|
|
last_name = Column(String(50))
|
|
first_name = Column(String(50))
|
|
middle_initial = Column(String(10))
|
|
company = Column(String(100))
|
|
address = Column(String(255))
|
|
city = Column(String(50))
|
|
state = Column(String(2))
|
|
zip_code = Column(String(10))
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
|
|
|
# Relationships
|
|
phones = relationship("Phone", back_populates="client")
|
|
cases = relationship("Case", back_populates="client")
|
|
|
|
def __repr__(self):
|
|
return f"<Client(id={self.id}, name='{self.first_name} {self.last_name}')>"
|
|
|
|
|
|
class Phone(Base):
|
|
"""
|
|
Phone number model linked to clients.
|
|
|
|
Stores phone number information for clients.
|
|
"""
|
|
__tablename__ = "phones"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
client_id = Column(Integer, ForeignKey("clients.id"), nullable=False)
|
|
phone_type = Column(String(20)) # home, work, mobile, fax, etc.
|
|
phone_number = Column(String(20))
|
|
extension = Column(String(10))
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
# Relationships
|
|
client = relationship("Client", back_populates="phones")
|
|
|
|
def __repr__(self):
|
|
return f"<Phone(id={self.id}, number='{self.phone_number}')>"
|
|
|
|
|
|
class Case(Base):
|
|
"""
|
|
Case model representing legal cases or files.
|
|
|
|
Main case information imported from FILES data.
|
|
"""
|
|
__tablename__ = "cases"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
file_no = Column(String(20), unique=True, index=True, nullable=False)
|
|
client_id = Column(Integer, ForeignKey("clients.id"), nullable=False)
|
|
status = Column(String(20), default="active")
|
|
case_type = Column(String(50))
|
|
description = Column(Text)
|
|
open_date = Column(DateTime(timezone=True))
|
|
close_date = Column(DateTime(timezone=True))
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
|
|
|
# Relationships
|
|
client = relationship("Client", back_populates="cases")
|
|
transactions = relationship("Transaction", back_populates="case")
|
|
documents = relationship("Document", back_populates="case")
|
|
payments = relationship("Payment", back_populates="case")
|
|
|
|
def __repr__(self):
|
|
return f"<Case(id={self.id}, file_no='{self.file_no}')>"
|
|
|
|
|
|
class Transaction(Base):
|
|
"""
|
|
Transaction model for financial transactions.
|
|
|
|
Records financial activities related to cases.
|
|
"""
|
|
__tablename__ = "transactions"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
case_id = Column(Integer, ForeignKey("cases.id"), nullable=False)
|
|
transaction_date = Column(DateTime(timezone=True))
|
|
# Legacy/basic fields
|
|
transaction_type = Column(String(20)) # Maps to legacy T_Type
|
|
amount = Column(Float)
|
|
description = Column(Text) # Maps to legacy Note
|
|
reference = Column(String(50)) # Previously used for Item_No
|
|
|
|
# Ledger-specific fields (added for File Cabinet MVP)
|
|
item_no = Column(Integer)
|
|
employee_number = Column(String(20)) # Empl_Num
|
|
t_code = Column(String(10)) # T_Code
|
|
t_type_l = Column(String(1)) # T_Type_L (Credit/Debit marker)
|
|
quantity = Column(Float)
|
|
rate = Column(Float)
|
|
billed = Column(String(1)) # 'Y' or 'N'
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
# Relationships
|
|
case = relationship("Case", back_populates="transactions")
|
|
|
|
def __repr__(self):
|
|
return f"<Transaction(id={self.id}, amount={self.amount})>"
|
|
|
|
|
|
class Document(Base):
|
|
"""
|
|
Document model for case-related documents.
|
|
|
|
Stores information about documents associated with cases.
|
|
"""
|
|
__tablename__ = "documents"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
case_id = Column(Integer, ForeignKey("cases.id"), nullable=False)
|
|
document_type = Column(String(50))
|
|
file_name = Column(String(255))
|
|
file_path = Column(String(500))
|
|
description = Column(Text)
|
|
uploaded_date = Column(DateTime(timezone=True))
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
# Relationships
|
|
case = relationship("Case", back_populates="documents")
|
|
|
|
def __repr__(self):
|
|
return f"<Document(id={self.id}, file_name='{self.file_name}')>"
|
|
|
|
|
|
class Payment(Base):
|
|
"""
|
|
Payment model for payment records.
|
|
|
|
Records payments made or received for cases.
|
|
"""
|
|
__tablename__ = "payments"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
case_id = Column(Integer, ForeignKey("cases.id"), nullable=False)
|
|
payment_date = Column(DateTime(timezone=True))
|
|
payment_type = Column(String(20))
|
|
amount = Column(Float)
|
|
description = Column(Text)
|
|
check_number = Column(String(20))
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
# Relationships
|
|
case = relationship("Case", back_populates="payments")
|
|
|
|
def __repr__(self):
|
|
return f"<Payment(id={self.id}, amount={self.amount})>"
|
|
|
|
|
|
class ImportLog(Base):
|
|
"""
|
|
ImportLog model for tracking CSV import operations.
|
|
|
|
Records the history and results of bulk data imports from legacy CSV files.
|
|
"""
|
|
__tablename__ = "import_logs"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
import_type = Column(String(50), nullable=False) # client, phone, case, transaction, document, payment
|
|
file_name = Column(String(255), nullable=False)
|
|
file_path = Column(String(500), nullable=False)
|
|
status = Column(String(20), default="pending") # pending, running, completed, failed
|
|
total_rows = Column(Integer, default=0)
|
|
processed_rows = Column(Integer, default=0)
|
|
success_count = Column(Integer, default=0)
|
|
error_count = Column(Integer, default=0)
|
|
error_details = Column(Text) # JSON string of error details
|
|
started_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
completed_at = Column(DateTime(timezone=True))
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
def __repr__(self):
|
|
return f"<ImportLog(id={self.id}, type='{self.import_type}', status='{self.status}')>"
|