diff --git a/src/__pycache__/api.cpython-311.pyc b/src/__pycache__/api.cpython-311.pyc index 11ddfb5..71794e2 100644 Binary files a/src/__pycache__/api.cpython-311.pyc and b/src/__pycache__/api.cpython-311.pyc differ diff --git a/src/acquisition_cost/router.py b/src/acquisition_cost/router.py new file mode 100644 index 0000000..86e201d --- /dev/null +++ b/src/acquisition_cost/router.py @@ -0,0 +1,95 @@ +from typing import Optional +from fastapi import APIRouter, HTTPException, status, Query + +from src.acquisition_cost.model import AcquisitionData +from src.acquisition_cost.schema import AcquisitionCostDataPagination, AcquisitionCostDataRead, AcquisitionCostDataCreate, AcquisitionCostDataUpdate +from src.acquisition_cost.service import get, get_all, create, update, delete + +from src.database.service import CommonParameters, search_filter_sort_paginate +from src.database.core import DbSession +from src.auth.service import CurrentUser +from src.models import StandardResponse + +router = APIRouter() + + +@router.get("", response_model=StandardResponse[AcquisitionCostDataPagination]) +async def get_yeardatas( + db_session: DbSession, + common: CommonParameters, + items_per_page: Optional[int] = Query(5), + search: Optional[str] = Query(None), +): + """Get all acquisition_cost_data pagination.""" + get_acquisition_cost_data = await get_all( + db_session=db_session, + items_per_page=items_per_page, + search=search, + common=common, + ) + # return + return StandardResponse( + data=get_acquisition_cost_data, + message="Data retrieved successfully", + ) + + +@router.get("/{acquisition_cost_data_id}", response_model=StandardResponse[AcquisitionCostDataRead]) +async def get_acquisition_cost_data(db_session: DbSession, acquisition_cost_data_id: str): + acquisition_cost_data = await get(db_session=db_session, acquisition_cost_data_id=acquisition_cost_data_id) + if not acquisition_cost_data: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="A data with this id does not exist.", + ) + + return StandardResponse(data=acquisition_cost_data, message="Data retrieved successfully") + + +@router.post("", response_model=StandardResponse[AcquisitionCostDataRead]) +async def create_acquisition_cost_data( + db_session: DbSession, acquisition_cost_data_in: AcquisitionCostDataCreate, current_user: CurrentUser +): + acquisition_cost_data_in.created_by = current_user.name + acquisition_cost_data = await create(db_session=db_session, acquisition_data_in=acquisition_cost_data_in) + + return StandardResponse(data=acquisition_cost_data, message="Data created successfully") + + +@router.put("/{acquisition_cost_data_id}", response_model=StandardResponse[AcquisitionCostDataRead]) +async def update_acquisition_cost_data( + db_session: DbSession, + acquisition_cost_data_id: str, + acquisition_cost_data_in: AcquisitionCostDataUpdate, + current_user: CurrentUser, +): + acquisition_cost_data = await get(db_session=db_session, acquisition_cost_data_id=acquisition_cost_data_id) + + if not acquisition_cost_data: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="A data with this id does not exist.", + ) + acquisition_cost_data_in.updated_by = current_user.name + + return StandardResponse( + data=await update( + db_session=db_session, acquisition_data=acquisition_cost_data, acquisition_data_in=acquisition_cost_data_in + ), + message="Data updated successfully", + ) + + +@router.delete("/{acquisition_cost_data_id}", response_model=StandardResponse[AcquisitionCostDataRead]) +async def delete_acquisition_cost_data(db_session: DbSession, acquisition_cost_data_id: str): + acquisition_cost_data = await get(db_session=db_session, acquisition_cost_data_id=acquisition_cost_data_id) + + if not acquisition_cost_data: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=[{"msg": "A data with this id does not exist."}], + ) + + await delete(db_session=db_session, acquisition_cost_data_id=acquisition_cost_data_id) + + return StandardResponse(message="Data deleted successfully", data=acquisition_cost_data) diff --git a/src/acquisition_cost/schema.py b/src/acquisition_cost/schema.py new file mode 100644 index 0000000..4c80f8a --- /dev/null +++ b/src/acquisition_cost/schema.py @@ -0,0 +1,33 @@ +from datetime import datetime +from typing import List, Optional +from uuid import UUID + +from pydantic import Field +from src.models import DefaultBase, Pagination + + +class AcquisitionCostDataBase(DefaultBase): + category_no: Optional[str] = Field(None, nullable=True) + name: Optional[str] = Field(None, nullable=True) + cost_unit_3_n_4: Optional[float] = Field(None, nullable=True) + cost_unit_3: Optional[float] = Field(None, nullable=True) + created_at: Optional[datetime] = Field(None, nullable=True) + updated_at: Optional[datetime] = Field(None, nullable=True) + created_by: Optional[str] = Field(None, nullable=True) + updated_by: Optional[str] = Field(None, nullable=True) + + +class AcquisitionCostDataCreate(AcquisitionCostDataBase): + pass + + +class AcquisitionCostDataUpdate(AcquisitionCostDataBase): + pass + + +class AcquisitionCostDataRead(AcquisitionCostDataBase): + id: UUID + + +class AcquisitionCostDataPagination(Pagination): + items: List[AcquisitionCostDataRead] = [] diff --git a/src/acquisition_cost/service.py b/src/acquisition_cost/service.py new file mode 100644 index 0000000..a41dcf0 --- /dev/null +++ b/src/acquisition_cost/service.py @@ -0,0 +1,119 @@ +from sqlalchemy import Select, Delete, cast, String +from src.acquisition_cost.model import AcquisitionData +from src.acquisition_cost.schema import AcquisitionCostDataCreate, AcquisitionCostDataUpdate +from src.database.service import search_filter_sort_paginate +from typing import Optional + +from src.database.core import DbSession +from src.auth.service import CurrentUser +from src.equipment.model import Equipment + + +def _calculate_cost_unit_3(cost_unit_3_n_4: Optional[float]) -> Optional[float]: + """Derive cost_unit_3 by splitting the combined unit 3&4 cost evenly.""" + if cost_unit_3_n_4 is None: + return None + return cost_unit_3_n_4 / 2 + + +async def _sync_equipment_acquisition_costs( + *, db_session: DbSession, category_no: Optional[str], cost_unit_3: Optional[float] +): + """Keep equipment acquisition cost in sync for the affected category.""" + if not category_no or cost_unit_3 is None: + return + + equipment_query = Select(Equipment).filter(Equipment.category_no == category_no) + equipment_result = await db_session.execute(equipment_query) + equipments = equipment_result.scalars().all() + + for equipment in equipments: + if equipment.proportion is None: + continue + equipment.acquisition_cost = (equipment.proportion * 0.01) * cost_unit_3 + + +async def get(*, db_session: DbSession, acquisition_cost_data_id: str) -> Optional[AcquisitionData]: + """Returns a document based on the given document id.""" + query = Select(AcquisitionData).filter(AcquisitionData.id == acquisition_cost_data_id) + result = await db_session.execute(query) + return result.scalars().one_or_none() + + +async def get_all( + *, + db_session: DbSession, + items_per_page: Optional[int], + search: Optional[str] = None, + common, +): + """Returns all documents.""" + query = Select(AcquisitionData).order_by(AcquisitionData.name.asc()) + if search: + query = query.filter(cast(AcquisitionData.name, String).ilike(f"%{search}%")) + + common["items_per_page"] = items_per_page + results = await search_filter_sort_paginate(model=query, **common) + + # return results.scalars().all() + return results + + +async def create(*, db_session: DbSession, acquisition_data_in: AcquisitionCostDataCreate): + """Creates a new document.""" + data = acquisition_data_in.model_dump() + cost_unit_changed = False + + if data.get("cost_unit_3_n_4") is not None: + derived_cost_unit = _calculate_cost_unit_3(data["cost_unit_3_n_4"]) + data["cost_unit_3"] = derived_cost_unit + cost_unit_changed = derived_cost_unit is not None + + acquisition_data = AcquisitionData(**data) + db_session.add(acquisition_data) + + if cost_unit_changed: + await _sync_equipment_acquisition_costs( + db_session=db_session, + category_no=acquisition_data.category_no, + cost_unit_3=acquisition_data.cost_unit_3, + ) + + await db_session.commit() + return acquisition_data + + +async def update( + *, db_session: DbSession, acquisition_data: AcquisitionData, acquisition_data_in: AcquisitionCostDataUpdate +): + """Updates a document.""" + data = acquisition_data_in.model_dump() + update_data = acquisition_data_in.model_dump(exclude_defaults=True) + + cost_unit_changed = False + if "cost_unit_3_n_4" in update_data: + derived_cost_unit = _calculate_cost_unit_3(update_data.get("cost_unit_3_n_4")) + update_data["cost_unit_3"] = derived_cost_unit + cost_unit_changed = derived_cost_unit is not None + + for field in data: + if field in update_data: + setattr(acquisition_data, field, update_data[field]) + + if cost_unit_changed: + await _sync_equipment_acquisition_costs( + db_session=db_session, + category_no=acquisition_data.category_no, + cost_unit_3=acquisition_data.cost_unit_3, + ) + + await db_session.commit() + + return acquisition_data + + +async def delete(*, db_session: DbSession, acquisition_cost_data_id: str): + """Deletes a document.""" + query = Delete(AcquisitionData).where(AcquisitionData.id == acquisition_cost_data_id) + await db_session.execute(query) + await db_session.commit() diff --git a/src/api.py b/src/api.py index 3f33702..e9b38c1 100644 --- a/src/api.py +++ b/src/api.py @@ -12,6 +12,7 @@ from src.masterdata.router import router as masterdata_router from src.plant_masterdata.router import router as plant_masterdata from src.plant_transaction_data.router import router as plant_transaction_data from src.equipment.router import router as equipment_router +from src.acquisition_cost.router import router as acquisition_data_router from src.yeardata.router import router as yeardata_router from src.equipment_master.router import router as equipment_master_router from src.uploaded_file.router import router as uploaded_file_router @@ -72,6 +73,10 @@ authenticated_api_router.include_router( equipment_master_router, prefix="/equipment-master", tags=["equipment_master"] ) +authenticated_api_router.include_router( + acquisition_data_router, prefix="/acquisition-data", tags=["acquisition_cost"] +) + authenticated_api_router.include_router( yeardata_router, prefix="/yeardata", tags=["yeardata"] ) diff --git a/src/equipment/__pycache__/model.cpython-311.pyc b/src/equipment/__pycache__/model.cpython-311.pyc index a8e1524..7b84e7e 100644 Binary files a/src/equipment/__pycache__/model.cpython-311.pyc and b/src/equipment/__pycache__/model.cpython-311.pyc differ diff --git a/src/equipment/__pycache__/router.cpython-311.pyc b/src/equipment/__pycache__/router.cpython-311.pyc index e6f5bd1..a8404e0 100644 Binary files a/src/equipment/__pycache__/router.cpython-311.pyc and b/src/equipment/__pycache__/router.cpython-311.pyc differ diff --git a/src/equipment/__pycache__/schema.cpython-311.pyc b/src/equipment/__pycache__/schema.cpython-311.pyc index 2cb302b..2b051e6 100644 Binary files a/src/equipment/__pycache__/schema.cpython-311.pyc and b/src/equipment/__pycache__/schema.cpython-311.pyc differ diff --git a/src/equipment/__pycache__/service.cpython-311.pyc b/src/equipment/__pycache__/service.cpython-311.pyc index 2c871cd..0c962e3 100644 Binary files a/src/equipment/__pycache__/service.cpython-311.pyc and b/src/equipment/__pycache__/service.cpython-311.pyc differ diff --git a/src/equipment/router.py b/src/equipment/router.py index f2969c9..69bf13a 100644 --- a/src/equipment/router.py +++ b/src/equipment/router.py @@ -3,8 +3,8 @@ from fastapi import APIRouter, HTTPException, status, Query from fastapi.responses import StreamingResponse import json -from .model import Equipment, EquipmentTransactionRecords -from .schema import ( +from src.equipment.model import Equipment, EquipmentTransactionRecords +from src.equipment.schema import ( EquipmentBase, EquipmentPagination, EquipmentRead, diff --git a/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc b/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc index 5cf07ad..8fc5f80 100644 Binary files a/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc and b/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc differ diff --git a/src/modules/equipment/__pycache__/insert_actual_data.cpython-311.pyc b/src/modules/equipment/__pycache__/insert_actual_data.cpython-311.pyc index b36f212..efcca10 100644 Binary files a/src/modules/equipment/__pycache__/insert_actual_data.cpython-311.pyc and b/src/modules/equipment/__pycache__/insert_actual_data.cpython-311.pyc differ