Skip to content

Commit

Permalink
Merge pull request #1563 from bcgov/feat/alex-suplemental-syncing-241224
Browse files Browse the repository at this point in the history
Feat: Supplemental Report Syncing
  • Loading branch information
AlexZorkin authored Dec 24, 2024
2 parents d62ab2a + d23ce4b commit 04035e3
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 37 deletions.
51 changes: 35 additions & 16 deletions backend/lcfs/services/rabbitmq/report_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ async def process_message(self, body: bytes):
Expected message structure:
{
"tfrs_id": int,
"root_report_id": int,
"organization_id": int,
"compliance_period": str,
"nickname": str,
"action": "Created"|"Submitted"|"Approved",
"credits": int (optional),
"user_id": int
Expand All @@ -96,8 +96,8 @@ async def process_message(self, body: bytes):
action=action,
compliance_period=message.get("compliance_period"),
compliance_units=message.get("credits"),
root_report_id=message["root_report_id"],
legacy_id=message["tfrs_id"],
nickname=message.get("nickname"),
org_id=org_id,
user_id=message["user_id"],
)
Expand Down Expand Up @@ -127,8 +127,8 @@ async def handle_message(
action: str,
compliance_period: str,
compliance_units: Optional[int],
root_report_id: int,
legacy_id: int,
nickname: Optional[str],
org_id: int,
user_id: int,
):
Expand Down Expand Up @@ -157,15 +157,18 @@ 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}")
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,
root_report_id,
legacy_id,
compliance_period,
nickname,
user,
compliance_report_repo,
compliance_report_service,
)
elif action == "Submitted":
Expand All @@ -190,25 +193,41 @@ async def handle_message(
async def _handle_created(
self,
org_id: int,
root_report_id: int,
legacy_id: int,
compliance_period: str,
nickname: str,
user: UserProfile,
compliance_report_repo: ComplianceReportRepository,
compliance_report_service: ComplianceReportServices,
):
"""
Handle the 'Created' action by creating a new compliance report draft.
"""
lcfs_report = ComplianceReportCreateSchema(
legacy_id=legacy_id,
compliance_period=compliance_period,
organization_id=org_id,
nickname=nickname,
status=ComplianceReportStatusEnum.Draft.value,
)
await compliance_report_service.create_compliance_report(
org_id, lcfs_report, user
)
if root_report_id == legacy_id: # this is a new initial report
lcfs_report = ComplianceReportCreateSchema(
legacy_id=legacy_id,
compliance_period=compliance_period,
organization_id=org_id,
nickname="Original Report",
status=ComplianceReportStatusEnum.Draft.value,
)
await compliance_report_service.create_compliance_report(
org_id, lcfs_report, user
)
else:
# Process a new supplemental report
root_report = (
await compliance_report_repo.get_compliance_report_by_legacy_id(
root_report_id
)
)
if not root_report:
raise ServiceException(
f"No original compliance report found for legacy ID {root_report_id}"
)
await compliance_report_service.create_supplemental_report(
root_report_id, user, legacy_id
)

async def _handle_approved(
self,
Expand Down
82 changes: 68 additions & 14 deletions backend/lcfs/tests/services/rabbitmq/test_report_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
from unittest.mock import AsyncMock, patch, MagicMock

import pytest
from pandas.io.formats.format import return_docstring

from lcfs.db.models.transaction.Transaction import TransactionActionEnum, Transaction
from lcfs.services.rabbitmq.report_consumer import (
ReportConsumer,
)
from lcfs.tests.fuel_export.conftest import mock_compliance_report_repo
from lcfs.services.rabbitmq.report_consumer import ReportConsumer
from lcfs.web.api.compliance_report.schema import ComplianceReportCreateSchema
from lcfs.db.models.compliance.ComplianceReportStatus import ComplianceReportStatusEnum
from lcfs.db.models.compliance.ComplianceReport import SupplementalInitiatorType


@pytest.fixture
Expand All @@ -33,11 +31,11 @@ def mock_session():

mock_session = AsyncMock(spec=AsyncSession)

# `async with mock_session:` should work, so we define what happens on enter/exit
# `async with mock_session:` should work, so define behavior for enter/exit
mock_session.__aenter__.return_value = mock_session
mock_session.__aexit__.return_value = None

# Now mock the transaction context manager returned by `session.begin()`
# Mock the transaction context manager returned by `session.begin()`
mock_transaction = AsyncMock()
mock_transaction.__aenter__.return_value = mock_transaction
mock_transaction.__aexit__.return_value = None
Expand Down Expand Up @@ -130,27 +128,30 @@ def setup_patches(mock_redis, mock_session, mock_repositories):


@pytest.mark.anyio
async def test_process_message_created(mock_app, setup_patches, mock_repositories):
async def test_process_message_created_new_initial_report(
mock_app, setup_patches, mock_repositories
):
"""Test the 'Created' action when root_report_id == legacy_id, indicating a new initial report."""
consumer = ReportConsumer(mock_app)

# Prepare a sample message for "Created" action
# Prepare a sample message for "Created" action (new report)
# Note root_report_id == tfrs_id => new initial report
message = {
"tfrs_id": 123,
"root_report_id": 123,
"organization_id": 1,
"compliance_period": "2023",
"nickname": "Test Report",
"action": "Created",
"user_id": 42,
}
body = json.dumps(message).encode()

# Ensure correct mock setup
mock_user = MagicMock()
mock_repositories["user_repo"].get_user_by_id.return_value = mock_user

await consumer.process_message(body)

# Assertions for "Created" action
# Assertions for "Created" action, new initial report
mock_repositories[
"compliance_service"
].create_compliance_report.assert_called_once_with(
Expand All @@ -159,23 +160,75 @@ async def test_process_message_created(mock_app, setup_patches, mock_repositorie
legacy_id=123,
compliance_period="2023",
organization_id=1,
nickname="Test Report",
nickname="Original Report",
status="Draft",
),
mock_user,
)


@pytest.mark.anyio
async def test_process_message_created_supplemental_report(
mock_app, setup_patches, mock_repositories
):
"""
Test the 'Created' action when root_report_id != legacy_id, indicating a supplemental report.
"""
consumer = ReportConsumer(mock_app)

# Prepare a sample message for "Created" action (supplemental)
message = {
"tfrs_id": 999, # This is the new supplemental's legacy ID
"root_report_id": 123, # The original (root) report ID
"organization_id": 1,
"compliance_period": "2023",
"action": "Created",
"user_id": 42,
}
body = json.dumps(message).encode()

mock_user = MagicMock()
mock_repositories["user_repo"].get_user_by_id.return_value = mock_user

# Mock root report so the repository call returns a valid object
mock_root_report = MagicMock()
mock_root_report.version = 2
mock_root_report.compliance_report_group_uuid = "test-uuid"
mock_repositories[
"compliance_report_repo"
].get_compliance_report_by_legacy_id.return_value = mock_root_report

await consumer.process_message(body)

# The code should create a supplemental report using the root report's group UUID
# and increment the version by 1
mock_repositories[
"compliance_service"
].create_supplemental_report.assert_called_once()

called_args = mock_repositories[
"compliance_service"
].create_supplemental_report.call_args[0]
root_report_id_arg = called_args[0]
user_arg = called_args[1]
legacy_id_arg = called_args[2]

assert root_report_id_arg == 123
assert user_arg == mock_user
# Check that the new supplemental report schema was built correctly
assert legacy_id_arg == 999


@pytest.mark.anyio
async def test_process_message_submitted(mock_app, setup_patches, mock_repositories):
consumer = ReportConsumer(mock_app)

# Prepare a sample message for "Submitted" action
message = {
"tfrs_id": 123,
"root_report_id": 123,
"organization_id": 1,
"compliance_period": "2023",
"nickname": "Test Report",
"action": "Submitted",
"credits": 50,
"user_id": 42,
Expand Down Expand Up @@ -203,6 +256,7 @@ async def test_process_message_approved(mock_app, setup_patches, mock_repositori
# Prepare a sample message for "Approved" action
message = {
"tfrs_id": 123,
"root_report_id": 123,
"organization_id": 1,
"action": "Approved",
"user_id": 42,
Expand Down
19 changes: 12 additions & 7 deletions backend/lcfs/web/api/compliance_report/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,16 @@ async def create_compliance_report(

@service_handler
async def create_supplemental_report(
self, report_id: int
self, report_id: int, user: UserProfile = None, legacy_id: int = None
) -> ComplianceReportBaseSchema:
"""
Creates a new supplemental compliance report.
The report_id can be any report in the series (original or supplemental).
Supplemental reports are only allowed if the status of the current report is 'Assessed'.
"""

user: UserProfile = self.request.user
# check if we're passing a specifc user otherwise use request user
if not user:
user = self.request.user

# Fetch the current report using the provided report_id
current_report = await self.repo.get_compliance_report_by_id(
Expand All @@ -103,11 +104,14 @@ async def create_supplemental_report(
"You do not have permission to create a supplemental report for this organization."
)

# TODO this logic to be re-instated once TFRS is shutdown
# TFRS allows supplementals on previously un-accepted reports
# so we have to support this until LCFS and TFRS are no longer synced
# Validate that the status of the current report is 'Assessed'
if current_report.current_status.status != ComplianceReportStatusEnum.Assessed:
raise ServiceException(
"A supplemental report can only be created if the current report's status is 'Assessed'."
)
# if current_report.current_status.status != ComplianceReportStatusEnum.Assessed:
# raise ServiceException(
# "A supplemental report can only be created if the current report's status is 'Assessed'."
# )

# Get the group_uuid from the current report
group_uuid = current_report.compliance_report_group_uuid
Expand All @@ -127,6 +131,7 @@ async def create_supplemental_report(
# Create the new supplemental compliance report
new_report = ComplianceReport(
compliance_period_id=current_report.compliance_period_id,
legacy_id=legacy_id,
organization_id=current_report.organization_id,
current_status_id=draft_status.compliance_report_status_id,
reporting_frequency=current_report.reporting_frequency,
Expand Down

0 comments on commit 04035e3

Please sign in to comment.