From 8c9f57323071dfd5adf90afa4ce3fd0b12a82a7f Mon Sep 17 00:00:00 2001 From: Cizz22 Date: Thu, 18 Sep 2025 03:40:18 +0700 Subject: [PATCH] fix --- .env | 10 +- src/api.py | 6 +- src/equipment_sparepart/service.py | 116 +++++++++++++------ src/sparepart/router.py | 101 +++++++++-------- src/sparepart/service.py | 174 ++++++++++++++++++++--------- 5 files changed, 264 insertions(+), 143 deletions(-) diff --git a/.env b/.env index 4a76757..2f7024b 100644 --- a/.env +++ b/.env @@ -9,11 +9,9 @@ DATABASE_CREDENTIAL_USER=postgres DATABASE_CREDENTIAL_PASSWORD=postgres DATABASE_NAME=digital_twin -COLLECTOR_HOSTNAME=192.168.1.86 -COLLECTOR_PORT=5432 -COLLECTOR_CREDENTIAL_USER=postgres -COLLECTOR_CREDENTIAL_PASSWORD=postgres +COLLECTOR_HOSTNAME=192.168.1.82 +COLLECTOR_PORT=1111 +COLLECTOR_CREDENTIAL_USER=digital_twin +COLLECTOR_CREDENTIAL_PASSWORD=Pr0jec7@D!g!tTwiN COLLECTOR_NAME=digital_twin - -BACKEND_SERVER= diff --git a/src/api.py b/src/api.py index 011dad3..012f164 100644 --- a/src/api.py +++ b/src/api.py @@ -24,7 +24,7 @@ from src.equipment_workscope_group.router import router as equipment_workscope_g # from src.scope_equipment_job.router import router as scope_equipment_job_router # from src.overhaul_schedule.router import router as overhaul_schedule_router from src.overhaul_gantt.router import router as gantt_router - +from src.sparepart.router import router as sparepart_router # from src.overhaul_scope.router import router as scope_router # from src.scope_equipment.router import router as scope_equipment_router @@ -90,6 +90,10 @@ authenticated_api_router.include_router( workscope_group_router, prefix="/workscopes", tags=["workscope_groups"] ) +authenticated_api_router.include_router( + sparepart_router, prefix="/spareparts", tags=["sparepart"] +) + authenticated_api_router.include_router( equipment_workscope_group_router, prefix="/equipment-workscopes", diff --git a/src/equipment_sparepart/service.py b/src/equipment_sparepart/service.py index 412f5e7..aa2c82c 100644 --- a/src/equipment_sparepart/service.py +++ b/src/equipment_sparepart/service.py @@ -1,7 +1,7 @@ import random from typing import Optional -from sqlalchemy import Delete, Select, and_ +from sqlalchemy import Delete, Select, and_, text from sqlalchemy.orm import selectinload from src.auth.service import CurrentUser @@ -37,39 +37,87 @@ def create_dummy_parts(assetnum: str, count: int = 5): return parts -async def get_all(db_session: DbSession, assetnum: Optional[str]): - # Example usage - dummy_parts = create_dummy_parts(assetnum, count=10) - - # if not assetnum: - # raise ValueError("assetnum parameter is required") - - # db_session: DbSession = common.get("db_session") - - # # 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(ScopeEquipmentPart) - # .join(ScopeEquipmentPart.master_equipments) - # .join(MasterEquipment.equipment_tree) - # .where( - # and_( - # MasterEquipment.parent_id == equipment.id, - # MasterEquipmentTree.level_no == 4 - # ) - # ).options(selectinload(ScopeEquipmentPart.master_equipments)) - # ) - - # results = await search_filter_sort_paginate(model=stmt, **common) - - return dummy_parts +async def get_all(db_session: DbSession, assetnum: Optional[str] = None): + """ + Get all spare parts with their latest PR and PO information. + + Args: + db_session: SQLAlchemy database session + assetnum: Optional asset number filter (not used in this query but kept for compatibility) + + Returns: + List of dictionaries containing spare part information + """ + # Define the SQL query + query = text(""" + WITH latest_prs AS ( + SELECT DISTINCT ON (pl.item_num) + pl.item_num, + h.num as pr_number, + h.issue_date as pr_issue_date, + h.status as pr_status, + pl.qty_ordered as pr_qty_ordered + FROM public.maximo_sparepart_pr_po h + JOIN public.maximo_sparepart_pr_po_line pl ON h.num = pl.num + WHERE h.type = 'PR' + AND h.issue_date IS NOT NULL + AND h.num LIKE 'K%' + ORDER BY pl.item_num, TO_DATE(h.issue_date, 'YYYY-MM-DD') DESC + ), + po_details AS ( + SELECT + h.num as po_number, + pl.item_num, + h.estimated_arrival_date, + pl.qty_received, + pl.qty_ordered + FROM public.maximo_sparepart_pr_po h + JOIN public.maximo_sparepart_pr_po_line pl ON h.num = pl.num + WHERE h.type = 'PO' + ) + SELECT + pr.item_num, + MAX(l.description) as description, + COALESCE(MAX(i.curbaltotal), 0) as current_balance_total, + pr.pr_number, + pr.pr_issue_date, + pr.pr_qty_ordered, + CASE + WHEN po.po_number IS NOT NULL THEN 'YES' + ELSE 'NO' + END as po_exists, + COALESCE(po.qty_received, 0) as po_qty_received, + COALESCE(po.qty_ordered, 0) as po_qty_ordered, + COALESCE(po.estimated_arrival_date, '') as po_estimated_arrival_date + FROM latest_prs pr + LEFT JOIN public.maximo_sparepart_pr_po_line l ON pr.item_num = l.item_num + LEFT JOIN public.maximo_inventory i ON pr.item_num = i.itemnum + LEFT JOIN po_details po ON po.po_number = pr.pr_number AND po.item_num = pr.item_num + GROUP BY pr.item_num, pr.pr_number, pr.pr_issue_date, pr.pr_qty_ordered, + po.po_number, po.qty_received, po.qty_ordered, po.estimated_arrival_date + ORDER BY pr.item_num + """) + + # Execute the query + result = await db_session.execute(query) + + # Fetch all results and convert to list of dictionaries + spare_parts = [] + for row in result: + spare_parts.append({ + "item_num": row.item_num, + "description": row.description, + "current_balance_total": float(row.current_balance_total) if row.current_balance_total is not None else 0.0, + "pr_number": row.pr_number, + "pr_issue_date": row.pr_issue_date, + "pr_qty_ordered": float(row.pr_qty_ordered) if row.pr_qty_ordered is not None else 0.0, + "po_exists": row.po_exists, + "po_qty_received": float(row.po_qty_received) if row.po_qty_received is not None else 0.0, + "po_qty_ordered": float(row.po_qty_ordered) if row.po_qty_ordered is not None else 0.0, + "po_estimated_arrival_date": row.po_estimated_arrival_date + }) + + return spare_parts # async def create(*, db_session: DbSession, scope_equipment_activty_in: ScopeEquipmentActivityCreate): diff --git a/src/sparepart/router.py b/src/sparepart/router.py index da4b81e..6398f93 100644 --- a/src/sparepart/router.py +++ b/src/sparepart/router.py @@ -1,21 +1,20 @@ from fastapi import APIRouter, HTTPException, Query, status +from src.database.core import CollectorDbSession from src.database.service import (CommonParameters, DbSession, search_filter_sort_paginate) from src.models import StandardResponse -from .schema import (ActivityMaster, ActivityMasterCreate, - ActivityMasterPagination) -from .service import create, delete, get, get_all, update +from .service import get_all router = APIRouter() -@router.get("", response_model=StandardResponse[ActivityMasterPagination]) -async def get_activities(common: CommonParameters): +@router.get("", response_model=StandardResponse[list]) +async def get_sparepart(collector_db_session:CollectorDbSession): """Get all scope activity pagination.""" # return - data = await get_all(common=common) + data = await get_all(collector_db_session) @@ -25,62 +24,62 @@ async def get_activities(common: CommonParameters): ) -@router.post("", response_model=StandardResponse[ActivityMasterCreate]) -async def create_activity(db_session: DbSession, activity_in: ActivityMasterCreate): +# @router.post("", response_model=StandardResponse[ActivityMasterCreate]) +# async def create_activity(db_session: DbSession, activity_in: ActivityMasterCreate): - activity = await create(db_session=db_session, activty_in=activity_in) +# activity = await create(db_session=db_session, activty_in=activity_in) - return StandardResponse(data=activity, message="Data created successfully") +# return StandardResponse(data=activity, message="Data created successfully") -@router.get( - "/{scope_equipment_activity_id}", response_model=StandardResponse[ActivityMaster] -) -async def get_activity(db_session: DbSession, activity_id: str): - activity = await get(db_session=db_session, activity_id=activity_id) - if not activity: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="A data with this id does not exist.", - ) +# @router.get( +# "/{scope_equipment_activity_id}", response_model=StandardResponse[ActivityMaster] +# ) +# async def get_activity(db_session: DbSession, activity_id: str): +# activity = await get(db_session=db_session, activity_id=activity_id) +# if not activity: +# raise HTTPException( +# status_code=status.HTTP_404_NOT_FOUND, +# detail="A data with this id does not exist.", +# ) - return StandardResponse(data=activity, message="Data retrieved successfully") +# return StandardResponse(data=activity, message="Data retrieved successfully") -@router.put( - "/{scope_equipment_activity_id}", response_model=StandardResponse[ActivityMaster] -) -async def update_scope( - db_session: DbSession, activity_in: ActivityMasterCreate, activity_id -): - activity = await get(db_session=db_session, activity_id=activity_id) +# @router.put( +# "/{scope_equipment_activity_id}", response_model=StandardResponse[ActivityMaster] +# ) +# async def update_scope( +# db_session: DbSession, activity_in: ActivityMasterCreate, activity_id +# ): +# activity = await get(db_session=db_session, activity_id=activity_id) - if not activity: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="A data with this id does not exist.", - ) +# if not activity: +# raise HTTPException( +# status_code=status.HTTP_404_NOT_FOUND, +# detail="A data with this id does not exist.", +# ) - return StandardResponse( - data=await update( - db_session=db_session, activity=activity, activity_in=activity_in - ), - message="Data updated successfully", - ) +# return StandardResponse( +# data=await update( +# db_session=db_session, activity=activity, activity_in=activity_in +# ), +# message="Data updated successfully", +# ) -@router.delete( - "/{scope_equipment_activity_id}", response_model=StandardResponse[ActivityMaster] -) -async def delete_scope(db_session: DbSession, activity_id: str): - activity = await get(db_session=db_session, activity_id=activity_id) +# @router.delete( +# "/{scope_equipment_activity_id}", response_model=StandardResponse[ActivityMaster] +# ) +# async def delete_scope(db_session: DbSession, activity_id: str): +# activity = await get(db_session=db_session, activity_id=activity_id) - if not activity: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=[{"msg": "A data with this id does not exist."}], - ) +# if not activity: +# 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, activity_id=activity_id) +# await delete(db_session=db_session, activity_id=activity_id) - return StandardResponse(message="Data deleted successfully", data=activity) +# return StandardResponse(message="Data deleted successfully", data=activity) diff --git a/src/sparepart/service.py b/src/sparepart/service.py index 652fcd2..a0b8d55 100644 --- a/src/sparepart/service.py +++ b/src/sparepart/service.py @@ -1,60 +1,132 @@ from typing import Optional -from sqlalchemy import Delete, Select +from sqlalchemy import Delete, Select, text from sqlalchemy.orm import joinedload, selectinload from src.auth.service import CurrentUser from src.database.core import DbSession from src.database.service import CommonParameters, search_filter_sort_paginate -from .model import MasterActivity -from .schema import ActivityMaster, ActivityMasterCreate - -async def get(*, db_session: DbSession, activity_id: str) -> Optional[ActivityMaster]: - """Returns a document based on the given document id.""" - result = await db_session.get(MasterActivity, activity_id) - return result - - -async def get_all(common: CommonParameters): - query = Select(MasterActivity) - - results = await search_filter_sort_paginate(model=query, **common) - - return results - - - -async def create(*, db_session: DbSession, activty_in: ActivityMasterCreate): - activity = MasterActivity(**activty_in.model_dump()) - db_session.add(activity) - await db_session.commit() - return activity - - -async def update( - *, - db_session: DbSession, - activity: MasterActivity, - activity_in: ActivityMasterCreate -): - """Updates a document.""" - data = activity_in.model_dump() - - update_data = activity_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, activity_id: str): - """Deletes a document.""" - activity = await db_session.get(MasterActivity, activity_id) - await db_session.delete(activity) - await db_session.commit() +# async def get(*, db_session: DbSession, activity_id: str) -> Optional[ActivityMaster]: +# """Returns a document based on the given document id.""" +# result = await db_session.get(MasterActivity, activity_id) +# return result + + +async def get_all(db_session: DbSession): + """ + Get all spare parts with their latest PR and PO information. + + Args: + db_session: SQLAlchemy database session + assetnum: Optional asset number filter (not used in this query but kept for compatibility) + + Returns: + List of dictionaries containing spare part information + """ + # Define the SQL query + query = text(""" + WITH latest_prs AS ( + SELECT DISTINCT ON (pl.item_num) + pl.item_num, + h.num as pr_number, + h.issue_date as pr_issue_date, + h.status as pr_status, + pl.qty_ordered as pr_qty_ordered + FROM public.maximo_sparepart_pr_po h + JOIN public.maximo_sparepart_pr_po_line pl ON h.num = pl.num + WHERE h.type = 'PR' + AND h.issue_date IS NOT NULL + AND h.num LIKE 'K%' + ORDER BY pl.item_num, TO_DATE(h.issue_date, 'YYYY-MM-DD') DESC + ), + po_details AS ( + SELECT + h.num as po_number, + pl.item_num, + h.estimated_arrival_date, + pl.qty_received, + pl.qty_ordered + FROM public.maximo_sparepart_pr_po h + JOIN public.maximo_sparepart_pr_po_line pl ON h.num = pl.num + WHERE h.type = 'PO' + ) + SELECT + pr.item_num, + MAX(l.description) as description, + COALESCE(MAX(i.curbaltotal), 0) as current_balance_total, + pr.pr_number, + pr.pr_issue_date, + pr.pr_qty_ordered, + CASE + WHEN po.po_number IS NOT NULL THEN 'YES' + ELSE 'NO' + END as po_exists, + COALESCE(po.qty_received, 0) as po_qty_received, + COALESCE(po.qty_ordered, 0) as po_qty_ordered, + COALESCE(po.estimated_arrival_date, '') as po_estimated_arrival_date + FROM latest_prs pr + LEFT JOIN public.maximo_sparepart_pr_po_line l ON pr.item_num = l.item_num + LEFT JOIN public.maximo_inventory i ON pr.item_num = i.itemnum + LEFT JOIN po_details po ON po.po_number = pr.pr_number AND po.item_num = pr.item_num + GROUP BY pr.item_num, pr.pr_number, pr.pr_issue_date, pr.pr_qty_ordered, + po.po_number, po.qty_received, po.qty_ordered, po.estimated_arrival_date + ORDER BY pr.item_num + """) + + # Execute the query + result = await db_session.execute(query) + + # Fetch all results and convert to list of dictionaries + spare_parts = [] + for row in result: + spare_parts.append({ + "item_num": row.item_num, + "description": row.description, + "current_balance_total": float(row.current_balance_total) if row.current_balance_total is not None else 0.0, + "pr_number": row.pr_number, + "pr_issue_date": row.pr_issue_date, + "pr_qty_ordered": float(row.pr_qty_ordered) if row.pr_qty_ordered is not None else 0.0, + "po_exists": row.po_exists, + "po_qty_received": float(row.po_qty_received) if row.po_qty_received is not None else 0.0, + "po_qty_ordered": float(row.po_qty_ordered) if row.po_qty_ordered is not None else 0.0, + "po_estimated_arrival_date": row.po_estimated_arrival_date + }) + + return spare_parts + + + +# async def create(*, db_session: DbSession, activty_in: ActivityMasterCreate): +# activity = MasterActivity(**activty_in.model_dump()) +# db_session.add(activity) +# await db_session.commit() +# return activity + + +# async def update( +# *, +# db_session: DbSession, +# activity: MasterActivity, +# activity_in: ActivityMasterCreate +# ): +# """Updates a document.""" +# data = activity_in.model_dump() + +# update_data = activity_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, activity_id: str): +# """Deletes a document.""" +# activity = await db_session.get(MasterActivity, activity_id) +# await db_session.delete(activity) +# await db_session.commit()