diff --git a/app/api/services/provide_loan.py b/app/api/services/provide_loan.py index aeeb575..b34ccd0 100644 --- a/app/api/services/provide_loan.py +++ b/app/api/services/provide_loan.py @@ -8,7 +8,7 @@ from app.models.loan_charge import LoanCharge from app.utils.logger import logger from app.api.schemas.provide_loan import ProvideLoanSchema from threading import Thread -from app.models import Loan, Offer +from app.models import Loan, Offer, Charge from app.api.enums import LoanStatus from app.extensions import db @@ -81,62 +81,16 @@ class ProvideLoanService(BaseService): "message": "Failed to save loan details." }), 400 + + charges = Charge.get_offer_charges(offer.id) - charges = [ - {"code": "INTEREST", "percent": 1.1, "due": 0, "description": "This is fee 9000"}, - {"code": "MGTFEE", "percent": 1.5, "due": 0, "description": "This is fee 90002"}, - {"code": "INSURANCE", "percent": 1.5, "due": 30, "description": "This is fee 90003"}, - {"code": "VAT", "percent": 1.5, "due": 60, "description": "This is fee 90004"}, - ] + logger.error(f"{charges}") + loan_id = loan.id loan_charges = LoanCharge.create_charges_for_loan(loan_id = loan_id, transaction_id = transaction_id, referenced_amount = 800, charges = charges) - # logger.error(f"********* We need to develop the fee array here") - - # loan_def = { - # "offers": [ - # { - # "offerId": "SAL90", - # "productId": "2030", - # "minAmount": 5000, - # "maxAmount": 100000, - # "tenor": 30 - # }, - # { - # "offerId": "SAL30", - # "productId": "2090", - # "minAmount": 3000, - # "maxAmount": 500000, - # "tenor": 90 - # } - # ], - # "loan_fee": { - # "SAL30": [ - # {"code": "INTEREST", "percent": 1.1, "due": 0, "description": "This is fee 000"}, - # {"code": "MGTFEE", "percent": 2.5, "due": 0, "description": "This is fee 001"}, - # {"code": "INSURANCE", "percent": 3.5, "due": 0, "description": "This is fee 001"}, - # {"code": "VAT", "percent": 1.0, "due": 0, "description": "This is fee 001"}, - # ], - # "SAL90": [ - # {"code": "INTEREST", "percent": 1.1, "due": 0, "description": "This is fee 9000"}, - # {"code": "MGTFEE", "percent": 1.5, "due": 0, "description": "This is fee 90002"}, - # {"code": "INSURANCE", "percent": 1.5, "due": 30, "description": "This is fee 90003"}, - # {"code": "VAT", "percent": 1.5, "due": 60, "description": "This is fee 90004"}, - # ] - # } - # } - - - # Log Transaction - # transaction = ProvideLoanService.log_transaction(validated_data = validated_data) - # - # if not transaction: - # logger.error(f"Failed to log transaction") - # return jsonify({ - # "message": "Failed to log transaction." - # }), 400 else: return jsonify({ diff --git a/app/models/charge.py b/app/models/charge.py index 16c97f7..cd79824 100644 --- a/app/models/charge.py +++ b/app/models/charge.py @@ -1,16 +1,14 @@ from datetime import datetime, timezone, timedelta from app.extensions import db from sqlalchemy.orm import relationship -from app.models.offer import Offer class Charge(db.Model): __tablename__ = 'charges' id = db.Column(db.Integer, primary_key=True, autoincrement=True) - offer_id = db.Column(db.Integer, nullable=False) - code = db.Column(db.String(50), nullable=False) - amount = db.Column(db.Float, default=0.0) + offer_id = db.Column(db.String(50), nullable=False) + code = db.Column(db.String(50), nullable=False) percent = db.Column(db.Float, default=0.0) description = db.Column(db.Text, nullable=True) due = db.Column(db.Integer, nullable=False) @@ -25,61 +23,73 @@ class Charge(db.Model): ) @classmethod - def create_charges_for_loan(cls, loan_id, transaction_id, charges, referenced_amount = 0.0): + def add_charges(cls, offer_id, charges): """ - Create loan charges for a given loan. + Add charges to an offer. Args: - loan_id (int): ID of the loan to associate charges with. - charges (list): A list of dictionaries with keys: + offer_id (int): ID of the offer to associate charges with. + charges (list): A list of dictionaries with keys: code (str), amount (float), percent (float), description (str), due (int) """ if not charges or not isinstance(charges, list): raise ValueError("Charges must be a non-empty list of dictionaries") - - if loan_id is None: - raise ValueError("loan_id cannot be None") - loan_charges = [] - now = datetime.now(timezone.utc) + if offer_id is None: + raise ValueError("offer_id cannot be None") + + offer_charges = [] + for charge in charges: - due_days = charge.get("due", 0) - amount = charge.get("amount", 0.0) + code = charge.get("code") percent = charge.get("percent", 0.0) + description = charge.get("description", "") + due_days = charge.get("due", 0) - if amount == 0.0: - amount = (percent / 100.0) * referenced_amount + existing = cls.query.filter_by(offer_id=offer_id, code=code).first() + + if existing: + continue charge_obj = cls( - loan_id = loan_id, - transaction_id = transaction_id, - code = charge.get("code"), - amount = amount, + offer_id = offer_id, + code = code, percent = percent, - description = charge.get("description", ""), - due = due_days, - due_date = now + timedelta(days=due_days) + description = description, + due = due_days ) db.session.add(charge_obj) - loan_charges.append(charge_obj) - - return loan_charges + offer_charges.append(charge_obj) + return offer_charges + @classmethod + def get_offer_charges(cls, offer_id): + """ + Get all charges for a particular offer as a dictionary + + Args: + offer_id (str): The offer ID. + """ + if not offer_id: + raise ValueError("offer_id not found") + + charges = cls.query.filter_by(offer_id=offer_id).all() + + return charges + def to_dict(self): return { 'id': self.id, - 'loanId': self.loan_id, - 'transactionId': self.transaction_id, + 'offerId': self.offer_id, 'code': self.code, - 'amount': self.amount, 'percent': self.percent, 'description': self.description, - 'due': self.due, + 'due': self.due } def __repr__(self): - return f"" + return f"" diff --git a/app/models/loan_charge.py b/app/models/loan_charge.py index a9ed283..5f7787e 100644 --- a/app/models/loan_charge.py +++ b/app/models/loan_charge.py @@ -45,9 +45,9 @@ class LoanCharge(db.Model): now = datetime.now(timezone.utc) for charge in charges: - due_days = charge.get("due", 0) - amount = charge.get("amount", 0.0) - percent = charge.get("percent", 0.0) + due_days = getattr(charge, "due", 0) + amount = getattr(charge, "amount", 0.0) + percent = getattr(charge, "percent", 0.0) if amount == 0.0: amount = (percent / 100.0) * referenced_amount @@ -55,10 +55,10 @@ class LoanCharge(db.Model): charge_obj = cls( loan_id = loan_id, transaction_id = transaction_id, - code = charge.get("code"), + code = getattr(charge, "code"), amount = amount, percent = percent, - description = charge.get("description", ""), + description = getattr(charge, "description", ""), due = due_days, due_date = now + timedelta(days=due_days) ) diff --git a/app/models/offer.py b/app/models/offer.py index 799983a..b51dc25 100644 --- a/app/models/offer.py +++ b/app/models/offer.py @@ -1,6 +1,7 @@ from datetime import datetime, timezone from app.extensions import db from app.models.charge import Charge +from sqlalchemy.orm import relationship class Offer(db.Model): __tablename__ = 'offers' diff --git a/migrations/versions/de9ad96ba34e_migration_on_thu_apr_17_14_15_36_utc_.py b/migrations/versions/de9ad96ba34e_migration_on_thu_apr_17_14_15_36_utc_.py new file mode 100644 index 0000000..a4f4276 --- /dev/null +++ b/migrations/versions/de9ad96ba34e_migration_on_thu_apr_17_14_15_36_utc_.py @@ -0,0 +1,38 @@ +"""Migration on Thu Apr 17 14:15:36 UTC 2025 + +Revision ID: de9ad96ba34e +Revises: ec8d97f9b584 +Create Date: 2025-04-17 14:16:16.537466 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'de9ad96ba34e' +down_revision = 'ec8d97f9b584' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('charges', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('offer_id', sa.String(length=50), nullable=False), + sa.Column('code', sa.String(length=50), nullable=False), + sa.Column('percent', sa.Float(), nullable=True), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('due', sa.Integer(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('charges') + # ### end Alembic commands ### diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index 9a12075..9066c97 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -1,8 +1,8 @@ #!/bin/sh # echo "Running DB migrations..." -# flask db migrate -m "Migration on $(date)" -# flask db upgrade +flask db migrate -m "Migration on $(date)" +flask db upgrade echo "Starting Gunicorn server..." exec gunicorn -w 4 -b 0.0.0.0:5000 wsgi:wsgi_app