From 8b438ffcbb2b1522e37d11ddb45edcc32379b50d Mon Sep 17 00:00:00 2001 From: Arturo Reyes Lopez Date: Thu, 5 Dec 2024 13:40:55 -0700 Subject: [PATCH 1/3] Add organization field to FSE --- .../versions/2024-12-05-22-59_9206124a098b.py | 25 ++++++++++++ .../models/compliance/FinalSupplyEquipment.py | 1 + .../lcfs/web/api/compliance_report/schema.py | 5 +-- .../web/api/final_supply_equipment/repo.py | 39 +++++++++++++++++-- .../web/api/final_supply_equipment/schema.py | 2 + .../api/final_supply_equipment/services.py | 8 +++- .../locales/en/finalSupplyEquipment.json | 1 + .../FinalSupplyEquipmentSummary.jsx | 6 +++ .../views/FinalSupplyEquipments/_schema.jsx | 38 +++++++++++++++++- 9 files changed, 114 insertions(+), 11 deletions(-) create mode 100644 backend/lcfs/db/migrations/versions/2024-12-05-22-59_9206124a098b.py diff --git a/backend/lcfs/db/migrations/versions/2024-12-05-22-59_9206124a098b.py b/backend/lcfs/db/migrations/versions/2024-12-05-22-59_9206124a098b.py new file mode 100644 index 000000000..a32a83434 --- /dev/null +++ b/backend/lcfs/db/migrations/versions/2024-12-05-22-59_9206124a098b.py @@ -0,0 +1,25 @@ +"""Add Organization to FSE + +Revision ID: 9206124a098b +Revises: aeaa26f5cdd5 +Create Date: 2024-12-04 09:59:22.876386 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = '9206124a098b' +down_revision = '8491890dd688' +branch_labels = None +depends_on = None + + +def upgrade(): + # Add the column 'organization' to 'final_supply_equipment' table + op.add_column("final_supply_equipment", sa.Column("organization", sa.String(), nullable=True)) + + +def downgrade(): + # Remove the column 'organization' from 'final_supply_equipment' table + op.drop_column("final_supply_equipment", "organization") \ No newline at end of file diff --git a/backend/lcfs/db/models/compliance/FinalSupplyEquipment.py b/backend/lcfs/db/models/compliance/FinalSupplyEquipment.py index e3b8d5685..263f38914 100644 --- a/backend/lcfs/db/models/compliance/FinalSupplyEquipment.py +++ b/backend/lcfs/db/models/compliance/FinalSupplyEquipment.py @@ -123,6 +123,7 @@ class FinalSupplyEquipment(BaseModel, Auditable): Double, nullable=False, comment="The longitude of the equipment location." ) notes = Column(Text, comment="Any additional notes related to the equipment.") + organization = Column(Text, comment="External organization.") # relationships compliance_report = relationship( diff --git a/backend/lcfs/web/api/compliance_report/schema.py b/backend/lcfs/web/api/compliance_report/schema.py index 0f157be8b..5d6f1ec1e 100644 --- a/backend/lcfs/web/api/compliance_report/schema.py +++ b/backend/lcfs/web/api/compliance_report/schema.py @@ -40,10 +40,6 @@ class CompliancePeriodSchema(BaseSchema): display_order: Optional[int] = None -class ComplianceReportOrganizationSchema(BaseSchema): - organization_id: int - name: str - class SummarySchema(BaseSchema): summary_id: int @@ -118,6 +114,7 @@ class FSEOptionsSchema(BaseSchema): class FinalSupplyEquipmentSchema(BaseSchema): final_supply_equipment_id: int compliance_report_id: int + organization: str supply_from_date: date supply_to_date: date registration_nbr: str diff --git a/backend/lcfs/web/api/final_supply_equipment/repo.py b/backend/lcfs/web/api/final_supply_equipment/repo.py index 398f01585..55763c0aa 100644 --- a/backend/lcfs/web/api/final_supply_equipment/repo.py +++ b/backend/lcfs/web/api/final_supply_equipment/repo.py @@ -1,6 +1,6 @@ import structlog from typing import List, Tuple -from lcfs.db.models.compliance import EndUserType, FinalSupplyEquipment +from lcfs.db.models.compliance import EndUserType, FinalSupplyEquipment, ComplianceReport from lcfs.db.models.compliance.FinalSupplyEquipmentRegNumber import ( FinalSupplyEquipmentRegNumber, ) @@ -27,8 +27,14 @@ def __init__(self, db: AsyncSession = Depends(get_async_db_session)): @repo_handler async def get_fse_options( - self, - ) -> Tuple[List[EndUseType], List[LevelOfEquipment], List[FuelMeasurementType], List[PortsEnum]]: + self, organization + ) -> Tuple[ + List[EndUseType], + List[LevelOfEquipment], + List[FuelMeasurementType], + List[PortsEnum], + List[str], + ]: """ Retrieve all FSE options in a single database transaction """ @@ -37,13 +43,15 @@ async def get_fse_options( levels_of_equipment = await self.get_levels_of_equipment() fuel_measurement_types = await self.get_fuel_measurement_types() intended_user_types = await self.get_intended_user_types() + organizations = await self.get_organizations(organization) ports = list(PortsEnum) return ( intended_use_types, levels_of_equipment, fuel_measurement_types, intended_user_types, - ports + ports, + organizations, ) async def get_intended_use_types(self) -> List[EndUseType]: @@ -94,6 +102,29 @@ async def get_intended_user_types(self) -> List[EndUserType]: .all() ) + async def get_organizations(self, organization) -> List[str]: + """ + Retrieve unique organization names for Final Supply Equipment records + associated with the given organization_id via ComplianceReport. + + Args: + organization_id (int): The ID of the organization. + + Returns: + List[str]: A list of unique organization names. + """ + organization_names = ( + await self.db.execute( + select(distinct(FinalSupplyEquipment.organization)) + .join(ComplianceReport, FinalSupplyEquipment.compliance_report_id == ComplianceReport.compliance_report_id) + .filter(ComplianceReport.organization_id == organization.organization_id) + .filter(FinalSupplyEquipment.organization.isnot(None)) + ) + ).all() + + # Extract strings from the list of tuples + return [name[0] for name in organization_names] + @repo_handler async def get_intended_user_by_name(self, intended_user: str) -> EndUseType: """ diff --git a/backend/lcfs/web/api/final_supply_equipment/schema.py b/backend/lcfs/web/api/final_supply_equipment/schema.py index 38f80b2fa..fbe7ed468 100644 --- a/backend/lcfs/web/api/final_supply_equipment/schema.py +++ b/backend/lcfs/web/api/final_supply_equipment/schema.py @@ -33,11 +33,13 @@ class FSEOptionsSchema(BaseSchema): levels_of_equipment: List[LevelOfEquipmentSchema] intended_user_types: List[EndUserTypeSchema] ports: List[PortsEnum] + organizations: List[str] class FinalSupplyEquipmentCreateSchema(BaseSchema): final_supply_equipment_id: Optional[int] = None compliance_report_id: Optional[int] = None + organization: str supply_from_date: date supply_to_date: date kwh_usage: float diff --git a/backend/lcfs/web/api/final_supply_equipment/services.py b/backend/lcfs/web/api/final_supply_equipment/services.py index 9cc225935..c2d1fb5e8 100644 --- a/backend/lcfs/web/api/final_supply_equipment/services.py +++ b/backend/lcfs/web/api/final_supply_equipment/services.py @@ -29,13 +29,15 @@ def __init__( @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 - ) = await self.repo.get_fse_options() + ports, + organizations + ) = await self.repo.get_fse_options(organization) return { "intended_use_types": [ @@ -52,6 +54,7 @@ async def get_fse_options(self): EndUserTypeSchema.model_validate(u) for u in intended_user_types ], "ports": [port.value for port in ports], + "organizations": organizations, } async def convert_to_fse_model(self, fse: FinalSupplyEquipmentCreateSchema): @@ -141,6 +144,7 @@ async def update_final_supply_equipment( if not existing_fse: raise ValueError("final supply equipment not found") + existing_fse.organization = fse_data.organization existing_fse.kwh_usage = fse_data.kwh_usage existing_fse.serial_nbr = fse_data.serial_nbr existing_fse.manufacturer = fse_data.manufacturer diff --git a/frontend/src/assets/locales/en/finalSupplyEquipment.json b/frontend/src/assets/locales/en/finalSupplyEquipment.json index 00e74933f..b8449d574 100644 --- a/frontend/src/assets/locales/en/finalSupplyEquipment.json +++ b/frontend/src/assets/locales/en/finalSupplyEquipment.json @@ -27,6 +27,7 @@ "rows": "rows", "finalSupplyEquipmentColLabels": { "complianceReportId": "Compliance Report ID", + "organization": "Organization", "supplyFrom": "Supply date range", "kwhUsage":"kWh usage", "supplyFromDate": "Dates of supply from", diff --git a/frontend/src/views/FinalSupplyEquipments/FinalSupplyEquipmentSummary.jsx b/frontend/src/views/FinalSupplyEquipments/FinalSupplyEquipmentSummary.jsx index 84437a91b..81ce8415d 100644 --- a/frontend/src/views/FinalSupplyEquipments/FinalSupplyEquipmentSummary.jsx +++ b/frontend/src/views/FinalSupplyEquipments/FinalSupplyEquipmentSummary.jsx @@ -48,6 +48,12 @@ export const FinalSupplyEquipmentSummary = ({ data }) => { ) const columns = useMemo( () => [ + { + headerName: t( + 'finalSupplyEquipment:finalSupplyEquipmentColLabels.organization' + ), + field: 'organization' + }, { headerName: t( 'finalSupplyEquipment:finalSupplyEquipmentColLabels.supplyFromDate' diff --git a/frontend/src/views/FinalSupplyEquipments/_schema.jsx b/frontend/src/views/FinalSupplyEquipments/_schema.jsx index 2974a9725..244eaa112 100644 --- a/frontend/src/views/FinalSupplyEquipments/_schema.jsx +++ b/frontend/src/views/FinalSupplyEquipments/_schema.jsx @@ -12,7 +12,7 @@ import i18n from '@/i18n' import { actions, validation } from '@/components/BCDataGrid/columns' import moment from 'moment' import { CommonArrayRenderer } from '@/utils/grid/cellRenderers' -import { StandardCellErrors } from '@/utils/grid/errorRenderers' +import { StandardCellWarningAndErrors, StandardCellErrors } from '@/utils/grid/errorRenderers' import { apiRoutes } from '@/constants/routes' import { numberFormatter } from '@/utils/formatters.js' @@ -41,6 +41,42 @@ export const finalSupplyEquipmentColDefs = ( cellDataType: 'text', hide: true }, + { + field: 'organization', + headerComponent: RequiredHeader, + headerName: i18n.t( + 'finalSupplyEquipment:finalSupplyEquipmentColLabels.organization' + ), + cellEditor: AutocompleteCellEditor, + cellRenderer: (params) => + params.value || + (!params.value && Select), + cellEditorParams: { + options: optionsData?.organizations?.sort() || [], + multiple: false, + disableCloseOnSelect: false, + freeSolo: true, + openOnFocus: true, + }, + cellStyle: (params) => + StandardCellWarningAndErrors(params, errors), + suppressKeyboardEvent, + minWidth: 260, + editable: true, + valueGetter: (params) => { + return params.data?.organization || ''; + }, + valueSetter: (params) => { + if (params.newValue) { + const isValidOrganization = optionsData?.organizations.includes(params.newValue); + + params.data.organization = isValidOrganization ? params.newValue : params.newValue; + return true; + } + return false; + }, + tooltipValueGetter: (params) => "Select the organization from the list" + }, { field: 'supplyFrom', headerName: i18n.t( From 81857ea7f1476c5636f7df81432654ddeecd42e5 Mon Sep 17 00:00:00 2001 From: Arturo Reyes Lopez Date: Mon, 9 Dec 2024 10:02:06 -0700 Subject: [PATCH 2/3] rename organization for organization_name --- ...24a098b.py => 2024-12-06-09-59_9206124a098b.py} | 12 ++++++------ .../db/models/compliance/FinalSupplyEquipment.py | 2 +- backend/lcfs/web/api/compliance_report/schema.py | 2 +- .../lcfs/web/api/final_supply_equipment/repo.py | 10 +++++----- .../lcfs/web/api/final_supply_equipment/schema.py | 4 ++-- .../web/api/final_supply_equipment/services.py | 8 ++++---- .../assets/locales/en/finalSupplyEquipment.json | 2 +- .../FinalSupplyEquipmentSummary.jsx | 4 ++-- .../src/views/FinalSupplyEquipments/_schema.jsx | 14 +++++++------- 9 files changed, 29 insertions(+), 29 deletions(-) rename backend/lcfs/db/migrations/versions/{2024-12-05-22-59_9206124a098b.py => 2024-12-06-09-59_9206124a098b.py} (53%) diff --git a/backend/lcfs/db/migrations/versions/2024-12-05-22-59_9206124a098b.py b/backend/lcfs/db/migrations/versions/2024-12-06-09-59_9206124a098b.py similarity index 53% rename from backend/lcfs/db/migrations/versions/2024-12-05-22-59_9206124a098b.py rename to backend/lcfs/db/migrations/versions/2024-12-06-09-59_9206124a098b.py index a32a83434..fc805ff14 100644 --- a/backend/lcfs/db/migrations/versions/2024-12-05-22-59_9206124a098b.py +++ b/backend/lcfs/db/migrations/versions/2024-12-06-09-59_9206124a098b.py @@ -1,4 +1,4 @@ -"""Add Organization to FSE +"""Add Organization name to FSE Revision ID: 9206124a098b Revises: aeaa26f5cdd5 @@ -10,16 +10,16 @@ # revision identifiers, used by Alembic. revision = '9206124a098b' -down_revision = '8491890dd688' +down_revision = '26ab15f8ab18' branch_labels = None depends_on = None def upgrade(): - # Add the column 'organization' to 'final_supply_equipment' table - op.add_column("final_supply_equipment", sa.Column("organization", sa.String(), nullable=True)) + # Add the column 'organization_name' to 'final_supply_equipment' table + op.add_column("final_supply_equipment", sa.Column("organization_name", sa.String(), nullable=True)) def downgrade(): - # Remove the column 'organization' from 'final_supply_equipment' table - op.drop_column("final_supply_equipment", "organization") \ No newline at end of file + # Remove the column 'organization_name' from 'final_supply_equipment' table + op.drop_column("final_supply_equipment", "organization_name") \ No newline at end of file diff --git a/backend/lcfs/db/models/compliance/FinalSupplyEquipment.py b/backend/lcfs/db/models/compliance/FinalSupplyEquipment.py index 263f38914..90bd37b27 100644 --- a/backend/lcfs/db/models/compliance/FinalSupplyEquipment.py +++ b/backend/lcfs/db/models/compliance/FinalSupplyEquipment.py @@ -123,7 +123,7 @@ class FinalSupplyEquipment(BaseModel, Auditable): Double, nullable=False, comment="The longitude of the equipment location." ) notes = Column(Text, comment="Any additional notes related to the equipment.") - organization = Column(Text, comment="External organization.") + organization_name = Column(Text, comment="External organization name.") # relationships compliance_report = relationship( diff --git a/backend/lcfs/web/api/compliance_report/schema.py b/backend/lcfs/web/api/compliance_report/schema.py index 5d6f1ec1e..9eb215c53 100644 --- a/backend/lcfs/web/api/compliance_report/schema.py +++ b/backend/lcfs/web/api/compliance_report/schema.py @@ -114,7 +114,7 @@ class FSEOptionsSchema(BaseSchema): class FinalSupplyEquipmentSchema(BaseSchema): final_supply_equipment_id: int compliance_report_id: int - organization: str + organization_name: str supply_from_date: date supply_to_date: date registration_nbr: str diff --git a/backend/lcfs/web/api/final_supply_equipment/repo.py b/backend/lcfs/web/api/final_supply_equipment/repo.py index 55763c0aa..b3680584f 100644 --- a/backend/lcfs/web/api/final_supply_equipment/repo.py +++ b/backend/lcfs/web/api/final_supply_equipment/repo.py @@ -43,7 +43,7 @@ async def get_fse_options( levels_of_equipment = await self.get_levels_of_equipment() fuel_measurement_types = await self.get_fuel_measurement_types() intended_user_types = await self.get_intended_user_types() - organizations = await self.get_organizations(organization) + organization_names = await self.get_organization_names(organization) ports = list(PortsEnum) return ( intended_use_types, @@ -51,7 +51,7 @@ async def get_fse_options( fuel_measurement_types, intended_user_types, ports, - organizations, + organization_names, ) async def get_intended_use_types(self) -> List[EndUseType]: @@ -102,7 +102,7 @@ async def get_intended_user_types(self) -> List[EndUserType]: .all() ) - async def get_organizations(self, organization) -> List[str]: + async def get_organization_names(self, organization) -> List[str]: """ Retrieve unique organization names for Final Supply Equipment records associated with the given organization_id via ComplianceReport. @@ -115,10 +115,10 @@ async def get_organizations(self, organization) -> List[str]: """ organization_names = ( await self.db.execute( - select(distinct(FinalSupplyEquipment.organization)) + 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.isnot(None)) + .filter(FinalSupplyEquipment.organization_name.isnot(None)) ) ).all() diff --git a/backend/lcfs/web/api/final_supply_equipment/schema.py b/backend/lcfs/web/api/final_supply_equipment/schema.py index fbe7ed468..2dc81e8f3 100644 --- a/backend/lcfs/web/api/final_supply_equipment/schema.py +++ b/backend/lcfs/web/api/final_supply_equipment/schema.py @@ -33,13 +33,13 @@ class FSEOptionsSchema(BaseSchema): levels_of_equipment: List[LevelOfEquipmentSchema] intended_user_types: List[EndUserTypeSchema] ports: List[PortsEnum] - organizations: List[str] + organization_names: List[str] class FinalSupplyEquipmentCreateSchema(BaseSchema): final_supply_equipment_id: Optional[int] = None compliance_report_id: Optional[int] = None - organization: str + organization_name: str supply_from_date: date supply_to_date: date kwh_usage: float diff --git a/backend/lcfs/web/api/final_supply_equipment/services.py b/backend/lcfs/web/api/final_supply_equipment/services.py index c2d1fb5e8..a70b1ce4b 100644 --- a/backend/lcfs/web/api/final_supply_equipment/services.py +++ b/backend/lcfs/web/api/final_supply_equipment/services.py @@ -29,14 +29,14 @@ def __init__( @service_handler async def get_fse_options(self): """Fetches all FSE options concurrently.""" - organization= self.request.user.organization + organization = self.request.user.organization ( intended_use_types, levels_of_equipment, fuel_measurement_types, intended_user_types, ports, - organizations + organization_names, ) = await self.repo.get_fse_options(organization) return { @@ -54,7 +54,7 @@ async def get_fse_options(self): EndUserTypeSchema.model_validate(u) for u in intended_user_types ], "ports": [port.value for port in ports], - "organizations": organizations, + "organization_names": organization_names, } async def convert_to_fse_model(self, fse: FinalSupplyEquipmentCreateSchema): @@ -144,7 +144,7 @@ async def update_final_supply_equipment( if not existing_fse: raise ValueError("final supply equipment not found") - existing_fse.organization = fse_data.organization + existing_fse.organization_name = fse_data.organization_name existing_fse.kwh_usage = fse_data.kwh_usage existing_fse.serial_nbr = fse_data.serial_nbr existing_fse.manufacturer = fse_data.manufacturer diff --git a/frontend/src/assets/locales/en/finalSupplyEquipment.json b/frontend/src/assets/locales/en/finalSupplyEquipment.json index b8449d574..d48862f59 100644 --- a/frontend/src/assets/locales/en/finalSupplyEquipment.json +++ b/frontend/src/assets/locales/en/finalSupplyEquipment.json @@ -27,7 +27,7 @@ "rows": "rows", "finalSupplyEquipmentColLabels": { "complianceReportId": "Compliance Report ID", - "organization": "Organization", + "organizationName": "Organization", "supplyFrom": "Supply date range", "kwhUsage":"kWh usage", "supplyFromDate": "Dates of supply from", diff --git a/frontend/src/views/FinalSupplyEquipments/FinalSupplyEquipmentSummary.jsx b/frontend/src/views/FinalSupplyEquipments/FinalSupplyEquipmentSummary.jsx index 81ce8415d..1a2f7851e 100644 --- a/frontend/src/views/FinalSupplyEquipments/FinalSupplyEquipmentSummary.jsx +++ b/frontend/src/views/FinalSupplyEquipments/FinalSupplyEquipmentSummary.jsx @@ -50,9 +50,9 @@ export const FinalSupplyEquipmentSummary = ({ data }) => { () => [ { headerName: t( - 'finalSupplyEquipment:finalSupplyEquipmentColLabels.organization' + 'finalSupplyEquipment:finalSupplyEquipmentColLabels.organizationName' ), - field: 'organization' + field: 'organizationName' }, { headerName: t( diff --git a/frontend/src/views/FinalSupplyEquipments/_schema.jsx b/frontend/src/views/FinalSupplyEquipments/_schema.jsx index 244eaa112..b00d5f437 100644 --- a/frontend/src/views/FinalSupplyEquipments/_schema.jsx +++ b/frontend/src/views/FinalSupplyEquipments/_schema.jsx @@ -42,17 +42,17 @@ export const finalSupplyEquipmentColDefs = ( hide: true }, { - field: 'organization', + field: 'organizationName', headerComponent: RequiredHeader, headerName: i18n.t( - 'finalSupplyEquipment:finalSupplyEquipmentColLabels.organization' + 'finalSupplyEquipment:finalSupplyEquipmentColLabels.organizationName' ), cellEditor: AutocompleteCellEditor, cellRenderer: (params) => params.value || (!params.value && Select), cellEditorParams: { - options: optionsData?.organizations?.sort() || [], + options: optionsData?.organizationNames?.sort() || [], multiple: false, disableCloseOnSelect: false, freeSolo: true, @@ -64,18 +64,18 @@ export const finalSupplyEquipmentColDefs = ( minWidth: 260, editable: true, valueGetter: (params) => { - return params.data?.organization || ''; + return params.data?.organizationName || ''; }, valueSetter: (params) => { if (params.newValue) { - const isValidOrganization = optionsData?.organizations.includes(params.newValue); + const isValidOrganizationName = optionsData?.organizationNames.includes(params.newValue); - params.data.organization = isValidOrganization ? params.newValue : params.newValue; + params.data.organizationName = isValidOrganizationName ? params.newValue : params.newValue; return true; } return false; }, - tooltipValueGetter: (params) => "Select the organization from the list" + tooltipValueGetter: (params) => "Select the organization name from the list" }, { field: 'supplyFrom', From 04f1b780b597c1d8964d3bfa36aa702bb4d9d390 Mon Sep 17 00:00:00 2001 From: Arturo Reyes Lopez Date: Wed, 11 Dec 2024 12:12:19 -0700 Subject: [PATCH 3/3] fixing migration issues --- .../db/migrations/versions/2024-12-09-22-33_cd8698fe40e6.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/lcfs/db/migrations/versions/2024-12-09-22-33_cd8698fe40e6.py b/backend/lcfs/db/migrations/versions/2024-12-09-22-33_cd8698fe40e6.py index d812b2a94..8ec2b8223 100644 --- a/backend/lcfs/db/migrations/versions/2024-12-09-22-33_cd8698fe40e6.py +++ b/backend/lcfs/db/migrations/versions/2024-12-09-22-33_cd8698fe40e6.py @@ -11,7 +11,7 @@ # revision identifiers, used by Alembic. revision = "cd8698fe40e6" -down_revision = "26ab15f8ab18" +down_revision = "9206124a098b" branch_labels = None depends_on = None