From c00bc3ddbf53335e77fbb4f69faff42d7f7f9a06 Mon Sep 17 00:00:00 2001 From: Azeez Muibi Date: Sun, 13 Apr 2025 23:58:02 +0100 Subject: [PATCH] update --- .idea/digifi-FirstCore.iml | 10 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + app/api/routes/routes.py | 10 +- app/api/services/loan.py | 64 ++- app/models/loan.py | 104 ++++- jmeter/digifi_first_core_test_plan.jmx | 402 ++++++++++++++++++ 9 files changed, 563 insertions(+), 53 deletions(-) create mode 100644 .idea/digifi-FirstCore.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 jmeter/digifi_first_core_test_plan.jmx diff --git a/.idea/digifi-FirstCore.iml b/.idea/digifi-FirstCore.iml new file mode 100644 index 0000000..e2fec49 --- /dev/null +++ b/.idea/digifi-FirstCore.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8bb642f --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..82bd6d1 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/api/routes/routes.py b/app/api/routes/routes.py index f4a8ffb..e5b2e1a 100644 --- a/app/api/routes/routes.py +++ b/app/api/routes/routes.py @@ -62,16 +62,20 @@ def get_loans(): filters = { 'customer_id': request.args.get('customer_id'), 'account_id': request.args.get('account_id'), - 'offer_id': request.args.get('offer_id'), 'status': request.args.get('status'), + 'offer_id': request.args.get('offer_id'), + 'product_id': request.args.get('product_id'), 'start_date': request.args.get('start_date'), - 'end_date': request.args.get('end_date') + 'end_date': request.args.get('end_date'), + 'due_before': request.args.get('due_before'), + 'due_after': request.args.get('due_after') } # logger.info(f"Get loans request received with filters: {filters}") response = LoanService.process_request(filters) return response + # Authorize endpoint @api.route("/Authorize", methods=["POST"]) def authorize(): @@ -88,4 +92,4 @@ def refresh(): data = request.get_json() # logger.info(f"Authorize refresh request received: {data}") response = AuthorizationService.process_refresh_request() - return response + return response \ No newline at end of file diff --git a/app/api/services/loan.py b/app/api/services/loan.py index 4964505..609924f 100644 --- a/app/api/services/loan.py +++ b/app/api/services/loan.py @@ -24,42 +24,36 @@ class LoanService(BaseService): # Extract filters customer_id = filters.get('customer_id') account_id = filters.get('account_id') - offer_id = filters.get('offer_id') status = filters.get('status') + offer_id = filters.get('offer_id') + product_id = filters.get('product_id') start_date = filters.get('start_date') end_date = filters.get('end_date') + due_before = filters.get('due_before') + due_after = filters.get('due_after') # Convert string dates to datetime objects if provided if start_date and isinstance(start_date, str): start_date = datetime.fromisoformat(start_date.replace('Z', '+00:00')) if end_date and isinstance(end_date, str): end_date = datetime.fromisoformat(end_date.replace('Z', '+00:00')) + if due_before and isinstance(due_before, str): + due_before = datetime.fromisoformat(due_before.replace('Z', '+00:00')) + if due_after and isinstance(due_after, str): + due_after = datetime.fromisoformat(due_after.replace('Z', '+00:00')) - # Get loans with filters - query = Loan.query - - if customer_id: - query = query.filter(Loan.customer_id == customer_id) - - if account_id: - query = query.filter(Loan.account_id == account_id) - - if offer_id: - query = query.filter(Loan.offer_id == offer_id) - - if status: - query = query.filter(Loan.status == status) - - if start_date: - query = query.filter(Loan.created_at >= start_date) - - if end_date: - query = query.filter(Loan.created_at <= end_date) - - # Order by created_at descending (newest first) - query = query.order_by(Loan.created_at.desc()) - - loans = query.all() + # Get loans with optional filters + loans = Loan.get_all_loans( + customer_id=customer_id, + account_id=account_id, + status=status, + offer_id=offer_id, + product_id=product_id, + start_date=start_date, + end_date=end_date, + due_before=due_before, + due_after=due_after + ) # Convert loans to dictionary format loans_data = [] @@ -69,10 +63,15 @@ class LoanService(BaseService): 'customer_id': loan.customer_id, 'account_id': loan.account_id, 'offer_id': loan.offer_id, - 'principal_amount': loan.principal_amount, + 'initial_loan_amount': loan.initial_loan_amount, + 'current_loan_amount': loan.current_loan_amount, 'status': loan.status, - 'created_at': loan.created_at.isoformat(), - 'updated_at': loan.updated_at.isoformat() + 'product_id': loan.product_id, + 'default_penalty_fee': loan.default_penalty_fee, + 'continuous_fee': loan.continuous_fee, + 'due_date': loan.due_date.isoformat() if loan.due_date else None, + 'created_at': loan.created_at.isoformat() if loan.created_at else None, + 'updated_at': loan.updated_at.isoformat() if loan.updated_at else None }) response_data = { @@ -83,8 +82,7 @@ class LoanService(BaseService): return response_data except Exception as e: - logger.error(f"Error retrieving loans: {str(e)}", exc_info=True) + logger.error(f"An error occurred: {str(e)}", exc_info=True) return jsonify({ - 'status': 'error', - 'message': f'Failed to retrieve loans: {str(e)}' - }), 500 + "message": "Internal Server Error" + }), 500 \ No newline at end of file diff --git a/app/models/loan.py b/app/models/loan.py index 33256e4..ea3a8bd 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -4,6 +4,7 @@ from app.models.customer import Customer from app.models.account import Account from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import relationship +from sqlalchemy import and_, or_, not_ from app.models import Customer @@ -18,10 +19,15 @@ class Loan(db.Model): customer_id = db.Column(db.String(50), nullable=False) account_id = db.Column(db.String(50), nullable=False) offer_id = db.Column(db.String(20), nullable=False) - principal_amount = db.Column(db.Float, nullable=False) + initial_loan_amount = db.Column(db.Float, nullable=False) status = db.Column(db.String(20), default='pending') 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)) + product_id = db.Column(db.String(20)) + current_loan_amount = db.Column(db.Float) + default_penalty_fee = db.Column(db.Float) + continuous_fee = db.Column(db.Float) + due_date = db.Column(db.DateTime) customer = relationship( "Customer", @@ -31,26 +37,32 @@ class Loan(db.Model): ) @classmethod - def create_loan(cls, customer_id, account_id, offer_id, principal_amount, status='pending'): + def create_loan(cls, customer_id, account_id, offer_id, initial_loan_amount, status='pending', + product_id=None, current_loan_amount=None, default_penalty_fee=None, + continuous_fee=None, due_date=None): # Check if customer exists is_valid = Customer.is_valid_customer(customer_id) if not is_valid: raise ValueError("Customer does not exist") - + # # Check for active loans # has_active_loans = cls.has_active_loans(customer_id) # if has_active_loans: # raise ValueError("Customer has active loans") - # Create and save the loan loan = cls( customer_id=customer_id, account_id=account_id, offer_id=offer_id, - principal_amount=principal_amount, - status=status + initial_loan_amount=initial_loan_amount, + status=status, + product_id=product_id, + current_loan_amount=current_loan_amount or initial_loan_amount, + default_penalty_fee=default_penalty_fee, + continuous_fee=continuous_fee, + due_date=due_date ) try: @@ -59,7 +71,6 @@ class Loan(db.Model): raise ValueError(f"Database integrity error: {err}") return loan - @classmethod def has_active_loans(cls, customer_id): active_loans = cls.query.filter_by( @@ -70,35 +81,89 @@ class Loan(db.Model): if active_loans > 0: return False return True - @classmethod def get_customer_loan(cls, loan_id, customer_id): """ Get customer's active loans. """ - loan = cls.query.filter_by(id = loan_id, customer_id = customer_id).first() + 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}.") + raise ValueError(f"Loan with ID {loan_id} does not exist or does not belong to customer {customer_id}.") return loan - + @classmethod def update_status(cls, loan_id, status): """ Update the status of the loan with the given loan_id. """ - # Retrieve loan + # Retrieve loan loan = cls.query.get(loan_id) - + if not loan: raise ValueError(f"Loan with ID {loan_id} does not exist.") - + if loan.status == status: return - + # Update loan status and the updated_at timestamp loan.status = status + @classmethod + def get_all_loans(cls, customer_id=None, account_id=None, status=None, offer_id=None, + product_id=None, start_date=None, end_date=None, due_before=None, due_after=None): + """ + Get all loans with optional filtering + + Args: + customer_id (str, optional): Filter by customer ID + account_id (str, optional): Filter by account ID + status (str, optional): Filter by loan status + offer_id (str, optional): Filter by offer ID + product_id (str, optional): Filter by product ID + start_date (datetime, optional): Filter by start date (created_at) + end_date (datetime, optional): Filter by end date (created_at) + due_before (datetime, optional): Filter loans due before this date + due_after (datetime, optional): Filter loans due after this date + + Returns: + list: List of Loan objects + """ + query = cls.query + + # Apply filters if provided + if customer_id: + query = query.filter(cls.customer_id == customer_id) + + if account_id: + query = query.filter(cls.account_id == account_id) + + if status: + query = query.filter(cls.status == status) + + if offer_id: + query = query.filter(cls.offer_id == offer_id) + + if product_id: + query = query.filter(cls.product_id == product_id) + + if start_date: + query = query.filter(cls.created_at >= start_date) + + if end_date: + query = query.filter(cls.created_at <= end_date) + + if due_before: + query = query.filter(cls.due_date <= due_before) + + if due_after: + query = query.filter(cls.due_date >= due_after) + + # Order by created_at descending (newest first) + query = query.order_by(cls.created_at.desc()) + + return query.all() + def to_dict(self): """ Convert the Loan object to a dictionary format for JSON serialization. @@ -108,11 +173,16 @@ class Loan(db.Model): 'customer_id': self.customer_id, 'account_id': self.account_id, 'offer_id': self.offer_id, - 'principal_amount': self.principal_amount, + 'initial_loan_amount': self.initial_loan_amount, + 'current_loan_amount': self.current_loan_amount, 'status': self.status, + 'product_id': self.product_id, + 'default_penalty_fee': self.default_penalty_fee, + 'continuous_fee': self.continuous_fee, + 'due_date': self.due_date.isoformat() if self.due_date else None, 'created_at': self.created_at.isoformat() if self.created_at else None, 'updated_at': self.updated_at.isoformat() if self.updated_at else None } - + def __repr__(self): return f'' \ No newline at end of file diff --git a/jmeter/digifi_first_core_test_plan.jmx b/jmeter/digifi_first_core_test_plan.jmx new file mode 100644 index 0000000..b1e3f4f --- /dev/null +++ b/jmeter/digifi_first_core_test_plan.jmx @@ -0,0 +1,402 @@ + + + + + + false + true + false + + + + + + + + 10000 + 30000 + localhost + 4300 + http + + + + + + + + + + customer_id + CUST123 + = + + + account_id + ACC456 + = + + + transaction_type + PAYMENT + = + + + channel + MOBILE + = + + + status + active + = + + + offer_id + OFFER789 + = + + + product_id + PROD101 + = + + + + + + 10 + 5 + true + continue + + 10 + false + + + + + /loans + true + GET + true + false + + + + + + + + 200 + + + Assertion.response_code + false + 8 + + + + $.loans + + false + false + false + true + + + + + /loans + true + GET + true + false + + + + false + ${customer_id} + = + true + customer_id + + + false + ${status} + = + true + status + + + + + + + + 200 + + + Assertion.response_code + false + 8 + + + + $.loans + + false + false + false + true + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + 10 + 5 + true + continue + + 10 + false + + + + + /transactions + true + GET + true + false + + + + + + + + 200 + + + Assertion.response_code + false + 8 + + + + $.transactions + + false + false + false + true + + + + + /transactions + true + GET + true + false + + + + false + ${account_id} + = + true + account_id + + + false + ${transaction_type} + = + true + type + + + false + ${channel} + = + true + channel + + + + + + + + 200 + + + Assertion.response_code + false + 8 + + + + $.transactions + + false + false + false + true + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + +