Skip to content

Commit

Permalink
Merge branch 'release-0.2.0' into LCFS-1515-FuelSupplyOtherJetFuelCat…
Browse files Browse the repository at this point in the history
…egory
  • Loading branch information
areyeslo authored Dec 20, 2024
2 parents eace15c + dfb5501 commit 8174d52
Show file tree
Hide file tree
Showing 31 changed files with 1,266 additions and 305 deletions.
3 changes: 3 additions & 0 deletions backend/lcfs/services/rabbitmq/report_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ async def handle_message(
)
user = await UserRepository(db=session).get_user_by_id(user_id)

if not user:
logger.error(f"Cannot parse Report {legacy_id} from TFRS, no user with ID {user_id}")

if action == "Created":
await self._handle_created(
org_id,
Expand Down
27 changes: 26 additions & 1 deletion backend/lcfs/services/s3/client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from fastapi import HTTPException
import os
import uuid
from lcfs.db.models.compliance.ComplianceReportStatus import ComplianceReportStatusEnum
from lcfs.db.models.user.Role import RoleEnum
from lcfs.web.api.compliance_report.services import ComplianceReportServices
from fastapi import Depends
from pydantic.v1 import ValidationError
from sqlalchemy import select
Expand All @@ -26,13 +30,34 @@ def __init__(
db: AsyncSession = Depends(get_async_db_session),
clamav_service: ClamAVService = Depends(),
s3_client=Depends(get_s3_client),
compliance_report_service: ComplianceReportServices = Depends(),
):
self.db = db
self.clamav_service = clamav_service
self.s3_client = s3_client
self.compliance_report_service = compliance_report_service

@repo_handler
async def upload_file(self, file, parent_id: str, parent_type="compliance_report"):
async def upload_file(
self, file, parent_id: str, parent_type="compliance_report", user=None
):
compliance_report = (
await self.compliance_report_service.get_compliance_report_by_id(parent_id)
)
if not compliance_report:
raise HTTPException(status_code=404, detail="Compliance report not found")

# Check if the user is a supplier and the compliance report status is different from Draft
if (
RoleEnum.SUPPLIER in user.role_names
and compliance_report.current_status.status
!= ComplianceReportStatusEnum.Draft.value
):
raise HTTPException(
status_code=400,
detail="Suppliers can only upload files when the compliance report status is Draft",
)

file_id = uuid.uuid4()
file_key = f"{settings.s3_docs_path}/{parent_type}/{parent_id}/{file_id}"

Expand Down
2 changes: 1 addition & 1 deletion backend/lcfs/tests/fuel_export/test_fuel_exports_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ async def test_update_fuel_export_success(fuel_export_repo, mock_db):
)
updated_fuel_export = FuelExport(fuel_export_id=1)

mock_db.merge = MagicMock(return_value=updated_fuel_export)
mock_db.merge = AsyncMock(return_value=updated_fuel_export)
mock_db.flush = AsyncMock()
mock_db.refresh = AsyncMock()

