Skip to content

Commit

Permalink
Merge pull request #1559 from bcgov/feat/prashanth-cr-return-supplier…
Browse files Browse the repository at this point in the history
…-1507

Feat: LCFS - Add a Return report to the supplier button #1507
  • Loading branch information
prv-proton authored Jan 2, 2025
2 parents 64b5b3d + 16a1a08 commit 53ad496
Show file tree
Hide file tree
Showing 16 changed files with 196 additions and 102 deletions.
6 changes: 0 additions & 6 deletions backend/lcfs/db/models/compliance/listeners.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,3 @@ def prevent_update_if_locked(mapper, connection, target):
raise InvalidRequestError("Cannot update a locked ComplianceReportSummary")


@event.listens_for(ComplianceReportSummary.is_locked, "set")
def prevent_unlock(target, value, oldvalue, initiator):
if oldvalue and not value:
raise InvalidRequestError(
"Cannot unlock a ComplianceReportSummary once it's locked"
)
10 changes: 0 additions & 10 deletions backend/lcfs/tests/compliance_report/test_update_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,16 +281,6 @@ async def test_handle_submitted_status_with_existing_summary(
report_id
)

# Ensure the adjust_balance method is called with the correct parameters
mock_org_service.adjust_balance.assert_called_once_with(
transaction_action=TransactionActionEnum.Reserved,
compliance_units=mock_report.summary.line_20_surplus_deficit_units,
organization_id=mock_report.organization_id,
)

# Check if the report was updated with the result of adjust_balance
assert mock_report.transaction == mock_org_service.adjust_balance.return_value

