This commit is contained in:
VivianDee
2025-05-10 08:53:09 +01:00
11 changed files with 207 additions and 32 deletions
+2 -2
View File
@@ -35,7 +35,7 @@ class SimbrellaIntegration:
],
}
logger.error(f"This is PayLoad: {str(payload)}", exc_info=True)
logger.info(f"This is PayLoad: {str(payload)}", exc_info=True)
headers = {
"Content-Type": "application/json",
@@ -46,7 +46,7 @@ class SimbrellaIntegration:
try:
response = httpx.post(url, json=payload, headers=headers, timeout=10.0)
logger.error(f"This is Response: {str(response)}", exc_info=True)
logger.info(f"This is Response: {str(response)}", exc_info=True)
return response
+1
View File
@@ -6,3 +6,4 @@ from app.api.services.repayment import RepaymentService
from app.api.services.customer_consent import CustomerConsentService
from app.api.services.notification_callback import NotificationCallbackService
from app.api.services.authorization import AuthorizationService
from app.api.services.offer_analysis import OfferAnalysis
+1
View File
@@ -123,6 +123,7 @@ class BaseService:
return {
"interest": interest,
"interest_amount": interest_amount,
"management": management,
"insurance": insurance,
"vat": vat,
+17 -2
View File
@@ -7,7 +7,7 @@ from marshmallow import ValidationError
from app.api.enums import TransactionType
from app.api.integrations import SimbrellaIntegration
from app.extensions import db
from app.models import Offer
from app.models import Offer, RACCheck
import random
@@ -57,12 +57,27 @@ class EligibilityCheckService(BaseService):
response = SimbrellaIntegration.rac_check(
customer_id = customer_id,
account_id = account_id,
transaction_id = transaction.id,
transaction_id = transaction.transaction_id,
)
# this chck for error is not valid
if response.status_code != 200:
return jsonify({"message": "RACCheck failed"}), 400
response = response.json()
rac_check = RACCheck.add_rac_check(
customer_id = customer_id,
account_id = account_id,
transaction_id = transaction.transaction_id,
data = response['RACResponse']
)
if not rac_check:
logger.error(f"Failed to save RACCheck")
return jsonify({
"message": "Failed to save RACCheck."
}), 400
offers = Offer.get_all_offers()
+37
View File
@@ -0,0 +1,37 @@
from app.models import Offer, TransactionOffer
from app.models.loan import Loan
import logging
logger = logging.getLogger(__name__)
class OfferAnalysis:
@staticmethod
def get_offer(transaction_id, rac_response, validated_data):
customer_id = validated_data.get("customerId")
product_id = validated_data.get("productId")
offer_id = validated_data.get("offerId")
transaction_offer_id = int(offer_id[5:]) # The last part is int
logger.info(f"customer_id == *************** : {customer_id}")
logger.info(f"product_id == *************** : {product_id}")
logger.info(f"offer_id == *************** : {offer_id}")
logger.info(f"transaction_offer_id == *************** : {transaction_offer_id}")
transaction_offer = TransactionOffer.is_valid_transaction_offer(transaction_offer_id, customer_id, product_id)
if not transaction_offer:
raise ValueError("Invalid Transaction Offer.")
eligible_amount = transaction_offer.eligible_amount
offer = Offer.is_valid_offer( transaction_offer.offer_id)
if not offer:
raise ValueError("Invalid Offer.")
original_transaction = transaction_id
# we can now find the origin transactions
customer_loan = Loan.get_customer_current_active_loan(customer_id)
return transaction_offer, offer, eligible_amount, original_transaction
+32 -16
View File
@@ -8,13 +8,13 @@ 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, Charge , TransactionOffer
from app.models import Loan, Offer, Charge , TransactionOffer, RACCheck
from app.api.enums import LoanStatus
from app.extensions import db
from datetime import datetime, timezone
from dateutil.relativedelta import relativedelta
from app.models import LoanRepaymentSchedule
from app.api.services.offer_analysis import OfferAnalysis
class ProvideLoanService(BaseService):
TRANSACTION_TYPE = TransactionType.PROVIDE_LOAN
@@ -45,25 +45,40 @@ class ProvideLoanService(BaseService):
customer = Customer.is_valid_customer(customer_id)
if (ProvideLoanService.validate_account_ownership(account_id = account_id, customer_id = customer_id)):
if (ProvideLoanService.validate_account_ownership(account_id = account_id, customer_id = customer_id)):
transaction_offer_id = int(offer_id[5:]) # The last part is int
rac_response = RACCheck.get_rac_check(customer_id = customer_id, account_id = account_id)
transaction_offer = TransactionOffer.is_valid_transaction_offer(transaction_offer_id)
if not transaction_offer:
logger.error(f"Invalid Transaction Offer")
try:
transaction_offer, offer, eligible_amount, original_transaction = OfferAnalysis.get_offer(
transaction_id=transaction_id,
rac_response=rac_response,
validated_data=validated_data
)
except ValueError as ve:
logger.error(str(ve))
return jsonify({
"message": "Invalid Transaction Offer."
"message": str(ve)
}), 400
# transaction_offer_id = int(offer_id[5:]) # The last part is int
eligible_amount = transaction_offer.eligible_amount
offer = Offer.is_valid_offer( transaction_offer.offer_id)
# transaction_offer = TransactionOffer.is_valid_transaction_offer(transaction_offer_id)
# if not transaction_offer:
# logger.error(f"Invalid Transaction Offer")
# return jsonify({
# "message": "Invalid Transaction Offer."
# }), 400
if not offer:
logger.error(f"Invalid Offer")
return jsonify({
"message": "Invalid Offer."
}), 400
# eligible_amount = transaction_offer.eligible_amount
# offer = Offer.is_valid_offer( transaction_offer.offer_id)
# if not offer:
# logger.error(f"Invalid Offer")
# return jsonify({
# "message": "Invalid Offer."
# }), 400
# Log Transaction
@@ -102,6 +117,7 @@ class ProvideLoanService(BaseService):
product_id = offer.product_id,
collection_type = collection_type,
transaction_id = validated_data.get('transactionId'),
original_transaction = validated_data.get('transactionId'),
initial_loan_amount = validated_data.get('requestedAmount'),
upfront_fee = upfront_fee,
repayment_amount = repayment_amount,
@@ -131,7 +147,7 @@ class ProvideLoanService(BaseService):
# charges = Charge.get_offer_charges(offer.id)
logger.error(f"{charges}")
logger.info(f"{charges}")
loan_id = loan.id
+8 -2
View File
@@ -32,10 +32,14 @@ class SelectOfferService(BaseService):
customer_id = validated_data.get("customerId")
amount = validated_data.get("requestedAmount")
product_id = validated_data.get("productId")
offer_id = validated_data.get("offerId")
transaction_offer_id = validated_data.get("offerId")
transaction_id = validated_data.get("transactionId")
request_id = validated_data.get("requestId")
offer_id = int(transaction_offer_id[5:]) # The last part is int
#"offerId": "SAL30001129",
if SelectOfferService.validate_account_ownership(
account_id=account_id, customer_id=customer_id
):
@@ -63,6 +67,7 @@ class SelectOfferService(BaseService):
insurance = charges["insurance"]
vat = charges["vat"]
repayment_amount = charges["repayment_amount"]
interest_amount = charges["interest_amount"]
# Calculate the repayment dates
@@ -81,11 +86,12 @@ class SelectOfferService(BaseService):
offers = [
{
"offerId": offer.id,
"offerId": transaction_offer_id,
"productId": product_id,
"amount": amount,
"upfrontPayment": upfront_payment,
"interestRate": offer.interest_rate,
"interestAmount": interest_amount,
"managementRate": offer.management_rate,
"managementFee": management["fee"],
"insuranceRate": offer.insurance_rate,
+26 -4
View File
@@ -5,6 +5,7 @@ from app.models.account import Account
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import relationship
from dateutil.relativedelta import relativedelta
from datetime import timedelta
class Loan(db.Model):
@@ -68,6 +69,7 @@ class Loan(db.Model):
initial_loan_amount,
collection_type,
transaction_id,
original_transaction,
upfront_fee,
repayment_amount,
installment_amount,
@@ -81,6 +83,7 @@ class Loan(db.Model):
raise ValueError("Customer does not exist")
now = datetime.now(timezone.utc)
due_date = now + timedelta(days=tenor)
# Create and save the loan
loan = cls(
@@ -90,13 +93,13 @@ class Loan(db.Model):
product_id = product_id,
collection_type = collection_type,
transaction_id = transaction_id,
original_transaction = transaction_id,
original_transaction = original_transaction,
initial_loan_amount = initial_loan_amount,
current_loan_amount = initial_loan_amount,
upfront_fee = upfront_fee,
repayment_amount = repayment_amount,
installment_amount = installment_amount,
due_date=now,
due_date=due_date,
tenor = tenor,
status = status,
eligible_amount =eligible_amount
@@ -123,13 +126,32 @@ class Loan(db.Model):
@classmethod
def get_customer_loan(cls, loan_id, customer_id):
"""
Get customer's active loans.
Get customer's active loans by loan_id.
"""
loan = cls.query.filter_by(id = loan_id, customer_id = customer_id).first()
if not loan:
raise ValueError(f"Loan with ID {loan_id} does not exist or does not belong to customer {customer_id}.")
return loan
@classmethod
def get_customer_current_active_loan(cls, customer_id):
"""
Get customer's active loans.
"""
loan = cls.query.filter_by( customer_id = customer_id).first()
if not loan:
loan = {
"eligible_amount": 0,
"loan_amount": 0,
"customer_id": customer_id,
"transaction_id": "",
"resultDescription": "No Active Loan"
}
logger.info(f" Active Loan ==>>>> {loan}")
return loan
@classmethod
def update_status(cls, loan_id, status):
"""
+26 -6
View File
@@ -1,14 +1,14 @@
from datetime import datetime, timezone
from app.extensions import db
from sqlalchemy.orm import relationship
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.exc import IntegrityError
from uuid import uuid4
from sqlalchemy.types import JSON
class RACCheck(db.Model):
__tablename__ = 'rac_checks'
id = db.Column(db.String, primary_key=True)
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
transaction_id = db.Column(db.String(50), nullable=False)
customer_id = db.Column(db.String, nullable=False)
account_id = db.Column(db.String, nullable=False)
@@ -16,6 +16,25 @@ class RACCheck(db.Model):
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))
@classmethod
def add_rac_check(cls, customer_id, account_id, transaction_id, data = None):
# Save the response
rac_check = cls(
customer_id = customer_id,
account_id = account_id,
transaction_id = transaction_id,
rac_response = data
)
try:
db.session.add(rac_check)
except IntegrityError as err:
raise ValueError(f"Database integrity error: {err}")
return rac_check
@classmethod
def get_all_rac_checks(cls):
"""
@@ -24,18 +43,19 @@ class RACCheck(db.Model):
rac_checks = cls.query.all()
if not rac_checks:
raise ValueError("No available RAC checks")
return None
return rac_checks
@classmethod
def get_rac_check_by_id(cls, check_id):
def get_rac_check(cls, customer_id, account_id):
"""
Return a RAC check by its ID.
"""
rac_check = cls.query.filter_by(id=check_id).first()
rac_check = cls.query.filter_by( customer_id = customer_id,
account_id = account_id,).first()
if not rac_check:
raise ValueError(f"RAC Check with ID {check_id} not found")
raise ValueError(f"RAC Check for customer not found")
return rac_check
def to_dict(self):
@@ -49,6 +49,11 @@
"format": "float",
"example": 3.0
},
"interestAmount": {
"type": "number",
"format": "float",
"example": 3000.00
},
"ManagementRate": {
"type": "number",
"format": "float",
+52
View File
@@ -0,0 +1,52 @@
"""empty message
Revision ID: 3105abd795d4
Revises: 95a52be203c4
Create Date: 2025-05-07 11:44:18.483694
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '3105abd795d4'
down_revision = '95a52be203c4'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('rac_checks', schema=None) as batch_op:
# Step 1: Drop the default value
batch_op.alter_column('id',
server_default=None,
existing_type=sa.VARCHAR(),
existing_nullable=False
)
with op.batch_alter_table('rac_checks', schema=None) as batch_op:
# Step 2: Change the column type
batch_op.alter_column('id',
existing_type=sa.VARCHAR(),
type_=sa.Integer(),
existing_nullable=False,
autoincrement=True,
postgresql_using='id::integer'
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('rac_checks', schema=None) as batch_op:
batch_op.alter_column('id',
existing_type=sa.Integer(),
type_=sa.VARCHAR(),
existing_nullable=False,
autoincrement=True,
existing_server_default=sa.text("''::character varying"))
# ### end Alembic commands ###