Merge branch 'Offers_update' of DigiFi/digifi-BankToProductCore into master

This commit is contained in:
2025-05-19 15:15:13 +00:00
committed by Gogs
13 changed files with 167 additions and 26 deletions
+37 -1
View File
@@ -1,4 +1,5 @@
from flask import session, jsonify from flask import session, jsonify
from app.models.loan import Loan
from app.models.transaction_offers import TransactionOffer from app.models.transaction_offers import TransactionOffer
from app.utils.logger import logger from app.utils.logger import logger
from app.api.services.base_service import BaseService from app.api.services.base_service import BaseService
@@ -51,6 +52,12 @@ class EligibilityCheckService(BaseService):
db.session.flush() db.session.flush()
# Determine Loan count
is_eligible = EligibilityCheckService.check_loan_limits(customer_id)
if not is_eligible:
return ResponseHelper.error(result_description="Max loan count reached")
# Call RACCheck # Call RACCheck
response = SimbrellaIntegration.rac_check( response = SimbrellaIntegration.rac_check(
customer_id = customer_id, customer_id = customer_id,
@@ -58,7 +65,7 @@ class EligibilityCheckService(BaseService):
transaction_id = transaction.transaction_id, transaction_id = transaction.transaction_id,
) )
# this chck for error is not valid # this chek for error is not valid
if response.status_code != 200: if response.status_code != 200:
return ResponseHelper.error(result_description="RACCheck failed") return ResponseHelper.error(result_description="RACCheck failed")
@@ -147,3 +154,32 @@ class EligibilityCheckService(BaseService):
logger.error(f"An error occurred: {str(e)}", exc_info=True) logger.error(f"An error occurred: {str(e)}", exc_info=True)
db.session.rollback() db.session.rollback()
return ResponseHelper.internal_server_error() return ResponseHelper.internal_server_error()
@staticmethod
def check_loan_limits(customer_id):
"""
Checks if a customer has exceeded the loan limits for given offer.
"""
loan = Loan.get_customer_last_loan(customer_id)
if not loan:
return True
offer_id = loan.offer_id[:5]
offer = Offer.get_offer_by_id(offer_id)
if not offer:
logger.error(f"Offer not found for offer_id: {offer_id} (customer_id: {customer_id})")
return False
daily_count = TransactionOffer.get_daily_loan_count(customer_id, offer_id)
logger.error(f"daily_count: {daily_count}, Max: {offer.max_daily_loans}")
if offer.max_daily_loans is not None and daily_count >= offer.max_daily_loans:
return False
return True
+4 -2
View File
@@ -13,7 +13,7 @@ class Account(db.Model):
status = db.Column(db.String(20), default='active') status = db.Column(db.String(20), default='active')
lien_amount = db.Column(db.Float, default=0.0) lien_amount = db.Column(db.Float, default=0.0)
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now()) created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now()) updated_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
customer = relationship( customer = relationship(
"Customer", "Customer",
@@ -27,7 +27,9 @@ class Account(db.Model):
account = cls( account = cls(
id=id, id=id,
customer_id=customer_id, customer_id=customer_id,
account_type=account_type account_type=account_type,
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
) )
try: try:
+4 -2
View File
@@ -14,7 +14,7 @@ class Charge(db.Model):
description = db.Column(db.Text, nullable=True) description = db.Column(db.Text, nullable=True)
due = db.Column(db.Integer, nullable=False) due = db.Column(db.Integer, nullable=False)
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now()) created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now()) updated_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
offer = relationship( offer = relationship(
"Offer", "Offer",
primaryjoin="Charge.offer_id == Offer.id", primaryjoin="Charge.offer_id == Offer.id",
@@ -57,7 +57,9 @@ class Charge(db.Model):
code = code, code = code,
percent = percent, percent = percent,
description = description, description = description,
due = due_days due = due_days,
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
) )
db.session.add(charge_obj) db.session.add(charge_obj)
+8 -2
View File
@@ -12,7 +12,7 @@ class Customer(db.Model):
msisdn = db.Column(db.String(20), unique=True, nullable=False) msisdn = db.Column(db.String(20), unique=True, nullable=False)
country_code = db.Column(db.String(3), nullable=False) country_code = db.Column(db.String(3), nullable=False)
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now()) created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now()) updated_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
accounts = relationship( accounts = relationship(
"Account", "Account",
primaryjoin="Customer.id == Account.customer_id", primaryjoin="Customer.id == Account.customer_id",
@@ -47,7 +47,13 @@ class Customer(db.Model):
raise ValueError("Customer already exists") raise ValueError("Customer already exists")
# Create the customer # Create the customer
customer = cls(id=id, msisdn=msisdn, country_code=country_code) customer = cls(
id=id,
msisdn=msisdn,
country_code=country_code,
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
try: try:
db.session.add(customer) db.session.add(customer)
+4 -2
View File
@@ -39,7 +39,7 @@ class Loan(db.Model):
tenor = db.Column(db.Integer, nullable=True) tenor = db.Column(db.Integer, nullable=True)
due_date = db.Column(db.DateTime, nullable=True) due_date = db.Column(db.DateTime, nullable=True)
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now()) created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now()) updated_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
eligible_amount = db.Column(db.Float, nullable=True, default=0.0) eligible_amount = db.Column(db.Float, nullable=True, default=0.0)
disburse_date = db.Column(db.DateTime, nullable=True) disburse_date = db.Column(db.DateTime, nullable=True)
disburse_verify = db.Column(db.DateTime, nullable=True) disburse_verify = db.Column(db.DateTime, nullable=True)
@@ -109,7 +109,9 @@ class Loan(db.Model):
due_date=due_date, due_date=due_date,
tenor = tenor, tenor = tenor,
status = status, status = status,
eligible_amount =eligible_amount eligible_amount =eligible_amount,
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
) )
try: try:
+4 -2
View File
@@ -18,7 +18,7 @@ class LoanCharge(db.Model):
due = db.Column(db.Integer, nullable=False) due = db.Column(db.Integer, nullable=False)
due_date = db.Column(db.DateTime, nullable=True) due_date = db.Column(db.DateTime, nullable=True)
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now()) created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now()) updated_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
loan = relationship( loan = relationship(
"Loan", "Loan",
primaryjoin="LoanCharge.loan_id == Loan.id", primaryjoin="LoanCharge.loan_id == Loan.id",
@@ -63,7 +63,9 @@ class LoanCharge(db.Model):
percent = percent, percent = percent,
description = description, description = description,
due = due_days, due = due_days,
due_date = now + timedelta(days=due_days) due_date = now + timedelta(days=due_days),
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
) )
db.session.add(charge_obj) db.session.add(charge_obj)
+4 -2
View File
@@ -19,7 +19,7 @@ class LoanRepaymentSchedule(db.Model):
paid_at = db.Column(db.DateTime, nullable=True) paid_at = db.Column(db.DateTime, nullable=True)
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now()) created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now()) updated_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
loan = relationship( loan = relationship(
"Loan", "Loan",
primaryjoin="LoanRepaymentSchedule.loan_id == Loan.id", primaryjoin="LoanRepaymentSchedule.loan_id == Loan.id",
@@ -45,7 +45,9 @@ class LoanRepaymentSchedule(db.Model):
total_repayment_amount = round(loan.repayment_amount, 2), total_repayment_amount = round(loan.repayment_amount, 2),
installment_amount=round(loan.installment_amount, 2), installment_amount=round(loan.installment_amount, 2),
product_id = loan.product_id, product_id = loan.product_id,
transaction_id = transaction_id transaction_id = transaction_id,
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
) )
db.session.add(schedule) db.session.add(schedule)
+10 -2
View File
@@ -18,8 +18,12 @@ class Offer(db.Model):
insurance_rate = db.Column(db.Float, default=1.0) insurance_rate = db.Column(db.Float, default=1.0)
vat_rate = db.Column(db.Float, default=7.5) vat_rate = db.Column(db.Float, default=7.5)
list_order = db.Column(db.Integer, nullable=True) list_order = db.Column(db.Integer, nullable=True)
max_daily_loans = db.Column(db.Integer, nullable=True)
max_active_loans = db.Column(db.Integer, nullable=True)
max_life_loans = db.Column(db.Integer, nullable=True)
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now()) created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now()) updated_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
charges = relationship( charges = relationship(
"Charge", "Charge",
primaryjoin="Offer.id == Charge.offer_id", primaryjoin="Offer.id == Charge.offer_id",
@@ -79,7 +83,11 @@ class Offer(db.Model):
"interest_rate": self.interest_rate, "interest_rate": self.interest_rate,
"management_rate": self.management_rate, "management_rate": self.management_rate,
"insurance_rate": self.insurance_rate, "insurance_rate": self.insurance_rate,
"vat_rate": self.vat_rate "vat_rate": self.vat_rate,
"maxDailyLoans": self.max_daily_loans,
"maxActiveLoans": self.max_active_loans,
"maxLifeLoans": self.max_life_loans
} }
def __repr__(self): def __repr__(self):
+4 -2
View File
@@ -15,7 +15,7 @@ class RACCheck(db.Model):
account_id = db.Column(db.String, nullable=False) account_id = db.Column(db.String, nullable=False)
rac_response = db.Column(db.JSON, nullable=False) rac_response = db.Column(db.JSON, nullable=False)
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now()) created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now()) updated_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
@classmethod @classmethod
def add_rac_check(cls, customer_id, account_id, transaction_id, data = None): def add_rac_check(cls, customer_id, account_id, transaction_id, data = None):
@@ -25,7 +25,9 @@ class RACCheck(db.Model):
customer_id = customer_id, customer_id = customer_id,
account_id = account_id, account_id = account_id,
transaction_id = transaction_id, transaction_id = transaction_id,
rac_response = data rac_response = data,
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
) )
try: try:
+4 -2
View File
@@ -19,7 +19,7 @@ class Repayment(db.Model):
customer_id = db.Column(db.String(50), nullable=False) customer_id = db.Column(db.String(50), nullable=False)
product_id = db.Column(db.String(20), nullable=True) product_id = db.Column(db.String(20), nullable=True)
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now()) created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now()) updated_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
transaction_id = db.Column(db.String(50), nullable=True) transaction_id = db.Column(db.String(50), nullable=True)
@classmethod @classmethod
@@ -42,7 +42,9 @@ class Repayment(db.Model):
customer_id=customer_id, customer_id=customer_id,
loan_id=loan_id, loan_id=loan_id,
product_id=product_id, product_id=product_id,
transaction_id = transaction_id transaction_id = transaction_id,
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
) )
try: try:
+4 -2
View File
@@ -18,7 +18,7 @@ class Transaction(db.Model):
type = db.Column(db.String(50), nullable=False) type = db.Column(db.String(50), nullable=False)
channel = db.Column(db.String(50), nullable=False) channel = db.Column(db.String(50), nullable=False)
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now()) created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now()) updated_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
def __repr__(self): def __repr__(self):
return f'<Transaction {self.id}>' return f'<Transaction {self.id}>'
@@ -38,7 +38,9 @@ class Transaction(db.Model):
customer_id = customer_id, customer_id = customer_id,
account_id = account_id, account_id = account_id,
type = type, type = type,
channel = channel channel = channel,
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
) )
try: try:
+43 -4
View File
@@ -1,4 +1,5 @@
from datetime import datetime, timezone from datetime import datetime, timezone, timedelta
from app.api.enums.loan_status import LoanStatus
from app.extensions import db from app.extensions import db
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from sqlalchemy.sql import func from sqlalchemy.sql import func
@@ -22,7 +23,7 @@ class TransactionOffer(db.Model):
tenor = db.Column(db.Integer, nullable=True) # tenor in months, typically tenor = db.Column(db.Integer, nullable=True) # tenor in months, typically
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now()) created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now()) updated_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
customer = relationship( customer = relationship(
"Customer", "Customer",
primaryjoin="Customer.id == TransactionOffer.customer_id", primaryjoin="Customer.id == TransactionOffer.customer_id",
@@ -31,7 +32,6 @@ class TransactionOffer(db.Model):
) )
@classmethod @classmethod
def is_valid_transaction_offer(cls, transaction_offer, customer_id, product_id): def is_valid_transaction_offer(cls, transaction_offer, customer_id, product_id):
transaction_offer = cls.query.filter_by( transaction_offer = cls.query.filter_by(
id = transaction_offer, id = transaction_offer,
@@ -59,7 +59,9 @@ class TransactionOffer(db.Model):
max_amount=max_amount, max_amount=max_amount,
eligible_amount=eligible_amount, eligible_amount=eligible_amount,
product_id=product_id, product_id=product_id,
tenor=tenor tenor=tenor,
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
) )
db.session.add(transaction_offer) db.session.add(transaction_offer)
@@ -67,6 +69,43 @@ class TransactionOffer(db.Model):
return transaction_offer return transaction_offer
@classmethod
def get_lifetime_loan_count(cls, customer_id):
"""
Returns the total number of loans ever created for a customer.
"""
return cls.query.filter_by(customer_id=customer_id).count()
@classmethod
def get_daily_loan_count(cls, customer_id, offer_id):
"""
Returns the count of loans created today for a customer.
"""
start_of_day = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
end_of_day = start_of_day + timedelta(days=1)
return cls.query.filter_by(
customer_id=customer_id,
offer_id=offer_id
).filter(
cls.created_at >= start_of_day,
cls.created_at < end_of_day
).count()
@classmethod
def get_latest_transaction_offer(cls, customer_id):
"""
Returns the most recent transaction offer for the given customer based on creation time.
"""
return cls.query.filter_by(customer_id=customer_id) \
.order_by(cls.created_at.desc()) \
.first()
def to_dict(self): def to_dict(self):
return { return {
'id': self.id, 'id': self.id,
+36
View File
@@ -0,0 +1,36 @@
"""empty message
Revision ID: e8dd9b841ad7
Revises: 2eee4157505f
Create Date: 2025-05-19 11:46:19.204637
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'e8dd9b841ad7'
down_revision = '2eee4157505f'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('offers', schema=None) as batch_op:
batch_op.add_column(sa.Column('max_daily_loans', sa.Integer(), nullable=True))
batch_op.add_column(sa.Column('max_active_loans', sa.Integer(), nullable=True))
batch_op.add_column(sa.Column('max_life_loans', sa.Integer(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('offers', schema=None) as batch_op:
batch_op.drop_column('max_life_loans')
batch_op.drop_column('max_active_loans')
batch_op.drop_column('max_daily_loans')
# ### end Alembic commands ###