|
|
|
|
@ -3,12 +3,28 @@ from sqlalchemy import Select, Delete
|
|
|
|
|
from src.database.service import search_filter_sort_paginate
|
|
|
|
|
from .model import MasterData
|
|
|
|
|
from .schema import MasterDataCreate, MasterDataUpdate
|
|
|
|
|
from typing import Optional
|
|
|
|
|
from typing import Optional, List
|
|
|
|
|
|
|
|
|
|
from src.database.core import DbSession
|
|
|
|
|
from src.auth.service import CurrentUser
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def calculate_pmt(rate, nper, pv):
|
|
|
|
|
"""
|
|
|
|
|
rate: interest rate per period
|
|
|
|
|
nper: total number of payment periods
|
|
|
|
|
pv: present value (loan amount)
|
|
|
|
|
"""
|
|
|
|
|
# Convert percentage to decimal if needed (e.g., 5% to 0.05)
|
|
|
|
|
rate = float(rate) / 100 if rate > 1 else float(rate)
|
|
|
|
|
|
|
|
|
|
# PMT formula: PMT = PV * (r * (1 + r)^n) / ((1 + r)^n - 1)
|
|
|
|
|
if rate == 0:
|
|
|
|
|
return -pv / nper
|
|
|
|
|
else:
|
|
|
|
|
return -pv * (rate * (1 + rate) ** nper) / ((1 + rate) ** nper - 1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def get(*, db_session: DbSession, masterdata_id: str) -> Optional[MasterData]:
|
|
|
|
|
"""Returns a document based on the given document id."""
|
|
|
|
|
query = Select(MasterData).filter(MasterData.id == masterdata_id)
|
|
|
|
|
@ -43,16 +59,155 @@ async def update(
|
|
|
|
|
):
|
|
|
|
|
"""Updates a document."""
|
|
|
|
|
data = masterdata_in.model_dump()
|
|
|
|
|
|
|
|
|
|
update_data = masterdata_in.model_dump(exclude_defaults=True)
|
|
|
|
|
|
|
|
|
|
for field in data:
|
|
|
|
|
if field in update_data:
|
|
|
|
|
def get_value(data_list, name):
|
|
|
|
|
return next((m.value_num for m in data_list if m.name == name), 0)
|
|
|
|
|
|
|
|
|
|
# First update the direct values from update_data
|
|
|
|
|
for field in update_data:
|
|
|
|
|
setattr(masterdata, field, update_data[field])
|
|
|
|
|
|
|
|
|
|
# Then check which formulas need to be recalculated based on updated fields
|
|
|
|
|
if "loan_portion" in update_data:
|
|
|
|
|
# Update equity_portion when loan_portion changes
|
|
|
|
|
equity_portion = 100 - get_value(masterdata, "loan_portion")
|
|
|
|
|
setattr(masterdata, "equity_portion", equity_portion)
|
|
|
|
|
|
|
|
|
|
# Update loan amount when loan_portion changes
|
|
|
|
|
total_project_cost = get_value(masterdata, "total_project_cost")
|
|
|
|
|
loan = total_project_cost * (get_value(masterdata, "loan_portion") / 100)
|
|
|
|
|
setattr(masterdata, "loan", loan)
|
|
|
|
|
|
|
|
|
|
# Update equity when loan_portion changes
|
|
|
|
|
equity = total_project_cost * (equity_portion / 100)
|
|
|
|
|
setattr(masterdata, "equity", equity)
|
|
|
|
|
|
|
|
|
|
if any(field in update_data for field in ["loan", "interest_rate", "loan_tenor"]):
|
|
|
|
|
# Recalculate PMT when loan, interest_rate, or loan_tenor changes
|
|
|
|
|
pmt = calculate_pmt(
|
|
|
|
|
rate=get_value(masterdata, "interest_rate"),
|
|
|
|
|
nper=get_value(masterdata, "loan_tenor"),
|
|
|
|
|
pv=get_value(masterdata, "loan"),
|
|
|
|
|
)
|
|
|
|
|
setattr(masterdata, "principal_interest_payment", pmt)
|
|
|
|
|
|
|
|
|
|
if any(
|
|
|
|
|
field in update_data
|
|
|
|
|
for field in [
|
|
|
|
|
"loan_portion",
|
|
|
|
|
"interest_rate",
|
|
|
|
|
"corporate_tax_rate",
|
|
|
|
|
"wacc_on_equity",
|
|
|
|
|
"equity_portion",
|
|
|
|
|
]
|
|
|
|
|
):
|
|
|
|
|
# Recalculate WACC when any of its components change
|
|
|
|
|
wacc = (
|
|
|
|
|
get_value(masterdata, "loan_portion")
|
|
|
|
|
* (
|
|
|
|
|
get_value(masterdata, "interest_rate")
|
|
|
|
|
* (1 - get_value(masterdata, "corporate_tax_rate"))
|
|
|
|
|
)
|
|
|
|
|
) + (
|
|
|
|
|
get_value(masterdata, "wacc_on_equity")
|
|
|
|
|
* get_value(masterdata, "equity_portion")
|
|
|
|
|
)
|
|
|
|
|
setattr(masterdata, "wacc_on_project", wacc)
|
|
|
|
|
|
|
|
|
|
await db_session.commit()
|
|
|
|
|
return masterdata
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def bulk_update(
|
|
|
|
|
*, db_session: DbSession, updates: List[MasterDataUpdate], ids: List[str]
|
|
|
|
|
) -> List[MasterData]:
|
|
|
|
|
"""
|
|
|
|
|
Performs bulk update on multiple MasterData records.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
db_session: Database session
|
|
|
|
|
updates: List of MasterDataUpdate objects containing the updates
|
|
|
|
|
ids: List of MasterData IDs to update
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
List of updated MasterData objects
|
|
|
|
|
"""
|
|
|
|
|
# Fetch all records to be updated in one query
|
|
|
|
|
query = Select(MasterData).where(MasterData.id.in_(ids))
|
|
|
|
|
result = await db_session.execute(query)
|
|
|
|
|
records = result.scalars().all()
|
|
|
|
|
|
|
|
|
|
# Create a mapping of id to record for easier access
|
|
|
|
|
records_map = {record.id: record for record in records}
|
|
|
|
|
|
|
|
|
|
# Process updates in batches
|
|
|
|
|
updated_records = []
|
|
|
|
|
for masterdata_id, masterdata_in in zip(ids, updates):
|
|
|
|
|
masterdata = records_map.get(masterdata_id)
|
|
|
|
|
if not masterdata:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
data = masterdata_in.model_dump()
|
|
|
|
|
update_data = masterdata_in.model_dump(exclude_defaults=True)
|
|
|
|
|
|
|
|
|
|
def get_value(obj, name):
|
|
|
|
|
return next((m.value_num for m in obj if m.name == name), 0)
|
|
|
|
|
|
|
|
|
|
# Update direct values
|
|
|
|
|
for field in update_data:
|
|
|
|
|
setattr(masterdata, field, update_data[field])
|
|
|
|
|
|
|
|
|
|
# Handle interdependent calculations
|
|
|
|
|
if "loan_portion" in update_data:
|
|
|
|
|
equity_portion = 100 - get_value(masterdata, "loan_portion")
|
|
|
|
|
setattr(masterdata, "equity_portion", equity_portion)
|
|
|
|
|
|
|
|
|
|
total_project_cost = get_value(masterdata, "total_project_cost")
|
|
|
|
|
loan = total_project_cost * (get_value(masterdata, "loan_portion") / 100)
|
|
|
|
|
setattr(masterdata, "loan", loan)
|
|
|
|
|
|
|
|
|
|
equity = total_project_cost * (equity_portion / 100)
|
|
|
|
|
setattr(masterdata, "equity", equity)
|
|
|
|
|
|
|
|
|
|
if any(
|
|
|
|
|
field in update_data for field in ["loan", "interest_rate", "loan_tenor"]
|
|
|
|
|
):
|
|
|
|
|
pmt = calculate_pmt(
|
|
|
|
|
rate=get_value(masterdata, "interest_rate"),
|
|
|
|
|
nper=get_value(masterdata, "loan_tenor"),
|
|
|
|
|
pv=get_value(masterdata, "loan"),
|
|
|
|
|
)
|
|
|
|
|
setattr(masterdata, "principal_interest_payment", pmt)
|
|
|
|
|
|
|
|
|
|
if any(
|
|
|
|
|
field in update_data
|
|
|
|
|
for field in [
|
|
|
|
|
"loan_portion",
|
|
|
|
|
"interest_rate",
|
|
|
|
|
"corporate_tax_rate",
|
|
|
|
|
"wacc_on_equity",
|
|
|
|
|
"equity_portion",
|
|
|
|
|
]
|
|
|
|
|
):
|
|
|
|
|
wacc = (
|
|
|
|
|
get_value(masterdata, "loan_portion")
|
|
|
|
|
* (
|
|
|
|
|
get_value(masterdata, "interest_rate")
|
|
|
|
|
* (1 - get_value(masterdata, "corporate_tax_rate"))
|
|
|
|
|
)
|
|
|
|
|
) + (
|
|
|
|
|
get_value(masterdata, "wacc_on_equity")
|
|
|
|
|
* get_value(masterdata, "equity_portion")
|
|
|
|
|
)
|
|
|
|
|
setattr(masterdata, "wacc_on_project", wacc)
|
|
|
|
|
|
|
|
|
|
updated_records.append(masterdata)
|
|
|
|
|
|
|
|
|
|
# Commit all changes in a single transaction
|
|
|
|
|
await db_session.commit()
|
|
|
|
|
|
|
|
|
|
return masterdata
|
|
|
|
|
return updated_records
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def delete(*, db_session: DbSession, masterdata_id: str):
|
|
|
|
|
|