Merge branch 'loan_repayment_dates' of DigiFi/digifi-BankToProductCore into master
This commit is contained in:
@@ -49,6 +49,8 @@ class SimbrellaIntegration:
|
|||||||
logger.error(f"This is Response: {str(response)}", exc_info=True)
|
logger.error(f"This is Response: {str(response)}", exc_info=True)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"RACCheck API call failed: {str(e)}", exc_info=True)
|
logger.error(f"RACCheck API call failed: {str(e)}", exc_info=True)
|
||||||
raise Exception(f"RACCheck API call failed: {str(e)}")
|
raise Exception(f"RACCheck API call failed: {str(e)}")
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ from app.api.enums import TransactionType
|
|||||||
from app.utils.logger import logger
|
from app.utils.logger import logger
|
||||||
from app.api.schemas.select_offer import SelectOfferSchema
|
from app.api.schemas.select_offer import SelectOfferSchema
|
||||||
from app.extensions import db
|
from app.extensions import db
|
||||||
|
from app.models import Offer
|
||||||
|
from datetime import date
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
class SelectOfferService(BaseService):
|
class SelectOfferService(BaseService):
|
||||||
TRANSACTION_TYPE = TransactionType.SELECT_OFFER
|
TRANSACTION_TYPE = TransactionType.SELECT_OFFER
|
||||||
@@ -28,6 +30,10 @@ class SelectOfferService(BaseService):
|
|||||||
)
|
)
|
||||||
account_id = validated_data.get("accountId")
|
account_id = validated_data.get("accountId")
|
||||||
customer_id = validated_data.get("customerId")
|
customer_id = validated_data.get("customerId")
|
||||||
|
amount = validated_data.get("requestedAmount")
|
||||||
|
product_id = validated_data.get("productId")
|
||||||
|
transaction_id = validated_data.get("transactionId")
|
||||||
|
request_id = validated_data.get("requestId")
|
||||||
|
|
||||||
if SelectOfferService.validate_account_ownership(
|
if SelectOfferService.validate_account_ownership(
|
||||||
account_id=account_id, customer_id=customer_id
|
account_id=account_id, customer_id=customer_id
|
||||||
@@ -42,30 +48,83 @@ class SelectOfferService(BaseService):
|
|||||||
else:
|
else:
|
||||||
return jsonify({"message": "Invalid Customer or Account"}), 400
|
return jsonify({"message": "Invalid Customer or Account"}), 400
|
||||||
|
|
||||||
|
# Get the offer by product ID
|
||||||
|
offer = Offer.get_offer_by_product_id(product_id)
|
||||||
|
|
||||||
|
if not offer:
|
||||||
|
logger.error(f"Offer with product ID {product_id} not found")
|
||||||
|
return jsonify({"message": "Offer not found"}), 404
|
||||||
|
|
||||||
|
# Get the loan charges for the offer
|
||||||
|
loan_charges = offer.charges
|
||||||
|
if not loan_charges:
|
||||||
|
logger.error(f"No charges found for offer ID {offer.id}")
|
||||||
|
return jsonify({"message": "No charges found for the offer"}), 404
|
||||||
|
|
||||||
|
logger.error(f"{loan_charges}")
|
||||||
|
|
||||||
|
|
||||||
|
db.session.flush()
|
||||||
|
interest = SelectOfferService.get_charge_detail(loan_charges, "INTEREST", amount)
|
||||||
|
management = SelectOfferService.get_charge_detail(loan_charges, "MGTFEE", amount)
|
||||||
|
insurance = SelectOfferService.get_charge_detail(loan_charges, "INSURANCE", amount)
|
||||||
|
vat = SelectOfferService.get_charge_detail(loan_charges, "VAT", amount)
|
||||||
|
|
||||||
|
|
||||||
|
# Up-front payment: (principal + only those fees due immediately i.e due_days == 0)
|
||||||
|
upfront_payment = amount + sum(
|
||||||
|
amount * charge.percent / 100
|
||||||
|
for charge in loan_charges
|
||||||
|
if charge.due == 0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Total amount (principal + all fees)
|
||||||
|
total_amount = amount + sum(
|
||||||
|
amount * charge.percent / 100
|
||||||
|
for charge in loan_charges
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Calculate the repayment dates
|
||||||
|
tenor = offer.tenor
|
||||||
|
start_date = date.today()
|
||||||
|
|
||||||
|
# Convert tenor to months
|
||||||
|
months = tenor // 30
|
||||||
|
|
||||||
|
recommended_repayment_dates = [
|
||||||
|
(start_date + relativedelta(months=i + 1)).isoformat()
|
||||||
|
for i in range(months)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Calculate the installment amount
|
||||||
|
installment_amount = total_amount / tenor
|
||||||
|
|
||||||
|
|
||||||
offers = [
|
offers = [
|
||||||
{
|
{
|
||||||
"offerId": "SAL90",
|
"offerId": offer.id,
|
||||||
"productId": "2030",
|
"productId": product_id,
|
||||||
"amount": 10000.0,
|
"amount": amount,
|
||||||
"upfrontPayment": 1000.0,
|
"upfrontPayment": upfront_payment,
|
||||||
"interestRate": 3.0,
|
"interestRate": interest["rate"],
|
||||||
"managementRate": 1.0,
|
"managementRate": management["rate"],
|
||||||
"managementFee": 1.0,
|
"managementFee": management["fee"],
|
||||||
"insuranceRate": 1.0,
|
"insuranceRate": insurance["rate"],
|
||||||
"insuranceFee": 100.0,
|
"insuranceFee": insurance["fee"],
|
||||||
"VATRate": 7.5,
|
"VATRate": vat["rate"],
|
||||||
"VATAmount": 100.0,
|
"VATAmount": vat["fee"],
|
||||||
"recommendedRepaymentDates": ["2022-11-30"],
|
"recommendedRepaymentDates": recommended_repayment_dates,
|
||||||
"installmentAmount": 11000.0,
|
"installmentAmount": installment_amount,
|
||||||
"totalRepaymentAmount": 11000.0,
|
"totalRepaymentAmount": total_amount,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
# Business logic - selecting an offer
|
# Business logic - selecting an offer
|
||||||
response_data = {
|
response_data = {
|
||||||
"outstandingDebtAmount": 0,
|
"outstandingDebtAmount": 0,
|
||||||
"requestId": "202111170001371256908",
|
"requestId": request_id,
|
||||||
"transactionId": transaction.id,
|
"transactionId": transaction_id,
|
||||||
"customerId": customer_id,
|
"customerId": customer_id,
|
||||||
"accountId": account_id,
|
"accountId": account_id,
|
||||||
"loan": offers,
|
"loan": offers,
|
||||||
@@ -91,3 +150,25 @@ class SelectOfferService(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 jsonify({"message": "Internal Server Error"}), 500
|
return jsonify({"message": "Internal Server Error"}), 500
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_charge_detail(charges, code, amount):
|
||||||
|
"""
|
||||||
|
Get details for a specific charge code from a list of loan charges.
|
||||||
|
|
||||||
|
Returns default values if not found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
for charge in charges:
|
||||||
|
if charge.code == code:
|
||||||
|
return {
|
||||||
|
"rate": charge.percent,
|
||||||
|
"fee": amount * charge.percent / 100,
|
||||||
|
"due_days": charge.due,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"rate": 0, "fee": 0, "due_days": 0}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,28 @@ class Offer(db.Model):
|
|||||||
if not offer:
|
if not offer:
|
||||||
return False
|
return False
|
||||||
return offer
|
return offer
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_offer_by_id(cls, offer_id):
|
||||||
|
"""
|
||||||
|
Return an offer by its ID.
|
||||||
|
"""
|
||||||
|
offer = cls.query.filter_by(id=str(offer_id)).first()
|
||||||
|
|
||||||
|
if not offer:
|
||||||
|
raise ValueError(f"Offer with ID {offer_id} not found")
|
||||||
|
return offer
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_offer_by_product_id(cls, product_id):
|
||||||
|
"""
|
||||||
|
Return an offer by its product ID.
|
||||||
|
"""
|
||||||
|
offer = cls.query.filter_by(product_id=str(product_id)).first()
|
||||||
|
|
||||||
|
if not offer:
|
||||||
|
raise ValueError(f"Offer with Product ID {product_id} not found")
|
||||||
|
return offer
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
from datetime import datetime, timezone
|
||||||
|
from app.extensions import db
|
||||||
|
|
||||||
|
class RACCheck(Base):
|
||||||
|
__tablename__ = "rac_checks"
|
||||||
|
|
||||||
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
||||||
|
transaction_id = Column(UUID, ForeignKey('transactions.id'), nullable=False)
|
||||||
|
customer_id = Column(String, nullable=False)
|
||||||
|
account_id = Column(String, nullable=False)
|
||||||
|
rac_response = Column(JSON, nullable=False)
|
||||||
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
@@ -35,3 +35,7 @@ flask-jwt-extended
|
|||||||
# Kafka
|
# Kafka
|
||||||
confluent-kafka==1.9.2
|
confluent-kafka==1.9.2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
python-dateutil>=2.8.0
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user