diff --git a/app/api/routes/routes.py b/app/api/routes/routes.py index 2c1078e..0f599c0 100644 --- a/app/api/routes/routes.py +++ b/app/api/routes/routes.py @@ -11,6 +11,7 @@ from app.api.services.auth_service import AuthService from app.api.services.dashboard_service import DashboardService from app.api.services.offer_service import OfferService from app.api.services.charge_service import ChargeService +from app.api.services.repayment_data_service import RepaymentDataService from functools import wraps from app.utils.logger import logger from app.api.middlewares import enforce_json, require_auth @@ -180,6 +181,22 @@ def get_all_repayments(): # logger.info(f"Get repayments request received with filters: {filters}") response = RepaymentService.get_all_repayments(filters) return response +@api.route('/repayment-data', methods=['GET']) +# @token_required +def get_all_repayments_data(): + # Extract query parameters for filtering + filters = { + 'customer_id': request.args.get('customer_id'), + 'account_id': request.args.get('account_id'), + 'added_date': request.args.get('added_date'), + 'transaction_id': request.args.get('transaction_id'), + 'fbn_transaction_id': request.args.get('fbn_transaction_id'), + 'page': request.args.get('page', 1), + 'limit': request.args.get('limit', 20) + } + # logger.info(f"Get repayments request received with filters: {filters}") + response = RepaymentDataService.get_all_repayments_data(filters) + return response @api.route('/loan-charges', methods=['GET']) # @token_required diff --git a/app/api/services/__init__.py b/app/api/services/__init__.py index 8ff98b2..8cb2682 100644 --- a/app/api/services/__init__.py +++ b/app/api/services/__init__.py @@ -9,3 +9,4 @@ from app.api.services.repayment_service import RepaymentService from app.api.services.loan_charge_service import LoanChargeService from app.api.services.loan_repayment_schedule_service import LoanRepaymentScheduleService from app.api.services.transaction_offers_service import TransactionOfferService +from app.api.services.repayment_data_service import RepaymentDataService diff --git a/app/api/services/repayment_data_service.py b/app/api/services/repayment_data_service.py new file mode 100644 index 0000000..3cf0d65 --- /dev/null +++ b/app/api/services/repayment_data_service.py @@ -0,0 +1,82 @@ +import logging +from datetime import datetime +from app.models.repayment_data import RepaymentsData +from dateutil.parser import parse as parse_date + +logger = logging.getLogger(__name__) + +class RepaymentDataService: + """ + Service class for handling repayment-data operations. + """ + + @staticmethod + def get_all_repayments_data(filters=None): + try: + if filters is None: + filters = {} + + customer_id = filters.get('customer_id') + account_id = filters.get('account_id') + added_date = filters.get('added_date') + transaction_id = filters.get('transaction_id') + fbn_transaction_id = filters.get('fbn_transaction_id') + + page = int(filters.get('page', 1)) + limit = int(filters.get('limit', 20)) + + if page < 1: + page = 1 + if limit < 1 or limit > 100: + limit = 20 + + if added_date and isinstance(added_date, str): + try: + added_date = parse_date(added_date) + except Exception as parse_err: + logger.error(f"Invalid date format for 'added_date': {added_date}") + return {"message": "Invalid date format for 'added_date'"}, 400 + + repayments_data, total_count = RepaymentsData.get_all_repayment_data( + customer_id=customer_id, + account_id=account_id, + added_date=added_date, + transaction_id=transaction_id, + fbn_transaction_id=fbn_transaction_id, + page=page, + limit=limit + ) + + repayments_list = [] + for repayment in repayments_data: + repayments_list.append({ + 'customer_id': repayment.customer_id, + 'account_id': repayment.account_id, + 'transaction_id': repayment.transaction_id, + 'fbn_transaction_id': repayment.fbn_transaction_id, + 'added_date': repayment.added_date.isoformat() if repayment.added_date else None, + 'response_code': repayment.response_code, + 'response_descr': repayment.response_descr, + 'repayment_amount': repayment.repayment_amount, + 'amount_collected': repayment.amount_collected, + 'balance': repayment.balance + }) + + total_pages = (total_count + limit - 1) // limit + + return { + 'repayment_data': repayments_list, + 'count': len(repayments_list), + 'pagination': { + 'total_count': total_count, + 'total_pages': total_pages, + 'current_page': page, + 'limit': limit, + 'has_next': page < total_pages, + 'has_prev': page > 1 + } + } + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return {"message": "Internal Server Error"}, 500 diff --git a/app/models/repayment_data.py b/app/models/repayment_data.py new file mode 100644 index 0000000..eaa018a --- /dev/null +++ b/app/models/repayment_data.py @@ -0,0 +1,80 @@ +from datetime import datetime, timezone +from app.extensions import db +from app.utils.logger import logger + +class RepaymentsData(db.Model): + __tablename__ = 'repayments_data' + + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + transaction_id = db.Column(db.String(50), nullable=False) + added_date = db.Column(db.DateTime(timezone=True), default=datetime.now(timezone.utc), nullable=False) + response_code = db.Column(db.String(10), nullable=True) + response_descr = db.Column(db.String(255), nullable=True) + fbn_transaction_id = db.Column(db.String(255),nullable=True) + account_id = db.Column(db.String(50), nullable=True) + customer_id = db.Column(db.String(50), nullable=True) + repayment_amount = db.Column(db.Float, nullable=True) + amount_collected = db.Column(db.Float, nullable=True) + balance = db.Column(db.Float, nullable=True, default=0.0) + + def to_dict(self): + return { + "id": self.id, + "transaction_id": self.transaction_id, + "added_date": self.added_date.isoformat() if self.added_date else None, + "response_code": self.response_code, + "response_descr": self.response_descr, + "customerId": self.customer_id, + "accountId": self.account_id, + "fbnTransactionId": self.fbn_transaction_id, + "repaymentAmount": self.repayment_amount, + "amountCollected": self.amount_collected, + "balance": self.balance + } + + + def __repr__(self): + return f"" + + @classmethod + def get_all_repayment_data(cls, customer_id=None, account_id=None, transaction_id=None, fbn_transaction_id=None, + added_date=None, page=1, limit=20): + """ + Get all repayment data with optional filtering + + Args: + customer_id (str, optional): Filter by customer ID + account_id (str, optional): Filter by account ID + added_date (datetime, optional): Filter by added date + transaction_id (str, optional): Filter by transaction ID + fbn_transaction_id (str, optional): Filter by FBN transaction ID + page (int, optional): Page number for pagination + limit (int, optional): Number of items per page + + Returns: + tuple: (list of Repayment objects, total count) + """ + query = cls.query + if customer_id: + query = query.filter(cls.customer_id == customer_id) + if account_id: + query = query.filter(cls.account_id == account_id) + if transaction_id: + query = query.filter(cls.transaction_id == transaction_id) + if fbn_transaction_id: + query = query.filter(cls.fbn_transaction_id == fbn_transaction_id) + + if added_date: + query = query.filter(cls.added_date >= added_date) + + # Get total count before pagination + total_count = query.count() + + #order + query = query.order_by(cls.added_date.desc()) + + # Apply pagination + offset = (page - 1) * limit + query = query.limit(limit).offset(offset) + + return query.all(), total_count \ No newline at end of file diff --git a/app/swagger/digifi_swagger.json b/app/swagger/digifi_swagger.json index 7df9c43..82befee 100644 --- a/app/swagger/digifi_swagger.json +++ b/app/swagger/digifi_swagger.json @@ -80,6 +80,14 @@ "url": "https://www.simbrellang.net" } }, + { + "name": "Repayment Data", + "description": "Get all repayment data with optional filtering.", + "externalDocs": { + "description": "Find out more", + "url": "https://www.simbrellang.net" + } + }, { "name": "Offers", "description": "Get all offers with optional filtering.", @@ -119,6 +127,9 @@ "/loan-charges": { "$ref": "../swagger/paths/LoanCharges.json" }, + "/repayment-data": { + "$ref": "../swagger/paths/RepaymentData.json" + }, "/repayment-schedules": { "$ref": "../swagger/paths/RepaymentSchedules.json" }, @@ -158,6 +169,9 @@ "RepaymentsResponse": { "$ref": "../swagger/schemas/RepaymentsResponse.json" }, + "RepaymentDataResponse": { + "$ref": "../swagger/schemas/RepaymentDataResponse.json" + }, "LoanChargesResponse": { "$ref": "../swagger/schemas/LoanChargesResponse.json" }, diff --git a/app/swagger/paths/RepaymentData.json b/app/swagger/paths/RepaymentData.json new file mode 100644 index 0000000..07f4151 --- /dev/null +++ b/app/swagger/paths/RepaymentData.json @@ -0,0 +1,104 @@ +{ + "get": { + "tags": ["RepaymentData"], + "summary": "Get all repayment data with optional filtering", + "description": "Retrieve repayment data records with optional filtering by transaction ID, customer ID, account ID, FBN transaction ID, and added date. Supports pagination.", + "operationId": "getRepaymentData", + "parameters": [ + { + "name": "transaction_id", + "in": "query", + "description": "Filter by transaction ID", + "required": false, + "schema": { + "type": "string" + }, + "example": "TRX123456789" + }, + { + "name": "customer_id", + "in": "query", + "description": "Filter by customer ID", + "required": false, + "schema": { + "type": "string" + }, + "example": "CID0000025585" + }, + { + "name": "account_id", + "in": "query", + "description": "Filter by account ID", + "required": false, + "schema": { + "type": "string" + }, + "example": "ACCT000000123" + }, + { + "name": "fbn_transaction_id", + "in": "query", + "description": "Filter by FBN transaction ID", + "required": false, + "schema": { + "type": "string" + }, + "example": "FBNTRX7890" + }, + { + "name": "added_date", + "in": "query", + "description": "Filter by added date (ISO format)", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2024-01-01T00:00:00Z" + }, + { + "name": "page", + "in": "query", + "description": "Page number for pagination", + "required": false, + "schema": { + "type": "integer", + "default": 1, + "minimum": 1 + }, + "example": 1 + }, + { + "name": "limit", + "in": "query", + "description": "Number of items per page (max 100)", + "required": false, + "schema": { + "type": "integer", + "default": 20, + "minimum": 1, + "maximum": 100 + }, + "example": 20 + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/RepaymentDataResponse.json" + } + } + } + }, + "400": { + "description": "Invalid request" + }, + "500": { + "description": "Internal server error" + } + } + } +} diff --git a/app/swagger/schemas/RepaymentDataResponse.json b/app/swagger/schemas/RepaymentDataResponse.json new file mode 100644 index 0000000..35cbfd2 --- /dev/null +++ b/app/swagger/schemas/RepaymentDataResponse.json @@ -0,0 +1,106 @@ +{ + "type": "object", + "properties": { + "repayment_data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 1 + }, + "transaction_id": { + "type": "string", + "example": "TRX123456", + "nullable": false + }, + "added_date": { + "type": "string", + "format": "date-time", + "example": "2025-04-10T16:45:47.879Z" + }, + "response_code": { + "type": "string", + "example": "00", + "nullable": true + }, + "response_descr": { + "type": "string", + "example": "Repayment successful", + "nullable": true + }, + "fbn_transaction_id": { + "type": "string", + "example": "FBN123456", + "nullable": true + }, + "customer_id": { + "type": "string", + "example": "CID0000025585", + "nullable": true + }, + "account_id": { + "type": "string", + "example": "ACCT000000123", + "nullable": true + }, + "repayment_amount": { + "type": "number", + "format": "float", + "example": 15000.0, + "nullable": true + }, + "amount_collected": { + "type": "number", + "format": "float", + "example": 14500.0, + "nullable": true + }, + "balance": { + "type": "number", + "format": "float", + "example": 500.0, + "nullable": true + } + } + } + }, + "count": { + "type": "integer", + "example": 1 + }, + "pagination": { + "type": "object", + "properties": { + "total_count": { + "type": "integer", + "example": 100 + }, + "total_pages": { + "type": "integer", + "example": 5 + }, + "current_page": { + "type": "integer", + "example": 1 + }, + "limit": { + "type": "integer", + "example": 20 + }, + "has_next": { + "type": "boolean", + "example": true + }, + "has_prev": { + "type": "boolean", + "example": false + } + } + } + }, + "xml": { + "name": "RepaymentDataResponse" + } +} diff --git a/requirements.txt b/requirements.txt index 9d5c945..7b051ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,8 @@ alembic Flask-Marshmallow==0.15.0 marshmallow==3.19.0 +python-dateutil==2.9.0 + # CORS Flask-Cors==3.0.10