From 7f44f2417f498394737a4a64906b5301e0f0a65c Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Mon, 25 Aug 2025 19:34:53 -0400 Subject: [PATCH] code clean up 2 --- app/api/schemas/provide_loan.py | 16 -- app/api/schemas/select_offer.py | 14 -- app/api/services/__init__.py | 9 - app/api/services/eligibility_check.py | 27 --- app/api/services/loan_status.py | 81 --------- app/api/services/offer_analysis.py | 250 -------------------------- app/api/services/provide_loan.py | 198 -------------------- app/api/services/repayment.py | 103 ----------- app/api/services/select_offer.py | 168 ----------------- 9 files changed, 866 deletions(-) delete mode 100644 app/api/schemas/provide_loan.py delete mode 100644 app/api/schemas/select_offer.py delete mode 100644 app/api/services/eligibility_check.py delete mode 100644 app/api/services/loan_status.py delete mode 100644 app/api/services/offer_analysis.py delete mode 100644 app/api/services/provide_loan.py delete mode 100644 app/api/services/repayment.py delete mode 100644 app/api/services/select_offer.py diff --git a/app/api/schemas/provide_loan.py b/app/api/schemas/provide_loan.py deleted file mode 100644 index ad6c8b1..0000000 --- a/app/api/schemas/provide_loan.py +++ /dev/null @@ -1,16 +0,0 @@ -from marshmallow import Schema, fields - -# Provide Loan Schema -class ProvideLoanSchema(Schema): - type = fields.Str(required=False) - requestId = fields.Str(required=True) - transactionId = fields.Str(required=True) - customerId = fields.Str(required=True) - accountId = fields.Str(required=True) - msisdn = fields.Str(required=False) - # productId = fields.Str(required=True) - # lienAmount = fields.Float(required=True) - requestedAmount = fields.Float(required=True) - collectionType = fields.Int(required=True) - offerId = fields.Str(required=True) - channel = fields.Str(required=True) \ No newline at end of file diff --git a/app/api/schemas/select_offer.py b/app/api/schemas/select_offer.py deleted file mode 100644 index defb702..0000000 --- a/app/api/schemas/select_offer.py +++ /dev/null @@ -1,14 +0,0 @@ -from marshmallow import Schema, fields - -# Select Offer Schema -class SelectOfferSchema(Schema): - requestId = fields.Str(required=True) - transactionId = fields.Str(required=True) - customerId = fields.Str(required=True) - accountId = fields.Str(required=True) - msisdn = fields.Str(required=True) - requestedAmount = fields.Float(required=True) - productId = fields.Str(required=True) - offerId = fields.Str(required=True) - channel = fields.Str(required=True) - diff --git a/app/api/services/__init__.py b/app/api/services/__init__.py index b8f817e..7351637 100644 --- a/app/api/services/__init__.py +++ b/app/api/services/__init__.py @@ -1,12 +1,4 @@ -# from app.api.services.eligibility_check import EligibilityCheckService -# from app.api.services.select_offer import SelectOfferService -# from app.api.services.provide_loan import ProvideLoanService -# from app.api.services.loan_status import LoanStatusService -# 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 from app.api.services.login import LoginService from app.api.services.register import RegisterService from app.api.services.products import ProductsService @@ -14,7 +6,6 @@ from app.api.services.account import AccountService from app.api.services.myproduct import MyProductsService from app.api.services.contacts import ContactService from app.api.services.office_auth import OfficeAuthService -# from app.api.services.office_dashboard import OfficeDashboardService from app.api.services.web_contents import WebContentsService from app.api.services.subscription import SubscriptionService from app.api.services.common_data import CommonDataService diff --git a/app/api/services/eligibility_check.py b/app/api/services/eligibility_check.py deleted file mode 100644 index d050031..0000000 --- a/app/api/services/eligibility_check.py +++ /dev/null @@ -1,27 +0,0 @@ -from flask import session, jsonify -from app.models.loan import Loan -from app.utils.logger import logger -from app.api.services.base_service import BaseService -# from app.api.schemas.eligibility_check import EligibilityCheckSchema -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, RACCheck -# from app.api.services.offer_analysis import OfferAnalysis -# from app.api.helpers.response_helper import ResponseHelper - -import random - - -class EligibilityCheckService(BaseService): - TRANSACTION_TYPE = TransactionType.ELIGIBILITY_CHECK - - @staticmethod - def process_request(data): - pass - - - @staticmethod - def check_loan_limits(customer_id): - pass \ No newline at end of file diff --git a/app/api/services/loan_status.py b/app/api/services/loan_status.py deleted file mode 100644 index a9f2ad5..0000000 --- a/app/api/services/loan_status.py +++ /dev/null @@ -1,81 +0,0 @@ -from flask import request, jsonify -from marshmallow import ValidationError -from app.api.enums.loan_status import LoanStatus -from app.models import Customer -from app.utils.logger import logger -from app.api.schemas.loan_status import LoanStatusSchema -from app.api.services.base_service import BaseService -from app.api.enums import TransactionType -from app.extensions import db -from app.api.helpers.response_helper import ResponseHelper - - -class LoanStatusService(BaseService): - TRANSACTION_TYPE = TransactionType.LOAN_STATUS - - @staticmethod - def process_request(data): - """ - Process the Loan Information request. - - Args: - data (dict): The request data. - - Returns: - dict: A standardized response. - """ - try: - with db.session.begin(): - # Validate data - validated_data = LoanStatusService.validate_data(data, LoanStatusSchema()) - - customer_id = validated_data.get('customerId') - - logger.info(f"Looking for customer *** {customer_id}") - customer = Customer.get_customer_with_loan_list(customer_id) - - transactionId = validated_data.get('transactionId') - account_id = validated_data.get('accountId') - - if(LoanStatusService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): - # Get loans - loans = [loan.to_dict() for loan in customer.loans if loan.status == LoanStatus.ACTIVE] - transaction = LoanStatusService.log_transaction(validated_data = validated_data) - if not transaction: - logger.error(f"Failed to log transaction") - return ResponseHelper.error(result_description="Failed to log transaction.") - else: - return ResponseHelper.error(result_description="Invalid Customer or Account") - - total_debt_amount = sum( - loan.get("currentLoanAmount") or 0 - for loan in loans - ) - - # Simulated processing logic - response_data = { - "customerId": customer_id, - "accountId": account_id, - "transactionId": transactionId, - "loans": loans, - "totalDebtAmount": total_debt_amount, - } - - db.session.commit() - return ResponseHelper.success(data=response_data) - - except ValidationError as err: - - logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") - db.session.rollback() - return ResponseHelper.unprocessable_entity(result_description="Validation exception") - - except ValueError as err: - logger.error(f"{getattr(err, 'messages', str(err))}") - db.session.rollback() - return ResponseHelper.error(result_description=str(err)) - - except Exception as e: - logger.error(f"An error occurred: {str(e)}", exc_info=True) - db.session.rollback() - return ResponseHelper.internal_server_error() \ No newline at end of file diff --git a/app/api/services/offer_analysis.py b/app/api/services/offer_analysis.py deleted file mode 100644 index 279de6f..0000000 --- a/app/api/services/offer_analysis.py +++ /dev/null @@ -1,250 +0,0 @@ -from decimal import Decimal -from app.models import Offer, TransactionOffer -from app.models.loan import Loan -import random -import logging - -from app.config import Config - -RAC_TRUE_CHECK_RULES = Config.rac_true_rules -RAC_FALSE_CHECK_RULES = Config.rac_false_rules -RAC_SALARY_PAYMENTS = Config.rac_salary_payments - -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 - - return transaction_offer, offer, eligible_amount, original_transaction - @staticmethod - def _analyze_rack_checks(rack_response, offer): - logger.info(f"This is PayLoad for ANALYSYS ***** : {str(rack_response)}", exc_info=True) - logger.info(f"RACk TRUE RULES {str(RAC_TRUE_CHECK_RULES)}", exc_info=True) - logger.info(f"RACk FALSE RULES {str(RAC_FALSE_CHECK_RULES)}", exc_info=True) - logger.info(f"RACk SALARY PAYMENTS {str(RAC_SALARY_PAYMENTS)}", exc_info=True) - - if not isinstance(rack_response, dict) or not offer : - raise ValueError("Invalid RAC response format.") - - - - failed_true_rules = [] - failed_false_rules = [] - salaries = [] - - # Expects true - for rule in RAC_TRUE_CHECK_RULES: - if not rack_response.get(rule, False): - failed_true_rules.append(rule) - - # Expects false - for rule in RAC_FALSE_CHECK_RULES: - if rack_response.get(rule, True): - failed_false_rules.append(rule) - - - # Salary rules - for key in RAC_SALARY_PAYMENTS: - value = rack_response.get(key) - - - if isinstance(value, Decimal): - # Only use values greater than 0 - if value > 0: - salaries.append(value) - elif isinstance(value, (int, float, str)): - try: - value = Decimal(str(value)) - if value > 0: - salaries.append(value) - except: - logger.warning(f"Could not convert value of {key} to Decimal: {value}") - - - if failed_true_rules or failed_false_rules or not salaries: - logger.warning(f"Failed TRUE rules: {failed_true_rules}") - logger.warning(f"Failed FALSE rules: {failed_false_rules}") - logger.warning("No salary records found in RAC response.") - raise ValueError(f"RAC analysis failed") - - - - logger.info(f"These are the salary amounts ***** : {str(salaries)}", exc_info=True) - - #Least salary in the last 6 months - min_salary = min(salaries) - - # Check consistency rule - consistent_income = rack_response.get("rule7_consistent_salary_amount", False) - - # Determine percentage based on offer tenor - tenor = offer.tenor - - if tenor == 30 and consistent_income: - eligible_amount = min_salary * Decimal("0.5") - logger.info("Applying 50% of least salary in 6 months due to 1-month offer tenor with stable income.") - elif tenor == 90 and consistent_income: - eligible_amount = min_salary * Decimal("0.75") - logger.info("Applying 75% of least salary in 6 months due to 3-months offer tenor with stable income.") - - else: # Income is not consistent - eligible_amount = 0 - logger.info("Applying no percentage on least salary due unstable income.") - - - - logger.info(f"Calculated eligible amount from RAC: {eligible_amount} based on {'stable' if consistent_income else 'unstable'} income.") - - return eligible_amount.quantize(Decimal("1.00")) - - # "racResponse": { - # "accountStatus": true, - # "bvnValidated": true, - # "creditBureauCheck": false, - # "crmsCheck": true, - # "hasLien": false, - # "hasPastDueLoan": false, - # "hasSalaryAccount": true, - # "isWhitelisted": true, - # "noBouncedCheck": true - # }, - # - - ''' - 30 days - Eligibility amount (monthly SOL) - Adoption of 50% of the least salary inflow in the past 6 months - to determine loan eligibility for potential customers. - - 3 months - Adoption of 75% of the least salary inflow in the past 6 months to determine loan eligibility for - potential customers" for customers that have unstable income. 3 months - ''' - # rac_true_rules - - return 0 - - @staticmethod - def decide_offer(transaction_id, rac_check, validated_data, customer_id, rack_checks_response): - eligible_offers = [] - # if we have active offers - we have to feed off it - logger.info(f"**RACK ANALYSIS** {customer_id}") - # Analyze Rack Checks - # new_eligible_amount = OfferAnalysis._analyze_rack_checks(rack_checks_response) #--> We need detail analysis - - # we can now find the origin transactions - # Find the last loan - it will have original_transaction - last_customer_loan = Loan.get_customer_last_loan(customer_id) - # logger.info(f"{last_customer_loan}") - - if last_customer_loan: - original_transaction = last_customer_loan.original_transaction or last_customer_loan.transaction_id - logger.info(f"transaction_id |-| original_transaction === > {transaction_id} {original_transaction}") - original_loan = Loan.get_customer_original_loan(customer_id, original_transaction) - if original_loan is not None: - logger.info(f"original_loan === > {original_loan}") - logger.info(f"loan_offer_id === > {original_loan.offer_id}") - - original_offer_id = str(original_loan.offer_id[:5]) # The last part is str - transaction_offer_id = int(original_loan.offer_id[5:]) # The last part is int - original_transaction_offer = TransactionOffer.is_valid_transaction_offer(transaction_offer_id, customer_id, original_loan.product_id) - - active_loans = Loan.get_active_loans_by_original_transaction(original_transaction) - sum_active_loans = sum(loan.current_loan_amount for loan in active_loans) - logger.info(f"sum_active_loans === > {sum_active_loans}") - real_eligible_amount = original_loan.eligible_amount - sum_active_loans - - if real_eligible_amount < original_transaction_offer.min_amount: - logger.error(f"Max eligible amount ({real_eligible_amount}) is less than the minimum offer amount ({original_transaction_offer.min_amount}).") - raise ValueError("You are not eligible for a loan at this time.") - - transaction_offer = TransactionOffer.create_transaction_offer( - customer_id=customer_id, - transaction_id=transaction_id, - original_transaction=original_transaction, - offer_id=original_offer_id, - min_amount=original_transaction_offer.min_amount, - max_amount=original_transaction_offer.max_amount, - eligible_amount=real_eligible_amount, - product_id=original_loan.product_id, - tenor=original_loan.tenor - ) - - # Visible offer ID: offer_id + padded(transaction_offer.id) - padded_id = str(transaction_offer.id).zfill(6) - public_offer_id = f"{original_offer_id}{padded_id}" - - eligible_offers.append({ - "offerId": public_offer_id, - "product_id": original_transaction_offer.product_id, - "min_amount": original_transaction_offer.min_amount, - "max_amount": round(real_eligible_amount, 2), - "tenor": original_loan.tenor - }) - return eligible_offers - - - offers = Offer.get_all_offers() - - - for offer in offers: - - new_eligible_amount = OfferAnalysis._analyze_rack_checks(rack_checks_response, offer) - - - approved_amount = new_eligible_amount - approved_amount = round(approved_amount, 2) - - if approved_amount < offer.min_amount: - logger.error(f"Max eligible amount ({approved_amount}) is less than the minimum offer amount ({offer.min_amount}).") - raise ValueError("You are not eligible for a loan at this time.") - - - transaction_offer = TransactionOffer.create_transaction_offer( - customer_id=customer_id, - transaction_id=transaction_id, - original_transaction=transaction_id, - offer_id=offer.id, - min_amount=offer.min_amount, - max_amount=offer.max_amount, - eligible_amount=approved_amount, - product_id=offer.product_id, - tenor=offer.tenor - ) - - # Visible offer ID: offer_id + padded(transaction_offer.id) - padded_id = str(transaction_offer.id).zfill(6) - public_offer_id = f"{offer.id}{padded_id}" - - eligible_offers.append({ - "offerId": public_offer_id, - "product_id": offer.product_id, - "min_amount": offer.min_amount, - "max_amount": approved_amount, - "tenor": offer.tenor - }) - - return eligible_offers \ No newline at end of file diff --git a/app/api/services/provide_loan.py b/app/api/services/provide_loan.py deleted file mode 100644 index f09cd31..0000000 --- a/app/api/services/provide_loan.py +++ /dev/null @@ -1,198 +0,0 @@ -from flask import request, jsonify -from marshmallow import ValidationError -from app.api.integrations.kafka import KafkaIntegration -from app.api.services.base_service import BaseService -from app.api.enums import TransactionType -from app.models.customer import Customer -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, 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 -from app.api.helpers.response_helper import ResponseHelper - -class ProvideLoanService(BaseService): - TRANSACTION_TYPE = TransactionType.PROVIDE_LOAN - - - @staticmethod - def process_request(data): - """ - Process the ProvideLoan request. - - Args: - data (dict): The request data. - - Returns: - dict: A standardized response. - """ - try: - with db.session.begin(): - validated_data = ProvideLoanService.validate_data(data, ProvideLoanSchema()) - account_id = validated_data.get('accountId') - customer_id = validated_data.get('customerId') - request_id = validated_data.get('requestId') - collection_type = validated_data.get('collectionType') - transaction_id = validated_data.get('transactionId') - offer_id = validated_data.get('offerId') - amount = validated_data.get("requestedAmount") - product_id = validated_data.get("productId") - channel = validated_data.get('channel') - - customer = Customer.is_valid_customer(customer_id) - - if (ProvideLoanService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): - - rac_response = RACCheck.get_rac_check(customer_id = customer_id, account_id = account_id) - - 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 ResponseHelper.error(result_description=str(ve)) - - - if(amount < transaction_offer.min_amount): - return ResponseHelper.error(result_description="The amount is less than the minimum allowed transaction amount.") - elif amount > transaction_offer.max_amount: - return ResponseHelper.error(result_description="The amount is greater than the maximum allowed transaction amount.") - - - # transaction_offer_id = int(offer_id[5:]) # The last part is int - - # 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 - - # 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 - transaction = ProvideLoanService.log_transaction(validated_data=validated_data) - - if not transaction: - logger.error(f"Failed to log transaction") - return ResponseHelper.error(result_description="Failed to log transaction.") - - - db.session.flush() - - charges = ProvideLoanService.calculate_charges(offer, amount) - upfront_fee = charges["upfront_payment"] - repayment_amount = charges["repayment_amount"] - #installment_amount = charges["installment_amount"] - num_schedules = offer.schedule - - upfront_payment = charges["upfront_payment"] - total_amount = charges["total_amount"] - installment_amount = charges["installment_amount"] - interest = charges["interest"] - management = charges["management"] - insurance = charges["insurance"] - vat = charges["vat"] - - padded_id = str(transaction_id).zfill(12) - loan_ref = f"{padded_id}{channel}{offer.product_id}" - - - # Save the loan details - loan = Loan.create_loan( - customer_id = customer_id, - account_id = account_id, - offer_id = offer_id, - product_id = offer.product_id, - collection_type = collection_type, - transaction_id = validated_data.get('transactionId'), - original_transaction = transaction_offer.original_transaction, - initial_loan_amount = validated_data.get('requestedAmount'), - upfront_fee = upfront_fee, - repayment_amount = repayment_amount, - installment_amount = installment_amount, - eligible_amount=eligible_amount, - status = LoanStatus.ACTIVE, - tenor = offer.tenor, - reference = loan_ref - ) - - if not loan: - logger.error(f"Failed to save loan details") - - return ResponseHelper.error(result_description="Failed to save loan details.") - - db.session.flush() - current_product_id = offer.product_id - schedule = LoanRepaymentSchedule.add_repayment_schedule(loan = loan, num_schedules = num_schedules, transaction_id = transaction_id) - - - if not schedule: - logger.error(f"Failed to create repayment schedule for loan ID {loan.id}") - return ResponseHelper.error(result_description="Failed to generate loan repayment schedule.") - - # charges = Charge.get_offer_charges(offer.id) - - # logger.info(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) - - - - else: - return ResponseHelper.error(result_description="Invalid Customer or Account") - - - - response_data = { - "requestId": request_id, - "transactionId": transaction_id, - "loanRef": loan_ref, - "customerId": customer_id, - "accountId": account_id, - "msisdn": customer.msisdn - } - - # KafkaIntegration.send_loan_request(loan_data = response_data, request_id = request_id) - # Call Kafka in a background thread - thread = Thread(target=ProvideLoanService.async_send_to_kafka, args=(response_data, request_id, "PROCESS_PAYMENT")) - thread.start() - - db.session.commit() - return ResponseHelper.success(data=response_data) - - except ValidationError as err: - - logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") - db.session.rollback() - return ResponseHelper.unprocessable_entity(result_description="Validation exception") - - except ValueError as err: - logger.error(f"{getattr(err, 'messages', str(err))}") - db.session.rollback() - return ResponseHelper.error(result_description=str(err)) - - except Exception as e: - logger.error(f"An error occurred: {str(e)}", exc_info=True) - db.session.rollback() - return ResponseHelper.internal_server_error() \ No newline at end of file diff --git a/app/api/services/repayment.py b/app/api/services/repayment.py deleted file mode 100644 index 8def45d..0000000 --- a/app/api/services/repayment.py +++ /dev/null @@ -1,103 +0,0 @@ -from flask import request, jsonify -from marshmallow import ValidationError -from app.api.enums.loan_status import LoanStatus -from app.api.helpers.response_helper import ResponseHelper -from app.models import Repayment -from app.models.customer import Customer -from app.models.loan import Loan -from app.utils.logger import logger -from app.api.schemas.repayment import RepaymentSchema -from app.api.services.base_service import BaseService -from app.api.enums import TransactionType -from threading import Thread -from app.extensions import db - -class RepaymentService(BaseService): - TRANSACTION_TYPE = TransactionType.REPAYMENT - - @staticmethod - def process_request(data): - """ - Process the Repayment request. - - Args: - data (dict): The request data. - - Returns: - dict: A standardized response. - """ - try: - with db.session.begin(): - validated_data = RepaymentService.validate_data(data, RepaymentSchema()) - - customer_id = validated_data.get('customerId') - request_id = validated_data.get('requestId') - loan_id = validated_data.get('debtId') - account_id = validated_data.get('accountId') - loan_ref = validated_data.get('loanRef') - # customer = Customer.get_customer_with_loan_list(customer_id) - transaction_id = validated_data.get('transactionId') - initiated_by = validated_data.get('initiatedBy') - logger.error(f"HERE 0002a **** ") - if(RepaymentService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): - logger.error(f"HERE 0001a **** ") - # Check loan exists - loan = Loan.get_customer_loan(loan_id = loan_id, customer_id = customer_id) - - # Save the repayment details - repayment = Repayment.create_repayment( - customer_id = customer_id, - loan = loan, - transaction_id = transaction_id - ) - - if not repayment: - logger.error(f"Failed to save repayment details") - return ResponseHelper.error(result_description="Failed to save repayment details.") - - #Update Loan status - Loan.update_status(loan_id = loan_id, status = LoanStatus.START_REPAY) # repay started bu user - transaction = RepaymentService.log_transaction(validated_data = validated_data) - - if not transaction: - logger.error(f"Failed to log transaction") - return ResponseHelper.error(result_description="Failed to log transaction.") - else: - logger.error(f"Invalid Customer or AccountID {account_id} to CustomerID{customer_id} ") - return ResponseHelper.error(result_description="Invalid Customer or Account") - - # Simulated processing logic - # TODO start using repayment_id instead if id or Id - response_data = { - "Id": repayment.id, - "repayment_id": repayment.id, - "initiated_by": repayment.initiated_by, - "transactionId": transaction_id, - "customerId": customer_id, - "productId": loan.product_id, - "loanRef": loan_ref, - "debtId": loan_id - } - - # Call Kafka in a background thread - thread = Thread(target=RepaymentService.async_send_to_kafka, args=(response_data, request_id, "LOAN_REPAYMENT")) - thread.start() - - db.session.commit() - return ResponseHelper.success(data=response_data) - - except ValidationError as err: - - logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") - db.session.rollback() - return ResponseHelper.unprocessable_entity(result_description="Validation exception") - - except ValueError as err: - logger.error(f"{getattr(err, 'messages', str(err))}") - db.session.rollback() - return ResponseHelper.error(result_description=str(err)) - - except Exception as e: - logger.error(f"An error occurred: {str(e)}", exc_info=True) - db.session.rollback() - return ResponseHelper.internal_server_error() diff --git a/app/api/services/select_offer.py b/app/api/services/select_offer.py deleted file mode 100644 index e2908e3..0000000 --- a/app/api/services/select_offer.py +++ /dev/null @@ -1,168 +0,0 @@ -from flask import request, jsonify -from marshmallow import ValidationError -from app.api.helpers.response_helper import ResponseHelper -from app.api.services.base_service import BaseService -from app.api.enums import TransactionType -from app.models.transaction_offers import TransactionOffer -from app.utils.logger import logger -from app.api.schemas.select_offer import SelectOfferSchema -from app.extensions import db -from app.models import Offer -from datetime import date -from dateutil.relativedelta import relativedelta - -class SelectOfferService(BaseService): - TRANSACTION_TYPE = TransactionType.SELECT_OFFER - - @staticmethod - def process_request(data): - """ - Process the SelectOffer request. - - Args: - data (dict): The request data. - - Returns: - dict: A standardized response. - """ - try: - with db.session.begin(): - validated_data = SelectOfferService.validate_data( - data, SelectOfferSchema() - ) - account_id = validated_data.get("accountId") - customer_id = validated_data.get("customerId") - amount = validated_data.get("requestedAmount") - product_id = validated_data.get("productId") - 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 - ): - transaction = SelectOfferService.log_transaction( - validated_data=validated_data - ) - - if not transaction: - logger.error(f"Failed to log transaction") - return ResponseHelper.error(result_description="Failed to log transaction.") - else: - return ResponseHelper.error(result_description="Invalid Customer or Account") - - # Get the offer by product ID - offer = Offer.get_offer_by_product_id(product_id) - - transaction_offer = TransactionOffer.get_transaction_offer(transaction_offer_id=offer_id) - - if not transaction_offer: - logger.error(f"offer {offer_id} not found for customer {customer_id} and transaction {transaction_id}.") - return ResponseHelper.error(result_description="Offer not found.") - - db.session.flush() - - if amount < transaction_offer.min_amount: - logger.error(f"The amount {amount} is less than the minimum allowed offer amount {transaction_offer.min_amount}.") - return ResponseHelper.error(result_description="The amount is less than the minimum allowed offer amount.") - elif amount > transaction_offer.eligible_amount: - logger.error(f"The amount {amount} is greater than the eligible offer amount {transaction_offer.eligible_amount}.") - return ResponseHelper.error(result_description="The amount is greater than the eligible offer amount.") - - - - charges = SelectOfferService.calculate_charges(offer, amount) - upfront_payment = charges["upfront_payment"] - total_amount = charges["total_amount"] - installment_amount = charges["installment_amount"] - interest = charges["interest"] - management = charges["management"] - insurance = charges["insurance"] - vat = charges["vat"] - repayment_amount = charges["repayment_amount"] - interest_amount = charges["interest_amount"] - - - # Calculate the repayment dates - tenor = offer.tenor - start_date = date.today() - - # Convert tenor to months - months = offer.schedule # tenor // 30 - - recommended_repayment_dates = [ - (start_date + relativedelta(months=i + 1)).isoformat() - for i in range(months) - ] - - - - offers = [ - { - "offerId": transaction_offer_id, - "productId": product_id, - "amount": amount, - "upfrontPayment": upfront_payment, - "interestRate": offer.interest_rate, - "interestFee": interest_amount, - "managementRate": offer.management_rate, - "managementFee": management["fee"], - "insuranceRate": offer.insurance_rate, - "insuranceFee": insurance["fee"], - "VATRate": offer.vat_rate, - "VATAmount": vat["fee"], - "recommendedRepaymentDates": recommended_repayment_dates, - "repaymentAmount": repayment_amount, - "installmentAmount": installment_amount, - "totalRepaymentAmount": total_amount, - } - ] - - # "offerId": offer.id, - # "productId": product_id, - # "amount": amount, - # "upfrontPayment": upfront_payment, - # "interestRate": interest["rate"], - # "managementRate": management["rate"], - # "managementFee": management["fee"], - # "insuranceRate": insurance["rate"], - # "insuranceFee": insurance["fee"], - # "VATRate": vat["rate"], - # "VATAmount": vat["fee"], - # "recommendedRepaymentDates": recommended_repayment_dates, - # "installmentAmount": installment_amount, - # "totalRepaymentAmount": total_amount, - # - # Business logic - selecting an offer - response_data = { - "outstandingDebtAmount": 0, - "requestId": request_id, - "transactionId": transaction_id, - "customerId": customer_id, - "accountId": account_id, - "loan": offers, - } - - db.session.commit() - return ResponseHelper.success(data=response_data) - - except ValidationError as err: - - logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") - db.session.rollback() - return ResponseHelper.unprocessable_entity(result_description="Validation exception") - - except ValueError as err: - logger.error(f"{getattr(err, 'messages', str(err))}") - db.session.rollback() - return ResponseHelper.error(result_description=str(err)) - - except Exception as e: - logger.error(f"An error occurred: {str(e)}", exc_info=True) - db.session.rollback() - return ResponseHelper.internal_server_error() - \ No newline at end of file