diff --git a/app/api/schemas/customer_consent.py b/app/api/schemas/customer_consent.py new file mode 100644 index 0000000..09c2d9c --- /dev/null +++ b/app/api/schemas/customer_consent.py @@ -0,0 +1,11 @@ +from marshmallow import Schema, fields + +# Customer Consent Schema +class CustomerConsentSchema(Schema): + type = fields.Str(required=True) + transactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + requestTime = fields.DateTime(required=True, format="%Y-%m-%d %H:%M:%S.%f") + consentType = fields.Str(required=True) + channel = fields.Str(required=True) \ No newline at end of file diff --git a/app/api/schemas/eligibility_check.py b/app/api/schemas/eligibility_check.py new file mode 100644 index 0000000..7b901c2 --- /dev/null +++ b/app/api/schemas/eligibility_check.py @@ -0,0 +1,10 @@ +from marshmallow import Schema, fields + +class EligibilityCheckSchema(Schema): + transactionId = fields.Str(required=True) + countryCode = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + msisdn = fields.Str(required=True) + accountId = fields.Str(required=True) + channel = fields.Str(required=True) diff --git a/app/api/schemas/loan_status.py b/app/api/schemas/loan_status.py new file mode 100644 index 0000000..7599d5f --- /dev/null +++ b/app/api/schemas/loan_status.py @@ -0,0 +1,8 @@ +from marshmallow import Schema, fields + +# Loan Information Schema +class LoanStatusSchema(Schema): + transactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + msisdn = fields.Str(required=False) + channel = fields.Str(required=True) \ No newline at end of file diff --git a/app/api/schemas/notification_callback.py b/app/api/schemas/notification_callback.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/schemas/provide_loan.py b/app/api/schemas/provide_loan.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/schemas/repayment.py b/app/api/schemas/repayment.py new file mode 100644 index 0000000..2d05321 --- /dev/null +++ b/app/api/schemas/repayment.py @@ -0,0 +1,11 @@ +from marshmallow import Schema, fields + +# Repayment Schema +class RepaymentSchema(Schema): + type = fields.Str(required=False) + msisdn = fields.Str(required=False) #optional + debtId = fields.Str(required=True) + productId = fields.Str(required=True) + transactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + channel = fields.Str(required=True) diff --git a/app/api/schemas/select_offer.py b/app/api/schemas/select_offer.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/services/__init__.py b/app/api/services/__init__.py index 2c97a5c..6ed730f 100644 --- a/app/api/services/__init__.py +++ b/app/api/services/__init__.py @@ -1,3 +1,10 @@ +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.transaction import TransactionService from app.api.services.loan import LoanService diff --git a/app/api/services/customer_consent.py b/app/api/services/customer_consent.py new file mode 100644 index 0000000..a6b4f07 --- /dev/null +++ b/app/api/services/customer_consent.py @@ -0,0 +1,77 @@ +from flask import request, jsonify +from app.api.services.base_service import BaseService +from marshmallow import ValidationError +from app.utils.logger import logger +from app.api.schemas.customer_consent import CustomerConsentSchema +from app.api.services.base_service import BaseService +from app.api.enums import TransactionType +from app.extensions import db + + +class CustomerConsentService(BaseService): + TRANSACTION_TYPE = TransactionType.CUSTOMER_CONSENT + + @staticmethod + def process_request(data): + """ + Process the CustomerConsent request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + with db.session.begin(): + validated_data = CustomerConsentService.validate_data(data, CustomerConsentSchema()) + account_id = validated_data.get('accountId') + customer_id = validated_data.get('customerId') + + if(CustomerConsentService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): + + transaction = CustomerConsentService.log_transaction(validated_data = validated_data) + + if not transaction: + logger.error(f"Failed to log transaction") + return jsonify({ + "message": "Failed to log transaction." + }), 400 + else: + return jsonify({ + "message": "Invalid Customer or Account" + }), 400 + + + # Simulated processing logic + response_data = { + "resultCode": "00", + "resultDescription": "Request is received" + } + + db.session.commit() + return response_data + + except ValidationError as err: + + logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") + db.session.rollback() + + return jsonify({ + "message": "Validation exception" + }) , 422 + + except ValueError as err: + logger.error(f"{getattr(err, 'messages', str(err))}") + db.session.rollback() + + return jsonify({ + "message": str(err) + }) , 400 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + db.session.rollback() + return jsonify({ + "message": "Internal Server Error" + }) , 500 \ No newline at end of file diff --git a/app/api/services/eligibility_check.py b/app/api/services/eligibility_check.py new file mode 100644 index 0000000..256ddf3 --- /dev/null +++ b/app/api/services/eligibility_check.py @@ -0,0 +1,113 @@ +from flask import session, jsonify +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 + +class EligibilityCheckService(BaseService): + TRANSACTION_TYPE = TransactionType.ELIGIBILITY_CHECK + + @staticmethod + def process_request(data): + """ + Process the EligibilityCheck request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + with db.session.begin(): + + validated_data = EligibilityCheckService.validate_data(data, EligibilityCheckSchema()) + account_id = validated_data.get('accountId') + customer_id = validated_data.get('customerId') + transactionId = validated_data.get('transactionId') + msisdn = validated_data.get('msisdn') + + customer = EligibilityCheckService.get_or_create_customer(validated_data = validated_data) + + if (EligibilityCheckService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): + + transaction = EligibilityCheckService.log_transaction(validated_data = validated_data) + + if not transaction: + logger.error(f"Failed to log transaction") + return jsonify({ + "message": "Failed to log transaction." + }), 400 + + else: + return jsonify({ + "message": "Invalid Customer or Account" + }), 400 + + # Call RACCheck + response = SimbrellaIntegration.rac_check( + customer_id = customer_id, + account_id = account_id, + transaction_id = transaction.id, + ) + logger.error(f"This is Response Returned ****** : {str(response)}") + + # this chck for error is not valid + logger.error(f"Check for ERROR is not valid ****** FIX THIS !!!!!") + #if "error" in response or response.get("status") != 200: + # return jsonify({"message": "RACCheck failed"}), 400 + + offers = [ + { + "offerId": "SAL90", + "productId": "2030", + "minAmount": 5000, + "maxAmount": 100000, + "tenor": 30 + }, + { + "offerId": "SAL30", + "productId": "2090", + "minAmount": 3000, + "maxAmount": 500000, + "tenor": 90 + } + ] + + # Simulate processing + response_data = { + "customerId": customer_id, + "transactionId": transactionId, + "countryCode": "NG", + "msisdn": msisdn, + "eligibleOffers": offers, + "resultDescription": "Successful", + "resultCode": "00", + "accountId": account_id + } + + return response_data + + except ValidationError as err: + + logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") + + return jsonify({ + "message": "Validation exception" + }) , 422 + + except ValueError as err: + logger.error(f"{getattr(err, 'messages', str(err))}") + + return jsonify({ + "message": str(err) + }) , 400 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 \ No newline at end of file diff --git a/app/api/services/loan_status.py b/app/api/services/loan_status.py new file mode 100644 index 0000000..f80b7f2 --- /dev/null +++ b/app/api/services/loan_status.py @@ -0,0 +1,100 @@ +from flask import request, jsonify +from marshmallow import ValidationError +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 + + +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') + customer = Customer.get_customer(customer_id) + transactionId = validated_data.get('transactionId') + + # Get loans + loans = [loan.to_dict() for loan in customer.loans] + + + validated_data['refId'] = customer.id + validated_data['refModel'] = "customer" + + + transaction = LoanStatusService.log_transaction(validated_data = validated_data) + + if not transaction: + logger.error(f"Failed to log transaction") + return jsonify({ + "message": "Failed to log transaction." + }), 400 + + + # loans = [ + # { + # "debtId": "123456789", + # "loanDate": "2019-10-18 14:26:21.063", + # "dueDate": "2019-11-20 14:26:21.063", + # "currentLoanAmount": 8500, + # "initialLoanAmount": 10000, + # "defaultPenaltyFee": 0, + # "continuousFee": 0, + # "productId": "101" + # } + # ] + + # Simulated processing logic + response_data = { + "customerId": customer_id, + "transactionId": transactionId, + "loans": loans, + "totalDebtAmount": 8500, + "resultCode": "00", + "resultDescription": "Successful" + } + + db.session.commit() + return response_data + + except ValidationError as err: + + logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") + db.session.rollback() + + return jsonify({ + "message": "Validation exception" + }) , 422 + + except ValueError as err: + logger.error(f"{getattr(err, 'messages', str(err))}") + db.session.rollback() + + return jsonify({ + "message": str(err) + }) , 400 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + db.session.rollback() + return jsonify({ + "message": "Internal Server Error" + }) , 500 \ No newline at end of file diff --git a/app/api/services/notification_callback.py b/app/api/services/notification_callback.py new file mode 100644 index 0000000..6257b2b --- /dev/null +++ b/app/api/services/notification_callback.py @@ -0,0 +1,63 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.api.services.base_service import BaseService +from app.api.enums import TransactionType +from app.utils.logger import logger +from app.api.schemas.notification_callback import NotificationCallbackSchema +from app.extensions import db + +class NotificationCallbackService(BaseService): + TRANSACTION_TYPE = TransactionType.NOTIFICATION_CALLBACK + + @staticmethod + def process_request(data): + """ + Process the NotificationCallback request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing NotificationCallback request") + + # Validate input data using the NotificationCallback schema + schema = NotificationCallbackSchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + # Simulated processing logic + response_data = { + "resultCode": "00", + "resultDescription": "Successful" + } + + + # return ResponseHelper.success( + # data=response_data, + # message="Notification callback processed successfully" + # ) + + return response_data + + except ValidationError as err: + + logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") + + return jsonify({ + "message": "Validation exception" + }) , 422 + + except ValueError as err: + logger.error(f"{getattr(err, 'messages', str(err))}") + + return jsonify({ + "message": str(err) + }) , 400 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/api/services/provide_loan.py b/app/api/services/provide_loan.py new file mode 100644 index 0000000..b496265 --- /dev/null +++ b/app/api/services/provide_loan.py @@ -0,0 +1,120 @@ +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.utils.logger import logger +from app.api.schemas.provide_loan import ProvideLoanSchema +from threading import Thread +from app.models.loan import Loan +from app.api.enums import LoanStatus +from app.extensions import db + +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') + transaction_id = validated_data.get('transactionId') + + if (ProvideLoanService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): + + + # Save the loan details + loan = Loan.create_loan( + customer_id=customer_id, + account_id=account_id, + offer_id=validated_data.get('offerId'), + principal_amount=validated_data.get('requestedAmount'), + status=LoanStatus.ACTIVE + ) + + if not loan: + logger.error(f"Failed to save loan details") + return jsonify({ + "message": "Failed to save loan details." + }), 400 + + db.session.flush() + validated_data['refId'] = loan.id + validated_data['refModel'] = "loan" + + # Log Transaction + transaction = ProvideLoanService.log_transaction(validated_data = validated_data) + + if not transaction: + logger.error(f"Failed to log transaction") + return jsonify({ + "message": "Failed to log transaction." + }), 400 + + + + else: + return jsonify({ + "message": "Invalid Customer or Account" + }), 400 + + + response_data = { + "requestId": request_id, + "transactionId": transaction_id, + "customerId": customer_id, + "accountId": account_id, + "msisdn": "3451342", + "resultCode": "00", + "resultDescription": "Successful" + } + + + # 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 response_data + + except ValidationError as err: + + logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") + db.session.rollback() + + return jsonify({ + "message": "Validation exception" + }) , 422 + + except ValueError as err: + logger.error(f"{getattr(err, 'messages', str(err))}") + db.session.rollback() + + return jsonify({ + "message": str(err) + }) , 400 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + db.session.rollback() + return jsonify({ + "message": "Internal Server Error" + }) , 500 + + + + diff --git a/app/api/services/repayment.py b/app/api/services/repayment.py new file mode 100644 index 0000000..33be586 --- /dev/null +++ b/app/api/services/repayment.py @@ -0,0 +1,111 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.api.enums.loan_status import LoanStatus +from app.models import Repayment +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') + product_id = validated_data.get('productId') + + + + # Save the repayment details + repayment = Repayment.create_repayment( + customer_id = customer_id, + loan_id = loan_id, + product_id = product_id + + ) + + if not repayment: + logger.error(f"Failed to save repayment details") + return jsonify({ + "message": "Failed to save repayment details." + }), 400 + + db.session.flush() + + validated_data['refId'] = repayment.id + validated_data['refModel'] = "repayment" + + #Update Loan status + Loan.update_status(loan_id = loan_id, status = LoanStatus.REPAID) + + transaction = RepaymentService.log_transaction(validated_data = validated_data) + + if not transaction: + logger.error(f"Failed to log transaction") + return jsonify({ + "message": "Failed to log transaction." + }), 400 + + + # Simulated processing logic + response_data = { + "customerId": customer_id, + "productId": product_id, + "debtId": loan_id, + "resultCode": "00", + "resultDescription": "Successful" + } + + # return ResponseHelper.success( + # data=response_data, + # message="Repayment processed successfully" + # ) + + # 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 response_data + + except ValidationError as err: + + logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") + db.session.rollback() + + return jsonify({ + "message": "Validation exception" + }) , 422 + + except ValueError as err: + logger.error(f"{getattr(err, 'messages', str(err))}") + db.session.rollback() + + return jsonify({ + "message": str(err) + }) , 400 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + db.session.rollback() + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/api/services/select_offer.py b/app/api/services/select_offer.py new file mode 100644 index 0000000..6a0a914 --- /dev/null +++ b/app/api/services/select_offer.py @@ -0,0 +1,93 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.api.services.base_service import BaseService +from app.api.enums import TransactionType +from app.utils.logger import logger +from app.api.schemas.select_offer import SelectOfferSchema +from app.extensions import db + + +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") + + 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 jsonify({"message": "Failed to log transaction."}), 400 + else: + return jsonify({"message": "Invalid Customer or Account"}), 400 + + offers = [ + { + "offerId": "14451", + "productId": "2030", + "amount": 10000.0, + "upfrontPayment": 1000.0, + "interestRate": 3.0, + "managementRate": 1.0, + "managementFee": 1.0, + "insuranceRate": 1.0, + "insuranceFee": 100.0, + "VATRate": 7.5, + "VATAmount": 100.0, + "recommendedRepaymentDates": ["2022-11-30"], + "installmentAmount": 11000.0, + "totalRepaymentAmount": 11000.0, + } + ] + + # Business logic - selecting an offer + response_data = { + "outstandingDebtAmount": 0, + "requestId": "202111170001371256908", + "transactionId": transaction.id, + "customerId": customer_id, + "accountId": account_id, + "loan": offers, + "resultCode": "00", + "resultDescription": "Successful", + } + + db.session.commit() + return response_data + + except ValidationError as err: + + logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") + db.session.rollback() + return jsonify({"message": "Validation exception"}), 422 + + except ValueError as err: + logger.error(f"{getattr(err, 'messages', str(err))}") + db.session.rollback() + return jsonify({"message": str(err)}), 400 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + db.session.rollback() + return jsonify({"message": "Internal Server Error"}), 500 diff --git a/app/models/account.py b/app/models/account.py new file mode 100644 index 0000000..e8a90ce --- /dev/null +++ b/app/models/account.py @@ -0,0 +1,49 @@ +from datetime import datetime, timezone +from sqlalchemy.orm import relationship +from app.extensions import db +from sqlalchemy.exc import IntegrityError + +class Account(db.Model): + __tablename__ = 'accounts' + + id = db.Column(db.String(50), primary_key=True) + customer_id = db.Column(db.String(50), nullable=False) + account_type = db.Column(db.String(50)) + status = db.Column(db.String(20), default='active') + lien_amount = db.Column(db.Float, default=0.0) + 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)) + + customer = relationship( + "Customer", + primaryjoin="Customer.id == Account.customer_id", + foreign_keys=[customer_id], + back_populates="accounts", + ) + + @classmethod + def create_account(cls, id, customer_id, account_type, status='active'): + account = cls( + id=id, + customer_id=customer_id, + account_type=account_type + ) + + try: + db.session.add(account) + except IntegrityError as err: + raise ValueError(f"Database integrity error: {err}") + return account + + @classmethod + def is_valid_account(cls, account_id, customer_id): + account = cls.query.filter_by(id=account_id, customer_id=customer_id).first() + if not account: + return False + if account.lien_amount > 0: + return False + return True + + def __repr__(self): + return f'' + \ No newline at end of file diff --git a/app/models/offer.py b/app/models/offer.py new file mode 100644 index 0000000..ce62fa2 --- /dev/null +++ b/app/models/offer.py @@ -0,0 +1,16 @@ +from datetime import datetime, timezone +from app.extensions import db + +class Offer(db.Model): + __tablename__ = 'offers' + + id = db.Column(db.Integer, primary_key=True) + product_id = db.Column(db.String, nullable=False) + min_amount = db.Column(db.Float, nullable=False) + max_amount = db.Column(db.Float, nullable=False) + tenor = db.Column(db.Integer, nullable=False) + 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)) + + def __repr__(self): + return f'' \ No newline at end of file diff --git a/app/models/repayment.py b/app/models/repayment.py new file mode 100644 index 0000000..c313131 --- /dev/null +++ b/app/models/repayment.py @@ -0,0 +1,53 @@ +from datetime import datetime, timezone +from app.api.enums.loan_status import LoanStatus +from app.extensions import db +from app.models.customer import Customer +from app.models.loan import Loan +from sqlalchemy.exc import IntegrityError + + +class Repayment(db.Model): + __tablename__ = 'repayments' + + id = db.Column( + db.Integer, + primary_key=True, + autoincrement=True, + ) + loan_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) + 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 create_repayment(cls, customer_id, loan_id, product_id): + + + # Check customer exists + if not Customer.is_valid_customer(customer_id): + raise ValueError("Invalid customer") + + # Check loan exists + loan = Loan.get_customer_loan(loan_id = loan_id, customer_id = customer_id) + + # Check that the loan is active + if loan.status != LoanStatus.ACTIVE: + raise ValueError(f"Repayment cannot be processed. Loan status: ({loan.status})") + + + repayment = cls( + customer_id=customer_id, + loan_id=loan_id, + product_id=product_id, + ) + + try: + db.session.add(repayment) + except IntegrityError as err: + raise ValueError(f"Database integrity error: {err}") + + return repayment + + def __repr__(self): + return f''