Merge branch 'testing' of DigiFi/FirstCore into master

This commit is contained in:
2025-05-19 15:04:49 +00:00
committed by Gogs
7 changed files with 458 additions and 31 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_service import LoanService
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.dashboard_service import DashboardService
from app.api.services.offer_service import OfferService
@@ -144,6 +145,24 @@ def get_transactions():
response = TransactionService.process_request(filters)
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'])
# @token_required
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.loan_charge_service import LoanChargeService
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() if offer.created_at else None,
'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
+88 -31
View File
@@ -2,9 +2,10 @@ from datetime import datetime, timezone
from app.extensions import db
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from sqlalchemy import and_, or_, not_
import logging
logger = logging.getLogger(__name__)
logger = logging.getLogger(__name__)
class TransactionOffer(db.Model):
@@ -21,46 +22,102 @@ class TransactionOffer(db.Model):
eligible_amount = db.Column(db.Float, nullable=True)
tenor = db.Column(db.Integer, nullable=True) # tenor in months, typically
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now())
customer = relationship(
"Customer",
primaryjoin="Customer.id == TransactionOffer.customer_id",
foreign_keys=[customer_id],
back_populates="transaction_offers",
)
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))
# created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
# updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now())
def __repr__(self):
return f'<TransactionOffer {self.id}>'
# customer = relationship(
# "Customer",
# primaryjoin="Customer.id == TransactionOffer.customer_id",
# foreign_keys=[customer_id],
# back_populates="transaction_offers",
# )
@classmethod
def is_valid_transaction_offer(cls, transaction_offer, customer_id, product_id):
transaction_offer = cls.query.filter_by(
id = transaction_offer,
customer_id = customer_id,
id=transaction_offer,
customer_id=customer_id,
# product_id = product_id
# transaction_id = transaction_id,
).first()
).first()
if not transaction_offer:
return False
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
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,
}
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 __repr__(self):
return f'<TransactionOffer {self.id}>'
Returns:
tuple: (List of TransactionOffer objects, total count)
"""
query = cls.query
# Apply filters if provided
if customer_id:
query = query.filter(cls.customer_id == customer_id)
if transaction_id:
query = query.filter(cls.transaction_id == transaction_id)
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,
# }
+14
View File
@@ -87,6 +87,14 @@
"description": "Find out more",
"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": {
@@ -116,6 +124,9 @@
},
"/offers": {
"$ref": "../swagger/paths/Offers.json"
},
"/transaction-offers": {
"$ref": "../swagger/paths/TransactionOffers.json"
}
},
"components": {
@@ -155,6 +166,9 @@
},
"OffersResponse": {
"$ref": "../swagger/schemas/OffersResponse.json"
},
"TransactionOffersResponse": {
"$ref": "../swagger/schemas/TransactionOffersResponse.json"
}
},
"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"
}
}