diff --git a/.env b/.env index 2f7024b..b79c063 100644 --- a/.env +++ b/.env @@ -9,9 +9,15 @@ DATABASE_CREDENTIAL_USER=postgres DATABASE_CREDENTIAL_PASSWORD=postgres DATABASE_NAME=digital_twin -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 +# 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 + +COLLECTOR_HOSTNAME=192.168.1.86 +COLLECTOR_PORT=5432 +COLLECTOR_CREDENTIAL_USER=postgres +COLLECTOR_CREDENTIAL_PASSWORD=postgres +COLLECTOR_NAME=digital_twin diff --git a/src/overhaul_activity/router.py b/src/overhaul_activity/router.py index 3e1c057..280f98b 100644 --- a/src/overhaul_activity/router.py +++ b/src/overhaul_activity/router.py @@ -10,7 +10,7 @@ from src.models import StandardResponse from .schema import (OverhaulActivityCreate, OverhaulActivityPagination, OverhaulActivityRead, OverhaulActivityUpdate) -from .service import add_multiple_equipment_to_session, create, delete, get, get_all, update +from .service import add_multiple_equipment_to_session, get, get_all, remove_equipment_from_session, update router = APIRouter() @@ -79,46 +79,38 @@ async def get_overhaul_equipment( return StandardResponse(data=equipment, message="Data retrieved successfully") -@router.put( - "/{overhaul_session}/{assetnum}", - response_model=StandardResponse[OverhaulActivityRead], -) -async def update_scope( - db_session: DbSession, - scope_equipment_activity_in: OverhaulActivityUpdate, - assetnum: str, -): - activity = await get(db_session=db_session, assetnum=assetnum) - - 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, - scope_equipment_activity_in=scope_equipment_activity_in, - ), - message="Data updated successfully", - ) +# @router.put( +# "/{overhaul_session}/{assetnum}", +# response_model=StandardResponse[OverhaulActivityRead], +# ) +# async def update_scope( +# db_session: DbSession, +# scope_equipment_activity_in: OverhaulActivityUpdate, +# assetnum: str, +# ): +# activity = await get(db_session=db_session, assetnum=assetnum) + +# 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, +# scope_equipment_activity_in=scope_equipment_activity_in, +# ), +# message="Data updated successfully", +# ) @router.delete( - "/{overhaul_session}/{assetnum}", - response_model=StandardResponse[OverhaulActivityRead], + "/{overhaul_session}/{location_tag}", + response_model=StandardResponse[None], ) -async def delete_scope(db_session: DbSession, assetnum: str): - activity = await get(db_session=db_session, assetnum=assetnum) - - 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, assetnum=assetnum) +async def delete_scope(db_session: DbSession, location_tag: str, overhaul_session:UUID): + await remove_equipment_from_session(db_session=db_session, overhaul_session_id=overhaul_session, location_tag=location_tag) - return StandardResponse(message="Data deleted successfully", data=activity) + return StandardResponse(message="Data deleted successfully", data=None) diff --git a/src/overhaul_activity/service.py b/src/overhaul_activity/service.py index 2139e25..952c733 100644 --- a/src/overhaul_activity/service.py +++ b/src/overhaul_activity/service.py @@ -1,8 +1,9 @@ import asyncio +import datetime from typing import List, Optional -from uuid import UUID +from uuid import UUID, uuid4 -from sqlalchemy import Delete, Select, func, select +from sqlalchemy import Delete, Select, and_, func, select from sqlalchemy import update as sqlUpdate from sqlalchemy.dialects.postgresql import insert from sqlalchemy.orm import joinedload, selectinload @@ -213,8 +214,8 @@ async def add_equipment_to_session( ) -> Optional[StandardScope]: """ Add a new equipment to an existing overhaul session. - Creates a dummy workscope group if needed. - Silently skips if equipment already exists in the session. + If equipment's workscope already maps to the overhaul type, skip. + Otherwise, attach a dummy workscope group for inclusion. Args: db_session: Database session @@ -223,99 +224,97 @@ async def add_equipment_to_session( location_tag: The location tag of the equipment to add Returns: - StandardScope: The newly created StandardScope record, or None if already exists + StandardScope: The newly created or updated StandardScope record, or None if skipped """ try: # Get the overhaul session - overhaul = await get_session(db_session=db_session, overhaul_session_id=overhaul_session_id) - + overhaul = await get_session( + db_session=db_session, overhaul_session_id=overhaul_session_id + ) + # Check if MasterEquipment exists master_eq_query = select(MasterEquipment).filter( MasterEquipment.location_tag == location_tag ) master_eq_result = await db_session.execute(master_eq_query) master_equipment = master_eq_result.scalar_one_or_none() - + if not master_equipment: - print("equipment not found in master") + print("Equipment not found in master") return None - + # Check if equipment already exists in StandardScope existing_query = select(StandardScope).filter( StandardScope.location_tag == location_tag ) existing_result = await db_session.execute(existing_query) existing_equipment = existing_result.scalar_one_or_none() - - if existing_equipment: - # Equipment already in StandardScope, check if it's in current session - # by verifying if it matches the session's maintenance type criteria - check_query = ( - Select(StandardScope) - .outerjoin(StandardScope.oh_history) - .join(StandardScope.workscope_groups) - .join(EquipmentWorkscopeGroup.workscope_group) - .join(MasterActivity.oh_types) - .join(WorkscopeOHType.oh_type) - .filter(StandardScope.location_tag == location_tag) - .filter(MaintenanceType.name == overhaul.maintenance_type.name) - .filter( - (StandardScope.is_alternating_oh == False) - | (StandardScope.oh_history is None) - | ( - StandardScope.oh_history.has( - EquipmentOHHistory.last_oh_type != overhaul.maintenance_type.name - ) - ) - ) + + # --- Step 1: Fetch equipment's actual workscope mappings --- + eq_workscope_query = ( + select(EquipmentWorkscopeGroup) + .join(EquipmentWorkscopeGroup.workscope_group) + .join( + WorkscopeOHType, + WorkscopeOHType.workscope_group_id == MasterActivity.id, + ) + .join( + MaintenanceType, + MaintenanceType.id == WorkscopeOHType.maintenance_type_id, + ) + .filter(EquipmentWorkscopeGroup.location_tag == location_tag) + ) + eq_workscopes = (await db_session.execute(eq_workscope_query)).scalars().all() + + # --- Step 2: Check if already included via natural workscope --- + is_already_included = any( + ws.workscope_group + and any( + wot.oh_type.name == overhaul.maintenance_type.name + for wot in ws.workscope_group.oh_types ) - check_result = await db_session.execute(check_query) - in_session = check_result.scalar_one_or_none() - - if in_session: - # Already in current session, skip silently - return None - - # Equipment exists but not in this session, need to add workscope - # Find or create dummy workscope group + for ws in eq_workscopes + ) + + if is_already_included: + # Already belongs to this overhaul naturally → skip + return None + + # --- Step 3: Handle existing equipment --- + if existing_equipment: + # Add dummy workscope if not already attached dummy_workscope = await get_or_create_dummy_workscope( db_session=db_session, - maintenance_type_name=overhaul.maintenance_type.name + maintenance_type_name=overhaul.maintenance_type.name, ) - - # Add the dummy workscope to existing equipment if not already there + if dummy_workscope not in existing_equipment.workscope_groups: - existing_equipment.workscope_groups.append(dummy_workscope) + dummy_workscope.location_tag = location_tag await db_session.commit() await db_session.refresh(existing_equipment) - + return existing_equipment - - # Equipment not in StandardScope at all, create new - # First, get or create dummy workscope group + + # --- Step 4: Create new StandardScope for fresh equipment --- dummy_workscope = await get_or_create_dummy_workscope( db_session=db_session, - maintenance_type_name=overhaul.maintenance_type.name + maintenance_type_name=overhaul.maintenance_type.name, ) - - # Create new StandardScope record + new_equipment = StandardScope( location_tag=location_tag, - is_alternating_oh=False, - # Add other required fields based on your model + is_alternating_oh=True, + assigned_date=datetime.date.today(), ) - - # Associate with dummy workscope group - new_equipment.workscope_groups = [dummy_workscope] - + dummy_workscope.location_tag = location_tag + db_session.add(new_equipment) await db_session.commit() await db_session.refresh(new_equipment) - + return new_equipment - + except Exception as e: - # Log the error but don't raise it print(f"Error adding equipment {location_tag}: {str(e)}") await db_session.rollback() return None @@ -324,7 +323,7 @@ async def add_equipment_to_session( async def get_or_create_dummy_workscope( *, db_session: DbSession, - maintenance_type_name: str + maintenance_type_name: str, ) -> EquipmentWorkscopeGroup: """ Get or create a dummy workscope group for included equipment. @@ -337,50 +336,49 @@ async def get_or_create_dummy_workscope( EquipmentWorkscopeGroup: The dummy workscope group """ dummy_name = f"Included Equipment Workscope - {maintenance_type_name}" - - # Check if dummy workscope already exists - query = ( - select(EquipmentWorkscopeGroup) - .join(EquipmentWorkscopeGroup.workscope_group) - .join(MasterActivity.oh_types) - .join(WorkscopeOHType.oh_type) - .filter(MasterActivity.name == dummy_name) - .filter(MaintenanceType.name == maintenance_type_name) - ) - + + # --- Step 1: Check if dummy MasterActivity exists --- + query = select(MasterActivity).filter(MasterActivity.workscope == dummy_name) result = await db_session.execute(query) - existing = result.scalar_one_or_none() - - if existing: - return existing - - # Create dummy workscope group - # First, get the maintenance type + master_activity = result.scalar_one_or_none() + + if not master_activity: + master_activity = MasterActivity(workscope=dummy_name) + db_session.add(master_activity) + await db_session.commit() + await db_session.refresh(master_activity) + + # --- Step 2: Ensure WorkscopeOHType link exists --- mt_query = select(MaintenanceType).filter(MaintenanceType.name == maintenance_type_name) mt_result = await db_session.execute(mt_query) maintenance_type = mt_result.scalar_one() - - # Create MasterActivity (workscope_group) - master_activity = MasterActivity( - workscope=dummy_name, - ) - - # Create WorkscopeOHType relationship - workscope_oh_type = WorkscopeOHType( - workscope_group=master_activity, - oh_type=maintenance_type + + wo_oh_type_query = select(WorkscopeOHType).filter( + and_( + WorkscopeOHType.workscope_group_id == master_activity.id, + WorkscopeOHType.maintenance_type_id == maintenance_type.id, + ) ) - - # Create EquipmentWorkscopeGroup + workscope_oh_type = (await db_session.execute(wo_oh_type_query)).scalar_one_or_none() + + if not workscope_oh_type: + workscope_oh_type = WorkscopeOHType( + id=uuid4(), + workscope_group=master_activity, + oh_type=maintenance_type, + ) + db_session.add(workscope_oh_type) + await db_session.commit() + await db_session.refresh(workscope_oh_type) + + # --- Step 3: Create new EquipmentWorkscopeGroup --- equipment_workscope_group = EquipmentWorkscopeGroup( workscope_group=master_activity, - # Add other required fields ) - db_session.add(equipment_workscope_group) await db_session.commit() await db_session.refresh(equipment_workscope_group) - + return equipment_workscope_group @@ -433,62 +431,6 @@ async def get_all_by_session_id(*, db_session: DbSession, overhaul_session_id): -async def create( - *, - db_session: DbSession, - overhaul_activty_in: OverhaulActivityCreate, - overhaul_session_id: UUID -): - """Creates a new document.""" - assetnums = overhaul_activty_in.assetnums - if not assetnums: - return [] - - # Get session and count in parallel - session = await get_session( - db_session=db_session, overhaul_session_id=overhaul_session_id - ) - equipment_count = await db_session.scalar( - select(func.count()) - .select_from(OverhaulActivity) - .where(OverhaulActivity.overhaul_scope_id == overhaul_session_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) - - # Create the insert statement - stmt = insert(OverhaulActivity).values( - [ - { - "assetnum": assetnum, - "overhaul_scope_id": overhaul_session_id, - "material_cost": material_cost, - "service_cost": service_cost, - } - for assetnum in assetnums - ] - ) - - # Add the ON CONFLICT DO NOTHING clause - stmt = stmt.on_conflict_do_nothing(index_elements=["assetnum", "overhaul_scope_id"]) - - # Execute the statement - await db_session.execute(stmt) - await db_session.execute( - sqlUpdate(OverhaulActivity) - .where(OverhaulActivity.overhaul_scope_id == overhaul_session_id) - .values(material_cost=material_cost, service_cost=service_cost) - ) - - await db_session.commit() - return assetnums - - async def update( *, db_session: DbSession, @@ -509,8 +451,95 @@ async def update( return activity -async def delete(*, db_session: DbSession, overhaul_activity_id: str): - """Deletes a document.""" - activity = await db_session.get(OverhaulActivity, overhaul_activity_id) - await db_session.delete(activity) - await db_session.commit() +async def remove_equipment_from_session( + *, + db_session: DbSession, + overhaul_session_id: UUID, + location_tag: str +) -> bool: + """ + Remove equipment from a given overhaul session. + Only removes dummy workscope group links; natural workscope mappings are preserved. + + Args: + db_session: Database session + overhaul_session_id: The UUID of the overhaul session + location_tag: The location tag of the equipment to remove + + Returns: + bool: True if removed, False if skipped (not found or naturally included) + """ + try: + # Get the overhaul session + overhaul = await get_session( + db_session=db_session, overhaul_session_id=overhaul_session_id + ) + + # Find the equipment in StandardScope + existing_query = select(StandardScope).filter( + StandardScope.location_tag == location_tag + ) + existing_result = await db_session.execute(existing_query) + equipment = existing_result.scalar_one_or_none() + + if not equipment: + print(f"Equipment {location_tag} not found in StandardScope") + return False + + # --- Step 1: Check if equipment belongs naturally (exclude dummy groups) --- + eq_workscope_query = ( + select(EquipmentWorkscopeGroup) + .join(EquipmentWorkscopeGroup.workscope_group) + .join( + WorkscopeOHType, + WorkscopeOHType.workscope_group_id == MasterActivity.id, + ) + .join( + MaintenanceType, + MaintenanceType.id == WorkscopeOHType.maintenance_type_id, + ) + .filter(EquipmentWorkscopeGroup.location_tag == location_tag) + .filter(~MasterActivity.workscope.like("Included Equipment Workscope%")) # 🚨 exclude dummy + ) + eq_workscopes = (await db_session.execute(eq_workscope_query)).scalars().all() + + is_natural_inclusion = any( + ws.workscope_group + and any( + wot.oh_type.name == overhaul.maintenance_type.name + for wot in ws.workscope_group.oh_types + ) + for ws in eq_workscopes + ) + + if is_natural_inclusion: + print(f"Equipment {location_tag} is naturally part of OH {overhaul.maintenance_type.name}, skip removal") + return False + + # --- Step 2: Remove dummy workscope group for this overhaul --- + dummy_name = f"Included Equipment Workscope - {overhaul.maintenance_type.name}" + dummy_group = next( + (wg for wg in equipment.workscope_groups if wg.workscope_group.workscope == dummy_name), + None, + ) + + if dummy_group: + equipment.workscope_groups.remove(dummy_group) + await db_session.commit() + await db_session.refresh(equipment) + + # --- Step 3 (optional): Delete StandardScope if no groups left --- + if not equipment.workscope_groups: + await db_session.delete(equipment) + await db_session.commit() + print(f"Equipment {location_tag} completely removed from StandardScope") + + return True + + print(f"No dummy workscope group found for {location_tag} in OH {overhaul.maintenance_type.name}") + return False + + except Exception as e: + print(f"Error removing equipment {location_tag}: {str(e)}") + await db_session.rollback() + return False