Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a6e7eaac3c | |||
| 482a860bd2 | |||
| 6476b62d3c | |||
| e69335cb71 | |||
| 8a018545ec | |||
| 3031251519 | |||
| 1d97304f4e | |||
| 9f9512b060 | |||
| d397c834f4 | |||
| a196d4d3c4 | |||
| e5320c075e | |||
| 1081467f6f | |||
| f252e33be2 | |||
| 6f15ae97f4 | |||
| 7cea5390c0 | |||
| 729ebd5d08 |
@@ -1 +1,2 @@
|
||||
from .transaction_type import TransactionType
|
||||
from .transaction_type import TransactionType
|
||||
from .loan_status import LoanStatus
|
||||
@@ -0,0 +1,6 @@
|
||||
from enum import Enum
|
||||
|
||||
class LoanStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
ACTIVE = "active"
|
||||
REPAID = "repaid"
|
||||
@@ -43,9 +43,9 @@ class KafkaIntegration:
|
||||
|
||||
|
||||
@staticmethod
|
||||
def send_loan_request(loan_data, request_id):
|
||||
def send_loan_request(loan_data, request_id, topic):
|
||||
"""
|
||||
Send loan request to PROCESS_PAYMENT topic
|
||||
Send loan request to topic
|
||||
|
||||
Args:
|
||||
loan_data: Loan request payload as dict
|
||||
@@ -58,7 +58,7 @@ class KafkaIntegration:
|
||||
|
||||
# Sending loan request message to Kafka
|
||||
producer.produce(
|
||||
topic="PROCESS_PAYMENT",
|
||||
topic=topic,
|
||||
key=str(request_id),
|
||||
value=json.dumps(loan_data).encode("utf-8"),
|
||||
callback=KafkaIntegration.delivery_report,
|
||||
|
||||
@@ -3,6 +3,7 @@ from marshmallow import Schema, fields
|
||||
# Loan Information Schema
|
||||
class LoanStatusSchema(Schema):
|
||||
transactionId = fields.Str(required=True)
|
||||
accountId = fields.Str(required=True)
|
||||
customerId = fields.Str(required=True)
|
||||
msisdn = fields.Str(required=False)
|
||||
channel = fields.Str(required=True)
|
||||
@@ -7,5 +7,6 @@ class RepaymentSchema(Schema):
|
||||
debtId = fields.Str(required=True)
|
||||
productId = fields.Str(required=True)
|
||||
transactionId = fields.Str(required=True)
|
||||
accountId = fields.Str(required=True)
|
||||
customerId = fields.Str(required=True)
|
||||
channel = fields.Str(required=True)
|
||||
|
||||
@@ -3,6 +3,7 @@ from app.api.enums import TransactionType
|
||||
from flask import jsonify
|
||||
from marshmallow import ValidationError
|
||||
import logging
|
||||
from app.api.integrations import KafkaIntegration
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -48,8 +49,14 @@ class BaseService:
|
||||
Create a new transaction.
|
||||
"""
|
||||
return Transaction.create_transaction(
|
||||
transaction_id =validated_data.get("transactionId"),
|
||||
account_id=validated_data.get("accountId"),
|
||||
type=cls.TRANSACTION_TYPE,
|
||||
channel=validated_data.get("channel"),
|
||||
transaction_id = validated_data.get("transactionId"),
|
||||
customer_id = validated_data.get('customerId', None),
|
||||
account_id = validated_data.get("accountId", None),
|
||||
type = cls.TRANSACTION_TYPE,
|
||||
channel = validated_data.get("channel"),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def async_send_to_kafka(cls, loan_data, request_id, topic):
|
||||
KafkaIntegration.send_loan_request(loan_data = loan_data, request_id = request_id, topic = topic)
|
||||
KafkaIntegration.flush()
|
||||
|
||||
@@ -4,7 +4,8 @@ from marshmallow import ValidationError
|
||||
from app.utils.logger import logger
|
||||
from app.api.schemas.customer_consent import CustomerConsentSchema
|
||||
from app.api.services.base_service import BaseService
|
||||
from app.api.enums import TransactionType
|
||||
from app.api.enums import TransactionType
|
||||
from app.extensions import db
|
||||
|
||||
|
||||
class CustomerConsentService(BaseService):
|
||||
@@ -22,36 +23,39 @@ class CustomerConsentService(BaseService):
|
||||
dict: A standardized response.
|
||||
"""
|
||||
try:
|
||||
with db.session.begin():
|
||||
validated_data = CustomerConsentService.validate_data(data, CustomerConsentSchema())
|
||||
account_id = validated_data.get('accountId')
|
||||
customer_id = validated_data.get('customerId')
|
||||
|
||||
validated_data = CustomerConsentService.validate_data(data, CustomerConsentSchema())
|
||||
account_id = validated_data.get('accountId')
|
||||
customer_id = validated_data.get('customerId')
|
||||
if(CustomerConsentService.validate_account_ownership(account_id = account_id, customer_id = customer_id)):
|
||||
|
||||
transaction = CustomerConsentService.log_transaction(validated_data = validated_data)
|
||||
|
||||
if(CustomerConsentService.validate_account_ownership(account_id = account_id, customer_id = customer_id)):
|
||||
transaction = CustomerConsentService.log_transaction(validated_data = validated_data)
|
||||
|
||||
if not transaction:
|
||||
logger.error(f"Failed to log transaction")
|
||||
if not transaction:
|
||||
logger.error(f"Failed to log transaction")
|
||||
return jsonify({
|
||||
"message": "Failed to log transaction."
|
||||
}), 400
|
||||
else:
|
||||
return jsonify({
|
||||
"message": "Failed to log transaction."
|
||||
}), 400
|
||||
else:
|
||||
return jsonify({
|
||||
"message": "Invalid Customer or Account"
|
||||
}), 400
|
||||
"message": "Invalid Customer or Account"
|
||||
}), 400
|
||||
|
||||
|
||||
# Simulated processing logic
|
||||
response_data = {
|
||||
"resultCode": "00",
|
||||
"resultDescription": "Request is received"
|
||||
}
|
||||
|
||||
# Simulated processing logic
|
||||
response_data = {
|
||||
"resultCode": "00",
|
||||
"resultDescription": "Request is received"
|
||||
}
|
||||
|
||||
return response_data
|
||||
|
||||
db.session.commit()
|
||||
return response_data
|
||||
|
||||
except ValidationError as err:
|
||||
|
||||
logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}")
|
||||
db.session.rollback()
|
||||
|
||||
return jsonify({
|
||||
"message": "Validation exception"
|
||||
@@ -59,6 +63,7 @@ class CustomerConsentService(BaseService):
|
||||
|
||||
except ValueError as err:
|
||||
logger.error(f"{getattr(err, 'messages', str(err))}")
|
||||
db.session.rollback()
|
||||
|
||||
return jsonify({
|
||||
"message": str(err)
|
||||
@@ -66,6 +71,7 @@ class CustomerConsentService(BaseService):
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {str(e)}", exc_info=True)
|
||||
db.session.rollback()
|
||||
return jsonify({
|
||||
"message": "Internal Server Error"
|
||||
}) , 500
|
||||
@@ -5,6 +5,7 @@ from app.api.schemas.eligibility_check import EligibilityCheckSchema
|
||||
from marshmallow import ValidationError
|
||||
from app.api.enums import TransactionType
|
||||
from app.api.integrations import SimbrellaIntegration
|
||||
from app.extensions import db
|
||||
|
||||
class EligibilityCheckService(BaseService):
|
||||
TRANSACTION_TYPE = TransactionType.ELIGIBILITY_CHECK
|
||||
@@ -21,71 +22,75 @@ class EligibilityCheckService(BaseService):
|
||||
dict: A standardized response.
|
||||
"""
|
||||
try:
|
||||
with db.session.begin():
|
||||
|
||||
validated_data = EligibilityCheckService.validate_data(data, EligibilityCheckSchema())
|
||||
account_id = validated_data.get('accountId')
|
||||
customer_id = validated_data.get('customerId')
|
||||
transactionId = validated_data.get('transactionId')
|
||||
msisdn = validated_data.get('msisdn')
|
||||
validated_data = EligibilityCheckService.validate_data(data, EligibilityCheckSchema())
|
||||
account_id = validated_data.get('accountId')
|
||||
customer_id = validated_data.get('customerId')
|
||||
transactionId = validated_data.get('transactionId')
|
||||
msisdn = validated_data.get('msisdn')
|
||||
|
||||
customer = EligibilityCheckService.get_or_create_customer(validated_data = validated_data)
|
||||
customer = EligibilityCheckService.get_or_create_customer(validated_data = validated_data)
|
||||
|
||||
if (EligibilityCheckService.validate_account_ownership(account_id = account_id, customer_id = customer_id)):
|
||||
transaction = EligibilityCheckService.log_transaction(validated_data = validated_data)
|
||||
if (EligibilityCheckService.validate_account_ownership(account_id = account_id, customer_id = customer_id)):
|
||||
|
||||
transaction = EligibilityCheckService.log_transaction(validated_data = validated_data)
|
||||
|
||||
if not transaction:
|
||||
logger.error(f"Failed to log transaction")
|
||||
if not transaction:
|
||||
logger.error(f"Failed to log transaction")
|
||||
return jsonify({
|
||||
"message": "Failed to log transaction."
|
||||
}), 400
|
||||
|
||||
else:
|
||||
return jsonify({
|
||||
"message": "Failed to log transaction."
|
||||
}), 400
|
||||
else:
|
||||
return jsonify({
|
||||
"message": "Invalid Customer or Account"
|
||||
}), 400
|
||||
|
||||
# Call RACCheck
|
||||
response = SimbrellaIntegration.rac_check(
|
||||
customer_id = customer_id,
|
||||
account_id = account_id,
|
||||
transaction_id = transaction.id,
|
||||
)
|
||||
logger.error(f"This is Response Returned ****** : {str(response)}")
|
||||
"message": "Invalid Customer or Account"
|
||||
}), 400
|
||||
|
||||
# Call RACCheck
|
||||
response = SimbrellaIntegration.rac_check(
|
||||
customer_id = customer_id,
|
||||
account_id = account_id,
|
||||
transaction_id = transaction.id,
|
||||
)
|
||||
logger.error(f"This is Response Returned ****** : {str(response)}")
|
||||
|
||||
# this chck for error is not valid
|
||||
logger.error(f"Check for ERROR is not valid ****** FIX THIS !!!!!")
|
||||
#if "error" in response or response.get("status") != 200:
|
||||
# return jsonify({"message": "RACCheck failed"}), 400
|
||||
# this chck for error is not valid
|
||||
logger.error(f"Check for ERROR is not valid ****** FIX THIS !!!!!")
|
||||
#if "error" in response or response.get("status") != 200:
|
||||
# return jsonify({"message": "RACCheck failed"}), 400
|
||||
|
||||
offers = [
|
||||
{
|
||||
"offerId": "SAL90",
|
||||
"productId": "2030",
|
||||
"minAmount": 5000,
|
||||
"maxAmount": 100000,
|
||||
"tenor": 30
|
||||
},
|
||||
{
|
||||
"offerId": "SAL30",
|
||||
"productId": "2090",
|
||||
"minAmount": 3000,
|
||||
"maxAmount": 500000,
|
||||
"tenor": 90
|
||||
}
|
||||
]
|
||||
offers = [
|
||||
{
|
||||
"offerId": "SAL90",
|
||||
"productId": "2030",
|
||||
"minAmount": 5000,
|
||||
"maxAmount": 100000,
|
||||
"tenor": 30
|
||||
},
|
||||
{
|
||||
"offerId": "SAL30",
|
||||
"productId": "2090",
|
||||
"minAmount": 3000,
|
||||
"maxAmount": 500000,
|
||||
"tenor": 90
|
||||
}
|
||||
]
|
||||
|
||||
# Simulate processing
|
||||
response_data = {
|
||||
"customerId": customer_id,
|
||||
"transactionId": transactionId,
|
||||
"countryCode": "NG",
|
||||
"msisdn": msisdn,
|
||||
"eligibleOffers": offers,
|
||||
"resultDescription": "Successful",
|
||||
"resultCode": "00",
|
||||
"accountId": account_id
|
||||
}
|
||||
# Simulate processing
|
||||
response_data = {
|
||||
"customerId": customer_id,
|
||||
"transactionId": transactionId,
|
||||
"countryCode": "NG",
|
||||
"msisdn": msisdn,
|
||||
"eligibleOffers": offers,
|
||||
"resultDescription": "Successful",
|
||||
"resultCode": "00",
|
||||
"accountId": account_id
|
||||
}
|
||||
|
||||
return response_data
|
||||
return response_data
|
||||
|
||||
except ValidationError as err:
|
||||
|
||||
logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}")
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
from flask import request, jsonify
|
||||
from marshmallow import ValidationError
|
||||
from app.api.enums.loan_status import LoanStatus
|
||||
from app.models import Customer
|
||||
from app.utils.logger import logger
|
||||
from app.api.schemas.loan_status import LoanStatusSchema
|
||||
from app.api.services.base_service import BaseService
|
||||
from app.api.enums import TransactionType
|
||||
from app.api.enums import TransactionType
|
||||
from app.extensions import db
|
||||
|
||||
|
||||
class LoanStatusService(BaseService):
|
||||
@@ -21,54 +24,69 @@ class LoanStatusService(BaseService):
|
||||
dict: A standardized response.
|
||||
"""
|
||||
try:
|
||||
validated_data = LoanStatusService.validate_data(data, LoanStatusSchema())
|
||||
customer_id = validated_data.get('customerId')
|
||||
customer = LoanStatusService.get_or_create_customer(validated_data)
|
||||
account = customer.accounts[0]
|
||||
with db.session.begin():
|
||||
# Validate data
|
||||
validated_data = LoanStatusService.validate_data(data, LoanStatusSchema())
|
||||
|
||||
if (LoanStatusService.validate_account_ownership(account_id = account.id, customer_id = customer_id)):
|
||||
transaction = LoanStatusService.log_transaction(validated_data = validated_data)
|
||||
|
||||
if not transaction:
|
||||
logger.error(f"Failed to log transaction")
|
||||
customer_id = validated_data.get('customerId')
|
||||
customer = Customer.get_customer(customer_id)
|
||||
transactionId = validated_data.get('transactionId')
|
||||
account_id = validated_data.get('accountId')
|
||||
|
||||
if(LoanStatusService.validate_account_ownership(account_id = account_id, customer_id = customer_id)):
|
||||
|
||||
# Get loans
|
||||
loans = [loan.to_dict() for loan in customer.loans if loan.status == LoanStatus.ACTIVE]
|
||||
|
||||
transaction = LoanStatusService.log_transaction(validated_data = validated_data)
|
||||
|
||||
if not transaction:
|
||||
logger.error(f"Failed to log transaction")
|
||||
return jsonify({
|
||||
"message": "Failed to log transaction."
|
||||
}), 400
|
||||
else:
|
||||
return jsonify({
|
||||
"message": "Failed to log transaction."
|
||||
}), 400
|
||||
else:
|
||||
return jsonify({
|
||||
"message": "Invalid Customer or Account"
|
||||
}), 400
|
||||
|
||||
"message": "Invalid Customer or Account"
|
||||
}), 400
|
||||
|
||||
|
||||
loans = [
|
||||
{
|
||||
"debtId": "123456789",
|
||||
"loanDate": "2019-10-18 14:26:21.063",
|
||||
"dueDate": "2019-11-20 14:26:21.063",
|
||||
"currentLoanAmount": 8500,
|
||||
"initialLoanAmount": 10000,
|
||||
"defaultPenaltyFee": 0,
|
||||
"continuousFee": 0,
|
||||
"productId": "101"
|
||||
# loans = [
|
||||
# {
|
||||
# "debtId": "123456789",
|
||||
# "loanDate": "2019-10-18 14:26:21.063",
|
||||
# "dueDate": "2019-11-20 14:26:21.063",
|
||||
# "currentLoanAmount": 8500,
|
||||
# "initialLoanAmount": 10000,
|
||||
# "defaultPenaltyFee": 0,
|
||||
# "continuousFee": 0,
|
||||
# "productId": "101"
|
||||
# }
|
||||
# ]
|
||||
|
||||
total_debt_amount = sum(
|
||||
loan.get("currentLoanAmount") or 0
|
||||
for loan in loans
|
||||
)
|
||||
|
||||
# Simulated processing logic
|
||||
response_data = {
|
||||
"customerId": customer_id,
|
||||
"transactionId": transactionId,
|
||||
"loans": loans,
|
||||
"totalDebtAmount": total_debt_amount,
|
||||
"resultCode": "00",
|
||||
"resultDescription": "Successful"
|
||||
}
|
||||
]
|
||||
|
||||
# Simulated processing logic
|
||||
response_data = {
|
||||
"customerId": "CN621868",
|
||||
"transactionId": "Tr201712RK9232P115",
|
||||
"loans": loans,
|
||||
"totalDebtAmount": 8500,
|
||||
"resultCode": "00",
|
||||
"resultDescription": "Successful"
|
||||
}
|
||||
|
||||
|
||||
return response_data
|
||||
db.session.commit()
|
||||
return response_data
|
||||
|
||||
except ValidationError as err:
|
||||
|
||||
logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}")
|
||||
db.session.rollback()
|
||||
|
||||
return jsonify({
|
||||
"message": "Validation exception"
|
||||
@@ -76,6 +94,7 @@ class LoanStatusService(BaseService):
|
||||
|
||||
except ValueError as err:
|
||||
logger.error(f"{getattr(err, 'messages', str(err))}")
|
||||
db.session.rollback()
|
||||
|
||||
return jsonify({
|
||||
"message": str(err)
|
||||
@@ -83,6 +102,7 @@ class LoanStatusService(BaseService):
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {str(e)}", exc_info=True)
|
||||
db.session.rollback()
|
||||
return jsonify({
|
||||
"message": "Internal Server Error"
|
||||
}) , 500
|
||||
@@ -3,7 +3,8 @@ from marshmallow import ValidationError
|
||||
from app.api.services.base_service import BaseService
|
||||
from app.api.enums import TransactionType
|
||||
from app.utils.logger import logger
|
||||
from app.api.schemas.notification_callback import NotificationCallbackSchema
|
||||
from app.api.schemas.notification_callback import NotificationCallbackSchema
|
||||
from app.extensions import db
|
||||
|
||||
class NotificationCallbackService(BaseService):
|
||||
TRANSACTION_TYPE = TransactionType.NOTIFICATION_CALLBACK
|
||||
|
||||
@@ -5,10 +5,10 @@ from app.api.services.base_service import BaseService
|
||||
from app.api.enums import TransactionType
|
||||
from app.utils.logger import logger
|
||||
from app.api.schemas.provide_loan import ProvideLoanSchema
|
||||
from app.api.integrations import KafkaIntegration
|
||||
from threading import Thread
|
||||
from app.models.loan import Loan
|
||||
|
||||
from app.api.enums import LoanStatus
|
||||
from app.extensions import db
|
||||
|
||||
class ProvideLoanService(BaseService):
|
||||
TRANSACTION_TYPE = TransactionType.PROVIDE_LOAN
|
||||
@@ -26,67 +26,74 @@ class ProvideLoanService(BaseService):
|
||||
dict: A standardized response.
|
||||
"""
|
||||
try:
|
||||
validated_data = ProvideLoanService.validate_data(data, ProvideLoanSchema())
|
||||
account_id = validated_data.get('accountId')
|
||||
customer_id = validated_data.get('customerId')
|
||||
request_id = validated_data.get('requestId')
|
||||
transaction_id = validated_data.get('transactionId')
|
||||
with db.session.begin():
|
||||
validated_data = ProvideLoanService.validate_data(data, ProvideLoanSchema())
|
||||
account_id = validated_data.get('accountId')
|
||||
customer_id = validated_data.get('customerId')
|
||||
request_id = validated_data.get('requestId')
|
||||
collection_type = validated_data.get('collectionType')
|
||||
transaction_id = validated_data.get('transactionId')
|
||||
|
||||
if (ProvideLoanService.validate_account_ownership(account_id = account_id, customer_id = customer_id)):
|
||||
if (ProvideLoanService.validate_account_ownership(account_id = account_id, customer_id = customer_id)):
|
||||
|
||||
transaction = ProvideLoanService.log_transaction(validated_data = validated_data)
|
||||
|
||||
if not transaction:
|
||||
logger.error(f"Failed to log transaction")
|
||||
return jsonify({
|
||||
"message": "Failed to log transaction."
|
||||
}), 400
|
||||
|
||||
# Save the loan details
|
||||
loan_id = f"loan_{transaction_id}"
|
||||
|
||||
loan = Loan.create_loan(
|
||||
customer_id=customer_id,
|
||||
account_id=account_id,
|
||||
offer_id=validated_data.get('offerId'),
|
||||
principal_amount=validated_data.get('requestedAmount'),
|
||||
status="active"
|
||||
)
|
||||
# Save the loan details
|
||||
loan = Loan.create_loan(
|
||||
customer_id = customer_id,
|
||||
account_id = account_id,
|
||||
offer_id = validated_data.get('offerId'),
|
||||
collection_type = collection_type,
|
||||
transaction_id = validated_data.get('transactionId'),
|
||||
initial_loan_amount = validated_data.get('requestedAmount'),
|
||||
status= LoanStatus.ACTIVE
|
||||
)
|
||||
|
||||
if not loan:
|
||||
logger.error(f"Failed to save loan details")
|
||||
return jsonify({
|
||||
"message": "Failed to save loan details."
|
||||
}), 400
|
||||
|
||||
# Log Transaction
|
||||
transaction = ProvideLoanService.log_transaction(validated_data = validated_data)
|
||||
|
||||
if not transaction:
|
||||
logger.error(f"Failed to log transaction")
|
||||
return jsonify({
|
||||
"message": "Failed to log transaction."
|
||||
}), 400
|
||||
|
||||
if not loan:
|
||||
logger.error(f"Failed to save loan details")
|
||||
return jsonify({
|
||||
"message": "Failed to save loan details."
|
||||
}), 400
|
||||
|
||||
|
||||
else:
|
||||
return jsonify({
|
||||
"message": "Invalid Customer or Account"
|
||||
}), 400
|
||||
|
||||
|
||||
response_data = {
|
||||
"requestId": request_id,
|
||||
"transactionId": transaction_id,
|
||||
"customerId": customer_id,
|
||||
"accountId": account_id,
|
||||
"msisdn": "3451342",
|
||||
"resultCode": "00",
|
||||
"resultDescription": "Successful"
|
||||
}
|
||||
|
||||
else:
|
||||
return jsonify({
|
||||
"message": "Invalid Customer or Account"
|
||||
}), 400
|
||||
|
||||
|
||||
response_data = {
|
||||
"requestId": request_id,
|
||||
"transactionId": transaction_id,
|
||||
"customerId": customer_id,
|
||||
"accountId": account_id,
|
||||
"msisdn": "3451342",
|
||||
"resultCode": "00",
|
||||
"resultDescription": "Successful"
|
||||
}
|
||||
|
||||
|
||||
# KafkaIntegration.send_loan_request(loan_data = response_data, request_id = request_id)
|
||||
# Call Kafka in a background thread
|
||||
thread = Thread(target=ProvideLoanService.async_send_to_kafka, args=(response_data, request_id))
|
||||
thread.start()
|
||||
# KafkaIntegration.send_loan_request(loan_data = response_data, request_id = request_id)
|
||||
# Call Kafka in a background thread
|
||||
thread = Thread(target=ProvideLoanService.async_send_to_kafka, args=(response_data, request_id, "PROCESS_PAYMENT"))
|
||||
thread.start()
|
||||
|
||||
return response_data
|
||||
db.session.commit()
|
||||
return response_data
|
||||
|
||||
except ValidationError as err:
|
||||
|
||||
logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}")
|
||||
db.session.rollback()
|
||||
|
||||
return jsonify({
|
||||
"message": "Validation exception"
|
||||
@@ -94,6 +101,7 @@ class ProvideLoanService(BaseService):
|
||||
|
||||
except ValueError as err:
|
||||
logger.error(f"{getattr(err, 'messages', str(err))}")
|
||||
db.session.rollback()
|
||||
|
||||
return jsonify({
|
||||
"message": str(err)
|
||||
@@ -101,12 +109,11 @@ class ProvideLoanService(BaseService):
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {str(e)}", exc_info=True)
|
||||
db.session.rollback()
|
||||
return jsonify({
|
||||
"message": "Internal Server Error"
|
||||
}) , 500
|
||||
|
||||
|
||||
def async_send_to_kafka(loan_data, request_id):
|
||||
KafkaIntegration.send_loan_request(loan_data = loan_data, request_id = request_id)
|
||||
KafkaIntegration.flush()
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
from flask import request, jsonify
|
||||
from marshmallow import ValidationError
|
||||
from app.api.enums.loan_status import LoanStatus
|
||||
from app.models import Repayment
|
||||
from app.models.customer import Customer
|
||||
from app.models.loan import Loan
|
||||
from app.utils.logger import logger
|
||||
from app.api.schemas.repayment import RepaymentSchema
|
||||
from app.api.services.base_service import BaseService
|
||||
from app.api.enums import TransactionType
|
||||
from app.api.enums import TransactionType
|
||||
from threading import Thread
|
||||
from app.extensions import db
|
||||
|
||||
class RepaymentService(BaseService):
|
||||
TRANSACTION_TYPE = TransactionType.REPAYMENT
|
||||
@@ -20,45 +26,74 @@ class RepaymentService(BaseService):
|
||||
dict: A standardized response.
|
||||
"""
|
||||
try:
|
||||
validated_data = RepaymentService.validate_data(data, RepaymentSchema())
|
||||
customer_id = validated_data.get('customerId')
|
||||
customer = RepaymentService.get_or_create_customer(validated_data)
|
||||
account = customer.accounts[0]
|
||||
validated_data['accountId'] = account.id
|
||||
with db.session.begin():
|
||||
validated_data = RepaymentService.validate_data(data, RepaymentSchema())
|
||||
customer_id = validated_data.get('customerId')
|
||||
request_id = validated_data.get('requestId')
|
||||
loan_id = validated_data.get('debtId')
|
||||
product_id = validated_data.get('productId')
|
||||
account_id = validated_data.get('accountId')
|
||||
customer = Customer.get_customer(customer_id)
|
||||
|
||||
if(RepaymentService.validate_account_ownership(account_id = account_id, customer_id = customer_id)):
|
||||
|
||||
# Save the repayment details
|
||||
repayment = Repayment.create_repayment(
|
||||
customer_id = customer_id,
|
||||
loan_id = loan_id,
|
||||
product_id = product_id
|
||||
|
||||
if (RepaymentService.validate_account_ownership(account_id = account.id, customer_id = customer_id)):
|
||||
transaction = RepaymentService.log_transaction(validated_data = validated_data)
|
||||
)
|
||||
|
||||
if not transaction:
|
||||
logger.error(f"Failed to log transaction")
|
||||
if not repayment:
|
||||
logger.error(f"Failed to save repayment details")
|
||||
return jsonify({
|
||||
"message": "Failed to save repayment details."
|
||||
}), 400
|
||||
|
||||
|
||||
#Update Loan status
|
||||
Loan.update_status(loan_id = loan_id, status = LoanStatus.REPAID)
|
||||
|
||||
transaction = RepaymentService.log_transaction(validated_data = validated_data)
|
||||
|
||||
if not transaction:
|
||||
logger.error(f"Failed to log transaction")
|
||||
return jsonify({
|
||||
"message": "Failed to log transaction."
|
||||
}), 400
|
||||
else:
|
||||
return jsonify({
|
||||
"message": "Failed to log transaction."
|
||||
}), 400
|
||||
else:
|
||||
return jsonify({
|
||||
"message": "Invalid Customer or Account"
|
||||
}), 400
|
||||
"message": "Invalid Customer or Account"
|
||||
}), 400
|
||||
|
||||
|
||||
# Simulated processing logic
|
||||
response_data = {
|
||||
"customerId": "CN621868",
|
||||
"productId": "101",
|
||||
"debtId": "273194670",
|
||||
"resultCode": "00",
|
||||
"resultDescription": "Successful"
|
||||
}
|
||||
|
||||
# return ResponseHelper.success(
|
||||
# data=response_data,
|
||||
# message="Repayment processed successfully"
|
||||
# )
|
||||
# Simulated processing logic
|
||||
response_data = {
|
||||
"customerId": customer_id,
|
||||
"productId": product_id,
|
||||
"debtId": loan_id,
|
||||
"resultCode": "00",
|
||||
"resultDescription": "Successful"
|
||||
}
|
||||
|
||||
return response_data
|
||||
# return ResponseHelper.success(
|
||||
# data=response_data,
|
||||
# message="Repayment processed successfully"
|
||||
# )
|
||||
|
||||
# Call Kafka in a background thread
|
||||
thread = Thread(target=RepaymentService.async_send_to_kafka, args=(response_data, request_id, "LOAN_REPAYMENT"))
|
||||
thread.start()
|
||||
|
||||
db.session.commit()
|
||||
return response_data
|
||||
|
||||
except ValidationError as err:
|
||||
|
||||
logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}")
|
||||
db.session.rollback()
|
||||
|
||||
return jsonify({
|
||||
"message": "Validation exception"
|
||||
@@ -66,6 +101,7 @@ class RepaymentService(BaseService):
|
||||
|
||||
except ValueError as err:
|
||||
logger.error(f"{getattr(err, 'messages', str(err))}")
|
||||
db.session.rollback()
|
||||
|
||||
return jsonify({
|
||||
"message": str(err)
|
||||
@@ -73,6 +109,7 @@ class RepaymentService(BaseService):
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {str(e)}", exc_info=True)
|
||||
db.session.rollback()
|
||||
return jsonify({
|
||||
"message": "Internal Server Error"
|
||||
}) , 500
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
from flask import request, jsonify
|
||||
from marshmallow import ValidationError
|
||||
from app.api.services.base_service import BaseService
|
||||
from app.api.enums import TransactionType
|
||||
from app.api.enums import TransactionType
|
||||
from app.utils.logger import logger
|
||||
from app.api.schemas.select_offer import SelectOfferSchema
|
||||
from app.extensions import db
|
||||
|
||||
|
||||
class SelectOfferService(BaseService):
|
||||
TRANSACTION_TYPE = TransactionType.SELECT_OFFER
|
||||
TRANSACTION_TYPE = TransactionType.SELECT_OFFER
|
||||
|
||||
@staticmethod
|
||||
def process_request(data):
|
||||
@@ -20,74 +22,72 @@ class SelectOfferService(BaseService):
|
||||
dict: A standardized response.
|
||||
"""
|
||||
try:
|
||||
validated_data = SelectOfferService.validate_data(data, SelectOfferSchema())
|
||||
account_id = validated_data.get('accountId')
|
||||
customer_id = validated_data.get('customerId')
|
||||
with db.session.begin():
|
||||
validated_data = SelectOfferService.validate_data(
|
||||
data, SelectOfferSchema()
|
||||
)
|
||||
account_id = validated_data.get("accountId")
|
||||
customer_id = validated_data.get("customerId")
|
||||
|
||||
if (SelectOfferService.validate_account_ownership(account_id = account_id, customer_id = customer_id)):
|
||||
transaction = SelectOfferService.log_transaction(validated_data = validated_data)
|
||||
if SelectOfferService.validate_account_ownership(
|
||||
account_id=account_id, customer_id=customer_id
|
||||
):
|
||||
transaction = SelectOfferService.log_transaction(
|
||||
validated_data=validated_data
|
||||
)
|
||||
|
||||
if not transaction:
|
||||
logger.error(f"Failed to log transaction")
|
||||
return jsonify({
|
||||
"message": "Failed to log transaction."
|
||||
}), 400
|
||||
else:
|
||||
return jsonify({
|
||||
"message": "Invalid Customer or Account"
|
||||
}), 400
|
||||
|
||||
offers = [
|
||||
if not transaction:
|
||||
logger.error(f"Failed to log transaction")
|
||||
return jsonify({"message": "Failed to log transaction."}), 400
|
||||
else:
|
||||
return jsonify({"message": "Invalid Customer or Account"}), 400
|
||||
|
||||
offers = [
|
||||
{
|
||||
"offerId": "14451",
|
||||
"productId": "2030",
|
||||
"amount": 10000.0,
|
||||
"upfrontPayment": 1000.0,
|
||||
"interestRate": 3.0,
|
||||
"managementRate": 1.0,
|
||||
"managementFee": 1.0,
|
||||
"insuranceRate": 1.0,
|
||||
"insuranceFee": 100.0,
|
||||
"VATRate": 7.5,
|
||||
"VATAmount": 100.0,
|
||||
"recommendedRepaymentDates": ["2022-11-30"],
|
||||
"installmentAmount": 11000.0,
|
||||
"totalRepaymentAmount": 11000.0
|
||||
"offerId": "14451",
|
||||
"productId": "2030",
|
||||
"amount": 10000.0,
|
||||
"upfrontPayment": 1000.0,
|
||||
"interestRate": 3.0,
|
||||
"managementRate": 1.0,
|
||||
"managementFee": 1.0,
|
||||
"insuranceRate": 1.0,
|
||||
"insuranceFee": 100.0,
|
||||
"VATRate": 7.5,
|
||||
"VATAmount": 100.0,
|
||||
"recommendedRepaymentDates": ["2022-11-30"],
|
||||
"installmentAmount": 11000.0,
|
||||
"totalRepaymentAmount": 11000.0,
|
||||
}
|
||||
]
|
||||
|
||||
# Business logic - selecting an offer
|
||||
response_data = {
|
||||
"outstandingDebtAmount": 0,
|
||||
"requestId": "202111170001371256908",
|
||||
"transactionId": transaction.id,
|
||||
"customerId": customer_id,
|
||||
"accountId": account_id,
|
||||
"loan": offers,
|
||||
"resultCode": "00",
|
||||
"resultDescription": "Successful"
|
||||
# Business logic - selecting an offer
|
||||
response_data = {
|
||||
"outstandingDebtAmount": 0,
|
||||
"requestId": "202111170001371256908",
|
||||
"transactionId": transaction.id,
|
||||
"customerId": customer_id,
|
||||
"accountId": account_id,
|
||||
"loan": offers,
|
||||
"resultCode": "00",
|
||||
"resultDescription": "Successful",
|
||||
}
|
||||
|
||||
|
||||
return response_data
|
||||
db.session.commit()
|
||||
return response_data
|
||||
|
||||
except ValidationError as err:
|
||||
|
||||
logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}")
|
||||
db.session.rollback()
|
||||
return jsonify({"message": "Validation exception"}), 422
|
||||
|
||||
return jsonify({
|
||||
"message": "Validation exception"
|
||||
}) , 422
|
||||
|
||||
except ValueError as err:
|
||||
except ValueError as err:
|
||||
logger.error(f"{getattr(err, 'messages', str(err))}")
|
||||
|
||||
return jsonify({
|
||||
"message": str(err)
|
||||
}) , 400
|
||||
db.session.rollback()
|
||||
return jsonify({"message": str(err)}), 400
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {str(e)}", exc_info=True)
|
||||
return jsonify({
|
||||
"message": "Internal Server Error"
|
||||
}) , 500
|
||||
db.session.rollback()
|
||||
return jsonify({"message": "Internal Server Error"}), 500
|
||||
|
||||
@@ -32,7 +32,6 @@ class Config:
|
||||
)
|
||||
|
||||
KAFKA_BROKER = 'dev-events.simbrellang.net:9085'
|
||||
KAFKA_PAYMENT_TOPIC = 'PROCESS_PAYMENT'
|
||||
|
||||
|
||||
settings = Config()
|
||||
|
||||
@@ -2,5 +2,6 @@ from .customer import Customer
|
||||
from .account import Account
|
||||
from .loan import Loan
|
||||
from .transaction import Transaction
|
||||
from .repayment import Repayment
|
||||
|
||||
__all__ = ['Customer', 'Account', 'Loan', 'Transaction']
|
||||
__all__ = ['Customer', 'Account', 'Loan', 'Transaction', 'Repayment']
|
||||
@@ -1,6 +1,7 @@
|
||||
from datetime import datetime, timezone
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.extensions import db
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
class Account(db.Model):
|
||||
__tablename__ = 'accounts'
|
||||
@@ -27,8 +28,11 @@ class Account(db.Model):
|
||||
customer_id=customer_id,
|
||||
account_type=account_type
|
||||
)
|
||||
db.session.add(account)
|
||||
db.session.commit()
|
||||
|
||||
try:
|
||||
db.session.add(account)
|
||||
except IntegrityError as err:
|
||||
raise ValueError(f"Database integrity error: {err}")
|
||||
return account
|
||||
|
||||
@classmethod
|
||||
|
||||
+31
-10
@@ -2,6 +2,7 @@ from datetime import datetime, timezone
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.extensions import db
|
||||
from app.models.account import Account
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
class Customer(db.Model):
|
||||
__tablename__ = 'customers'
|
||||
@@ -19,6 +20,13 @@ class Customer(db.Model):
|
||||
back_populates="customer",
|
||||
)
|
||||
|
||||
loans = relationship(
|
||||
"Loan",
|
||||
primaryjoin="Customer.id == Loan.customer_id",
|
||||
foreign_keys="Loan.customer_id",
|
||||
back_populates="customer",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def is_valid_customer(cls, customer_id):
|
||||
customer = cls.query.filter_by(id=customer_id).first()
|
||||
@@ -33,16 +41,29 @@ class Customer(db.Model):
|
||||
|
||||
# Create the customer
|
||||
customer = cls(id=id, msisdn=msisdn, country_code=country_code)
|
||||
db.session.add(customer)
|
||||
|
||||
# Create an associated account
|
||||
account = Account.create_account(
|
||||
id=account_id,
|
||||
customer_id=id,
|
||||
account_type=account_type
|
||||
)
|
||||
|
||||
db.session.commit()
|
||||
try:
|
||||
db.session.add(customer)
|
||||
|
||||
# Create an associated account
|
||||
account = Account.create_account(
|
||||
id=account_id,
|
||||
customer_id=id,
|
||||
account_type=account_type
|
||||
)
|
||||
|
||||
except IntegrityError as err:
|
||||
raise ValueError(f"Database integrity error: {err}")
|
||||
return customer
|
||||
|
||||
@classmethod
|
||||
def get_customer(cls, customer_id):
|
||||
"""
|
||||
Get customer by ID.
|
||||
"""
|
||||
customer = cls.query.filter_by(id=customer_id).first()
|
||||
|
||||
if not customer:
|
||||
raise ValueError(f"Customer does not exist")
|
||||
return customer
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
+74
-14
@@ -2,6 +2,9 @@ from datetime import datetime, timezone
|
||||
from app.extensions import db
|
||||
from app.models.customer import Customer
|
||||
from app.models.account import Account
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.models import Customer
|
||||
|
||||
|
||||
class Loan(db.Model):
|
||||
@@ -13,39 +16,53 @@ class Loan(db.Model):
|
||||
autoincrement=True,
|
||||
)
|
||||
customer_id = db.Column(db.String(50), nullable=False)
|
||||
transaction_id = db.Column(db.String(50), nullable=True)
|
||||
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)
|
||||
collection_type = db.Column(db.String(20), nullable=True)
|
||||
current_loan_amount = db.Column(db.Float, nullable=True)
|
||||
initial_loan_amount = db.Column(db.Float, nullable=False)
|
||||
default_penalty_fee = db.Column(db.Float, default=0)
|
||||
continuous_fee = db.Column(db.Float, default=0)
|
||||
status = db.Column(db.String(20), default='pending')
|
||||
due_date = db.Column(db.DateTime, nullable=True)
|
||||
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))
|
||||
|
||||
customer = relationship(
|
||||
"Customer",
|
||||
primaryjoin="Customer.id == Loan.customer_id",
|
||||
foreign_keys=[customer_id],
|
||||
back_populates="loans",
|
||||
)
|
||||
|
||||
@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, collection_type, transaction_id, status='pending'):
|
||||
|
||||
# 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")
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
# 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
|
||||
customer_id = customer_id,
|
||||
account_id = account_id,
|
||||
offer_id = offer_id,
|
||||
collection_type = collection_type,
|
||||
transaction_id = transaction_id,
|
||||
initial_loan_amount = initial_loan_amount,
|
||||
current_loan_amount = initial_loan_amount,
|
||||
due_date=now,
|
||||
status = status
|
||||
)
|
||||
|
||||
db.session.add(loan)
|
||||
db.session.commit()
|
||||
try:
|
||||
db.session.add(loan)
|
||||
except IntegrityError as err:
|
||||
raise ValueError(f"Database integrity error: {err}")
|
||||
return loan
|
||||
|
||||
|
||||
@@ -60,6 +77,49 @@ class Loan(db.Model):
|
||||
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()
|
||||
if not loan:
|
||||
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
|
||||
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
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Convert the Loan object to a dictionary format for JSON serialization.
|
||||
"""
|
||||
return {
|
||||
'debtId': self.id,
|
||||
'initialLoanAmount': self.initial_loan_amount,
|
||||
'currentLoanAmount': self.current_loan_amount,
|
||||
'defaultPenaltyFee': self.default_penalty_fee,
|
||||
'continuousFee': self.continuous_fee,
|
||||
'collectionType': self.collection_type,
|
||||
'status': self.status,
|
||||
'dueDate': self.due_date.isoformat() if self.due_date else None,
|
||||
'loanDate': self.created_at.isoformat() if self.created_at else None,
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Loan {self.id}>'
|
||||
@@ -0,0 +1,53 @@
|
||||
from datetime import datetime, timezone
|
||||
from app.api.enums.loan_status import LoanStatus
|
||||
from app.extensions import db
|
||||
from app.models.customer import Customer
|
||||
from app.models.loan import Loan
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
|
||||
class Repayment(db.Model):
|
||||
__tablename__ = 'repayments'
|
||||
|
||||
id = db.Column(
|
||||
db.Integer,
|
||||
primary_key=True,
|
||||
autoincrement=True,
|
||||
)
|
||||
loan_id = db.Column(db.String(50), nullable=False)
|
||||
customer_id = db.Column(db.String(50), nullable=False)
|
||||
product_id = db.Column(db.String(20), nullable=True)
|
||||
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))
|
||||
|
||||
@classmethod
|
||||
def create_repayment(cls, customer_id, loan_id, product_id):
|
||||
|
||||
|
||||
# Check customer exists
|
||||
if not Customer.is_valid_customer(customer_id):
|
||||
raise ValueError("Invalid customer")
|
||||
|
||||
# Check loan exists
|
||||
loan = Loan.get_customer_loan(loan_id = loan_id, customer_id = customer_id)
|
||||
|
||||
# Check that the loan is active
|
||||
if loan.status != LoanStatus.ACTIVE:
|
||||
raise ValueError(f"Repayment cannot be processed. Loan status: ({loan.status})")
|
||||
|
||||
|
||||
repayment = cls(
|
||||
customer_id=customer_id,
|
||||
loan_id=loan_id,
|
||||
product_id=product_id,
|
||||
)
|
||||
|
||||
try:
|
||||
db.session.add(repayment)
|
||||
except IntegrityError as err:
|
||||
raise ValueError(f"Database integrity error: {err}")
|
||||
|
||||
return repayment
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Repayment {self.id}>'
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime, timezone
|
||||
from app.extensions import db
|
||||
from app.models import account
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy import and_, or_, not_
|
||||
|
||||
@@ -10,9 +11,9 @@ class Transaction(db.Model):
|
||||
primary_key=True,
|
||||
autoincrement=True,
|
||||
)
|
||||
#id = db.Column(db.Int, primary_key=True)
|
||||
transaction_id = db.Column(db.String(50), nullable=False)
|
||||
account_id = db.Column(db.String(50), nullable=False)
|
||||
account_id = db.Column(db.String(50), nullable=True)
|
||||
customer_id = db.Column(db.String(50), nullable=True)
|
||||
type = db.Column(db.String(50), nullable=False)
|
||||
channel = db.Column(db.String(50), nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc))
|
||||
@@ -22,7 +23,7 @@ class Transaction(db.Model):
|
||||
return f'<Transaction {self.id}>'
|
||||
|
||||
@classmethod
|
||||
def create_transaction(cls, transaction_id, account_id, type, channel):
|
||||
def create_transaction(cls, transaction_id, account_id, customer_id, type, channel):
|
||||
|
||||
# if cls.query.filter_by(transaction_id=transaction_id).first():
|
||||
# raise ValueError("Duplicate Transaction")
|
||||
@@ -33,17 +34,16 @@ class Transaction(db.Model):
|
||||
|
||||
|
||||
transaction = cls(
|
||||
transaction_id=transaction_id,
|
||||
account_id=account_id,
|
||||
type=type,
|
||||
channel=channel
|
||||
transaction_id = transaction_id,
|
||||
customer_id = customer_id,
|
||||
account_id = account_id,
|
||||
type = type,
|
||||
channel = channel
|
||||
)
|
||||
|
||||
try:
|
||||
db.session.add(transaction)
|
||||
db.session.commit()
|
||||
except IntegrityError as err:
|
||||
db.session.rollback()
|
||||
raise ValueError(f"Database integrity error: {err}")
|
||||
|
||||
return transaction
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
"channel": {
|
||||
"type": "string",
|
||||
"example": "USSD"
|
||||
},
|
||||
"accountId": {
|
||||
"type": "string",
|
||||
"example": "ACN8263457"
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
},
|
||||
"debtId": {
|
||||
"type": "string",
|
||||
"example": "273194670"
|
||||
"example": "10"
|
||||
},
|
||||
"productId": {
|
||||
"type": "string",
|
||||
@@ -19,11 +19,15 @@
|
||||
},
|
||||
"customerId": {
|
||||
"type": "string",
|
||||
"example": "CN621868"
|
||||
"example": "CID0000025585"
|
||||
},
|
||||
"channel": {
|
||||
"type": "string",
|
||||
"example": "USSD"
|
||||
},
|
||||
"accountId": {
|
||||
"type": "string",
|
||||
"example": "ACN8263457"
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Migration on Thu Apr 10 21:50:01 UTC 2025
|
||||
|
||||
Revision ID: 1340e7e578b9
|
||||
Revises: b8f6fd76ead8
|
||||
Create Date: 2025-04-10 21:50:32.113149
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '1340e7e578b9'
|
||||
down_revision = 'b8f6fd76ead8'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('transactions', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('ref_model', sa.String(length=50), nullable=True))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('transactions', schema=None) as batch_op:
|
||||
batch_op.drop_column('ref_model')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Migration on Fri Apr 11 14:15:19 UTC 2025
|
||||
|
||||
Revision ID: 610b7e9d15a6
|
||||
Revises: 9bb0367eb486
|
||||
Create Date: 2025-04-11 14:16:12.533227
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '610b7e9d15a6'
|
||||
down_revision = '9bb0367eb486'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('loans', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('transaction_id', sa.String(length=50), nullable=True))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('loans', schema=None) as batch_op:
|
||||
batch_op.drop_column('transaction_id')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Migration on Mon Apr 14 15:15:05 UTC 2025
|
||||
|
||||
Revision ID: 783a023a477f
|
||||
Revises: f6cd1bfc8832
|
||||
Create Date: 2025-04-14 15:15:36.991148
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '783a023a477f'
|
||||
down_revision = 'f6cd1bfc8832'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('transactions', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('customer_id', sa.String(length=50), nullable=True))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('transactions', schema=None) as batch_op:
|
||||
batch_op.drop_column('customer_id')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,40 @@
|
||||
"""Migration on Fri Apr 11 12:48:01 UTC 2025
|
||||
|
||||
Revision ID: 9bb0367eb486
|
||||
Revises: fd447d78b161
|
||||
Create Date: 2025-04-11 12:48:36.145311
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '9bb0367eb486'
|
||||
down_revision = 'fd447d78b161'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('loans', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('product_id', sa.String(length=20), nullable=True))
|
||||
batch_op.add_column(sa.Column('current_loan_amount', sa.Float(), nullable=True))
|
||||
batch_op.add_column(sa.Column('default_penalty_fee', sa.Float(), nullable=True))
|
||||
batch_op.add_column(sa.Column('continuous_fee', sa.Float(), nullable=True))
|
||||
batch_op.add_column(sa.Column('due_date', sa.DateTime(), nullable=True))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('loans', schema=None) as batch_op:
|
||||
batch_op.drop_column('due_date')
|
||||
batch_op.drop_column('continuous_fee')
|
||||
batch_op.drop_column('default_penalty_fee')
|
||||
batch_op.drop_column('current_loan_amount')
|
||||
batch_op.drop_column('product_id')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,86 @@
|
||||
"""Migration on Thu Apr 10 16:21:45 UTC 2025
|
||||
|
||||
Revision ID: b8f6fd76ead8
|
||||
Revises:
|
||||
Create Date: 2025-04-10 16:22:15.946157
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'b8f6fd76ead8'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('repayments',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('loan_id', sa.String(length=50), nullable=False),
|
||||
sa.Column('customer_id', sa.String(length=50), nullable=False),
|
||||
sa.Column('product_id', sa.String(length=20), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
with op.batch_alter_table('loans', schema=None) as batch_op:
|
||||
batch_op.alter_column('id',
|
||||
existing_type=sa.VARCHAR(length=50),
|
||||
type_=sa.Integer(),
|
||||
existing_nullable=False,
|
||||
autoincrement=True,
|
||||
existing_server_default=sa.text("nextval('loan_id_seq'::regclass)"))
|
||||
|
||||
with op.batch_alter_table('transactions', schema=None) as batch_op:
|
||||
batch_op.alter_column('channel',
|
||||
existing_type=sa.VARCHAR(length=8),
|
||||
type_=sa.String(length=50),
|
||||
existing_nullable=False)
|
||||
batch_op.alter_column('created_at',
|
||||
existing_type=postgresql.TIMESTAMP(timezone=True),
|
||||
type_=sa.DateTime(),
|
||||
existing_nullable=True,
|
||||
existing_server_default=sa.text('now()'))
|
||||
batch_op.alter_column('updated_at',
|
||||
existing_type=postgresql.TIMESTAMP(timezone=True),
|
||||
type_=sa.DateTime(),
|
||||
existing_nullable=True,
|
||||
existing_server_default=sa.text('now()'))
|
||||
batch_op.drop_constraint('transactions_id_key', type_='unique')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('transactions', schema=None) as batch_op:
|
||||
batch_op.create_unique_constraint('transactions_id_key', ['id'])
|
||||
batch_op.alter_column('updated_at',
|
||||
existing_type=sa.DateTime(),
|
||||
type_=postgresql.TIMESTAMP(timezone=True),
|
||||
existing_nullable=True,
|
||||
existing_server_default=sa.text('now()'))
|
||||
batch_op.alter_column('created_at',
|
||||
existing_type=sa.DateTime(),
|
||||
type_=postgresql.TIMESTAMP(timezone=True),
|
||||
existing_nullable=True,
|
||||
existing_server_default=sa.text('now()'))
|
||||
batch_op.alter_column('channel',
|
||||
existing_type=sa.String(length=50),
|
||||
type_=sa.VARCHAR(length=8),
|
||||
existing_nullable=False)
|
||||
|
||||
with op.batch_alter_table('loans', schema=None) as batch_op:
|
||||
batch_op.alter_column('id',
|
||||
existing_type=sa.Integer(),
|
||||
type_=sa.VARCHAR(length=50),
|
||||
existing_nullable=False,
|
||||
autoincrement=True,
|
||||
existing_server_default=sa.text("nextval('loan_id_seq'::regclass)"))
|
||||
|
||||
op.drop_table('repayments')
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Migration on Fri Apr 11 14:34:36 UTC 2025
|
||||
|
||||
Revision ID: f6cd1bfc8832
|
||||
Revises: 610b7e9d15a6
|
||||
Create Date: 2025-04-11 14:35:07.093967
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'f6cd1bfc8832'
|
||||
down_revision = '610b7e9d15a6'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('loans', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('collection_type', sa.String(length=20), nullable=True))
|
||||
batch_op.drop_column('product_id')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('loans', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('product_id', sa.VARCHAR(length=20), autoincrement=False, nullable=True))
|
||||
batch_op.drop_column('collection_type')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Migration on Fri Apr 11 12:02:45 UTC 2025
|
||||
|
||||
Revision ID: fd447d78b161
|
||||
Revises: 1340e7e578b9
|
||||
Create Date: 2025-04-11 12:03:28.346671
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'fd447d78b161'
|
||||
down_revision = '1340e7e578b9'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('transactions', schema=None) as batch_op:
|
||||
batch_op.alter_column('account_id',
|
||||
existing_type=sa.VARCHAR(length=50),
|
||||
nullable=True)
|
||||
batch_op.drop_column('ref_model')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('transactions', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('ref_model', sa.VARCHAR(length=50), autoincrement=False, nullable=True))
|
||||
batch_op.alter_column('account_id',
|
||||
existing_type=sa.VARCHAR(length=50),
|
||||
nullable=False)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Running DB migrations..."
|
||||
flask db migrate -m "Migration on $(date)"
|
||||
flask db upgrade
|
||||
|
||||
echo "Starting Gunicorn server..."
|
||||
|
||||
Reference in New Issue
Block a user