Added Transaction offer

This commit is contained in:
Azeez Muibi
2025-05-19 13:24:17 +01:00
parent 636f0f1a95
commit 3ae29b69b5
7 changed files with 446 additions and 22 deletions
+19
View File
@@ -6,6 +6,7 @@ from app.api.services.repayment_service import RepaymentService
from app.api.services.loan_charge_service import LoanChargeService from app.api.services.loan_charge_service import LoanChargeService
from app.api.services.loan_service import LoanService from app.api.services.loan_service import LoanService
from app.api.services.transaction_service import TransactionService from app.api.services.transaction_service import TransactionService
from app.api.services.transaction_offers_service import TransactionOfferService
from app.api.services.auth_service import AuthService from app.api.services.auth_service import AuthService
from app.api.services.dashboard_service import DashboardService from app.api.services.dashboard_service import DashboardService
from app.api.services.offer_service import OfferService from app.api.services.offer_service import OfferService
@@ -144,6 +145,24 @@ def get_transactions():
response = TransactionService.process_request(filters) response = TransactionService.process_request(filters)
return response return response
@api.route('/transaction-offers', methods=['GET'])
# @token_required
def get_transaction_offers():
# Extract query parameters for filtering
filters = {
'customer_id': request.args.get('customer_id'),
'transaction_id': request.args.get('transaction_id'),
'offer_id': request.args.get('offer_id'),
'product_id': request.args.get('product_id'),
'original_transaction': request.args.get('original_transaction'),
'start_date': request.args.get('start_date'),
'end_date': request.args.get('end_date'),
'page': request.args.get('page', 1),
'limit': request.args.get('limit', 20)
}
response = TransactionOfferService.process_request(filters)
return response
@api.route('/repayments', methods=['GET']) @api.route('/repayments', methods=['GET'])
# @token_required # @token_required
def get_all_repayments(): def get_all_repayments():
+1
View File
@@ -8,3 +8,4 @@ from app.api.services.dashboard_service import DashboardService
from app.api.services.repayment_service import RepaymentService from app.api.services.repayment_service import RepaymentService
from app.api.services.loan_charge_service import LoanChargeService from app.api.services.loan_charge_service import LoanChargeService
from app.api.services.loan_repayment_schedule_service import LoanRepaymentScheduleService from app.api.services.loan_repayment_schedule_service import LoanRepaymentScheduleService
from app.api.services.transaction_offers_service import TransactionOfferService
@@ -0,0 +1,109 @@
import logging
from datetime import datetime
from flask import jsonify
from app.models.transaction_offers import TransactionOffer
logger = logging.getLogger(__name__)
class TransactionOfferService:
"""
Service class for handling transaction offer-related operations.
"""
@staticmethod
def process_request(filters=None):
"""
Process the get transaction offers request.
Args:
filters (dict, optional): Filters for the transaction offers query.
Returns:
dict: A standardized response with transaction offers data.
"""
try:
if filters is None:
filters = {}
# Extract filters
customer_id = filters.get('customer_id')
transaction_id = filters.get('transaction_id')
offer_id = filters.get('offer_id')
product_id = filters.get('product_id')
original_transaction = filters.get('original_transaction')
start_date = filters.get('start_date')
end_date = filters.get('end_date')
# Extract pagination parameters
page = int(filters.get('page', 1))
limit = int(filters.get('limit', 20))
# Ensure page and limit are valid
if page < 1:
page = 1
if limit < 1 or limit > 100:
limit = 20
# 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'))
# Get transaction offers with optional filters and pagination
transaction_offers, total_count = TransactionOffer.get_all_transaction_offers(
customer_id=customer_id,
transaction_id=transaction_id,
offer_id=offer_id,
product_id=product_id,
original_transaction=original_transaction,
start_date=start_date,
end_date=end_date,
page=page,
limit=limit
)
# Convert transaction offers to dictionary format
transaction_offers_data = []
for offer in transaction_offers:
transaction_offers_data.append({
'id': offer.id,
'customer_id': offer.customer_id,
'transaction_id': offer.transaction_id,
'original_transaction': offer.original_transaction,
'offer_id': offer.offer_id,
'product_id': offer.product_id,
'min_amount': offer.min_amount,
'max_amount': offer.max_amount,
'eligible_amount': offer.eligible_amount,
'tenor': offer.tenor,
'created_at': offer.created_at.isoformat(),
'updated_at': offer.updated_at.isoformat() if offer.updated_at else None
})
# Calculate total pages
total_pages = (total_count + limit - 1) // limit
response_data = {
'transaction_offers': transaction_offers_data,
'count': len(transaction_offers_data),
'pagination': {
'total_count': total_count,
'total_pages': total_pages,
'current_page': page,
'limit': limit,
'has_next': page < total_pages,
'has_prev': page > 1
}
}
return response_data
except Exception as e:
logger.error(f"An error occurred: {str(e)}", exc_info=True)
return jsonify({
"message": "Internal Server Error"
}), 500
+74 -20
View File
@@ -2,9 +2,10 @@ from datetime import datetime, timezone
from app.extensions import db from app.extensions import db
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from sqlalchemy.sql import func from sqlalchemy.sql import func
from sqlalchemy import and_, or_, not_
import logging import logging
logger = logging.getLogger(__name__)
logger = logging.getLogger(__name__)
class TransactionOffer(db.Model): class TransactionOffer(db.Model):
@@ -31,36 +32,89 @@ class TransactionOffer(db.Model):
) )
@classmethod @classmethod
def is_valid_transaction_offer(cls, transaction_offer, customer_id, product_id): def is_valid_transaction_offer(cls, transaction_offer, customer_id, product_id):
transaction_offer = cls.query.filter_by( transaction_offer = cls.query.filter_by(
id = transaction_offer, id=transaction_offer,
customer_id = customer_id, customer_id=customer_id,
# product_id = product_id # product_id = product_id
# transaction_id = transaction_id, # transaction_id = transaction_id,
).first() ).first()
if not transaction_offer: if not transaction_offer:
return False return False
return transaction_offer return transaction_offer
@classmethod
def get_all_transaction_offers(cls, customer_id=None, transaction_id=None, offer_id=None,
product_id=None, original_transaction=None,
start_date=None, end_date=None, page=1, limit=20):
"""
Get all transaction offers with optional filtering
Args:
customer_id (str, optional): Filter by customer ID
transaction_id (str, optional): Filter by transaction ID
offer_id (str, optional): Filter by offer ID
product_id (str, optional): Filter by product ID
original_transaction (str, optional): Filter by original transaction
start_date (datetime, optional): Filter by start date
end_date (datetime, optional): Filter by end date
page (int, optional): Page number for pagination
limit (int, optional): Number of items per page
def to_dict(self): Returns:
return { tuple: (List of TransactionOffer objects, total count)
'id': self.id, """
'customerId': self.customer_id, query = cls.query
'transactionId': self.transaction_id,
'offerId': self.offer_id, # Apply filters if provided
'productId': self.product_id, if customer_id:
'minAmount': self.min_amount, query = query.filter(cls.customer_id == customer_id)
'maxAmount': self.max_amount,
'eligibleAmount': self.eligible_amount, if transaction_id:
'tenor': self.tenor, query = query.filter(cls.transaction_id == transaction_id)
'createdAt': self.created_at.isoformat() if self.created_at else None,
'updatedAt': self.updated_at.isoformat() if self.updated_at else None, if offer_id:
} query = query.filter(cls.offer_id == offer_id)
if product_id:
query = query.filter(cls.product_id == product_id)
if original_transaction:
query = query.filter(cls.original_transaction == original_transaction)
if start_date:
query = query.filter(cls.created_at >= start_date)
if end_date:
query = query.filter(cls.created_at <= end_date)
# Order by created_at descending (newest first)
query = query.order_by(cls.created_at.desc())
# Get total count before pagination
total_count = query.count()
# Apply pagination
offset = (page - 1) * limit
query = query.limit(limit).offset(offset)
return query.all(), total_count
# def to_dict(self):
# return {
# 'id': self.id,
# 'customerId': self.customer_id,
# 'transactionId': self.transaction_id,
# 'offerId': self.offer_id,
# 'productId': self.product_id,
# 'minAmount': self.min_amount,
# 'maxAmount': self.max_amount,
# 'eligibleAmount': self.eligible_amount,
# 'tenor': self.tenor,
# 'createdAt': self.created_at.isoformat() if self.created_at else None,
# 'updatedAt': self.updated_at.isoformat() if self.updated_at else None,
# }
def __repr__(self): def __repr__(self):
return f'<TransactionOffer {self.id}>' return f'<TransactionOffer {self.id}>'
+14
View File
@@ -87,6 +87,14 @@
"description": "Find out more", "description": "Find out more",
"url": "https://www.simbrellang.net" "url": "https://www.simbrellang.net"
} }
},
{
"name": "Transaction Offers",
"description": "Get all transaction offers with optional filtering.",
"externalDocs": {
"description": "Find out more",
"url": "https://www.simbrellang.net"
}
} }
], ],
"paths": { "paths": {
@@ -116,6 +124,9 @@
}, },
"/offers": { "/offers": {
"$ref": "../swagger/paths/Offers.json" "$ref": "../swagger/paths/Offers.json"
},
"/transaction-offers": {
"$ref": "../swagger/paths/TransactionOffers.json"
} }
}, },
"components": { "components": {
@@ -155,6 +166,9 @@
}, },
"OffersResponse": { "OffersResponse": {
"$ref": "../swagger/schemas/OffersResponse.json" "$ref": "../swagger/schemas/OffersResponse.json"
},
"TransactionOffersResponse": {
"$ref": "../swagger/schemas/TransactionOffersResponse.json"
} }
}, },
"securitySchemes": { "securitySchemes": {
+125
View File
@@ -0,0 +1,125 @@
{
"get": {
"tags": ["Transaction Offers"],
"summary": "Get all transaction offers with optional filtering",
"description": "Retrieve transaction offers with various filter options including customer ID, transaction ID, offer ID, etc.",
"operationId": "getTransactionOffers",
"parameters": [
{
"name": "customer_id",
"in": "query",
"description": "Filter by customer ID",
"required": false,
"schema": {
"type": "string"
},
"example": "CUST123"
},
{
"name": "transaction_id",
"in": "query",
"description": "Filter by transaction ID",
"required": false,
"schema": {
"type": "string"
},
"example": "TRX123456"
},
{
"name": "offer_id",
"in": "query",
"description": "Filter by offer ID",
"required": false,
"schema": {
"type": "string"
},
"example": "OFFER456"
},
{
"name": "product_id",
"in": "query",
"description": "Filter by product ID",
"required": false,
"schema": {
"type": "string"
},
"example": "PROD789"
},
{
"name": "original_transaction",
"in": "query",
"description": "Filter by original transaction ID",
"required": false,
"schema": {
"type": "string"
},
"example": "TRX789012"
},
{
"name": "start_date",
"in": "query",
"description": "Filter by start date (ISO format)",
"required": false,
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2023-01-01T00:00:00Z"
},
{
"name": "end_date",
"in": "query",
"description": "Filter by end date (ISO format)",
"required": false,
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2023-12-31T23:59:59Z"
},
{
"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/TransactionOffersResponse.json"
}
}
}
},
"400": {
"description": "Invalid request"
},
"500": {
"description": "Internal server error"
}
}
}
}
@@ -0,0 +1,102 @@
{
"type": "object",
"properties": {
"transaction_offers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"example": 1
},
"customer_id": {
"type": "string",
"example": "CUST123"
},
"transaction_id": {
"type": "string",
"example": "TRX123456"
},
"original_transaction": {
"type": "string",
"example": "TRX789012"
},
"offer_id": {
"type": "string",
"example": "OFFER456"
},
"product_id": {
"type": "string",
"example": "PROD789"
},
"min_amount": {
"type": "number",
"format": "float",
"example": 1000.0
},
"max_amount": {
"type": "number",
"format": "float",
"example": 5000.0
},
"eligible_amount": {
"type": "number",
"format": "float",
"example": 3000.0
},
"tenor": {
"type": "integer",
"example": 6
},
"created_at": {
"type": "string",
"format": "date-time",
"example": "2023-01-15T10:30:00Z"
},
"updated_at": {
"type": "string",
"format": "date-time",
"example": "2023-01-15T10:30:00Z"
}
}
}
},
"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": "TransactionOffersResponse"
}
}