# Check if the summary is locked
saved_summary = mock_repo.save_compliance_report_summary.call_args[0][0]
assert saved_summary.is_locked == True
Expand Down
12 changes: 11 additions & 1 deletion backend/lcfs/web/api/compliance_report/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from lcfs.db.models.fuel.FuelType import FuelType
from lcfs.db.models.fuel.FuelCategory import FuelCategory
from lcfs.db.models.fuel.ExpectedUseType import ExpectedUseType
from sqlalchemy import func, select, and_, asc, desc
from sqlalchemy import func, select, and_, asc, desc, update
from sqlalchemy.orm import joinedload
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import Depends
Expand Down Expand Up @@ -570,6 +570,16 @@ async def add_compliance_report_summary(
await self.db.refresh(summary)
return summary

@repo_handler
async def reset_summary_lock(self, compliance_report_id: int):
query = (
update(ComplianceReportSummary)
.where(ComplianceReportSummary.compliance_report_id == compliance_report_id)
.values(is_locked=False)
)
await self.db.execute(query)
return True

@repo_handler
async def save_compliance_report_summary(
self, summary: ComplianceReportSummaryUpdateSchema
Expand Down
19 changes: 16 additions & 3 deletions backend/lcfs/web/api/compliance_report/schema.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from enum import Enum
from typing import ClassVar, Optional, List, Union
from typing import ClassVar, Optional, List
from datetime import datetime, date
from enum import Enum
from lcfs.db.models.compliance.ComplianceReportStatus import ComplianceReportStatusEnum
from lcfs.web.api.fuel_code.schema import EndUseTypeSchema, EndUserTypeSchema

from lcfs.web.api.base import BaseSchema, FilterModel, SortOrder
from lcfs.web.api.base import PaginationResponseSchema
from pydantic import Field, Extra
from pydantic import Field

"""
Base - all shared attributes of a resource
Expand All @@ -17,6 +18,18 @@
"""


class ReturnStatus(Enum):
ANALYST = "Return to analyst"
MANAGER = "Return to manager"
SUPPLIER = "Return to supplier"

RETURN_STATUS_MAPPER = {
ReturnStatus.ANALYST.value: ComplianceReportStatusEnum.Submitted.value,
ReturnStatus.MANAGER.value: ComplianceReportStatusEnum.Recommended_by_analyst.value,
ReturnStatus.SUPPLIER.value: ComplianceReportStatusEnum.Draft.value,
}


class SupplementalInitiatorType(str, Enum):
SUPPLIER_SUPPLEMENTAL = "Supplier Supplemental"
GOVERNMENT_REASSESSMENT = "Government Reassessment"
Expand Down Expand Up @@ -45,7 +58,7 @@ class SummarySchema(BaseSchema):
is_locked: bool

class Config:
extra = Extra.allow
extra = 'allow'


class ComplianceReportStatusSchema(BaseSchema):
Expand Down
84 changes: 57 additions & 27 deletions backend/lcfs/web/api/compliance_report/update_service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from typing import Tuple
from fastapi import Depends, HTTPException, Request
from lcfs.web.api.notification.schema import (
COMPLIANCE_REPORT_STATUS_NOTIFICATION_MAPPER,
Expand All @@ -12,7 +13,11 @@
from lcfs.db.models.transaction.Transaction import TransactionActionEnum
from lcfs.db.models.user.Role import RoleEnum
from lcfs.web.api.compliance_report.repo import ComplianceReportRepository
from lcfs.web.api.compliance_report.schema import ComplianceReportUpdateSchema
from lcfs.web.api.compliance_report.schema import (
RETURN_STATUS_MAPPER,
ComplianceReportUpdateSchema,
ReturnStatus,
)
from lcfs.web.api.compliance_report.summary_service import (
ComplianceReportSummaryService,
)
Expand All @@ -39,48 +44,67 @@ def __init__(
self.trx_service = trx_service
self.notfn_service = notfn_service

async def update_compliance_report(
self, report_id: int, report_data: ComplianceReportUpdateSchema
) -> ComplianceReport:
"""Updates an existing compliance report."""
RETURN_STATUSES = ["Return to analyst", "Return to manager"]
async def _handle_return_status(
self, report_data: ComplianceReportUpdateSchema
) -> Tuple[str, bool]:
"""Handle return status logic and return new status and change flag."""
mapped_status = RETURN_STATUS_MAPPER.get(report_data.status)
return mapped_status, False

async def _check_report_exists(self, report_id: int) -> ComplianceReport:
"""Verify report exists and return it."""
report = await self.repo.get_compliance_report_by_id(report_id, is_model=True)
if not report:
raise DataNotFoundException(
f"Compliance report with ID {report_id} not found"
)
return report

async def update_compliance_report(
self, report_id: int, report_data: ComplianceReportUpdateSchema
) -> ComplianceReport:
"""Updates an existing compliance report."""
# Get and validate report
report = await self._check_report_exists(report_id)

# Store original status
current_status = report_data.status
# if we're just returning the compliance report back to either compliance manager or analyst,
# then neither history nor any updates to summary is required.
if report_data.status in RETURN_STATUSES:
status_has_changed = False
notifications = COMPLIANCE_REPORT_STATUS_NOTIFICATION_MAPPER.get(
report_data.status

# Handle status changes
if report_data.status in [status.value for status in ReturnStatus]:
new_status, status_has_changed = await self._handle_return_status(
report_data
)
if report_data.status == "Return to analyst":
report_data.status = ComplianceReportStatusEnum.Submitted.value
else:
report_data.status = (
ComplianceReportStatusEnum.Recommended_by_analyst.value
)
report_data.status = new_status

# Handle "Return to supplier"
if current_status == ReturnStatus.SUPPLIER.value:
await self.repo.reset_summary_lock(report.compliance_report_id)
else:
# Handle normal status change
status_has_changed = report.current_status.status != getattr(
ComplianceReportStatusEnum, report_data.status.replace(" ", "_")
)

# Get new status object
new_status = await self.repo.get_compliance_report_status_by_desc(
report_data.status
)
# Update fields

# Update report
report.current_status = new_status
report.supplemental_note = report_data.supplemental_note

updated_report = await self.repo.update_compliance_report(report)

# Handle status change related actions
if status_has_changed:
await self.handle_status_change(report, new_status.status)
# Add history record
await self.repo.add_compliance_report_history(report, self.request.user)

# Handle notifications
await self._perform_notification_call(report, current_status)

return updated_report

async def _perform_notification_call(self, report, status):
Expand All @@ -102,7 +126,7 @@ async def _perform_notification_call(self, report, status):
"status": status.lower(),
}
notification_data = NotificationMessageSchema(
type=f"Compliance report {status.lower()}",
type=f"Compliance report {status.lower().replace('return', 'returned')}",
related_transaction_id=f"CR{report.compliance_report_id}",
message=json.dumps(message_data),
related_organization_id=report.organization_id,
Expand Down Expand Up @@ -228,12 +252,18 @@ async def handle_submitted_status(self, report: ComplianceReport):
report.summary = new_summary

if report.summary.line_20_surplus_deficit_units != 0:
# Create a new reserved transaction for receiving organization
report.transaction = await self.org_service.adjust_balance(
transaction_action=TransactionActionEnum.Reserved,
compliance_units=report.summary.line_20_surplus_deficit_units,
organization_id=report.organization_id,
)
if report.transaction is not None:
# Update existing transaction
report.transaction.compliance_units = (
report.summary.line_20_surplus_deficit_units
)
else:
# Create a new reserved transaction for receiving organization
report.transaction = await self.org_service.adjust_balance(
transaction_action=TransactionActionEnum.Reserved,
compliance_units=report.summary.line_20_surplus_deficit_units,
organization_id=report.organization_id,
)
await self.repo.update_compliance_report(report)

return calculated_summary
Expand Down
3 changes: 3 additions & 0 deletions backend/lcfs/web/api/notification/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ class NotificationRequestSchema(BaseSchema):
"Return to manager": [
NotificationTypeEnum.IDIR_COMPLIANCE_MANAGER__COMPLIANCE_REPORT__ANALYST_RECOMMENDATION
],
"Return to supplier": [
NotificationTypeEnum.BCEID__COMPLIANCE_REPORT__DIRECTOR_ASSESSMENT
]
}


Expand Down
4 changes: 3 additions & 1 deletion frontend/src/assets/locales/en/reports.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,19 @@
"submitReportBtn": "Submit report",
"returnToAnalyst": "Return to analyst",
"returnToManager": "Return to compliance manager",
"returnToSupplier": "Return report to the supplier",
"recommendReportAnalystBtn": "Recommend to compliance manager",
"recommendReportManagerBtn": "Recommend to director",
"assessReportBtn": "Issue assessment",
"reAssessReportBtn": "Re-assess report"
},
"savedSuccessText": "Compliance report successfully saved",
"savedSuccessText": "Compliance report successfully {{status}}",
"submitConfirmText": "Are you sure you want to sign and submit this compliance report?",
"deleteConfirmText": "Are you sure you want to delete this compliance report?",
"recommendConfirmText": "Are you sure you want to recommend this compliance report?",
"returnToAnalystConfirmText": "Are you sure you want to return this compliance report back to analyst?",
"returnToManagerConfirmText": "Are you sure you want to return this compliance report back to compliance manager?",
"returnToSupplierConfirmText": "Are you sure you want to return this compliance report back to the supplier?",
"assessConfirmText": "Are you sure you want to assess this compliance report?",
"uploadLabel": "Upload supporting documents for your report.",
"introduction": "Introduction",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ export const BCColumnSetFilter = forwardRef((props, ref) => {
limitTags={1}
className="bc-column-set-filter ag-list ag-select-list ag-ltr ag-popup-child ag-popup-positioned-under"
role="list-box"
sx={{ width: '100%' }}
sx={{
width: '100%',
'.MuiInputBase-root': {
borderRadius: 'inherit'
}
}}
options={options}
loading={optionsIsLoading}
autoHighlight
Expand Down Expand Up @@ -126,13 +131,6 @@ export const BCColumnSetFilter = forwardRef((props, ref) => {

BCColumnSetFilter.displayName = 'BCColumnSetFilter'

BCColumnSetFilter.defaultProps = {
// apiQuery: () => ({ data: [], isLoading: false }),
apiOptionField: 'name',
multiple: false,
disableCloseOnSelect: false
}

BCColumnSetFilter.propTypes = {
apiQuery: PropTypes.func.isRequired, // react query or a fetch query which will return data, isLoading and Error fields.
// for static data, use the following format:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,17 @@ function DefaultNavbarLink({
onMouseLeave={() => setHover(false)}
onClick={onClick}
>
{icon && (
{icon && typeof icon === 'string' ? (
<Icon
sx={{
color: ({ palette: { secondary, primary } }) =>
light ? primary.main : secondary.main,
color: '#fff',
verticalAlign: 'middle'
}}
>
{icon}
</Icon>
) : (
<>{icon}</>
)}
<BCTypography
variant="body2"
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/constants/statuses.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export const COMPLIANCE_REPORT_STATUSES = {
RECOMMENDED_BY_ANALYST: 'Recommended by analyst',
RECOMMENDED_BY_MANAGER: 'Recommended by manager',
RETURN_TO_ANALYST: 'Return to analyst',
RETURN_TO_MANAGER: 'Return to manager'
RETURN_TO_MANAGER: 'Return to manager',
RETURN_TO_SUPPLIER: 'Return to supplier'
}
export function getAllOrganizationStatuses() {
return Object.values(ORGANIZATION_STATUSES)
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/hooks/useComplianceReports.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ export const useUpdateComplianceReport = (reportID, options) => {
},
onSettled: () => {
queryClient.invalidateQueries(['compliance-report', reportID])
queryClient.invalidateQueries(['compliance-report-summary', reportID])
queryClient.invalidateQueries(['compliance-reports'])
}
})
}
Expand Down
Loading

0 comments on commit 53ad496

Please sign in to comment.