Merge branch 'loan_repayment_event' of DigiFi/digifi-BankToProductCore into master

This commit is contained in:
2025-04-11 15:08:47 +00:00
committed by Gogs
11 changed files with 192 additions and 48 deletions
+1 -2
View File
@@ -50,8 +50,7 @@ class BaseService:
""" """
return Transaction.create_transaction( return Transaction.create_transaction(
transaction_id = validated_data.get("transactionId"), transaction_id = validated_data.get("transactionId"),
ref_id = validated_data.get("refId") or validated_data.get("accountId"), account_id = validated_data.get("accountId", None),
ref_model = validated_data.get("refModel", "account"),
type = cls.TRANSACTION_TYPE, type = cls.TRANSACTION_TYPE,
channel = validated_data.get("channel"), channel = validated_data.get("channel"),
) )
+8 -7
View File
@@ -1,5 +1,6 @@
from flask import request, jsonify from flask import request, jsonify
from marshmallow import ValidationError from marshmallow import ValidationError
from app.api.enums.loan_status import LoanStatus
from app.models import Customer from app.models import Customer
from app.utils.logger import logger from app.utils.logger import logger
from app.api.schemas.loan_status import LoanStatusSchema from app.api.schemas.loan_status import LoanStatusSchema
@@ -33,12 +34,7 @@ class LoanStatusService(BaseService):
transactionId = validated_data.get('transactionId') transactionId = validated_data.get('transactionId')
# Get loans # Get loans
loans = [loan.to_dict() for loan in customer.loans] loans = [loan.to_dict() for loan in customer.loans if loan.status == LoanStatus.ACTIVE]
validated_data['refId'] = customer.id
validated_data['refModel'] = "customer"
transaction = LoanStatusService.log_transaction(validated_data = validated_data) transaction = LoanStatusService.log_transaction(validated_data = validated_data)
@@ -62,12 +58,17 @@ class LoanStatusService(BaseService):
# } # }
# ] # ]
total_debt_amount = sum(
loan.get("currentLoanAmount") or 0
for loan in loans
)
# Simulated processing logic # Simulated processing logic
response_data = { response_data = {
"customerId": customer_id, "customerId": customer_id,
"transactionId": transactionId, "transactionId": transactionId,
"loans": loans, "loans": loans,
"totalDebtAmount": 8500, "totalDebtAmount": total_debt_amount,
"resultCode": "00", "resultCode": "00",
"resultDescription": "Successful" "resultDescription": "Successful"
} }
+8 -9
View File
@@ -31,6 +31,7 @@ class ProvideLoanService(BaseService):
account_id = validated_data.get('accountId') account_id = validated_data.get('accountId')
customer_id = validated_data.get('customerId') customer_id = validated_data.get('customerId')
request_id = validated_data.get('requestId') request_id = validated_data.get('requestId')
collection_type = validated_data.get('collectionType')
transaction_id = validated_data.get('transactionId') 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)):
@@ -38,11 +39,13 @@ class ProvideLoanService(BaseService):
# Save the loan details # Save the loan details
loan = Loan.create_loan( loan = Loan.create_loan(
customer_id=customer_id, customer_id = customer_id,
account_id=account_id, account_id = account_id,
offer_id=validated_data.get('offerId'), offer_id = validated_data.get('offerId'),
principal_amount=validated_data.get('requestedAmount'), collection_type = collection_type,
status=LoanStatus.ACTIVE transaction_id = validated_data.get('transactionId'),
initial_loan_amount = validated_data.get('requestedAmount'),
status= LoanStatus.ACTIVE
) )
if not loan: if not loan:
@@ -51,10 +54,6 @@ class ProvideLoanService(BaseService):
"message": "Failed to save loan details." "message": "Failed to save loan details."
}), 400 }), 400
db.session.flush()
validated_data['refId'] = loan.id
validated_data['refModel'] = "loan"
# Log Transaction # Log Transaction
transaction = ProvideLoanService.log_transaction(validated_data = validated_data) transaction = ProvideLoanService.log_transaction(validated_data = validated_data)
-4
View File
@@ -48,10 +48,6 @@ class RepaymentService(BaseService):
"message": "Failed to save repayment details." "message": "Failed to save repayment details."
}), 400 }), 400
db.session.flush()
validated_data['refId'] = repayment.id
validated_data['refModel'] = "repayment"
#Update Loan status #Update Loan status
Loan.update_status(loan_id = loan_id, status = LoanStatus.REPAID) Loan.update_status(loan_id = loan_id, status = LoanStatus.REPAID)
-1
View File
@@ -32,7 +32,6 @@ class Config:
) )
KAFKA_BROKER = 'dev-events.simbrellang.net:9085' KAFKA_BROKER = 'dev-events.simbrellang.net:9085'
KAFKA_PAYMENT_TOPIC = 'PROCESS_PAYMENT'
settings = Config() settings = Config()
+26 -19
View File
@@ -16,10 +16,16 @@ class Loan(db.Model):
autoincrement=True, autoincrement=True,
) )
customer_id = db.Column(db.String(50), nullable=False) 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) account_id = db.Column(db.String(50), nullable=False)
offer_id = db.Column(db.String(20), 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') 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)) 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)) updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
@@ -31,26 +37,26 @@ class Loan(db.Model):
) )
@classmethod @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 # Check if customer exists
is_valid = Customer.is_valid_customer(customer_id) is_valid = Customer.is_valid_customer(customer_id)
if not is_valid: if not is_valid:
raise ValueError("Customer does not exist") raise ValueError("Customer does not exist")
# # Check for active loans now = datetime.now(timezone.utc)
# has_active_loans = cls.has_active_loans(customer_id)
# if has_active_loans:
# raise ValueError("Customer has active loans")
# Create and save the loan # Create and save the loan
loan = cls( loan = cls(
customer_id=customer_id, customer_id = customer_id,
account_id=account_id, account_id = account_id,
offer_id=offer_id, offer_id = offer_id,
principal_amount=principal_amount, collection_type = collection_type,
status=status transaction_id = transaction_id,
initial_loan_amount = initial_loan_amount,
current_loan_amount = initial_loan_amount,
due_date=now,
status = status
) )
try: try:
@@ -104,14 +110,15 @@ class Loan(db.Model):
Convert the Loan object to a dictionary format for JSON serialization. Convert the Loan object to a dictionary format for JSON serialization.
""" """
return { return {
'id': self.id, 'debtId': self.id,
'customer_id': self.customer_id, 'initialLoanAmount': self.initial_loan_amount,
'account_id': self.account_id, 'currentLoanAmount': self.current_loan_amount,
'offer_id': self.offer_id, 'defaultPenaltyFee': self.default_penalty_fee,
'principal_amount': self.principal_amount, 'continuousFee': self.continuous_fee,
'collectionType': self.collection_type,
'status': self.status, 'status': self.status,
'created_at': self.created_at.isoformat() if self.created_at else None, 'dueDate': self.due_date.isoformat() if self.due_date else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None 'loanDate': self.created_at.isoformat() if self.created_at else None,
} }
def __repr__(self): def __repr__(self):
+4 -5
View File
@@ -1,5 +1,6 @@
from datetime import datetime, timezone from datetime import datetime, timezone
from app.extensions import db from app.extensions import db
from app.models import account
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlalchemy import and_, or_, not_ from sqlalchemy import and_, or_, not_
@@ -11,8 +12,7 @@ class Transaction(db.Model):
autoincrement=True, autoincrement=True,
) )
transaction_id = db.Column(db.String(50), nullable=False) transaction_id = db.Column(db.String(50), nullable=False)
ref_id = db.Column(db.String(50), nullable=False) account_id = db.Column(db.String(50), nullable=True)
ref_model = db.Column(db.String(50), nullable=True, default='account')
type = db.Column(db.String(50), nullable=False) type = db.Column(db.String(50), nullable=False)
channel = 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)) created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc))
@@ -22,7 +22,7 @@ class Transaction(db.Model):
return f'<Transaction {self.id}>' return f'<Transaction {self.id}>'
@classmethod @classmethod
def create_transaction(cls, transaction_id, ref_id, ref_model, type, channel): def create_transaction(cls, transaction_id, account_id, type, channel):
# if cls.query.filter_by(transaction_id=transaction_id).first(): # if cls.query.filter_by(transaction_id=transaction_id).first():
# raise ValueError("Duplicate Transaction") # raise ValueError("Duplicate Transaction")
@@ -34,8 +34,7 @@ class Transaction(db.Model):
transaction = cls( transaction = cls(
transaction_id = transaction_id, transaction_id = transaction_id,
ref_id = ref_id, account_id = account_id,
ref_model = ref_model,
type = type, type = type,
channel = channel channel = channel
) )
@@ -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,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,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 ###