from datetime import datetime, timezone, timedelta from os.path import devnull from sqlalchemy.exc import IntegrityError from app.extensions import db from sqlalchemy.orm import relationship class LoanCharge(db.Model): __tablename__ = 'loan_charges' id = db.Column(db.Integer, primary_key=True, autoincrement=True) loan_id = db.Column(db.Integer, nullable=False) transaction_id = db.Column(db.String(50), nullable=True) code = db.Column(db.String(50), nullable=False) amount = db.Column(db.Float, default=0.0) percent = db.Column(db.Float, default=0.0) description = db.Column(db.Text, nullable=True) due = db.Column(db.Integer, nullable=False) due_date = db.Column(db.DateTime, nullable=True) created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc)) updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc)) loan = relationship( "Loan", primaryjoin="LoanCharge.loan_id == Loan.id", foreign_keys=[loan_id], back_populates="loan_charges", ) def __repr__(self): return f"" def to_dict(self): """ Convert the Loan charge object to a dictionary format for JSON serialization. """ return { 'id': self.id, 'loanId': self.loan_id, 'transactionId': self.transaction_id, 'code': self.code, 'amount': self.amount, 'percent': self.percent, 'description': self.description, 'due': self.due } #get last penal @classmethod def get_last_penal_no(cls, loan_id): """ Returns the last penal number created for a loan. Example: PENAL1 -> returns 1 PENAL3 -> returns 3 If none exists, returns 0. """ last_penal = ( cls.query .filter(cls.loan_id == loan_id) .filter(cls.code.like("PENAL%")) .order_by(cls.id.desc()) .first() ) if not last_penal: return 0 try: return int(last_penal.code.replace("PENAL", "")) except ValueError: return 0 @classmethod def get_penal_charges_by_loan_id(cls, loan_id): """ Returns all penal charges for a specific loan. """ return cls.query.filter( cls.loan_id == loan_id, cls.code.like("PENAL%") ).all() @classmethod def get_loan_charge_by_debt_id(cls, debt_id): return cls.query.filter_by(loan_id=debt_id) #create penal charge @classmethod def create_penal_charges_for_loan(cls, loan_id, transaction_id, percent, penal_no, schedule_number, penal_amount=0.0): """ Create a penal charge for a given loan and schedule. """ if loan_id is None: raise ValueError("loan_id cannot be None") code = f"PENAL{penal_no:02d}-SCHEDULE{schedule_number:02d}" # Check if this penal charge already exists existing = cls.query.filter_by( loan_id=loan_id, code=code ).first() if existing: return existing now = datetime.now(timezone.utc) penal_charge = cls( loan_id=loan_id, transaction_id=transaction_id, code=code, amount=penal_amount, percent=percent, description=f"Penal Charge {penal_no} for loan {loan_id} schedule {schedule_number}", due=True, due_date=now ) try: db.session.add(penal_charge) db.session.commit() except IntegrityError as err: db.session.rollback() raise ValueError(f"Database integrity error: {err}") return penal_charge