초기화

This commit is contained in:
2026-07-15 18:37:19 +09:00
commit 94abc5461d
1268 changed files with 380198 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
# inventory package marker
from .db_manager import (
add_material_property,
update_material_property,
delete_material_property,
get_all_material_properties,
get_material_property_by_id,
add_inventory_to_db,
update_inventory_in_db,
deplete_inventory_in_db,
get_all_inventory_from_db,
get_inventory_details_from_db,
get_total_stock_for_material
)
from .analysis import calculate_cost_analysis
from .router import router
+221
View File
@@ -0,0 +1,221 @@
import math
import logging
from .db_manager import get_inventory_details_from_db
from setting.db_manager import get_all_settings
def _safe_float_conversion(value, default_value=None):
try:
if value is not None and value != '':
return float(value)
except (ValueError, TypeError):
pass
return default_value
def calculate_cost_analysis(inputs: dict, target_production_quantity: float = None) -> dict:
"""
원가 분석 계산 엔진 (순수 연산 비즈니스 로직)
"""
yarn_inventory_id = inputs.get("yarn_inventory_id")
bond_inventory_id = inputs.get("bond_inventory_id")
if not yarn_inventory_id:
raise ValueError("분석할 원사 재고가 선택되지 않았습니다.")
yarn_data_wrapper = get_inventory_details_from_db(yarn_inventory_id)
if not yarn_data_wrapper or "inventory" not in yarn_data_wrapper:
raise ValueError("선택된 원사의 물성 또는 단가 정보를 찾을 수 없습니다.")
yarn_data = yarn_data_wrapper["inventory"]
yarn_density_g_cm3 = _safe_float_conversion(yarn_data.get("density"), 1.0)
yarn_unit_cost = _safe_float_conversion(yarn_data.get("unit_cost"), 0.0)
bond_unit_cost = 0.0
if bond_inventory_id and bond_inventory_id != 'null':
bond_data_wrapper = get_inventory_details_from_db(bond_inventory_id)
if bond_data_wrapper and "inventory" in bond_data_wrapper:
bond_data = bond_data_wrapper["inventory"]
bond_unit_cost = _safe_float_conversion(bond_data.get("unit_cost"), 0.0)
yarn_diameter_micron = _safe_float_conversion(inputs.get("yarn_diameter_micron"), 7)
yarn_k = _safe_float_conversion(inputs.get("yarn_k"), 12) * 1000
ports = _safe_float_conversion(inputs.get("ports"), 40)
cycle_time_hz = _safe_float_conversion(inputs.get("cycle_time_hz"), 8)
cut_length_mm = _safe_float_conversion(inputs.get("cut_length_mm"), 6)
work_hours_day = _safe_float_conversion(inputs.get("work_hours_day"), 16)
work_days_month = _safe_float_conversion(inputs.get("work_days_month"), 20)
num_machines = _safe_float_conversion(inputs.get("num_machines"), 2)
bond_percentage = _safe_float_conversion(inputs.get("bond_percentage"), 3.0) / 100.0
yarn_radius_cm = (yarn_diameter_micron / 2) * 1e-4
yarn_cross_section_cm2 = math.pi * (yarn_radius_cm ** 2)
pure_yarn_weight_kg_per_m = (yarn_cross_section_cm2 * 100 * yarn_density_g_cm3) / 1000
cut_length_m = cut_length_mm / 1000
production_length_m_per_hour_per_mc = cycle_time_hz * 3600 * cut_length_m
pure_yarn_kg_per_hour_per_mc = production_length_m_per_hour_per_mc * pure_yarn_weight_kg_per_m * ports * yarn_k
if 0 < bond_percentage < 1.0:
total_kg_per_hour_per_mc = pure_yarn_kg_per_hour_per_mc / (1.0 - bond_percentage)
else:
total_kg_per_hour_per_mc = pure_yarn_kg_per_hour_per_mc
if total_kg_per_hour_per_mc <= 0:
raise ValueError("시간당 생산량이 0 이하여서 계산할 수 없습니다.")
settings = get_all_settings()
settings_dict = {f"{s['setting_group']}_{s['setting_key']}": s['setting_value'] for s in settings}
def get_s(key, default):
return _safe_float_conversion(settings_dict.get(key, default), default)
avg_power_per_machine_kw = get_s("CostAnalysis_AvgPowerPerMachine", 5000) / 1000
electricity_price_kwh = get_s("CostAnalysis_ElectricityUnitPricePerKWH", 94.4)
hourly_power_cost = avg_power_per_machine_kw * electricity_price_kwh
max_machines_per_person = get_s("CostAnalysis_MaxMachinesPerPerson", 10)
num_people_needed = math.ceil(num_machines / max_machines_per_person)
monthly_labor_cost_person = get_s("CostAnalysis_MonthlyLaborCostPerPerson", 6000000)
meal_cost_day = get_s("CostAnalysis_MealCostPerDay", 15000)
hourly_labor_cost = (num_people_needed * monthly_labor_cost_person + num_people_needed * meal_cost_day * work_days_month) / (work_days_month * work_hours_day * num_machines)
packaging_cost_kg = get_s("CostAnalysis_PackagingCostPerKg", 500)
packaging_weight_kg = get_s("CostAnalysis_PackagingBaseWeightKg", 25)
hourly_packaging_cost = (total_kg_per_hour_per_mc / packaging_weight_kg) * packaging_cost_kg if packaging_weight_kg > 0 else 0
monthly_rent = get_s("CostAnalysis_MonthlyRentCost", 0)
monthly_admin = get_s("CostAnalysis_MonthlyGeneralAdminCost", 50000)
monthly_delivery = get_s("CostAnalysis_MonthlyDeliveryCost", 300000)
hourly_overhead_cost = (monthly_rent + monthly_admin + monthly_delivery) / (work_days_month * work_hours_day * num_machines)
mass_mc_cost = get_s("Investment_MassProductionEquipmentCost", 10000000)
dep_mass_mc = get_s("Investment_DepreciationPeriodMassProduction", 3) * 12
office_cost = get_s("Investment_OfficeSuppliesCost", 5000000)
dep_office = get_s("Investment_DepreciationPeriodOfficeSupplies", 3) * 12
proto_cost = get_s("Investment_ProtoEquipmentCost", 5000000)
monthly_depreciation = (mass_mc_cost / dep_mass_mc if dep_mass_mc > 0 else 0) + \
(office_cost / dep_office if dep_office > 0 else 0) + \
(proto_cost / dep_office if dep_office > 0 else 0)
hourly_depreciation = monthly_depreciation / (work_days_month * work_hours_day)
hourly_net_cost_per_machine = hourly_power_cost + hourly_labor_cost + hourly_packaging_cost + hourly_overhead_cost + hourly_depreciation
company_margin_rate = get_s("CostAnalysis_CompanyMarginRate", 30) / 100.0
standard_processing_cost_kg = get_s("CostAnalysis_StandardProcessingCost", 0)
pure_processing_cost_kg = hourly_net_cost_per_machine / total_kg_per_hour_per_mc if total_kg_per_hour_per_mc > 0 else 0
calculated_profit_margin_kg = 0
if pure_processing_cost_kg > 0:
if pure_processing_cost_kg * (1 + company_margin_rate) < standard_processing_cost_kg:
calculated_profit_margin_kg = standard_processing_cost_kg - pure_processing_cost_kg
else:
calculated_profit_margin_kg = pure_processing_cost_kg * company_margin_rate
hourly_profit_per_machine = calculated_profit_margin_kg * total_kg_per_hour_per_mc
analysis_result = {}
total_kg_hour_all_mc = total_kg_per_hour_per_mc * num_machines
total_kg_day_all_mc = total_kg_hour_all_mc * work_hours_day
total_kg_month = total_kg_day_all_mc * work_days_month
monthly_power_cost = hourly_power_cost * work_hours_day * work_days_month * num_machines
monthly_labor_cost_total = hourly_labor_cost * work_hours_day * work_days_month * num_machines
monthly_packaging_cost = hourly_packaging_cost * work_hours_day * work_days_month * num_machines
monthly_overhead_cost = hourly_overhead_cost * work_hours_day * work_days_month * num_machines
monthly_depreciation_cost_total = hourly_depreciation * work_hours_day * work_days_month * num_machines
monthly_total_expenses = monthly_power_cost + monthly_labor_cost_total + monthly_packaging_cost + monthly_overhead_cost + monthly_depreciation_cost_total
pure_yarn_kg_per_hour_all_mc = pure_yarn_kg_per_hour_per_mc * num_machines
required_yarn_kg_month = (pure_yarn_kg_per_hour_all_mc / total_kg_hour_all_mc) * total_kg_month if total_kg_hour_all_mc > 0 else 0
bond_kg_per_hour_all_mc = (total_kg_hour_all_mc - pure_yarn_kg_per_hour_all_mc)
required_bond_kg_month = (bond_kg_per_hour_all_mc / total_kg_hour_all_mc) * total_kg_month if total_kg_hour_all_mc > 0 else 0
current_yarn_stock = _safe_float_conversion(yarn_data.get("stock"), 0.0)
purchase_order_yarn_kg = max(0, required_yarn_kg_month - current_yarn_stock)
current_bond_stock = 0.0
purchase_order_bond_kg = 0.0
if bond_inventory_id and bond_inventory_id != 'null':
bond_data_wrapper = get_inventory_details_from_db(bond_inventory_id)
if bond_data_wrapper and "inventory" in bond_data_wrapper:
current_bond_stock = _safe_float_conversion(bond_data_wrapper["inventory"].get("stock"), 0.0)
purchase_order_bond_kg = max(0, required_bond_kg_month - current_bond_stock)
total_yarn_purchase_cost = required_yarn_kg_month * yarn_unit_cost
total_bond_purchase_cost = required_bond_kg_month * bond_unit_cost
total_raw_material_purchase_cost = total_yarn_purchase_cost + total_bond_purchase_cost
analysis_result.update({
"production_kg_per_hour": total_kg_hour_all_mc,
"production_kg_per_day": total_kg_day_all_mc,
"production_ton_per_month": total_kg_month / 1000,
"total_product_kg_per_month": total_kg_month,
"pure_processing_cost_per_kg": pure_processing_cost_kg,
"calculated_profit_margin_per_kg": calculated_profit_margin_kg,
"processing_cost_per_kg_total": pure_processing_cost_kg + calculated_profit_margin_kg,
"current_yarn_stock": current_yarn_stock,
"required_yarn_kg": required_yarn_kg_month,
"purchase_order_kg": purchase_order_yarn_kg,
"yarn_unit_cost": yarn_unit_cost,
"current_bond_stock": current_bond_stock,
"required_bond_kg": required_bond_kg_month,
"purchase_order_bond_kg": purchase_order_bond_kg,
"bond_unit_cost": bond_unit_cost,
"total_monthly_expenses": monthly_total_expenses,
"monthly_power_cost": monthly_power_cost,
"total_monthly_kwh": (avg_power_per_machine_kw * num_machines * work_hours_day * work_days_month),
"monthly_labor_cost": monthly_labor_cost_total,
"monthly_meal_cost": (num_people_needed * meal_cost_day * work_days_month),
"monthly_packaging_cost": monthly_packaging_cost,
"monthly_depreciation_cost": monthly_depreciation_cost_total,
"monthly_rent_cost": monthly_rent,
"monthly_general_admin_cost_setting": monthly_admin,
"monthly_delivery_cost": monthly_delivery,
"total_revenue": total_raw_material_purchase_cost + ((pure_processing_cost_kg + calculated_profit_margin_kg) * total_kg_month)
})
# 2. 목표 생산량 분석 (요청 시 추가 수행)
if target_production_quantity and target_production_quantity > 0:
total_work_hours_target = target_production_quantity / (total_kg_hour_all_mc if total_kg_hour_all_mc > 0 else 1)
work_days_target = total_work_hours_target / work_hours_day
required_yarn_kg_target = (pure_yarn_kg_per_hour_all_mc / total_kg_hour_all_mc) * target_production_quantity if total_kg_hour_all_mc > 0 else 0
purchase_order_yarn_kg_target = max(0, required_yarn_kg_target - current_yarn_stock)
required_bond_kg_target = (bond_kg_per_hour_all_mc / total_kg_hour_all_mc) * target_production_quantity if total_kg_hour_all_mc > 0 else 0
purchase_order_bond_kg_target = max(0, required_bond_kg_target - current_bond_stock)
plan_yarn_cost = required_yarn_kg_target * yarn_unit_cost
plan_bond_cost = required_bond_kg_target * bond_unit_cost
plan_total_material_cost = plan_yarn_cost + plan_bond_cost
plan_net_processing_cost = hourly_net_cost_per_machine * num_machines * total_work_hours_target
plan_company_margin = hourly_profit_per_machine * num_machines * total_work_hours_target
plan_processing_fee = plan_net_processing_cost + plan_company_margin
plan_estimated_sales = plan_processing_fee + plan_total_material_cost
analysis_result.update({
"required_days_target": work_days_target,
"current_yarn_stock_target": current_yarn_stock,
"required_yarn_kg_target": required_yarn_kg_target,
"purchase_order_kg_target": purchase_order_yarn_kg_target,
"current_bond_stock_target": current_bond_stock,
"required_bond_kg_target": required_bond_kg_target,
"purchase_order_bond_kg_target": purchase_order_bond_kg_target,
"plan_yarn_cost": plan_yarn_cost,
"plan_bond_cost": plan_bond_cost,
"plan_total_material_cost": plan_total_material_cost,
"plan_net_processing_cost": plan_net_processing_cost,
"plan_company_margin": plan_company_margin,
"plan_processing_fee": plan_processing_fee,
"plan_estimated_sales": plan_estimated_sales
})
analysis_result.update({
"hourly_net_cost_per_machine": hourly_net_cost_per_machine,
"hourly_profit_per_machine": hourly_profit_per_machine
})
return analysis_result
+237
View File
@@ -0,0 +1,237 @@
import mysql.connector
from database.connection import get_db_connection
import logging
# 물성 정보 추가
def add_material_property(data):
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
sql = """
INSERT INTO material_properties (material_code, material_name, material_type, density, remarks)
VALUES (%s, %s, %s, %s, %s)
"""
cursor.execute(sql, (
data.get('material_code'),
data.get('material_name'),
data.get('material_type'),
data.get('density'),
data.get('remarks')
))
conn.commit()
return cursor.lastrowid
except mysql.connector.Error as err:
logging.error(f"Error adding material property: {err}")
return None
# 물성 정보 수정
def update_material_property(data):
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
sql = """
UPDATE material_properties SET
material_code = %s,
material_name = %s,
material_type = %s,
density = %s,
remarks = %s
WHERE id = %s
"""
cursor.execute(sql, (
data.get('material_code'),
data.get('material_name'),
data.get('material_type'),
data.get('density'),
data.get('remarks'),
data.get('id')
))
conn.commit()
return True
except mysql.connector.Error as err:
logging.error(f"Error updating material property: {err}")
return False
# 물성 정보 삭제
def delete_material_property(material_id):
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
cursor.execute("SELECT COUNT(*) as count FROM inventory WHERE material_id = %s", (material_id,))
if cursor.fetchone()['count'] > 0:
logging.warning(f"Cannot delete material property {material_id} as it is in use by inventory.")
return False
cursor.execute("DELETE FROM material_properties WHERE id = %s", (material_id,))
conn.commit()
return True
except mysql.connector.Error as err:
logging.error(f"Error deleting material property: {err}")
return False
# 모든 물성 정보 조회
def get_all_material_properties():
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
cursor.execute("SELECT * FROM material_properties")
return cursor.fetchall()
except mysql.connector.Error as err:
logging.error(f"Error getting all material properties: {err}")
return []
# ID로 특정 물성 정보 조회
def get_material_property_by_id(material_id):
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
cursor.execute("SELECT * FROM material_properties WHERE id = %s", (material_id,))
return cursor.fetchone()
except mysql.connector.Error as err:
logging.error(f"Error getting material property by id: {err}")
return None
# 재고 입고 (추가)
def add_inventory_to_db(data):
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
stock = data.get('receipt_quantity', 0) - data.get('usage_quantity', 0)
sql = """
INSERT INTO inventory (material_id, receipt_date, receipt_quantity, usage_quantity, stock, unit_cost, remarks, is_depleted, supplier_id, item_name, item_number)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
cursor.execute(sql, (
data.get('material_id'),
data.get('receipt_date'),
data.get('receipt_quantity'),
data.get('usage_quantity', 0),
stock,
data.get('unit_cost'),
data.get('remarks'),
1 if stock <= 0 else 0,
data.get('supplier_id'),
data.get('item_name'),
data.get('item_number')
))
conn.commit()
return cursor.lastrowid
except mysql.connector.Error as err:
logging.error(f"Error adding inventory: {err}")
return None
# 재고 정보 수정
def update_inventory_in_db(data):
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
stock = data.get('receipt_quantity', 0) - data.get('usage_quantity', 0)
sql = """
UPDATE inventory SET
material_id = %s,
receipt_date = %s,
receipt_quantity = %s,
usage_quantity = %s,
stock = %s,
unit_cost = %s,
remarks = %s,
is_depleted = %s,
supplier_id = %s,
item_name = %s,
item_number = %s
WHERE id = %s
"""
cursor.execute(sql, (
data.get('material_id'),
data.get('receipt_date'),
data.get('receipt_quantity'),
data.get('usage_quantity'),
stock,
data.get('unit_cost'),
data.get('remarks'),
1 if stock <= 0 else 0,
data.get('supplier_id'),
data.get('item_name'),
data.get('item_number'),
data.get('id')
))
conn.commit()
return True
except mysql.connector.Error as err:
logging.error(f"Error updating inventory: {err}")
return False
# 재고 소진 처리
def deplete_inventory_in_db(inventory_id):
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
sql = "UPDATE inventory SET is_depleted = 1, stock = 0 WHERE id = %s"
cursor.execute(sql, (inventory_id,))
conn.commit()
return True
except mysql.connector.Error as err:
logging.error(f"Error depleting inventory: {err}")
return False
# 모든 재고 목록 조회
def get_all_inventory_from_db():
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
sql = """
SELECT
i.id, i.material_id, i.receipt_date, i.receipt_quantity, i.usage_quantity,
i.stock, i.unit_cost, i.remarks, i.is_depleted, i.supplier_id,
i.item_name, i.item_number,
mp.material_code, mp.material_name, mp.material_type, mp.density,
s.name_kr as supplier
FROM inventory i
LEFT JOIN material_properties mp ON i.material_id = mp.id
LEFT JOIN suppliers s ON i.supplier_id = s.id
ORDER BY i.receipt_date DESC
"""
cursor.execute(sql)
return cursor.fetchall()
except mysql.connector.Error as err:
logging.error(f"Error getting all inventory: {err}")
return []
# 특정 재고 상세 정보 조회
def get_inventory_details_from_db(inventory_id):
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
sql = """
SELECT
i.*,
mp.material_code, mp.material_name, mp.material_type, mp.density,
s.name_kr as supplier_name
FROM inventory i
LEFT JOIN material_properties mp ON i.material_id = mp.id
LEFT JOIN suppliers s ON i.supplier_id = s.id
WHERE i.id = %s
"""
cursor.execute(sql, (inventory_id,))
inventory_data = cursor.fetchone()
if not inventory_data:
return None
return {
"inventory": inventory_data
}
except mysql.connector.Error as err:
logging.error(f"Error getting inventory details: {err}")
return None
# 특정 자재의 총 재고량 조회
def get_total_stock_for_material(material_id):
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
sql = "SELECT SUM(stock) as total_stock FROM inventory WHERE material_id = %s AND is_depleted = 0"
cursor.execute(sql, (material_id,))
result = cursor.fetchone()
return result['total_stock'] if result and result['total_stock'] is not None else 0
except mysql.connector.Error as err:
logging.error(f"Error getting total stock for material: {err}")
return 0
+144
View File
@@ -0,0 +1,144 @@
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field
from typing import List, Optional
from .db_manager import (
add_material_property,
update_material_property,
delete_material_property,
get_all_material_properties,
get_material_property_by_id,
add_inventory_to_db,
update_inventory_in_db,
deplete_inventory_in_db,
get_all_inventory_from_db,
get_inventory_details_from_db
)
from .analysis import calculate_cost_analysis
router = APIRouter(prefix="/inventory", tags=["inventory"])
# --- Pydantic Schemas ---
class MaterialPropertySchema(BaseModel):
id: Optional[int] = None
material_code: str
material_name: str
material_type: str
density: float
remarks: Optional[str] = None
class InventorySchema(BaseModel):
id: Optional[int] = None
material_id: int
receipt_date: str # YYYY-MM-DD
receipt_quantity: float
usage_quantity: Optional[float] = 0.0
stock: Optional[float] = None
unit_cost: float
remarks: Optional[str] = None
is_depleted: Optional[int] = 0
supplier_id: Optional[int] = None
item_name: Optional[str] = None
item_number: Optional[str] = None
class AnalysisInputSchema(BaseModel):
yarn_inventory_id: int
bond_inventory_id: Optional[int] = None
yarn_diameter_micron: Optional[float] = 7.0
yarn_k: Optional[float] = 12.0
ports: Optional[float] = 40.0
cycle_time_hz: Optional[float] = 8.0
cut_length_mm: Optional[float] = 6.0
work_hours_day: Optional[float] = 16.0
work_days_month: Optional[float] = 20.0
num_machines: Optional[float] = 2.0
bond_percentage: Optional[float] = 3.0
class CostAnalysisRequestSchema(BaseModel):
inputs: AnalysisInputSchema
targetProductionQuantity: Optional[float] = None
# --- Material Properties APIs ---
@router.post("/materials", response_model=dict)
def create_material_property(material: MaterialPropertySchema):
result = add_material_property(material.dict())
if result is None:
raise HTTPException(status_code=500, detail="Failed to create material property")
return {"id": result, "status": "success"}
@router.put("/materials/{material_id}", response_model=dict)
def update_material(material_id: int, material: MaterialPropertySchema):
data = material.dict()
data['id'] = material_id
success = update_material_property(data)
if not success:
raise HTTPException(status_code=500, detail="Failed to update material property")
return {"status": "success"}
@router.delete("/materials/{material_id}", response_model=dict)
def delete_material(material_id: int):
success = delete_material_property(material_id)
if not success:
raise HTTPException(status_code=400, detail="Failed to delete material property. Check if it's in use.")
return {"status": "success"}
@router.get("/materials", response_model=List[MaterialPropertySchema])
def list_material_properties():
return get_all_material_properties()
@router.get("/materials/{material_id}", response_model=MaterialPropertySchema)
def get_material_property(material_id: int):
material = get_material_property_by_id(material_id)
if not material:
raise HTTPException(status_code=404, detail="Material property not found")
return material
# --- Inventory APIs ---
@router.post("/", response_model=dict)
def create_inventory(inventory: InventorySchema):
result = add_inventory_to_db(inventory.dict())
if result is None:
raise HTTPException(status_code=500, detail="Failed to create inventory record")
return {"id": result, "status": "success"}
@router.put("/{inventory_id}", response_model=dict)
def update_inventory(inventory_id: int, inventory: InventorySchema):
data = inventory.dict()
data['id'] = inventory_id
success = update_inventory_in_db(data)
if not success:
raise HTTPException(status_code=500, detail="Failed to update inventory record")
return {"status": "success"}
@router.put("/{inventory_id}/deplete", response_model=dict)
def deplete_inventory(inventory_id: int):
success = deplete_inventory_in_db(inventory_id)
if not success:
raise HTTPException(status_code=500, detail="Failed to deplete inventory")
return {"status": "success"}
@router.get("/", response_model=List[dict])
def list_inventory():
return get_all_inventory_from_db()
@router.get("/{inventory_id}", response_model=dict)
def get_inventory_details(inventory_id: int):
details = get_inventory_details_from_db(inventory_id)
if not details:
raise HTTPException(status_code=404, detail="Inventory details not found")
return details
# --- Cost Analysis APIs ---
@router.post("/analysis/calculate", response_model=dict)
def cost_analysis(request_data: CostAnalysisRequestSchema):
try:
inputs_dict = request_data.inputs.dict()
# Null 문자열 파라미터 방어용 가공
if not inputs_dict.get("bond_inventory_id"):
inputs_dict["bond_inventory_id"] = "null"
result = calculate_cost_analysis(inputs_dict, request_data.targetProductionQuantity)
return result
except ValueError as val_err:
raise HTTPException(status_code=400, detail=str(val_err))
except Exception as exc:
raise HTTPException(status_code=500, detail=f"Internal calculations failed: {exc}")