Expand Down
66 changes: 47 additions & 19 deletions backend/lcfs/web/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,6 @@ def validate_pagination(pagination: PaginationRequestSchema):
Args:
pagination (PaginationRequestSchema): The pagination object to validate.
"""
logger.info("Validating pagination")
logger.debug("Pagination details", pagination=pagination)

if not pagination.page or pagination.page < 1:
pagination.page = 1
if not pagination.size or pagination.size < 1:
Expand Down Expand Up @@ -373,24 +370,55 @@ async def lcfs_cache_key_builder(
# Return the cache key
return cache_key


class NotificationTypeEnum(Enum):
BCEID__COMPLIANCE_REPORT__DIRECTOR_ASSESSMENT = "BCEID__COMPLIANCE_REPORT__DIRECTOR_ASSESSMENT"
BCEID__INITIATIVE_AGREEMENT__DIRECTOR_APPROVAL = "BCEID__INITIATIVE_AGREEMENT__DIRECTOR_APPROVAL"
BCEID__COMPLIANCE_REPORT__DIRECTOR_ASSESSMENT = (
"BCEID__COMPLIANCE_REPORT__DIRECTOR_ASSESSMENT"
)
BCEID__INITIATIVE_AGREEMENT__DIRECTOR_APPROVAL = (
"BCEID__INITIATIVE_AGREEMENT__DIRECTOR_APPROVAL"
)
BCEID__TRANSFER__DIRECTOR_DECISION = "BCEID__TRANSFER__DIRECTOR_DECISION"
BCEID__TRANSFER__PARTNER_ACTIONS = "BCEID__TRANSFER__PARTNER_ACTIONS"
IDIR_ANALYST__COMPLIANCE_REPORT__DIRECTOR_DECISION = "IDIR_ANALYST__COMPLIANCE_REPORT__DIRECTOR_DECISION"
IDIR_ANALYST__COMPLIANCE_REPORT__MANAGER_RECOMMENDATION = "IDIR_ANALYST__COMPLIANCE_REPORT__MANAGER_RECOMMENDATION"
IDIR_ANALYST__COMPLIANCE_REPORT__SUBMITTED_FOR_REVIEW = "IDIR_ANALYST__COMPLIANCE_REPORT__SUBMITTED_FOR_REVIEW"
IDIR_ANALYST__INITIATIVE_AGREEMENT__RETURNED_TO_ANALYST = "IDIR_ANALYST__INITIATIVE_AGREEMENT__RETURNED_TO_ANALYST"
IDIR_ANALYST__TRANSFER__DIRECTOR_RECORDED = "IDIR_ANALYST__TRANSFER__DIRECTOR_RECORDED"
IDIR_ANALYST__TRANSFER__RESCINDED_ACTION = "IDIR_ANALYST__TRANSFER__RESCINDED_ACTION"
IDIR_ANALYST__TRANSFER__SUBMITTED_FOR_REVIEW = "IDIR_ANALYST__TRANSFER__SUBMITTED_FOR_REVIEW"
IDIR_COMPLIANCE_MANAGER__COMPLIANCE_REPORT__ANALYST_RECOMMENDATION = "IDIR_COMPLIANCE_MANAGER__COMPLIANCE_REPORT__ANALYST_RECOMMENDATION"
IDIR_COMPLIANCE_MANAGER__COMPLIANCE_REPORT__DIRECTOR_ASSESSMENT = "IDIR_COMPLIANCE_MANAGER__COMPLIANCE_REPORT__DIRECTOR_ASSESSMENT"
IDIR_COMPLIANCE_MANAGER__COMPLIANCE_REPORT__SUBMITTED_FOR_REVIEW = "IDIR_COMPLIANCE_MANAGER__COMPLIANCE_REPORT__SUBMITTED_FOR_REVIEW"
IDIR_DIRECTOR__COMPLIANCE_REPORT__MANAGER_RECOMMENDATION = "IDIR_DIRECTOR__COMPLIANCE_REPORT__MANAGER_RECOMMENDATION"
IDIR_DIRECTOR__INITIATIVE_AGREEMENT__ANALYST_RECOMMENDATION = "IDIR_DIRECTOR__INITIATIVE_AGREEMENT__ANALYST_RECOMMENDATION"
IDIR_DIRECTOR__TRANSFER__ANALYST_RECOMMENDATION = "IDIR_DIRECTOR__TRANSFER__ANALYST_RECOMMENDATION"
IDIR_ANALYST__COMPLIANCE_REPORT__DIRECTOR_DECISION = (
"IDIR_ANALYST__COMPLIANCE_REPORT__DIRECTOR_DECISION"
)
IDIR_ANALYST__COMPLIANCE_REPORT__MANAGER_RECOMMENDATION = (
"IDIR_ANALYST__COMPLIANCE_REPORT__MANAGER_RECOMMENDATION"
)
IDIR_ANALYST__COMPLIANCE_REPORT__SUBMITTED_FOR_REVIEW = (
"IDIR_ANALYST__COMPLIANCE_REPORT__SUBMITTED_FOR_REVIEW"
)
IDIR_ANALYST__INITIATIVE_AGREEMENT__RETURNED_TO_ANALYST = (
"IDIR_ANALYST__INITIATIVE_AGREEMENT__RETURNED_TO_ANALYST"
)
IDIR_ANALYST__TRANSFER__DIRECTOR_RECORDED = (
"IDIR_ANALYST__TRANSFER__DIRECTOR_RECORDED"
)
IDIR_ANALYST__TRANSFER__RESCINDED_ACTION = (
"IDIR_ANALYST__TRANSFER__RESCINDED_ACTION"
)
IDIR_ANALYST__TRANSFER__SUBMITTED_FOR_REVIEW = (
"IDIR_ANALYST__TRANSFER__SUBMITTED_FOR_REVIEW"
)
IDIR_COMPLIANCE_MANAGER__COMPLIANCE_REPORT__ANALYST_RECOMMENDATION = (
"IDIR_COMPLIANCE_MANAGER__COMPLIANCE_REPORT__ANALYST_RECOMMENDATION"
)
IDIR_COMPLIANCE_MANAGER__COMPLIANCE_REPORT__DIRECTOR_ASSESSMENT = (
"IDIR_COMPLIANCE_MANAGER__COMPLIANCE_REPORT__DIRECTOR_ASSESSMENT"
)
IDIR_COMPLIANCE_MANAGER__COMPLIANCE_REPORT__SUBMITTED_FOR_REVIEW = (
"IDIR_COMPLIANCE_MANAGER__COMPLIANCE_REPORT__SUBMITTED_FOR_REVIEW"
)
IDIR_DIRECTOR__COMPLIANCE_REPORT__MANAGER_RECOMMENDATION = (
"IDIR_DIRECTOR__COMPLIANCE_REPORT__MANAGER_RECOMMENDATION"
)
IDIR_DIRECTOR__INITIATIVE_AGREEMENT__ANALYST_RECOMMENDATION = (
"IDIR_DIRECTOR__INITIATIVE_AGREEMENT__ANALYST_RECOMMENDATION"
)
IDIR_DIRECTOR__TRANSFER__ANALYST_RECOMMENDATION = (
"IDIR_DIRECTOR__TRANSFER__ANALYST_RECOMMENDATION"
)

def __str__(self):
return self.value
return self.value
2 changes: 1 addition & 1 deletion backend/lcfs/web/api/compliance_report/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ class CompliancePeriodSchema(BaseSchema):
display_order: Optional[int] = None



class SummarySchema(BaseSchema):
summary_id: int
is_locked: bool
Expand Down Expand Up @@ -154,6 +153,7 @@ class ComplianceReportBaseSchema(BaseSchema):
update_date: Optional[datetime] = None
history: Optional[List[ComplianceReportHistorySchema]] = None
has_supplemental: bool
legacy_id: Optional[int] = None


class ChainedComplianceReportSchema(BaseSchema):
Expand Down
9 changes: 7 additions & 2 deletions backend/lcfs/web/api/document/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from http.client import HTTPException
from lcfs.db.models.compliance.ComplianceReportStatus import ComplianceReportStatusEnum
from lcfs.web.api.compliance_report.services import ComplianceReportServices
import structlog
from typing import List

Expand Down Expand Up @@ -40,15 +43,17 @@ async def get_all_documents(
response_model=FileResponseSchema,
status_code=status.HTTP_201_CREATED,
)
@view_handler([RoleEnum.SUPPLIER])
@view_handler([RoleEnum.SUPPLIER, RoleEnum.ANALYST])
async def upload_file(
request: Request,
parent_id: int,
parent_type: str,
file: UploadFile = File(...),
document_service: DocumentService = Depends(),
) -> FileResponseSchema:
document = await document_service.upload_file(file, parent_id, parent_type)
document = await document_service.upload_file(
file, parent_id, parent_type, request.user
)
return FileResponseSchema.model_validate(document)


Expand Down
2 changes: 1 addition & 1 deletion backend/lcfs/web/api/fuel_export/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ async def update_fuel_export(self, fuel_export: FuelExport) -> FuelExport:
"""
Update an existing fuel supply row in the database.
"""
updated_fuel_export = self.db.merge(fuel_export)
updated_fuel_export = await self.db.merge(fuel_export)
await self.db.flush()
await self.db.refresh(
updated_fuel_export,
Expand Down
18 changes: 11 additions & 7 deletions backend/lcfs/web/api/fuel_supply/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,20 +443,23 @@ async def get_effective_fuel_supplies(
query = (
select(FuelSupply)
.options(
# Use joinedload for scalar relationships
joinedload(FuelSupply.fuel_code).options(
joinedload(FuelCode.fuel_code_status),
joinedload(FuelCode.fuel_code_prefix),
# Use selectinload for collections
selectinload(FuelSupply.fuel_code).options(
selectinload(FuelCode.fuel_code_status),
selectinload(FuelCode.fuel_code_prefix),
),
joinedload(FuelSupply.fuel_category).options(
joinedload(FuelCategory.target_carbon_intensities),
joinedload(FuelCategory.energy_effectiveness_ratio),
# Use selectinload for one-to-many relationships
selectinload(FuelSupply.fuel_category).options(
selectinload(FuelCategory.target_carbon_intensities),
selectinload(FuelCategory.energy_effectiveness_ratio),
),
# Use joinedload for many-to-one relationships
joinedload(FuelSupply.fuel_type).options(
joinedload(FuelType.energy_density),
joinedload(FuelType.additional_carbon_intensity),
joinedload(FuelType.energy_effectiveness_ratio),
),
# Use joinedload for single relationships
joinedload(FuelSupply.provision_of_the_act),
selectinload(FuelSupply.end_use_type),
)
Expand All @@ -467,6 +470,7 @@ async def get_effective_fuel_supplies(
FuelSupply.version == valid_fuel_supplies_subq.c.max_version,
user_type_priority == valid_fuel_supplies_subq.c.max_role_priority,
),
isouter=False # Explicit inner join
)
.order_by(FuelSupply.create_date.asc())
)
Expand Down
6 changes: 3 additions & 3 deletions backend/lcfs/web/api/user/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,14 @@ async def get_all_users(self, pagination: PaginationRequestSchema) -> UsersSchem
Get all users
"""
users, total_count = await self.repo.get_users_paginated(pagination=pagination)
if len(users) == 0:
raise DataNotFoundException("No users found")
return UsersSchema(
pagination=PaginationResponseSchema(
total=total_count,
page=pagination.page,
size=pagination.size,
total_pages=math.ceil(total_count / pagination.size),
total_pages=(
math.ceil(total_count / pagination.size) if total_count > 0 else 0
),
),
users=users,
)
Expand Down
1 change: 1 addition & 0 deletions frontend/public/config/config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const config = {
api_base: 'http://localhost:8000/api',
tfrs_base: 'http://localhost:3000',
keycloak: {
REALM: 'standard',
CLIENT_ID: 'low-carbon-fuel-standard-5147',
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import { AddEditFuelExports } from './views/FuelExports/AddEditFuelExports'
import { AddEditAllocationAgreements } from './views/AllocationAgreements/AddEditAllocationAgreements'
import { logout } from '@/utils/keycloak.js'
import { CompareReports } from '@/views/CompareReports/CompareReports'
import { ViewLegacyComplianceReport } from '@/views/ComplianceReports/ViewLegacyComplianceReport.jsx'
import { ComplianceReportViewSelector } from '@/views/ComplianceReports/ComplianceReportViewSelector.jsx'

const router = createBrowserRouter([
{
Expand Down Expand Up @@ -196,7 +198,7 @@ const router = createBrowserRouter([
},
{
path: ROUTES.REPORTS_VIEW,
element: <EditViewComplianceReport />,
element: <ComplianceReportViewSelector />,
handle: { title: '' }
},
{
Expand Down
1 change: 1 addition & 0 deletions frontend/src/assets/locales/en/fuelCode.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"fuelCodeDownloadFailMsg": "Failed to download fuel code information.",
"fuelCodeLoadFailMsg": "Failed to load fuel code information.",
"newFuelCodeTitle": "Add new fuel code(s)",
"fuelCodeEntryGuide": "Draft fuel codes are saved automatically when all mandatory fields have been entered and validated. Click “Approve code” to make it available in compliance reporting.",
"editFuelCodeTitle": "Edit draft fuel code",
"viewFuelCodeTitle": "View fuel code",
"approvedFuelCodeTitle": "Approved draft fuel code",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/assets/locales/en/reports.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,6 @@
"totalValue": "Total Value"
},
"summaryLoadingMsg": "Loading compliance report summary...",
"noSigningAuthorityTooltip": "Signing authority role required."
"noSigningAuthorityTooltip": "Signing authority role required.",
"viewLegacyBtn": "View Full Historical Report in TFRS"
}
1 change: 1 addition & 0 deletions frontend/src/constants/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const FEATURE_FLAGS = {

export const CONFIG = {
API_BASE: getApiBaseUrl(),
TFRS_BASE: window.lcfs_config.tfrs_base,
KEYCLOAK: {
REALM: window.lcfs_config.keycloak.REALM ?? 'standard',
CLIENT_ID:
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/constants/routes/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ export const ORGANIZATIONS_EDITUSER = `${ORGANIZATIONS_VIEWUSER}/edit-user`

export const REPORTS = '/compliance-reporting'
export const REPORTS_VIEW = `${REPORTS}/:compliancePeriod/:complianceReportId`
export const REPORTS_COMPARE = `/compare-reporting`
export const REPORTS_ADD_SUPPLY_OF_FUEL = `${REPORTS_VIEW}/supply-of-fuel`
export const REPORTS_ADD_FINAL_SUPPLY_EQUIPMENTS = `${REPORTS_VIEW}/final-supply-equipments`
export const REPORTS_ADD_ALLOCATION_AGREEMENTS = `${REPORTS_VIEW}/allocation-agreements`
export const REPORTS_ADD_NOTIONAL_TRANSFERS = `${REPORTS_VIEW}/notional-transfers`
export const REPORTS_ADD_OTHER_USE_FUELS = `${REPORTS_VIEW}/fuels-other-use`
export const REPORTS_ADD_FUEL_EXPORTS = `${REPORTS_VIEW}/fuel-exports`
export const REPORTS_COMPARE = '/compare-reporting'

export const NOTIFICATIONS = '/notifications'
export const NOTIFICATIONS_SETTINGS = `${NOTIFICATIONS}/configure`
Expand Down
9 changes: 7 additions & 2 deletions frontend/src/utils/formatters.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ import { ROLES_BADGE_SIZE } from '@/constants/common'
* @param {Object|number|string|null} params - The input parameter which can be an object with a `value` property, a number, or a string.
* @param {number|string} [params.value] - The value to be formatted, if params is an object.
* @param {boolean} [useParentheses=false] - Whether to use parentheses for negative numbers.
* @param maxDecimals the max number of decimals to return
* @returns {string} - The formatted number as a string, or the original value if it cannot be parsed as a number.
*/
export const numberFormatter = (params, useParentheses = false) => {
export const numberFormatter = (
params,
useParentheses = false,
maxDecimals = 10
) => {
if (params == null || (typeof params === 'object' && params.value == null))
return ''

Expand All @@ -21,7 +26,7 @@ export const numberFormatter = (params, useParentheses = false) => {
const absValue = Math.abs(parsedValue)
const formattedValue = absValue.toLocaleString(undefined, {
minimumFractionDigits: 0,
maximumFractionDigits: 10
maximumFractionDigits: maxDecimals
})

if (parsedValue < 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useGetComplianceReport } from '@/hooks/useComplianceReports.js'
import { useCurrentUser } from '@/hooks/useCurrentUser.js'
import Loading from '@/components/Loading.jsx'
import { ViewLegacyComplianceReport } from '@/views/ComplianceReports/ViewLegacyComplianceReport.jsx'
import { useParams } from 'react-router-dom'
import { EditViewComplianceReport } from '@/views/ComplianceReports/EditViewComplianceReport.jsx'

export const ComplianceReportViewSelector = () => {
const { complianceReportId } = useParams()
const { data: currentUser, isLoading: isCurrentUserLoading } =
useCurrentUser()

const {
data: reportData,
isLoading: isReportLoading,
isError,
error
} = useGetComplianceReport(
currentUser?.organization?.organizationId,
complianceReportId
)

if (isReportLoading || isCurrentUserLoading) {
return <Loading />
}

return reportData.report.legacyId ? (
<ViewLegacyComplianceReport
reportData={reportData}
error={error}
isError={isError}
/>
) : (
<EditViewComplianceReport
reportData={reportData}
error={error}
isError={isError}
/>
)
}
Loading

0 comments on commit 8174d52

Please sign in to comment.