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()