diff --git a/src/__pycache__/exceptions.cpython-311.pyc b/src/__pycache__/exceptions.cpython-311.pyc index c896da4..f76a8b3 100644 Binary files a/src/__pycache__/exceptions.cpython-311.pyc and b/src/__pycache__/exceptions.cpython-311.pyc differ diff --git a/src/auth/__pycache__/service.cpython-311.pyc b/src/auth/__pycache__/service.cpython-311.pyc index fd2da1d..e6d8d51 100644 Binary files a/src/auth/__pycache__/service.cpython-311.pyc and b/src/auth/__pycache__/service.cpython-311.pyc differ diff --git a/src/auth/service.py b/src/auth/service.py index 75f582b..975ca23 100644 --- a/src/auth/service.py +++ b/src/auth/service.py @@ -13,21 +13,24 @@ class JWTBearer(HTTPBearer): super(JWTBearer, self).__init__(auto_error=auto_error) async def __call__(self, request: Request): - credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request) + credentials: HTTPAuthorizationCredentials = await super( + JWTBearer, self + ).__call__(request) if credentials: if not credentials.scheme == "Bearer": raise HTTPException( - status_code=403, detail="Invalid authentication scheme.") + status_code=403, detail="Invalid authentication scheme." + ) user_info = self.verify_jwt(credentials.credentials) if not user_info: raise HTTPException( - status_code=403, detail="Invalid token or expired token.") + status_code=403, detail="Invalid token or expired token." + ) request.state.user = user_info return user_info else: - raise HTTPException( - status_code=403, detail="Invalid authorization code.") + raise HTTPException(status_code=403, detail="Invalid authorization code.") def verify_jwt(self, jwtoken: str) -> Optional[UserBase]: try: @@ -40,7 +43,7 @@ class JWTBearer(HTTPBearer): return None user_data = response.json() - return UserBase(**user_data['data']) + return UserBase(**user_data["data"]) except Exception as e: print(f"Token verification error: {str(e)}") @@ -52,4 +55,14 @@ async def get_current_user(request: Request) -> UserBase: return request.state.user +async def get_token(request: Request): + token = request.headers.get("Authorization") + + if token: + return token.split(" ")[1] + + return " " + + CurrentUser = Annotated[UserBase, Depends(get_current_user)] +Token = Annotated[str, Depends(get_token)] diff --git a/src/equipment/__pycache__/router.cpython-311.pyc b/src/equipment/__pycache__/router.cpython-311.pyc index d70c62c..b34a8c7 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__/service.cpython-311.pyc b/src/equipment/__pycache__/service.cpython-311.pyc index 778a0a2..eb27d58 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 07a7340..83100ba 100644 --- a/src/equipment/router.py +++ b/src/equipment/router.py @@ -13,7 +13,7 @@ from .service import get_master_by_assetnum, get_by_id, get_all, create, update, from src.database.service import CommonParameters, search_filter_sort_paginate from src.database.core import DbSession -from src.auth.service import CurrentUser +from src.auth.service import CurrentUser, Token from src.models import StandardResponse router = APIRouter() @@ -78,10 +78,15 @@ async def get_equipment(db_session: DbSession, assetnum: str): @router.post("", response_model=StandardResponse[EquipmentCreate]) async def create_equipment( - db_session: DbSession, equipment_in: EquipmentCreate, current_user: CurrentUser + db_session: DbSession, + equipment_in: EquipmentCreate, + current_user: CurrentUser, + token: Token, ): equipment_in.created_by = current_user.name - equipment = await create(db_session=db_session, equipment_in=equipment_in) + equipment = await create( + db_session=db_session, equipment_in=equipment_in, token=token + ) return StandardResponse(data=equipment, message="Data created successfully") @@ -92,6 +97,7 @@ async def update_equipment( equipment_id: str, equipment_in: EquipmentUpdate, current_user: CurrentUser, + token: Token, ): equipment = await get_by_id(db_session=db_session, equipment_id=equipment_id) @@ -104,7 +110,10 @@ async def update_equipment( return StandardResponse( data=await update( - db_session=db_session, equipment=equipment, equipment_in=equipment_in + db_session=db_session, + equipment=equipment, + equipment_in=equipment_in, + token=token, ), message="Data updated successfully", ) diff --git a/src/equipment/service.py b/src/equipment/service.py index 5fac574..8b07e35 100644 --- a/src/equipment/service.py +++ b/src/equipment/service.py @@ -9,6 +9,7 @@ from typing import Optional from src.database.core import DbSession from src.auth.service import CurrentUser +import httpx async def get_master_by_assetnum( @@ -63,7 +64,14 @@ async def get_master_by_assetnum( ) min_seq = min_record[1] if min_record else None - return equipment_master_record, equipment_record, records, min_eac_value, min_seq, last_actual_year + return ( + equipment_master_record, + equipment_record, + records, + min_eac_value, + min_seq, + last_actual_year, + ) # return result.scalars().all() @@ -91,7 +99,9 @@ async def get_all( return result -async def generate_transaction(*, db_session: DbSession, data_in: EquipmentCreate): +async def generate_transaction( + *, db_session: DbSession, data_in: EquipmentCreate, token +): # Delete all existing master records for this asset number and prediction data query = ( Delete(MasterRecords) @@ -101,6 +111,23 @@ async def generate_transaction(*, db_session: DbSession, data_in: EquipmentCreat await db_session.execute(query) await db_session.commit() """Generate transaction for equipment.""" + + # Fetch data from external API + async def fetch_api_data(assetnum: str, year: int) -> dict: + async with httpx.AsyncClient() as client: + try: + response = await client.get( + f"http://192.168.1.82:8000/reliability/main/number-of-failures/{assetnum}/{year}/{year}", + timeout=30.0, + headers={"Authorization": f"Bearer {token}"}, + ) + response.raise_for_status() + return response.json() + except httpx.HTTPError as e: + print(f"HTTP error occurred: {e}") + return {} + + # Initialize base transaction with default values base_transaction = { "assetnum": data_in.assetnum, "is_actual": 0, @@ -142,10 +169,41 @@ async def generate_transaction(*, db_session: DbSession, data_in: EquipmentCreat transactions = [] + # Query existing records with is_actual=1 + actual_years_query = ( + Select(MasterRecords.tahun) + .filter(MasterRecords.assetnum == data_in.assetnum) + .filter(MasterRecords.is_actual == 1) + ) + result = await db_session.execute(actual_years_query) + actual_years = {row[0] for row in result.all()} + for sequence, year in enumerate( range(data_in.acquisition_year - 1, data_in.forecasting_target_year + 1), 0 ): + # Skip if year already has actual data + if year in actual_years: + continue + transaction = base_transaction.copy() + # Update values from API + api_data = await fetch_api_data(data_in.assetnum, year) + + if api_data: + # Get current num_fail + current_num_fail = api_data["data"][0]["num_fail"] + + # Calculate sum of previous failures for this asset + previous_failures_query = ( + Select(func.sum(MasterRecords.raw_cm_interval)) + .filter(MasterRecords.assetnum == data_in.assetnum) + .filter(MasterRecords.tahun < year) + ) + previous_failures_result = await db_session.execute(previous_failures_query) + previous_failures_sum = previous_failures_result.scalar() or 0 + + # Update with current minus sum of previous + transaction.update({"raw_cm_interval": current_num_fail - previous_failures_sum}) transaction.update({"tahun": int(year), "seq": int(sequence)}) transactions.append(MasterRecords(**transaction)) @@ -156,29 +214,34 @@ async def generate_transaction(*, db_session: DbSession, data_in: EquipmentCreat return len(transactions) -async def create(*, db_session: DbSession, equipment_in: EquipmentCreate): +async def create(*, db_session: DbSession, equipment_in: EquipmentCreate, token): """Creates a new document.""" equipment = Equipment(**equipment_in.model_dump()) db_session.add(equipment) await db_session.commit() - await generate_transaction(db_session=db_session, data_in=equipment_in) + await generate_transaction(db_session=db_session, data_in=equipment_in, token=token) return equipment async def update( - *, db_session: DbSession, equipment: Equipment, equipment_in: EquipmentUpdate + *, db_session: DbSession, equipment: Equipment, equipment_in: EquipmentUpdate, token ): """Updates a document.""" - data = equipment_in.model_dump() + data = equipment_in.model_dump() update_data = equipment_in.model_dump(exclude_defaults=True) - for field in data: if field in update_data: setattr(equipment, field, update_data[field]) await db_session.commit() + updated_data = vars(equipment) + equipment_create = EquipmentCreate(**updated_data) + await generate_transaction( + db_session=db_session, data_in=equipment_create, token=token + ) + return equipment diff --git a/src/equipment_master/__pycache__/service.cpython-311.pyc b/src/equipment_master/__pycache__/service.cpython-311.pyc index d84d616..cba13a1 100644 Binary files a/src/equipment_master/__pycache__/service.cpython-311.pyc and b/src/equipment_master/__pycache__/service.cpython-311.pyc differ diff --git a/src/equipment_master/service.py b/src/equipment_master/service.py index 8c50001..69a6864 100644 --- a/src/equipment_master/service.py +++ b/src/equipment_master/service.py @@ -29,7 +29,10 @@ async def get_all_master( query = query.filter(EquipmentMaster.parent_id == parent_id) if search: - query = query.filter(EquipmentMaster.name.ilike(f"%{search}%")) + query = query.filter( + (EquipmentMaster.name.ilike(f"%{search}%")) | + (EquipmentMaster.assetnum.ilike(f"%{search}%")) + ) query = query.options(recursive_load(5)) diff --git a/src/exceptions.py b/src/exceptions.py index 1694d00..769acc0 100644 --- a/src/exceptions.py +++ b/src/exceptions.py @@ -143,21 +143,32 @@ def handle_exception(request: Request, exc: Exception): ) # Log unexpected errors + error_message = f"{exc.__class__.__name__}: {str(exc)}" + error_traceback = getattr(exc, '__traceback__', None) + + # Get file and line info if available + if error_traceback: + tb = error_traceback + while tb.tb_next: + tb = tb.tb_next + file_name = tb.tb_frame.f_code.co_filename + line_num = tb.tb_lineno + error_message = f"{error_message}\nFile {file_name}, line {line_num}" + logging.error( - f"Unexpected Error | Error: {str(exc)} | Request: {request_info}", + f"Unexpected Error | Error: {error_message} | Request: {request_info}", extra={"error_category": "unexpected"}, ) - return JSONResponse( status_code=500, content={ "data": None, - "message": exc.__class__.__name__, + "message": error_message, "status": ResponseStatus.ERROR, "errors": [ ErrorDetail( - message="An unexpected error occurred." + message=error_message ).model_dump() ] }