Compare commits

...

9 Commits

Author SHA1 Message Date
VivianDee 70e15cd325 [add]: Repayments data Model 2025-06-10 08:42:41 +01:00
CHIEFSOFT\ameye e8d930f9b8 offer analysis 2025-06-05 23:06:09 -04:00
CHIEFSOFT\ameye c400f1d69d addded comments 2025-06-05 18:49:14 -04:00
vivian.d.simbrellang.com f7daa12531 Merge branch 'loan_and_repayment_tables_update' of DigiFi/digifi-BankToProductCore into master 2025-06-04 11:45:24 +00:00
VivianDee 3242a57586 [update]: Loan and repayment table 2025-06-04 12:43:38 +01:00
ameye 463c0a0def Merge branch 'middlewear-integration' of DigiFi/digifi-BankToProductCore into master 2025-06-04 02:36:14 +00:00
CHIEFSOFT\ameye bb4d7ac064 Log added 2025-05-31 21:31:12 -04:00
CHIEFSOFT\ameye 5a2161acaa added acont id 2025-05-31 20:44:42 -04:00
CHIEFSOFT\ameye 9ea0027f71 channel 2025-05-29 15:19:29 -04:00
14 changed files with 176 additions and 39 deletions
-30
View File
@@ -17,35 +17,6 @@ class SimbrellaIntegration:
url = f"{SimbrellaIntegration.BASE_URL}/{SimbrellaIntegration.ENDPOINT_RAC_CHECKS}"
logger.info(f"Contacting Rack Checks EndPoint: {str(url)}", exc_info=True)
# {
# "transactionId": "T001",
# "fbnTransactionId": "Tr201712RK9232P115",
# "customerId": "CN621868",
# "accountId": "2017821799",
# "channel": "USSD",
# "countryCode": "NG"
# }
#
payload_old = {
"customerId": customer_id,
"accountId": account_id,
"transactionId": str(transaction_id),
"fbnTransactionId": f"FBN{transaction_id}",
"RAC_Array": [
"SalaryAccount",
"BVN",
"BVNAttachedtoAccount",
"CRC",
"CRMS",
"AccountStatus",
"Lien",
"NoBouncedCheck",
"Whitelist",
"NoPastDueSalaryLoan",
"NoPastDueOtherLoan",
],
}
payload = {
"customerId": customer_id,
"accountId": account_id,
@@ -54,7 +25,6 @@ class SimbrellaIntegration:
"countryCode": "NG",
"channel": "USSD"
}
# logger.info(f"This is PayLoad: {str(payload)}", exc_info=True)
headers = {
"Content-Type": "application/json",
+1
View File
@@ -88,6 +88,7 @@ def loan_status():
@jwt_required()
def repayment():
data = request.get_json()
logger.error(f"HERE 0000a **** ")
# logger.info(f"Repayment request received: {data}")
response = RepaymentService.process_request(data)
return response
+3 -1
View File
@@ -48,12 +48,14 @@ class BaseService:
"""
Create a new transaction.
"""
channel = "USSD" if validated_data.get("channel") is None else validated_data.get("channel")
return Transaction.create_transaction(
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"),
channel = channel,
)
@classmethod
+1 -1
View File
@@ -81,7 +81,7 @@ class EligibilityCheckService(BaseService):
return ResponseHelper.error(result_description=f"RACCheck failed")
rack_checks_response = response['data']['racResponse']
rack_checks_response = response['racResponse']
rac_check = RACCheck.add_rac_check(
customer_id = customer_id,
+1
View File
@@ -55,6 +55,7 @@ class LoanStatusService(BaseService):
# Simulated processing logic
response_data = {
"customerId": customer_id,
"accountId": account_id,
"transactionId": transactionId,
"loans": loans,
"totalDebtAmount": total_debt_amount,
+18 -3
View File
@@ -2,6 +2,10 @@ from app.models import Offer, TransactionOffer
from app.models.loan import Loan
import random
import logging
from app.config import Config
RAC_CHECK_RULES = Config.rac_true_rules
logger = logging.getLogger(__name__)
class OfferAnalysis:
@@ -34,7 +38,8 @@ class OfferAnalysis:
return transaction_offer, offer, eligible_amount, original_transaction
@staticmethod
def _analyze_rack_checks(rack_response):
logger.info(f"This is PayLoad for ANALYSYS ***** : {str(rack_response)}", exc_info=True)
logger.info(f"RACk RUKES {str(RAC_CHECK_RULES)}", exc_info=True)
# "racResponse": {
# "accountStatus": true,
# "bvnValidated": true,
@@ -48,6 +53,17 @@ class OfferAnalysis:
# },
#
'''
30 days
Eligibility amount (monthly SOL) - Adoption of 50% of the least salary inflow in the past 6 months
to determine loan eligibility for potential customers.
3 months
Adoption of 75% of the least salary inflow in the past 6 months to determine loan eligibility for
potential customers" for customers that have unstable income. 3 months
'''
# rac_true_rules
return 0
@staticmethod
@@ -56,8 +72,7 @@ class OfferAnalysis:
# if we have active offers - we have to feed off it
logger.info(f"**RACK ANALYSIS** {customer_id}")
# Analyze Rack Checks
# _analyze_rack_checks(rack_checks_response) --> We need detail analysis
OfferAnalysis._analyze_rack_checks(rack_checks_response) #--> We need detail analysis
# we can now find the origin transactions
# Find the last loan - it will have original_transaction
+5 -3
View File
@@ -29,6 +29,7 @@ class RepaymentService(BaseService):
try:
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')
@@ -37,9 +38,9 @@ class RepaymentService(BaseService):
# customer = Customer.get_customer_with_loan_list(customer_id)
transaction_id = validated_data.get('transactionId')
initiated_by = validated_data.get('initiatedBy')
logger.error(f"HERE 0002a **** ")
if(RepaymentService.validate_account_ownership(account_id = account_id, customer_id = customer_id)):
logger.error(f"HERE 0001a **** ")
# Check loan exists
loan = Loan.get_customer_loan(loan_id = loan_id, customer_id = customer_id)
@@ -61,7 +62,8 @@ class RepaymentService(BaseService):
if not transaction:
logger.error(f"Failed to log transaction")
return ResponseHelper.error(result_description="Failed to log transaction.")
else:
else:
logger.error(f"Invalid Customer or AccountID {account_id} to CustomerID{customer_id} ")
return ResponseHelper.error(result_description="Invalid Customer or Account")
# Simulated processing logic
+22
View File
@@ -48,4 +48,26 @@ class Config:
RAC_RESULT_isWhitelisted = os.environ.get("RAC_RESULT_isWhitelisted", "true")
RAC_RESULT_noBouncedCheck = os.environ.get("RAC_RESULT_noBouncedCheck", "true")
rac_true_rules = [
"rule1-45day-sal",
"rule2-2m-sal",
"rule3-no-bounced-check",
"rule4-current-loan-payments",
"rule5-no-past-due-fadv-loan",
"rule6--no-past-due-other-loan",
"rule7-consistent-salary-amount",
"rule8-whitelisted",
"rule9-regular-account",
"rule10-bvn-validation",
"rule11-CRC-no-delinquency",
"rule12-CRMS-no-delinquency",
"rule13-BVN-ignore",
"rule14-no-lien",
"rule15-null-ignore"
]
settings = Config()
+2 -1
View File
@@ -9,6 +9,7 @@ from .charge import Charge
from .rac_checks import RACCheck
from .loan_repayment_schedule import LoanRepaymentSchedule
from .transaction_offers import TransactionOffer
from .repayments_data import RepaymentsData
__all__ = ['Customer', 'Account', 'Loan', 'Transaction', 'Repayment', 'LoanCharge', 'Offer', 'Charge', 'RACCheck', 'LoanRepaymentSchedule', 'TransactionOffer']
__all__ = ['Customer', 'Account', 'Loan', 'Transaction', 'Repayment', 'LoanCharge', 'Offer', 'Charge', 'RACCheck', 'LoanRepaymentSchedule', 'TransactionOffer', 'RepaymentsData']
+4
View File
@@ -45,6 +45,10 @@ class Loan(db.Model):
disburse_date = db.Column(db.DateTime, nullable=True)
disburse_verify = db.Column(db.DateTime, nullable=True)
reference = db.Column(db.String(50), nullable=True)
disburse_result = db.Column(db.String(10), nullable=True)
disburse_description = db.Column(db.String(100), nullable=True)
verify_result = db.Column(db.String(10), nullable=True)
verify_description = db.Column(db.String(100), nullable=True)
customer = relationship(
"Customer",
+6
View File
@@ -21,6 +21,12 @@ class Repayment(db.Model):
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
transaction_id = db.Column(db.String(50), nullable=True)
repay_date = db.Column(db.DateTime, default=datetime.now(timezone.utc))
repay_result = db.Column(db.String(10), nullable=True)
repay_description = db.Column(db.String(100), nullable=True)
verify_date = db.Column(db.DateTime, default=datetime.now(timezone.utc))
verify_result = db.Column(db.String(10), nullable=True)
verify_description = db.Column(db.String(100), nullable=True)
@classmethod
def create_repayment(cls, customer_id, loan, transaction_id):
+24
View File
@@ -0,0 +1,24 @@
from datetime import datetime, timezone
from app.extensions import db
class RepaymentsData(db.Model):
__tablename__ = 'repayments_data'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
transaction_id = db.Column(db.String(50), nullable=False)
added_date = db.Column(db.DateTime(timezone=True), default=datetime.now(timezone.utc), nullable=False)
response_code = db.Column(db.String(10), nullable=True)
response_descr = db.Column(db.String(255), nullable=True)
def to_dict(self):
return {
"id": self.id,
"transaction_id": self.transaction_id,
"added_date": self.added_date.isoformat() if self.added_date else None,
"response_code": self.response_code,
"response_descr": self.response_descr,
}
def __repr__(self):
return f"<RepaymentsData id={self.id}, transaction_id={self.transaction_id}>"
@@ -0,0 +1,35 @@
"""Migration on Tue Jun 10 08:41:00 WAT 2025
Revision ID: 0acd553309a1
Revises: 45790fd659fb
Create Date: 2025-06-10 08:41:45.222513
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '0acd553309a1'
down_revision = '45790fd659fb'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('repayments_data',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('transaction_id', sa.String(length=50), nullable=False),
sa.Column('added_date', sa.DateTime(timezone=True), nullable=False),
sa.Column('response_code', sa.String(length=10), nullable=True),
sa.Column('response_descr', sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('repayments_data')
# ### end Alembic commands ###
+54
View File
@@ -0,0 +1,54 @@
"""empty message
Revision ID: 45790fd659fb
Revises: b3a5e10bc77e
Create Date: 2025-06-04 12:37:48.180736
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '45790fd659fb'
down_revision = 'b3a5e10bc77e'
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('disburse_result', sa.String(length=10), nullable=True))
batch_op.add_column(sa.Column('disburse_description', sa.String(length=100), nullable=True))
batch_op.add_column(sa.Column('verify_result', sa.String(length=10), nullable=True))
batch_op.add_column(sa.Column('verify_description', sa.String(length=100), nullable=True))
with op.batch_alter_table('repayments', schema=None) as batch_op:
batch_op.add_column(sa.Column('repay_date', sa.DateTime(), nullable=True))
batch_op.add_column(sa.Column('repay_result', sa.String(length=10), nullable=True))
batch_op.add_column(sa.Column('repay_description', sa.String(length=100), nullable=True))
batch_op.add_column(sa.Column('verify_date', sa.DateTime(), nullable=True))
batch_op.add_column(sa.Column('verify_result', sa.String(length=10), nullable=True))
batch_op.add_column(sa.Column('verify_description', sa.String(length=100), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('repayments', schema=None) as batch_op:
batch_op.drop_column('verify_description')
batch_op.drop_column('verify_result')
batch_op.drop_column('verify_date')
batch_op.drop_column('repay_description')
batch_op.drop_column('repay_result')
batch_op.drop_column('repay_date')
with op.batch_alter_table('loans', schema=None) as batch_op:
batch_op.drop_column('verify_description')
batch_op.drop_column('verify_result')
batch_op.drop_column('disburse_description')
batch_op.drop_column('disburse_result')
# ### end Alembic commands ###