From b1f21c8c482c0fec2ce928cfe3796b56ca4f1899 Mon Sep 17 00:00:00 2001 From: Cizz22 Date: Wed, 15 Jan 2025 11:49:41 +0700 Subject: [PATCH] add new endpoint --- src/api.py | 5 ++ src/job/model.py | 20 +++--- src/overhaul_activity/schema.py | 3 +- src/overhaul_activity/service.py | 106 ++++++++++++++++++++-------- src/overhaul_activity/utils.py | 33 +++++++++ src/scope_equipment_job/__init__.py | 0 src/scope_equipment_job/model.py | 22 ++++++ src/scope_equipment_job/router.py | 41 +++++++++++ src/scope_equipment_job/schema.py | 70 ++++++++++++++++++ src/scope_equipment_job/service.py | 94 ++++++++++++++++++++++++ 10 files changed, 353 insertions(+), 41 deletions(-) create mode 100644 src/overhaul_activity/utils.py create mode 100644 src/scope_equipment_job/__init__.py create mode 100644 src/scope_equipment_job/model.py create mode 100644 src/scope_equipment_job/router.py create mode 100644 src/scope_equipment_job/schema.py create mode 100644 src/scope_equipment_job/service.py diff --git a/src/api.py b/src/api.py index 9305800..293737b 100644 --- a/src/api.py +++ b/src/api.py @@ -25,6 +25,7 @@ from src.scope_equipment.router import router as scope_equipment_router from src.overhaul_scope.router import router as scope_router from src.overhaul_activity.router import router as overhaul_activity_router from src.calculation_target_reliability.router import router as calculation_target_reliability +from src.scope_equipment_job.router import router as scope_equipment_job_router class ErrorMessage(BaseModel): msg: str @@ -70,6 +71,10 @@ authenticated_api_router.include_router( overhaul_activity_router, prefix="/overhaul-activity", tags=["activity"] ) +authenticated_api_router.include_router( + scope_equipment_job_router, prefix="/scope-equipment-jobs" +) + # authenticated_api_router.include_router( # overhaul_history_router, prefix="/overhaul-history", tags=["overhaul_history"] # ) diff --git a/src/job/model.py b/src/job/model.py index fd375f9..20452b2 100644 --- a/src/job/model.py +++ b/src/job/model.py @@ -8,21 +8,21 @@ from sqlalchemy.ext.hybrid import hybrid_property class MasterActivity(Base, DefaultMixin): - __tablename__ = "oh_ms_activity" + __tablename__ = "oh_ms_job" name = Column(String, nullable=False) - details = relationship( - "MasterActivityDetail", - lazy="raise", - primaryjoin="and_(MasterActivity.id == foreign(MasterActivityDetail.activity_id))", - ) + # details = relationship( + # "MasterActivityDetail", + # lazy="raise", + # primaryjoin="and_(MasterActivity.id == foreign(MasterActivityDetail.activity_id))", + # ) -class MasterActivityDetail(Base, DefaultMixin): - __tablename__ = "oh_ms_activity_detail" +# class MasterActivityDetail(Base, DefaultMixin): +# __tablename__ = "oh_ms_activity_detail" - name = Column(String, nullable=False) - activity_id = Column(UUID(as_uuid=True)) +# name = Column(String, nullable=False) +# activity_id = Column(UUID(as_uuid=True)) diff --git a/src/overhaul_activity/schema.py b/src/overhaul_activity/schema.py index 4a01fc8..315e1a4 100644 --- a/src/overhaul_activity/schema.py +++ b/src/overhaul_activity/schema.py @@ -13,8 +13,7 @@ class OverhaulActivityBase(DefultBase): class OverhaulActivityCreate(OverhaulActivityBase): - material_cost: Optional[float] = Field(0) - service_cost: Optional[float] = Field(0) + assetnums: List[str] class OverhaulActivityUpdate(OverhaulActivityBase): diff --git a/src/overhaul_activity/service.py b/src/overhaul_activity/service.py index 8a98bcf..1e1ca9e 100644 --- a/src/overhaul_activity/service.py +++ b/src/overhaul_activity/service.py @@ -1,11 +1,13 @@ +import asyncio from uuid import UUID -from sqlalchemy import Select, Delete +from sqlalchemy import Select, Delete, func, insert, select, update as sqlUpdate from sqlalchemy.orm import joinedload from typing import List, Optional -from src.scope_equipment.model import ScopeEquipment +from src.overhaul_activity.utils import get_material_cost, get_service_cost +from src.overhaul_scope.model import OverhaulScope from .model import OverhaulActivity from .schema import OverhaulActivityCreate, OverhaulActivityUpdate, OverhaulActivityRead @@ -13,6 +15,7 @@ from .schema import OverhaulActivityCreate, OverhaulActivityUpdate, OverhaulActi from src.database.core import DbSession from src.database.service import CommonParameters, search_filter_sort_paginate from src.auth.service import CurrentUser +from src.overhaul_scope.service import get as get_session async def get(*, db_session: DbSession, assetnum: str, overhaul_session_id: Optional[UUID] = None) -> Optional[OverhaulActivityRead]: @@ -45,38 +48,83 @@ async def get_all(*, common: CommonParameters, overhaul_session_id: UUID, assetn return results +# async def create(*, db_session: DbSession, overhaul_activty_in: OverhaulActivityCreate, overhaul_session_id: UUID): +# # Check if the combination of assetnum and activity_id already exists +# existing_equipment_query = ( +# Select(OverhaulActivity) +# .where( +# OverhaulActivity.assetnum == overhaul_activty_in.assetnum, +# OverhaulActivity.overhaul_scope_id == overhaul_session_id +# ) +# ) +# result = await db_session.execute(existing_equipment_query) +# existing_activity = result.scalar_one_or_none() + +# # If the combination exists, raise an exception or return the existing activity +# if existing_activity: +# raise ValueError("This assetnum already exist.") + +# activity = OverhaulActivity( +# **overhaul_activty_in.model_dump(), +# overhaul_scope_id=overhaul_session_id) +# db_session.add(activity) +# await db_session.commit() + +# # Refresh and load relationships using joinedload +# query = ( +# Select(OverhaulActivity) +# .options(joinedload(OverhaulActivity.equipment)) +# .where(OverhaulActivity.id == activity.id) +# ) +# result = await db_session.execute(query) +# activity_with_relationship = result.scalar_one() + +# return activity_with_relationship + async def create(*, db_session: DbSession, overhaul_activty_in: OverhaulActivityCreate, overhaul_session_id: UUID): - # Check if the combination of assetnum and activity_id already exists - existing_equipment_query = ( - Select(OverhaulActivity) - .where( - OverhaulActivity.assetnum == overhaul_activty_in.assetnum, - OverhaulActivity.overhaul_scope_id == overhaul_session_id + """Creates a new document.""" + assetnums = overhaul_activty_in.assetnums + if not assetnums: + return [] + + # Get session and count in parallel + session, equipment_count = await asyncio.gather( + get_session(db_session=db_session, overhaul_session_id=overhaul_session_id), + db_session.scalar( + select(func.count()) + .select_from(OverhaulActivity) + .where(OverhaulActivity.overhaul_scope_id == overhaul_session_id) ) ) - result = await db_session.execute(existing_equipment_query) - existing_activity = result.scalar_one_or_none() - - # If the combination exists, raise an exception or return the existing activity - if existing_activity: - raise ValueError("This assetnum already exist.") - activity = OverhaulActivity( - **overhaul_activty_in.model_dump(), - overhaul_scope_id=overhaul_session_id) - db_session.add(activity) - await db_session.commit() - - # Refresh and load relationships using joinedload - query = ( - Select(OverhaulActivity) - .options(joinedload(OverhaulActivity.equipment)) - .where(OverhaulActivity.id == activity.id) + # Calculate costs for all records + total_equipment = equipment_count + len(assetnums) + material_cost = get_material_cost(scope=session.type, total_equipment=total_equipment) + service_cost = get_service_cost(scope=session.type, total_equipment=total_equipment) + + # Bulk create new activities + overhaul_activities = [ + OverhaulActivity( + assetnum=assetnum, + overhaul_scope_id=overhaul_session_id, + material_cost=material_cost, + service_cost=service_cost + ) + for assetnum in assetnums + ] + + # Add new records and update all existing records in parallel + await asyncio.gather( + db_session.add_all(overhaul_activities), + db_session.execute( + update(OverhaulActivity) + .where(OverhaulActivity.overhaul_scope_id == overhaul_session_id) + .values(material_cost=material_cost, service_cost=service_cost) + ) ) - result = await db_session.execute(query) - activity_with_relationship = result.scalar_one() - - return activity_with_relationship + + await db_session.commit() + return assetnums async def update(*, db_session: DbSession, activity: OverhaulActivity, overhaul_activity_in: OverhaulActivityUpdate): diff --git a/src/overhaul_activity/utils.py b/src/overhaul_activity/utils.py new file mode 100644 index 0000000..6675945 --- /dev/null +++ b/src/overhaul_activity/utils.py @@ -0,0 +1,33 @@ +from decimal import Decimal, getcontext + +def get_material_cost(scope, total_equipment): + # Set precision to 28 digits (maximum precision for Decimal) + getcontext().prec = 28 + + if not total_equipment: # Guard against division by zero + return float(0) + + if scope == 'B': + result = Decimal('365539731101') / Decimal(str(total_equipment)) + return float(result) + else: + result = Decimal('8565468127') / Decimal(str(total_equipment)) + return float(result) + + return float(0) + +def get_service_cost(scope, total_equipment): + # Set precision to 28 digits (maximum precision for Decimal) + getcontext().prec = 28 + + if not total_equipment: # Guard against division by zero + return float(0) + + if scope == 'B': + result = Decimal('36405830225') / Decimal(str(total_equipment)) + return float(result) + else: + result = Decimal('36000000000') / Decimal(str(total_equipment)) + return float(result) + + return float(0) \ No newline at end of file diff --git a/src/scope_equipment_job/__init__.py b/src/scope_equipment_job/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/scope_equipment_job/model.py b/src/scope_equipment_job/model.py new file mode 100644 index 0000000..cba145b --- /dev/null +++ b/src/scope_equipment_job/model.py @@ -0,0 +1,22 @@ + +from sqlalchemy import UUID, Column, Float, Integer, String, ForeignKey +from src.database.core import Base +from src.models import DefaultMixin, IdentityMixin, TimeStampMixin +from sqlalchemy.orm import relationship +from src.workorder.model import MasterWorkOrder +from sqlalchemy.ext.hybrid import hybrid_property + + +class ScopeEquipmentJob(Base, DefaultMixin): + __tablename__ = "oh_mas_scope_equipment_job" + + assetnum = Column(String, nullable=False) + job_id = Column(UUID(as_uuid=True), ForeignKey( + "oh_ms_job.id", ondelete="cascade")) + + master_equipments = relationship( + "MasterEquipment", lazy="raise", primaryjoin="and_(ScopeEquipmentJob.assetnum == foreign(MasterEquipment.assetnum))", uselist=False) + + job = relationship( + "MasterActivity", lazy="raise" + ) diff --git a/src/scope_equipment_job/router.py b/src/scope_equipment_job/router.py new file mode 100644 index 0000000..9f65009 --- /dev/null +++ b/src/scope_equipment_job/router.py @@ -0,0 +1,41 @@ + +from typing import Dict, List +from fastapi import APIRouter, HTTPException, Query, status + +from .service import get_all, delete, create +from .schema import ScopeEquipmentJobCreate, ScopeEquipmentJobPagination + +from src.models import StandardResponse +from src.database.service import CommonParameters, search_filter_sort_paginate, DbSession + +router = APIRouter() + + +@router.get("/{assetnum}", response_model=StandardResponse[ScopeEquipmentJobPagination]) +async def get_scope_equipment_jobs(db_session: DbSession, assetnum, common: CommonParameters): + """Get all scope activity pagination.""" + # return + data = await get_all(db_session=db_session, assetnum=assetnum, common=common) + + return StandardResponse( + data=data, + message="Data retrieved successfully", + ) + + +@router.post("/{assetnum}", response_model=StandardResponse[None]) +async def create_scope_equipment_jobs(db_session: DbSession, assetnum, scope_job_in: ScopeEquipmentJobCreate): + """Get all scope activity pagination.""" + # return + await create(db_session=db_session, assetnum=assetnum, scope_job_in=scope_job_in) + + return StandardResponse( + data=None, + message="Data created successfully", + ) + + +@router.delete("/{assetnum}", response_model=StandardResponse[None]) +async def delete_scope_equipment_job(db_session: DbSession, assetnum, scope_job_id): + + await delete(db_session=db_session, assetnum=assetnum, scope_job_id=scope_job_id) diff --git a/src/scope_equipment_job/schema.py b/src/scope_equipment_job/schema.py new file mode 100644 index 0000000..26b4771 --- /dev/null +++ b/src/scope_equipment_job/schema.py @@ -0,0 +1,70 @@ + +from datetime import datetime +from typing import Any, Dict, List, Optional +from uuid import UUID + +from pydantic import Field, BaseModel +from src.job.schema import ActivityMasterRead +from src.models import DefultBase, Pagination + + +class ScopeEquipmentJobBase(DefultBase): + assetnum: str = Field(..., description="Assetnum is required") + + +class ScopeEquipmentJobCreate(ScopeEquipmentJobBase): + job_ids: Optional[List[UUID]] = [] + + +class ScopeEquipmentJobUpdate(ScopeEquipmentJobBase): + name: Optional[str] = Field(None) + cost: Optional[str] = Field(0) + + +class ScopeEquipmentJobRead(ScopeEquipmentJobBase): + id: UUID + assetnum: str + job: ActivityMasterRead + + +class ScopeEquipmentJobPagination(Pagination): + items: List[ScopeEquipmentJobRead] = [] + + +# { +# "overview": { +# "totalEquipment": 30, +# "nextSchedule": { +# "date": "2025-01-12", +# "Overhaul": "B", +# "equipmentCount": 30 +# } +# }, +# "criticalParts": [ +# "Boiler feed pump", +# "Boiler reheater system", +# "Drum Level (Right) Root Valve A", +# "BCP A Discharge Valve", +# "BFPT A EXH Press HI Root VLV" +# ], +# "schedules": [ +# { +# "date": "2025-01-12", +# "Overhaul": "B", +# "status": "upcoming" +# } +# // ... other scheduled overhauls +# ], +# "systemComponents": { +# "boiler": { +# "status": "operational", +# "lastOverhaul": "2024-06-15" +# }, +# "turbine": { +# "hpt": { "status": "operational" }, +# "ipt": { "status": "operational" }, +# "lpt": { "status": "operational" } +# } +# // ... other major components +# } +# } diff --git a/src/scope_equipment_job/service.py b/src/scope_equipment_job/service.py new file mode 100644 index 0000000..6ce245b --- /dev/null +++ b/src/scope_equipment_job/service.py @@ -0,0 +1,94 @@ + + +import random +from sqlalchemy import Select, Delete, and_ +from sqlalchemy.orm import selectinload +from typing import Optional + +from src.scope_equipment.model import MasterEquipment, MasterEquipmentTree +from src.scope_equipment.service import get_equipment_level_by_no + +from .model import ScopeEquipmentJob +from .schema import ScopeEquipmentJobCreate + +from src.database.core import DbSession +from src.database.service import CommonParameters, search_filter_sort_paginate +from src.auth.service import CurrentUser + + +# async def get(*, db_session: DbSession, scope_equipment_activity_id: str) -> Optional[ScopeEquipmentActivity]: +# """Returns a document based on the given document id.""" +# result = await db_session.get(ScopeEquipmentActivity, scope_equipment_activity_id) +# return result + + +async def get_all(db_session: DbSession, assetnum: Optional[str], common): + # Example usage + if not assetnum: + raise ValueError("assetnum parameter is required") + + # First get the parent equipment + equipment_stmt = Select(MasterEquipment).where( + MasterEquipment.assetnum == assetnum) + equipment: MasterEquipment = await db_session.scalar(equipment_stmt) + + if not equipment: + raise ValueError(f"No equipment found with assetnum: {assetnum}") + + # Build query for parts + stmt = ( + Select(ScopeEquipmentJob) + .where( + ScopeEquipmentJob.assetnum == assetnum + ).options(selectinload(ScopeEquipmentJob.job)) + ) + + results = await search_filter_sort_paginate(model=stmt, **common) + + return results + + +async def create(*, db_session: DbSession, assetnum, scope_job_in: ScopeEquipmentJobCreate): + scope_jobs = [] + + if not assetnum: + raise ValueError("assetnum parameter is required") + + # First get the parent equipment + equipment_stmt = Select(MasterEquipment).where( + MasterEquipment.assetnum == assetnum) + equipment: MasterEquipment = await db_session.scalar(equipment_stmt) + + if not equipment: + raise ValueError(f"No equipment found with assetnum: {assetnum}") + + for job_id in scope_job_in.job_ids: + scope_equipment_job = ScopeEquipmentJob( + assetnum=assetnum, job_id=job_id) + scope_jobs.appeand(scope_equipment_job) + + db_session.add_all(scope_jobs) + await db_session.commit() + return + + +# async def update(*, db_session: DbSession, activity: ScopeEquipmentActivity, scope_equipment_activty_in: ScopeEquipmentActivityUpdate): +# """Updates a document.""" +# data = scope_equipment_activty_in.model_dump() + +# update_data = scope_equipment_activty_in.model_dump(exclude_defaults=True) + +# for field in data: +# if field in update_data: +# setattr(activity, field, update_data[field]) + +# await db_session.commit() + +# return activity + + +async def delete(*, db_session: DbSession, assetnum, scope_job_id): + """Deletes a document.""" + activity = await db_session.get(ScopeEquipmentJob, scope_job_id) + await db_session.delete(activity) + await db_session.commit()