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
+
+
+
+
+
+
+
+
+