diff --git a/backend/lcfs/tests/fuel_export/conftest.py b/backend/lcfs/tests/fuel_export/conftest.py index fdb99e06d..e1225e581 100644 --- a/backend/lcfs/tests/fuel_export/conftest.py +++ b/backend/lcfs/tests/fuel_export/conftest.py @@ -1,5 +1,7 @@ +from datetime import datetime import pytest from unittest.mock import AsyncMock, MagicMock +from lcfs.web.api.compliance_report.schema import CompliancePeriodSchema, ComplianceReportHistorySchema, ComplianceReportOrganizationSchema, ComplianceReportStatusSchema, ComplianceReportUserSchema, SummarySchema from lcfs.web.api.fuel_export.repo import FuelExportRepository from lcfs.web.api.fuel_code.repo import FuelCodeRepository from lcfs.web.api.fuel_export.services import FuelExportServices @@ -45,6 +47,49 @@ def mock_compliance_report_repo(): repo = AsyncMock(spec=ComplianceReportRepository) return repo +@pytest.fixture +def compliance_period_schema(): + return CompliancePeriodSchema( + compliance_period_id=1, + description="2024", + effective_date=datetime(2024, 1, 1), + expiration_date=datetime(2024, 3, 31), + display_order=1, + ) + +@pytest.fixture +def compliance_report_organization_schema(): + return ComplianceReportOrganizationSchema( + organization_id=1, name="Acme Corporation" + ) + +@pytest.fixture +def summary_schema(): + return SummarySchema(summary_id=1, is_locked=False) + +@pytest.fixture +def compliance_report_status_schema(): + return ComplianceReportStatusSchema(compliance_report_status_id=1, status="Draft") + +@pytest.fixture +def compliance_report_user_schema(compliance_report_organization_schema): + return ComplianceReportUserSchema( + first_name="John", + last_name="Doe", + organization=compliance_report_organization_schema, + ) + +@pytest.fixture +def compliance_report_history_schema( + compliance_report_status_schema, compliance_report_user_schema +): + return ComplianceReportHistorySchema( + compliance_report_history_id=1, + compliance_report_id=1, + status=compliance_report_status_schema, + user_profile=compliance_report_user_schema, + create_date=datetime(2024, 4, 1, 12, 0, 0), + ) @pytest.fixture def mock_fuel_code_repo(): diff --git a/backend/lcfs/tests/fuel_export/test_fuel_exports_views.py b/backend/lcfs/tests/fuel_export/test_fuel_exports_views.py index e749a051e..14dd924df 100644 --- a/backend/lcfs/tests/fuel_export/test_fuel_exports_views.py +++ b/backend/lcfs/tests/fuel_export/test_fuel_exports_views.py @@ -6,6 +6,8 @@ from fastapi import FastAPI from fastapi.encoders import jsonable_encoder +from lcfs.tests.compliance_report.conftest import compliance_report_base_schema +from lcfs.web.api.compliance_report.schema import ChainedComplianceReportSchema from lcfs.web.api.fuel_export.schema import ( FuelExportSchema, FuelExportCreateUpdateSchema, @@ -68,18 +70,24 @@ async def test_get_fuel_exports_invalid_payload( @pytest.mark.anyio async def test_get_fuel_exports_paginated_success( - client: AsyncClient, fastapi_app: FastAPI, set_mock_user + client: AsyncClient, fastapi_app: FastAPI, set_mock_user, compliance_report_base_schema ): with patch( "lcfs.web.api.fuel_export.views.FuelExportServices.get_fuel_exports_paginated" ) as mock_get_fuel_exports_paginated, patch( "lcfs.web.api.fuel_export.views.ComplianceReportValidation.validate_organization_access" - ) as mock_validate_organization_access: + ) as mock_validate_organization_access, patch( + "lcfs.web.api.fuel_export.views.FuelExportServices.get_compliance_report_by_id" + ) as mock_get_compliance_report_by_id: mock_get_fuel_exports_paginated.return_value = FuelExportsSchema( fuel_exports=[] ) mock_validate_organization_access.return_value = True + + mock_compliance_report = compliance_report_base_schema() + + mock_get_compliance_report_by_id.return_value = mock_compliance_report set_mock_user(fastapi_app, [RoleEnum.ANALYST]) url = fastapi_app.url_path_for("get_fuel_exports") @@ -98,16 +106,23 @@ async def test_get_fuel_exports_paginated_success( @pytest.mark.anyio async def test_get_fuel_exports_list_success( - client: AsyncClient, fastapi_app: FastAPI, set_mock_user + client: AsyncClient, fastapi_app: FastAPI, set_mock_user, compliance_report_base_schema ): with patch( "lcfs.web.api.fuel_export.views.FuelExportServices.get_fuel_export_list" ) as mock_get_fuel_export_list, patch( "lcfs.web.api.fuel_export.views.ComplianceReportValidation.validate_organization_access" - ) as mock_validate_organization_access: + ) as mock_validate_organization_access, patch( + "lcfs.web.api.fuel_export.views.FuelExportServices.get_compliance_report_by_id" + ) as mock_get_compliance_report_by_id: mock_get_fuel_export_list.return_value = FuelExportsSchema(fuel_exports=[]) mock_validate_organization_access.return_value = True + + mock_compliance_report = compliance_report_base_schema() + + mock_get_compliance_report_by_id.return_value = mock_compliance_report + set_mock_user(fastapi_app, [RoleEnum.ANALYST]) url = fastapi_app.url_path_for("get_fuel_exports") diff --git a/backend/lcfs/tests/notional_transfer/test_notional_transfer_view.py b/backend/lcfs/tests/notional_transfer/test_notional_transfer_view.py index 0628782b0..8fef93c04 100644 --- a/backend/lcfs/tests/notional_transfer/test_notional_transfer_view.py +++ b/backend/lcfs/tests/notional_transfer/test_notional_transfer_view.py @@ -5,6 +5,7 @@ from lcfs.db.base import UserTypeEnum, ActionTypeEnum from lcfs.db.models.user.Role import RoleEnum +from lcfs.tests.compliance_report.conftest import compliance_report_base_schema from lcfs.web.api.base import ComplianceReportRequestSchema from lcfs.web.api.notional_transfer.schema import ( PaginatedNotionalTransferRequestSchema, @@ -70,12 +71,20 @@ async def test_get_notional_transfers( ): with patch( "lcfs.web.api.notional_transfer.views.ComplianceReportValidation.validate_organization_access" - ) as mock_validate_organization_access: + ) as mock_validate_organization_access,patch( + "lcfs.web.api.notional_transfer.views.ComplianceReportValidation.validate_compliance_report_access" + ) as mock_validate_compliance_report_access, patch( + "lcfs.web.api.notional_transfer.views.NotionalTransferServices.get_compliance_report_by_id" + ) as mock_get_compliance_report_by_id: set_mock_user(fastapi_app, [RoleEnum.SUPPLIER]) url = fastapi_app.url_path_for("get_notional_transfers") payload = ComplianceReportRequestSchema(compliance_report_id=1).model_dump() mock_validate_organization_access.return_value = True + + mock_get_compliance_report_by_id.return_value = compliance_report_base_schema + mock_validate_compliance_report_access.return_value = True + mock_notional_transfer_service.get_notional_transfers.return_value = { "notionalTransfers": [] } diff --git a/backend/lcfs/tests/other_uses/test_other_uses_view.py b/backend/lcfs/tests/other_uses/test_other_uses_view.py index 3f0e92f42..c2ce0dbca 100644 --- a/backend/lcfs/tests/other_uses/test_other_uses_view.py +++ b/backend/lcfs/tests/other_uses/test_other_uses_view.py @@ -5,6 +5,7 @@ from lcfs.db.base import UserTypeEnum, ActionTypeEnum from lcfs.db.models.user.Role import RoleEnum +from lcfs.tests.compliance_report.conftest import compliance_report_base_schema from lcfs.web.api.base import ComplianceReportRequestSchema from lcfs.web.api.other_uses.schema import ( PaginatedOtherUsesRequestSchema, @@ -69,12 +70,21 @@ async def test_get_other_uses( ): with patch( "lcfs.web.api.other_uses.views.ComplianceReportValidation.validate_organization_access" - ) as mock_validate_organization_access: + ) as mock_validate_organization_access, patch( + "lcfs.web.api.notional_transfer.views.ComplianceReportValidation.validate_compliance_report_access" + ) as mock_validate_compliance_report_access, patch( + "lcfs.web.api.notional_transfer.views.NotionalTransferServices.get_compliance_report_by_id" + ) as mock_get_compliance_report_by_id: + set_mock_user(fastapi_app, [RoleEnum.SUPPLIER]) url = fastapi_app.url_path_for("get_other_uses") payload = ComplianceReportRequestSchema(compliance_report_id=1).model_dump() mock_validate_organization_access.return_value = True + + mock_get_compliance_report_by_id.return_value = compliance_report_base_schema + mock_validate_compliance_report_access.return_value = True + mock_other_uses_service.get_other_uses.return_value = {"otherUses": []} fastapi_app.dependency_overrides[OtherUsesServices] = ( diff --git a/backend/lcfs/web/api/allocation_agreement/services.py b/backend/lcfs/web/api/allocation_agreement/services.py index 12954ec63..6516a8379 100644 --- a/backend/lcfs/web/api/allocation_agreement/services.py +++ b/backend/lcfs/web/api/allocation_agreement/services.py @@ -1,10 +1,11 @@ import math import structlog from typing import List -from fastapi import Depends +from fastapi import Depends, HTTPException, status from datetime import datetime from lcfs.web.api.allocation_agreement.repo import AllocationAgreementRepository +from lcfs.web.api.compliance_report.repo import ComplianceReportRepository from lcfs.web.core.decorators import service_handler from lcfs.db.models.compliance.AllocationAgreement import AllocationAgreement from lcfs.web.api.base import PaginationRequestSchema, PaginationResponseSchema @@ -34,9 +35,11 @@ def __init__( self, repo: AllocationAgreementRepository = Depends(AllocationAgreementRepository), fuel_repo: FuelCodeRepository = Depends(), + compliance_report_repo: ComplianceReportRepository = Depends(), ) -> None: self.repo = repo self.fuel_repo = fuel_repo + self.compliance_report_repo = compliance_report_repo async def convert_to_model( self, allocation_agreement: AllocationAgreementCreateSchema @@ -350,3 +353,18 @@ async def create_allocation_agreement( async def delete_allocation_agreement(self, allocation_agreement_id: int) -> str: """Delete an Allocation agreement""" return await self.repo.delete_allocation_agreement(allocation_agreement_id) + + @service_handler + async def get_compliance_report_by_id(self, compliance_report_id: int): + """Get compliance report by period with status""" + compliance_report = await self.compliance_report_repo.get_compliance_report_by_id( + compliance_report_id, + ) + + if not compliance_report: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Compliance report not found for this period" + ) + + return compliance_report diff --git a/backend/lcfs/web/api/allocation_agreement/views.py b/backend/lcfs/web/api/allocation_agreement/views.py index 3e30919de..67441ded0 100644 --- a/backend/lcfs/web/api/allocation_agreement/views.py +++ b/backend/lcfs/web/api/allocation_agreement/views.py @@ -8,6 +8,7 @@ from fastapi import ( APIRouter, Body, + HTTPException, status, Request, Response, @@ -69,10 +70,31 @@ async def get_allocation_agreements( report_validate: ComplianceReportValidation = Depends(), ): """Endpoint to get list of allocation agreements for a compliance report""" - await report_validate.validate_organization_access( - request_data.compliance_report_id - ) - return await service.get_allocation_agreements(request_data.compliance_report_id) + try: + compliance_report_id = request_data.compliance_report_id + + compliance_report = await service.get_compliance_report_by_id(compliance_report_id) + if not compliance_report: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Compliance report not found" + ) + + await report_validate.validate_compliance_report_access(compliance_report) + await report_validate.validate_organization_access( + request_data.compliance_report_id + ) + return await service.get_allocation_agreements(request_data.compliance_report_id) + except HTTPException as http_ex: + # Re-raise HTTP exceptions to preserve status code and message + raise http_ex + except Exception as e: + # Log and handle unexpected errors + logger.exception("Error occurred", error=str(e)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="An unexpected error occurred while processing your request" + ) @router.post( diff --git a/backend/lcfs/web/api/compliance_report/validation.py b/backend/lcfs/web/api/compliance_report/validation.py index 91bd315d2..f17129e8a 100644 --- a/backend/lcfs/web/api/compliance_report/validation.py +++ b/backend/lcfs/web/api/compliance_report/validation.py @@ -1,5 +1,6 @@ from fastapi import Depends, HTTPException, Request from lcfs.db.models.user.Role import RoleEnum +from lcfs.db.models.compliance.ComplianceReportStatus import ComplianceReportStatusEnum from lcfs.web.api.compliance_report.repo import ComplianceReportRepository from fastapi import status from lcfs.web.api.role.schema import user_has_roles @@ -41,3 +42,17 @@ async def validate_organization_access(self, compliance_report_id: int): ) return compliance_report + + async def validate_compliance_report_access(self, compliance_report): + """Validates government user access to draft reports""" + is_government = user_has_roles(self.request.user, [RoleEnum.GOVERNMENT]) + + if compliance_report: + status_enum = ComplianceReportStatusEnum(compliance_report.current_status.status) + is_draft = status_enum == ComplianceReportStatusEnum.Draft + + if is_government and is_draft: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Government users cannot access draft compliance reports" + ) diff --git a/backend/lcfs/web/api/compliance_report/views.py b/backend/lcfs/web/api/compliance_report/views.py index de43c0c26..0d89f51e9 100644 --- a/backend/lcfs/web/api/compliance_report/views.py +++ b/backend/lcfs/web/api/compliance_report/views.py @@ -83,7 +83,8 @@ async def get_compliance_report_by_id( service: ComplianceReportServices = Depends(), validate: ComplianceReportValidation = Depends(), ) -> ChainedComplianceReportSchema: - await validate.validate_organization_access(report_id) + compliance_report = await validate.validate_organization_access(report_id) + await validate.validate_compliance_report_access(compliance_report) mask_statuses = not user_has_roles(request.user, [RoleEnum.GOVERNMENT]) diff --git a/backend/lcfs/web/api/final_supply_equipment/repo.py b/backend/lcfs/web/api/final_supply_equipment/repo.py index f2353b624..8c8163baf 100644 --- a/backend/lcfs/web/api/final_supply_equipment/repo.py +++ b/backend/lcfs/web/api/final_supply_equipment/repo.py @@ -113,17 +113,23 @@ async def get_organization_names(self, organization) -> List[str]: Returns: List[str]: A list of unique organization names. """ - organization_names = ( - await self.db.execute( - select(distinct(FinalSupplyEquipment.organization_name)) - .join(ComplianceReport, FinalSupplyEquipment.compliance_report_id == ComplianceReport.compliance_report_id) - .filter(ComplianceReport.organization_id == organization.organization_id) - .filter(FinalSupplyEquipment.organization_name.isnot(None)) - ) - ).all() + try: + if not organization or not organization.organization_id: + return [] + + organization_names = ( + await self.db.execute( + select(distinct(FinalSupplyEquipment.organization_name)) + .join(ComplianceReport, FinalSupplyEquipment.compliance_report_id == ComplianceReport.compliance_report_id) + .filter(ComplianceReport.organization_id == organization.organization_id) + .filter(FinalSupplyEquipment.organization_name.isnot(None)) + ) + ).all() - # Extract strings from the list of tuples - return [name[0] for name in organization_names] + return [name[0] for name in organization_names] + except Exception as e: + logger.error("Error getting organization names", error=str(e)) + return [] @repo_handler async def get_intended_user_by_name(self, intended_user: str) -> EndUseType: diff --git a/backend/lcfs/web/api/final_supply_equipment/services.py b/backend/lcfs/web/api/final_supply_equipment/services.py index a70b1ce4b..a0251fa7c 100644 --- a/backend/lcfs/web/api/final_supply_equipment/services.py +++ b/backend/lcfs/web/api/final_supply_equipment/services.py @@ -1,10 +1,11 @@ import structlog import math import re -from fastapi import Depends, Request +from fastapi import Depends, HTTPException, Request, status from lcfs.db.models.compliance import FinalSupplyEquipment from lcfs.web.api.base import PaginationRequestSchema, PaginationResponseSchema +from lcfs.web.api.compliance_report.repo import ComplianceReportRepository from lcfs.web.api.compliance_report.schema import FinalSupplyEquipmentSchema from lcfs.web.api.final_supply_equipment.schema import ( FinalSupplyEquipmentCreateSchema, @@ -21,41 +22,52 @@ class FinalSupplyEquipmentServices: def __init__( - self, request: Request = None, repo: FinalSupplyEquipmentRepository = Depends() + self, + request: Request = None, + repo: FinalSupplyEquipmentRepository = Depends(), + compliance_report_repo: ComplianceReportRepository = Depends(), ) -> None: self.request = request self.repo = repo + self.compliance_report_repo = compliance_report_repo @service_handler async def get_fse_options(self): """Fetches all FSE options concurrently.""" - organization = self.request.user.organization - ( - intended_use_types, - levels_of_equipment, - fuel_measurement_types, - intended_user_types, - ports, - organization_names, - ) = await self.repo.get_fse_options(organization) - - return { - "intended_use_types": [ - EndUseTypeSchema.model_validate(t) for t in intended_use_types - ], - "levels_of_equipment": [ - LevelOfEquipmentSchema.model_validate(l) for l in levels_of_equipment - ], - "fuel_measurement_types": [ - FuelMeasurementTypeSchema.model_validate(f) - for f in fuel_measurement_types - ], - "intended_user_types": [ - EndUserTypeSchema.model_validate(u) for u in intended_user_types - ], - "ports": [port.value for port in ports], - "organization_names": organization_names, - } + try: + organization = getattr(self.request.user, 'organization', None) + ( + intended_use_types, + levels_of_equipment, + fuel_measurement_types, + intended_user_types, + ports, + organization_names, + ) = await self.repo.get_fse_options(organization) + + return { + "intended_use_types": [ + EndUseTypeSchema.model_validate(t) for t in intended_use_types + ], + "levels_of_equipment": [ + LevelOfEquipmentSchema.model_validate(l) for l in levels_of_equipment + ], + "fuel_measurement_types": [ + FuelMeasurementTypeSchema.model_validate(f) + for f in fuel_measurement_types + ], + "intended_user_types": [ + EndUserTypeSchema.model_validate(u) for u in intended_user_types + ], + "ports": [port.value for port in ports], + "organization_names": organization_names, + } + except Exception as e: + logger.error("Error getting FSE options", error=str(e)) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Error retrieving FSE options" + ) async def convert_to_fse_model(self, fse: FinalSupplyEquipmentCreateSchema): fse_model = FinalSupplyEquipment( @@ -296,3 +308,18 @@ async def generate_registration_number(self, postal_code: str) -> str: async def search_manufacturers(self, query: str) -> list[str]: """Search for manufacturers based on the provided query.""" return await self.repo.search_manufacturers(query) + + @service_handler + async def get_compliance_report_by_id(self, compliance_report_id: int): + """Get compliance report by period with status""" + compliance_report = await self.compliance_report_repo.get_compliance_report_by_id( + compliance_report_id, + ) + + if not compliance_report: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Compliance report not found for this period" + ) + + return compliance_report \ No newline at end of file diff --git a/backend/lcfs/web/api/final_supply_equipment/views.py b/backend/lcfs/web/api/final_supply_equipment/views.py index b061922df..e1070f439 100644 --- a/backend/lcfs/web/api/final_supply_equipment/views.py +++ b/backend/lcfs/web/api/final_supply_equipment/views.py @@ -4,6 +4,7 @@ from fastapi import ( APIRouter, Body, + HTTPException, Query, status, Request, @@ -13,6 +14,7 @@ from lcfs.db import dependencies from lcfs.web.api.base import PaginationRequestSchema +from lcfs.web.api.compliance_report.validation import ComplianceReportValidation from lcfs.web.api.compliance_report.schema import ( CommonPaginatedReportRequestSchema, FinalSupplyEquipmentSchema, @@ -59,22 +61,46 @@ async def get_final_supply_equipments( service: FinalSupplyEquipmentServices = Depends(), report_validate: ComplianceReportValidation = Depends(), ) -> FinalSupplyEquipmentsSchema: - """Endpoint to get list of final supply equipments for a compliance report""" - compliance_report_id = request_data.compliance_report_id - await report_validate.validate_organization_access(compliance_report_id) - if hasattr(request_data, "page") and request_data.page is not None: - # handle pagination. - pagination = PaginationRequestSchema( - page=request_data.page, - size=request_data.size, - sort_orders=request_data.sort_orders, - filters=request_data.filters, - ) - return await service.get_final_supply_equipments_paginated( - pagination, compliance_report_id + """ + Endpoint to get list of final supply equipments for a compliance report + """ + try: + compliance_report_id = request_data.compliance_report_id + + compliance_report = await service.get_compliance_report_by_id(compliance_report_id) + if not compliance_report: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Compliance report not found" + ) + + await report_validate.validate_compliance_report_access(compliance_report) + await report_validate.validate_organization_access(compliance_report_id) + + if hasattr(request_data, "page") and request_data.page is not None: + # Handle pagination + pagination = PaginationRequestSchema( + page=request_data.page, + size=request_data.size, + sort_orders=request_data.sort_orders, + filters=request_data.filters, + ) + return await service.get_final_supply_equipments_paginated( + pagination, compliance_report_id + ) + else: + return await service.get_fse_list(compliance_report_id) + except HTTPException as http_ex: + # Re-raise HTTP exceptions to preserve status code and message + raise http_ex + except Exception as e: + # Log and handle unexpected errors + logger.exception("Error occurred", error=str(e)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="An unexpected error occurred while processing your request" ) - else: - return await service.get_fse_list(compliance_report_id) + @router.post( diff --git a/backend/lcfs/web/api/fuel_export/services.py b/backend/lcfs/web/api/fuel_export/services.py index 3fc118a71..4315cfa3a 100644 --- a/backend/lcfs/web/api/fuel_export/services.py +++ b/backend/lcfs/web/api/fuel_export/services.py @@ -1,7 +1,7 @@ import math import structlog -from fastapi import Depends, Request +from fastapi import Depends, HTTPException, Request, status from lcfs.utils.constants import default_ci from lcfs.web.api.base import ( @@ -259,3 +259,18 @@ async def get_fuel_exports_paginated( ), fuel_exports=[FuelExportSchema.model_validate(fs) for fs in fuel_exports], ) + + @service_handler + async def get_compliance_report_by_id(self, compliance_report_id: int): + """Get compliance report by period with status""" + compliance_report = await self.compliance_report_repo.get_compliance_report_by_id( + compliance_report_id, + ) + + if not compliance_report: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Compliance report not found for this period" + ) + + return compliance_report \ No newline at end of file diff --git a/backend/lcfs/web/api/fuel_export/views.py b/backend/lcfs/web/api/fuel_export/views.py index 292f61326..d44321419 100644 --- a/backend/lcfs/web/api/fuel_export/views.py +++ b/backend/lcfs/web/api/fuel_export/views.py @@ -58,21 +58,42 @@ async def get_fuel_exports( report_validate: ComplianceReportValidation = Depends(), ) -> FuelExportsSchema: """Endpoint to get list of fuel supplied list for a compliance report""" - compliance_report_id = request_data.compliance_report_id - await report_validate.validate_organization_access(compliance_report_id) - if hasattr(request_data, "page") and request_data.page is not None: - # handle pagination. - pagination = PaginationRequestSchema( - page=request_data.page, - size=request_data.size, - sort_orders=request_data.sort_orders, - filters=request_data.filters, - ) - return await service.get_fuel_exports_paginated( - pagination, compliance_report_id + try: + compliance_report_id = request_data.compliance_report_id + + compliance_report = await service.get_compliance_report_by_id(compliance_report_id) + if not compliance_report: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Compliance report not found" + ) + + await report_validate.validate_compliance_report_access(compliance_report) + await report_validate.validate_organization_access(compliance_report_id) + if hasattr(request_data, "page") and request_data.page is not None: + # handle pagination. + pagination = PaginationRequestSchema( + page=request_data.page, + size=request_data.size, + sort_orders=request_data.sort_orders, + filters=request_data.filters, + ) + return await service.get_fuel_exports_paginated( + pagination, compliance_report_id + ) + else: + return await service.get_fuel_export_list(compliance_report_id) + except HTTPException as http_ex: + # Re-raise HTTP exceptions to preserve status code and message + raise http_ex + except Exception as e: + # Log and handle unexpected errors + logger.exception("Error occurred", error=str(e)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="An unexpected error occurred while processing your request" ) - else: - return await service.get_fuel_export_list(compliance_report_id) + @router.post( diff --git a/backend/lcfs/web/api/fuel_supply/services.py b/backend/lcfs/web/api/fuel_supply/services.py index 3eb146e1e..f9d6460ee 100644 --- a/backend/lcfs/web/api/fuel_supply/services.py +++ b/backend/lcfs/web/api/fuel_supply/services.py @@ -1,8 +1,9 @@ import structlog import math -from fastapi import Depends, Request, HTTPException +from fastapi import Depends, Request, HTTPException, status from lcfs.web.api.base import PaginationRequestSchema, PaginationResponseSchema +from lcfs.web.api.compliance_report.repo import ComplianceReportRepository from lcfs.web.api.fuel_code.repo import FuelCodeRepository from lcfs.web.api.fuel_supply.schema import ( EndUseTypeSchema, @@ -33,10 +34,12 @@ def __init__( request: Request = None, repo: FuelSupplyRepository = Depends(), fuel_repo: FuelCodeRepository = Depends(), + compliance_report_repo: ComplianceReportRepository = Depends(), ) -> None: self.request = request self.repo = repo self.fuel_repo = fuel_repo + self.compliance_report_repo = compliance_report_repo def fuel_type_row_mapper(self, compliance_period, fuel_types, row): column_names = row._fields @@ -268,3 +271,18 @@ async def get_fuel_supplies_paginated( FuelSupplyResponseSchema.model_validate(fs) for fs in fuel_supplies ], ) + + @service_handler + async def get_compliance_report_by_id(self, compliance_report_id: int): + """Get compliance report by period with status""" + compliance_report = await self.compliance_report_repo.get_compliance_report_by_id( + compliance_report_id, + ) + + if not compliance_report: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Compliance report not found for this period" + ) + + return compliance_report \ No newline at end of file diff --git a/backend/lcfs/web/api/fuel_supply/views.py b/backend/lcfs/web/api/fuel_supply/views.py index cb95c2597..ead977380 100644 --- a/backend/lcfs/web/api/fuel_supply/views.py +++ b/backend/lcfs/web/api/fuel_supply/views.py @@ -48,21 +48,40 @@ async def get_fuel_supply( report_validate: ComplianceReportValidation = Depends(), ) -> FuelSuppliesSchema: """Endpoint to get list of fuel supplied list for a compliance report""" - compliance_report_id = request_data.compliance_report_id - await report_validate.validate_organization_access(compliance_report_id) - if hasattr(request_data, "page") and request_data.page is not None: - # Handle pagination. - pagination = PaginationRequestSchema( - page=request_data.page, - size=request_data.size, - sort_orders=request_data.sort_orders, - filters=request_data.filters, - ) - return await service.get_fuel_supplies_paginated( - pagination, compliance_report_id + try: + compliance_report_id = request_data.compliance_report_id + compliance_report = await service.get_compliance_report_by_id(compliance_report_id) + if not compliance_report: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Compliance report not found" + ) + + await report_validate.validate_compliance_report_access(compliance_report) + await report_validate.validate_organization_access(compliance_report_id) + if hasattr(request_data, "page") and request_data.page is not None: + # Handle pagination. + pagination = PaginationRequestSchema( + page=request_data.page, + size=request_data.size, + sort_orders=request_data.sort_orders, + filters=request_data.filters, + ) + return await service.get_fuel_supplies_paginated( + pagination, compliance_report_id + ) + else: + return await service.get_fuel_supply_list(compliance_report_id) + except HTTPException as http_ex: + # Re-raise HTTP exceptions to preserve status code and message + raise http_ex + except Exception as e: + # Log and handle unexpected errors + logger.exception("Error occurred", error=str(e)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="An unexpected error occurred while processing your request" ) - else: - return await service.get_fuel_supply_list(compliance_report_id) @router.post( diff --git a/backend/lcfs/web/api/notional_transfer/services.py b/backend/lcfs/web/api/notional_transfer/services.py index 5b66c5cfc..86f3ce212 100644 --- a/backend/lcfs/web/api/notional_transfer/services.py +++ b/backend/lcfs/web/api/notional_transfer/services.py @@ -3,10 +3,11 @@ from typing import Optional import structlog -from fastapi import Depends +from fastapi import Depends, HTTPException, status from lcfs.db.base import UserTypeEnum, ActionTypeEnum from lcfs.db.models.compliance.NotionalTransfer import NotionalTransfer +from lcfs.web.api.compliance_report.repo import ComplianceReportRepository from lcfs.web.api.base import PaginationRequestSchema, PaginationResponseSchema from lcfs.web.api.fuel_code.repo import FuelCodeRepository from lcfs.web.api.notional_transfer.repo import NotionalTransferRepository @@ -40,9 +41,11 @@ def __init__( self, repo: NotionalTransferRepository = Depends(NotionalTransferRepository), fuel_repo: FuelCodeRepository = Depends(), + compliance_report_repo: ComplianceReportRepository = Depends(), ) -> None: self.repo = repo self.fuel_repo = fuel_repo + self.compliance_report_repo = compliance_report_repo async def convert_to_model( self, notional_transfer_data: NotionalTransferCreateSchema @@ -235,3 +238,18 @@ async def delete_notional_transfer( await self.repo.create_notional_transfer(deleted_entity) return DeleteNotionalTransferResponseSchema(message="Marked as deleted.") + + @service_handler + async def get_compliance_report_by_id(self, compliance_report_id: int): + """Get compliance report by period with status""" + compliance_report = await self.compliance_report_repo.get_compliance_report_by_id( + compliance_report_id, + ) + + if not compliance_report: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Compliance report not found for this period" + ) + + return compliance_report \ No newline at end of file diff --git a/backend/lcfs/web/api/notional_transfer/views.py b/backend/lcfs/web/api/notional_transfer/views.py index 056cc07d5..97a108a3e 100644 --- a/backend/lcfs/web/api/notional_transfer/views.py +++ b/backend/lcfs/web/api/notional_transfer/views.py @@ -67,11 +67,32 @@ async def get_notional_transfers( report_validate: ComplianceReportValidation = Depends(), ): """Endpoint to get list of notional transfers for a compliance report""" - await report_validate.validate_organization_access( + try: request_data.compliance_report_id - ) - return await service.get_notional_transfers(request_data.compliance_report_id) + compliance_report = await service.get_compliance_report_by_id(request_data.compliance_report_id) + if not compliance_report: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Compliance report not found" + ) + + await report_validate.validate_compliance_report_access(compliance_report) + await report_validate.validate_organization_access( + request_data.compliance_report_id + ) + return await service.get_notional_transfers(request_data.compliance_report_id) + + except HTTPException as http_ex: + # Re-raise HTTP exceptions to preserve status code and message + raise http_ex + except Exception as e: + # Log and handle unexpected errors + logger.exception("Error occurred", error=str(e)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="An unexpected error occurred while processing your request" + ) @router.post( "/list", diff --git a/backend/lcfs/web/api/other_uses/services.py b/backend/lcfs/web/api/other_uses/services.py index ac4bd29f7..61e2c52d7 100644 --- a/backend/lcfs/web/api/other_uses/services.py +++ b/backend/lcfs/web/api/other_uses/services.py @@ -3,9 +3,10 @@ from typing import Optional import structlog -from fastapi import Depends +from fastapi import Depends, HTTPException, status from lcfs.db.base import UserTypeEnum, ActionTypeEnum +from lcfs.web.api.compliance_report.repo import ComplianceReportRepository from lcfs.web.api.other_uses.repo import OtherUsesRepository from lcfs.web.core.decorators import service_handler from lcfs.db.models.compliance.OtherUses import OtherUses @@ -44,9 +45,11 @@ def __init__( self, repo: OtherUsesRepository = Depends(OtherUsesRepository), fuel_repo: FuelCodeRepository = Depends(), + compliance_report_repo: ComplianceReportRepository = Depends(), ) -> None: self.repo = repo self.fuel_repo = fuel_repo + self.compliance_report_repo = compliance_report_repo async def schema_to_model(self, other_use: OtherUsesCreateSchema) -> OtherUses: """ @@ -294,3 +297,18 @@ async def delete_other_use( await self.repo.create_other_use(deleted_entity) return DeleteOtherUsesResponseSchema(success=True, message="Marked as deleted.") + + @service_handler + async def get_compliance_report_by_id(self, compliance_report_id: int): + """Get compliance report by period with status""" + compliance_report = await self.compliance_report_repo.get_compliance_report_by_id( + compliance_report_id, + ) + + if not compliance_report: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Compliance report not found for this period" + ) + + return compliance_report \ No newline at end of file diff --git a/backend/lcfs/web/api/other_uses/views.py b/backend/lcfs/web/api/other_uses/views.py index 915f691e6..47f4c3018 100644 --- a/backend/lcfs/web/api/other_uses/views.py +++ b/backend/lcfs/web/api/other_uses/views.py @@ -61,10 +61,31 @@ async def get_other_uses( report_validate: ComplianceReportValidation = Depends(), ): """Endpoint to get list of other uses for a compliance report""" - await report_validate.validate_organization_access( - request_data.compliance_report_id - ) - return await service.get_other_uses(request_data.compliance_report_id) + try: + compliance_report_id = request_data.compliance_report_id + + compliance_report = await service.get_compliance_report_by_id(compliance_report_id) + if not compliance_report: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Compliance report not found" + ) + + await report_validate.validate_compliance_report_access(compliance_report) + await report_validate.validate_organization_access( + request_data.compliance_report_id + ) + return await service.get_other_uses(request_data.compliance_report_id) + except HTTPException as http_ex: + # Re-raise HTTP exceptions to preserve status code and message + raise http_ex + except Exception as e: + # Log and handle unexpected errors + logger.exception("Error occurred", error=str(e)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="An unexpected error occurred while processing your request" + ) @router.post(