Compare commits

...

16 Commits

Author SHA1 Message Date
VivianDee b1260895e0 Update provide_loan.py 2025-04-30 12:24:26 +01:00
VivianDee 2addf25a67 [fix]: Offer schedules 2025-04-30 12:11:32 +01:00
VivianDee 9dae2d951c [add]: transaction id to loan schedules, [add]: tenor to loans 2025-04-30 09:57:49 +01:00
ameye a1d44e0e23 Transaction ID on payment Schedule table 2025-04-30 03:28:59 -04:00
ameye d9f972a425 Merge branch 'advanced_eligibility' of DigiFi/digifi-BankToProductCore into master 2025-04-29 19:51:23 +00:00
VivianDee 8aa2c86ea2 [fix]: transaction id 2025-04-29 16:52:51 +01:00
vivian.d.simbrellang.com 9c42332a83 Merge branch 'advanced_eligibility' of DigiFi/digifi-BankToProductCore into master 2025-04-29 07:27:41 +00:00
VivianDee 92eadbfa16 [update]: Eligibility check 2025-04-29 08:26:41 +01:00
ameye 0fbdebceb3 Merge branch 'DIG-loan-linking-001' of DigiFi/digifi-BankToProductCore into master 2025-04-26 19:40:29 +00:00
ameye 488a1b4bdd First step to linked loans 2025-04-26 15:39:37 -04:00
ameye cdc74d05c4 Merge branch 'DIG-rework-rates-fee-calulations-001' of DigiFi/digifi-BankToProductCore into master 2025-04-26 18:34:27 +00:00
ameye 1b92ede296 Reworked the fee calculations structure 2025-04-26 14:31:06 -04:00
ameye 7de4e3651f Merge branch 'DIG-move-data-new-columns-001' of DigiFi/digifi-BankToProductCore into master 2025-04-26 12:56:10 +00:00
ameye 5f9b1f4cb8 Added rates with offers 2025-04-26 08:53:06 -04:00
ameye ed95865834 Merge branch 'loan_repayment_schedules' of DigiFi/digifi-BankToProductCore into master 2025-04-25 15:58:25 +00:00
ameye e8044d8fed Merge branch 'loan_repayment_schedules' of DigiFi/digifi-BankToProductCore into master 2025-04-25 14:42:09 +00:00
18 changed files with 436 additions and 55 deletions
+35
View File
@@ -0,0 +1,35 @@
# Environment Variables
BASIC_AUTH_USERNAME=user
BASIC_AUTH_PASSWORD=password
#swagger Configuration
SWAGGER_URL="/documentation"
API_URL="/swagger.json"
# Database Configuration
DATABASE_USER=firstadvance
DATABASE_PASSWORD=FirstAdvance!
DATABASE_HOST=dev-data.simbrellang.net
DATABASE_PORT=10532
DATABASE_NAME=firstadvancedev
# DATABASE_HOST=10.20.30.60
# DATABASE_USER=firstadvance
# DATABASE_PASSWORD=firstadvance
# DATABASE_NAME=firstadvancedev
# DATABASE_PORT=5432
# Flask Configuration
FLASK_APP=wsgi.py
FLASK_ENV=development
APP_PORT=4500
# Bank Call Service Connection
SIMBRELLA_BASE_URL="https://bank-emulator.dev.simbrellang.net"
VALID_APP_ID=app1
VALID_API_KEY=test-api-key-12345
# Event Bus Broker Configuration
KAFKA_BROKER="10.0.0.246:9092"
+46 -24
View File
@@ -79,11 +79,11 @@ class BaseService:
return {"error": "No charges found for the offer"}
loan_charges = offer.charges
tenor = offer.tenor // 30 # Convert to months
interest = cls.get_charge_detail(charges = loan_charges, code = "INTEREST", amount = amount)
management = cls.get_charge_detail(charges = loan_charges, code = "MGTFEE", amount = amount)
insurance = cls.get_charge_detail(charges = loan_charges, code = "INSURANCE", amount = amount)
vat = cls.get_charge_detail(charges = loan_charges, code = "VAT", amount = amount, management_fee = management["fee"])
tenor = offer.schedule # offer.tenor // 30 # Convert to months
interest = cls.get_charge_detail(rates = offer.interest_rate, charges = loan_charges, code = "INTEREST", amount = amount)
management = cls.get_charge_detail(rates = offer.management_rate, charges = loan_charges, code = "MGTFEE", amount = amount)
insurance = cls.get_charge_detail(rates = offer.insurance_rate, charges = loan_charges, code = "INSURANCE", amount = amount)
vat = cls.get_charge_detail(rates = offer.vat_rate, charges = loan_charges, code = "VAT", amount = amount, management_fee = management["fee"])
# Separate fees into upfront and postpaid
upfront_fees = [
@@ -97,18 +97,29 @@ class BaseService:
for fee in [interest, management, insurance, vat]
if fee["due_days"] != 0
]
vat_test = vat["fee"]
logger.info(f"VAT fee == *************** : {vat_test}")
# Up-front payment: (only those fees due immediately i.e due_days == 0)
upfront_payment = sum(upfront_fees)
# upfront_payment = sum(upfront_fees)
if offer.schedule == 1:
upfront_payment = vat["fee"] + management["fee"] + insurance["fee"] + interest["fee"]
interest_amount = interest["fee"]
repayment_amount = amount
else:
upfront_payment = vat["fee"] + insurance["fee"]+management["fee"]
interest_amount = interest["fee"]*offer.schedule
repayment_amount = amount + interest_amount
# Repayment amount: (principal + only those fees not due immediately i.e due_days != 0)
repayment_amount = amount + (sum(postpaid_fees) * tenor)
# repayment_amount = amount + (sum(postpaid_fees) * tenor)
# Total amount: (upfront_payment + repayment_amount)
total_amount = upfront_payment + repayment_amount
# Calculate the installment amount
installment_amount = repayment_amount / tenor
installment_amount = repayment_amount / offer.schedule
return {
"interest": interest,
@@ -123,28 +134,39 @@ class BaseService:
@classmethod
def get_charge_detail(cls, charges, code, amount, management_fee=None):
def get_charge_detail(cls, rates, charges, code, amount, management_fee= 0.0):
"""
Get details for a specific charge code from a list of loan charges.
Returns default values if not found.
"""
fee = 0.0
for charge in charges:
if charge.code == code:
fee = (
management_fee * charge.percent / 100
if code == "VAT" and management_fee is not None
else amount * charge.percent / 100
)
if code == "VAT" and management_fee > 0:
fee = management_fee * rates / 100
else:
fee = amount * rates / 100
return {
"rate": rates,
"fee": round(fee, 2),
"due_days": 30,
"code": code,
"description" : "have no idea how to get this yet"
}
# if charge.code == code:
# if code == "VAT" and management_fee > 0:
# fee = management_fee * rates / 100
# else:
# fee = amount * rates / 100
#
# return {
# "rate": rates,
# "fee": round(fee, 2),
# "due_days": charge.due
# }
return {
"rate": charge.percent,
"fee": round(fee, 2),
"due_days": charge.due
}
return {"rate": 0, "fee": 0, "due_days": 0}
# return {"rate": 0, "fee": 0, "due_days": 0}
+32 -2
View File
@@ -1,4 +1,5 @@
from flask import session, jsonify
from app.models.transaction_offers import TransactionOffer
from app.utils.logger import logger
from app.api.services.base_service import BaseService
from app.api.schemas.eligibility_check import EligibilityCheckSchema
@@ -61,7 +62,36 @@ class EligibilityCheckService(BaseService):
if response.status_code != 200:
return jsonify({"message": "RACCheck failed"}), 400
offers = [offer.to_dict() for offer in Offer.get_all_offers()]
offers = Offer.get_all_offers()
eligible_offers = []
for offer in offers:
# Determine an approved amount
approved_amount = min(offer.max_amount, 5000)
transaction_offer = TransactionOffer.create_transaction_offer(
customer_id = customer.id,
transaction_id = transaction.transaction_id,
offer_id = offer.id,
min_amount = offer.min_amount,
max_amount = offer.max_amount,
eligible_amount = approved_amount,
product_id = offer.product_id,
tenor = offer.tenor
)
# Visible offer ID: offer_id + padded(transaction_offer.id)
padded_id = str(transaction_offer.id).zfill(6)
public_offer_id = f"{offer.id}{padded_id}"
eligible_offers.append({
"offerId": public_offer_id,
"product_id": offer.product_id,
"min_amount": offer.min_amount,
"max_amount": approved_amount,
"tenor": offer.tenor
})
# Simulate processing
response_data = {
@@ -69,7 +99,7 @@ class EligibilityCheckService(BaseService):
"transactionId": transactionId,
"countryCode": "NG",
"msisdn": msisdn,
"eligibleOffers": offers,
"eligibleOffers": eligible_offers,
"resultDescription": "Successful",
"resultCode": "00",
"accountId": account_id
+14 -5
View File
@@ -70,8 +70,16 @@ class ProvideLoanService(BaseService):
charges = ProvideLoanService.calculate_charges(offer, amount)
upfront_fee = charges["upfront_payment"]
repayment_amount = charges["repayment_amount"]
#installment_amount = charges["installment_amount"]
num_schedules = offer.schedule
upfront_payment = charges["upfront_payment"]
total_amount = charges["total_amount"]
installment_amount = charges["installment_amount"]
tenor = offer.tenor // 30 # Convert to months
interest = charges["interest"]
management = charges["management"]
insurance = charges["insurance"]
vat = charges["vat"]
@@ -87,7 +95,8 @@ class ProvideLoanService(BaseService):
upfront_fee = upfront_fee,
repayment_amount = repayment_amount,
installment_amount = installment_amount,
status= LoanStatus.ACTIVE
status = LoanStatus.ACTIVE,
tenor = offer.tenor
)
if not loan:
@@ -98,7 +107,7 @@ class ProvideLoanService(BaseService):
db.session.flush()
schedule = LoanRepaymentSchedule.add_repayment_schedule(loan = loan, tenor = tenor)
schedule = LoanRepaymentSchedule.add_repayment_schedule(loan = loan, num_schedules = num_schedules, transaction_id = transaction_id)
if not schedule:
@@ -107,9 +116,9 @@ class ProvideLoanService(BaseService):
"message": "Failed to generate loan repayment schedule."
}), 400
charges = Charge.get_offer_charges(offer.id)
# charges = Charge.get_offer_charges(offer.id)
# logger.error(f"{charges}")
logger.error(f"{charges}")
loan_id = loan.id
+22 -5
View File
@@ -61,6 +61,7 @@ class SelectOfferService(BaseService):
management = charges["management"]
insurance = charges["insurance"]
vat = charges["vat"]
repayment_amount = charges["repayment_amount"]
# Calculate the repayment dates
@@ -68,7 +69,7 @@ class SelectOfferService(BaseService):
start_date = date.today()
# Convert tenor to months
months = tenor // 30
months = offer.schedule # tenor // 30
recommended_repayment_dates = [
(start_date + relativedelta(months=i + 1)).isoformat()
@@ -83,19 +84,35 @@ class SelectOfferService(BaseService):
"productId": product_id,
"amount": amount,
"upfrontPayment": upfront_payment,
"interestRate": interest["rate"],
"managementRate": management["rate"],
"interestRate": offer.interest_rate,
"managementRate": offer.management_rate,
"managementFee": management["fee"],
"insuranceRate": insurance["rate"],
"insuranceRate": offer.insurance_rate,
"insuranceFee": insurance["fee"],
"VATRate": vat["rate"],
"VATRate": offer.vat_rate,
"VATAmount": vat["fee"],
"recommendedRepaymentDates": recommended_repayment_dates,
"repaymentAmount": repayment_amount,
"installmentAmount": installment_amount,
"totalRepaymentAmount": total_amount,
}
]
# "offerId": offer.id,
# "productId": product_id,
# "amount": amount,
# "upfrontPayment": upfront_payment,
# "interestRate": interest["rate"],
# "managementRate": management["rate"],
# "managementFee": management["fee"],
# "insuranceRate": insurance["rate"],
# "insuranceFee": insurance["fee"],
# "VATRate": vat["rate"],
# "VATAmount": vat["fee"],
# "recommendedRepaymentDates": recommended_repayment_dates,
# "installmentAmount": installment_amount,
# "totalRepaymentAmount": total_amount,
#
# Business logic - selecting an offer
response_data = {
"outstandingDebtAmount": 0,
+2 -1
View File
@@ -31,7 +31,8 @@ class Config:
"JWT_REFRESH_TOKEN_EXPIRES", timedelta(days=30)
)
KAFKA_BROKER = 'dev-events.simbrellang.net:9085'
# KAFKA_BROKER = 'dev-events.simbrellang.net:9085'
KAFKA_BROKER = os.getenv("KAFKA_BROKER", "dev-events.simbrellang.net:9085")
settings = Config()
+2 -1
View File
@@ -8,6 +8,7 @@ from .offer import Offer
from .charge import Charge
from .rac_checks import RACCheck
from .loan_repayment_schedule import LoanRepaymentSchedule
from .transaction_offers import TransactionOffer
__all__ = ['Customer', 'Account', 'Loan', 'Transaction', 'Repayment', 'LoanCharge', 'Offer', 'Charge', 'RACCheck', 'LoanRepaymentSchedule']
__all__ = ['Customer', 'Account', 'Loan', 'Transaction', 'Repayment', 'LoanCharge', 'Offer', 'Charge', 'RACCheck', 'LoanRepaymentSchedule', 'TransactionOffer']
+7
View File
@@ -27,6 +27,13 @@ class Customer(db.Model):
back_populates="customer",
)
transaction_offers = relationship(
"TransactionOffer",
primaryjoin="Customer.id == TransactionOffer.customer_id",
foreign_keys="TransactionOffer.customer_id",
back_populates="customer",
)
@classmethod
def is_valid_customer(cls, customer_id):
customer = cls.query.filter_by(id=customer_id).first()
+8 -3
View File
@@ -17,6 +17,7 @@ class Loan(db.Model):
)
customer_id = db.Column(db.String(50), nullable=False)
transaction_id = db.Column(db.String(50), nullable=True)
original_transaction = 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)
product_id = db.Column(db.String(20), nullable=True)
@@ -29,6 +30,7 @@ class Loan(db.Model):
repayment_amount = db.Column(db.Float, nullable=True, default=0.0)
installment_amount = db.Column(db.Float, nullable=True, default=0.0)
status = db.Column(db.String(20), default='pending')
tenor = db.Column(db.Integer, nullable=True)
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))
@@ -68,7 +70,8 @@ class Loan(db.Model):
upfront_fee,
repayment_amount,
installment_amount,
status="pending",
tenor,
status = "pending",
):
# Check if customer exists
customer = Customer.is_valid_customer(customer_id)
@@ -85,13 +88,15 @@ class Loan(db.Model):
product_id = product_id,
collection_type = collection_type,
transaction_id = transaction_id,
original_transaction = transaction_id,
initial_loan_amount = initial_loan_amount,
current_loan_amount = initial_loan_amount,
upfront_fee = upfront_fee,
repayment_amount = repayment_amount,
installment_amount = installment_amount,
due_date=now,
status = status
due_date=now,
tenor = tenor,
status = status,
)
try:
+13 -10
View File
@@ -1,6 +1,7 @@
from datetime import datetime, timezone, timedelta
from app.extensions import db
from sqlalchemy.orm import relationship
from app.utils.logger import logger
class LoanCharge(db.Model):
@@ -35,8 +36,8 @@ class LoanCharge(db.Model):
charges (list): A list of dictionaries with keys:
code (str), amount (float), percent (float), description (str), due (int)
"""
if not charges or not isinstance(charges, list):
raise ValueError("Charges must be a non-empty list of dictionaries")
# if not charges or not isinstance(charges, list):
# raise ValueError("Charges must be a non-empty list of dictionaries")
if loan_id is None:
raise ValueError("loan_id cannot be None")
@@ -44,21 +45,23 @@ class LoanCharge(db.Model):
loan_charges = []
now = datetime.now(timezone.utc)
for charge in charges:
due_days = getattr(charge, "due", 0)
amount = getattr(charge, "amount", 0.0)
percent = getattr(charge, "percent", 0.0)
if amount == 0.0:
amount = (percent / 100.0) * referenced_amount
subset_keys = ['interest', 'management', 'insurance', 'vat']
for item in subset_keys:
charge = charges[item]
due_days = charge['due_days'] # getattr(charge, "due_days", 0)
amount = charge['fee'] # getattr(charge, "fee", 0.0)
percent = charge['rate'] # getattr(charge, "rate", 0.0)
code = charge['code'] # getattr(charge, "code","")
description = charge['description'] # getattr(charge, "description", "")
charge_obj = cls(
loan_id = loan_id,
transaction_id = transaction_id,
code = getattr(charge, "code"),
code = code,
amount = round(amount, 2),
percent = percent,
description = getattr(charge, "description", ""),
description = description,
due = due_days,
due_date = now + timedelta(days=due_days)
)
+5 -3
View File
@@ -8,6 +8,7 @@ class LoanRepaymentSchedule(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
loan_id = db.Column(db.Integer, nullable=False)
transaction_id = db.Column(db.String(50), nullable=True)
product_id = db.Column(db.String(20), nullable=True)
installment_number = db.Column(db.Integer, nullable=False)
due_date = db.Column(db.DateTime, nullable=False)
@@ -28,14 +29,14 @@ class LoanRepaymentSchedule(db.Model):
@classmethod
def add_repayment_schedule(cls, loan, tenor):
def add_repayment_schedule(cls, loan, num_schedules, transaction_id):
"""
Add repayment schedules for a given loan.
"""
now = datetime.now(timezone.utc)
schedules = []
for i in range(tenor):
for i in range(num_schedules):
due_date = now + relativedelta(months=i + 1)
schedule = LoanRepaymentSchedule(
loan_id=loan.id,
@@ -43,7 +44,8 @@ class LoanRepaymentSchedule(db.Model):
due_date=due_date,
total_repayment_amount = round(loan.repayment_amount, 2),
installment_amount=round(loan.installment_amount, 2),
product_id = loan.product_id
product_id = loan.product_id,
transaction_id = transaction_id
)
db.session.add(schedule)
+9 -1
View File
@@ -12,6 +12,10 @@ class Offer(db.Model):
max_amount = db.Column(db.Float, nullable=False)
tenor = db.Column(db.Integer, nullable=False)
schedule = db.Column(db.Integer, nullable=True)
interest_rate = db.Column(db.Float, default=3.0)
management_rate = db.Column(db.Float, default=1.0)
insurance_rate = db.Column(db.Float, default=1.0)
vat_rate = db.Column(db.Float, default=7.5)
list_order = db.Column(db.Integer, 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))
@@ -71,7 +75,11 @@ class Offer(db.Model):
"productId": self.product_id,
"minAmount": self.min_amount,
"maxAmount": self.max_amount,
"tenor": self.tenor
"tenor": self.tenor,
"interest_rate": self.interest_rate,
"management_rate": self.management_rate,
"insurance_rate": self.insurance_rate,
"vat_rate": self.vat_rate
}
def __repr__(self):
+66
View File
@@ -0,0 +1,66 @@
from datetime import datetime, timezone
from app.extensions import db
from sqlalchemy.orm import relationship
class TransactionOffer(db.Model):
__tablename__ = 'transaction_offers'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
customer_id = db.Column(db.String(50), nullable=False)
transaction_id = db.Column(db.String(50), nullable=False)
offer_id = db.Column(db.String(20), nullable=False)
product_id = db.Column(db.String(20), nullable=True)
min_amount = db.Column(db.Float, nullable=False)
max_amount = db.Column(db.Float, nullable=False)
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, 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 == TransactionOffer.customer_id",
foreign_keys=[customer_id],
back_populates="transaction_offers",
)
@classmethod
def create_transaction_offer(cls, customer_id, transaction_id, offer_id, min_amount, max_amount, eligible_amount=None, product_id=None, tenor=None):
"""
Class method to create and save a TransactionOffer.
"""
transaction_offer = cls(
customer_id=customer_id,
transaction_id=transaction_id,
offer_id=offer_id,
min_amount=min_amount,
max_amount=max_amount,
eligible_amount=eligible_amount,
product_id=product_id,
tenor=tenor
)
db.session.add(transaction_offer)
db.session.flush()
return transaction_offer
def to_dict(self):
return {
'id': self.id,
'customerId': self.customer_id,
'transactionId': self.transaction_id,
'offerId': self.offer_id,
'productId': self.product_id,
'minAmount': self.min_amount,
'maxAmount': self.max_amount,
'eligibleAmount': self.eligible_amount,
'tenor': self.tenor,
'createdAt': self.created_at.isoformat() if self.created_at else None,
'updatedAt': self.updated_at.isoformat() if self.updated_at else None,
}
def __repr__(self):
return f'<TransactionOffer {self.id}>'
@@ -0,0 +1,32 @@
"""Migration for mloan table
Revision ID: 38acee611d55
Revises: f1e83a993034
Create Date: 2025-04-30 09:55:30.552838
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '38acee611d55'
down_revision = 'f1e83a993034'
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('tenor', sa.Integer(), 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('tenor')
# ### end Alembic commands ###
+41
View File
@@ -0,0 +1,41 @@
"""empty message
Revision ID: 86e701febdda
Revises: eb99c7fb9e09
Create Date: 2025-04-29 07:59:33.305967
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '86e701febdda'
down_revision = 'eb99c7fb9e09'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('transaction_offers',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('customer_id', sa.String(length=50), nullable=False),
sa.Column('transaction_id', sa.String(length=50), nullable=False),
sa.Column('offer_id', sa.String(length=20), nullable=False),
sa.Column('product_id', sa.String(length=20), nullable=True),
sa.Column('min_amount', sa.Float(), nullable=False),
sa.Column('max_amount', sa.Float(), nullable=False),
sa.Column('eligible_amount', sa.Float(), nullable=True),
sa.Column('tenor', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('transaction_offers')
# ### end Alembic commands ###
@@ -0,0 +1,38 @@
"""Migration on Sat Apr 26 12:50:46 UTC 2025
Revision ID: 89759cebb9c6
Revises: 2a45dd99c9cb
Create Date: 2025-04-26 12:50:49.771355
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '89759cebb9c6'
down_revision = '2a45dd99c9cb'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('offers', schema=None) as batch_op:
batch_op.add_column(sa.Column('interest_rate', sa.Float(), nullable=True))
batch_op.add_column(sa.Column('management_rate', sa.Float(), nullable=True))
batch_op.add_column(sa.Column('insurance_rate', sa.Float(), nullable=True))
batch_op.add_column(sa.Column('vat_rate', sa.Float(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('offers', schema=None) as batch_op:
batch_op.drop_column('vat_rate')
batch_op.drop_column('insurance_rate')
batch_op.drop_column('management_rate')
batch_op.drop_column('interest_rate')
# ### end Alembic commands ###
@@ -0,0 +1,32 @@
"""Migration on Sat Apr 26 19:02:17 UTC 2025
Revision ID: eb99c7fb9e09
Revises: 89759cebb9c6
Create Date: 2025-04-26 19:02:20.443678
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'eb99c7fb9e09'
down_revision = '89759cebb9c6'
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('original_transaction', 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('original_transaction')
# ### end Alembic commands ###
@@ -0,0 +1,32 @@
"""Migration on Tue Apr 29 20:43:35 UTC 2025
Revision ID: f1e83a993034
Revises: 86e701febdda
Create Date: 2025-04-29 20:43:38.595543
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'f1e83a993034'
down_revision = '86e701febdda'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('loan_repayment_schedules', 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('loan_repayment_schedules', schema=None) as batch_op:
batch_op.drop_column('transaction_id')
# ### end Alembic commands ###