Skip to content

Commit 247f6dd

Browse files
Merge pull request #4366 from bcgov/461-explicit-crv-status-for-manual-handling
461 explicit crv status for manual handling
2 parents 52c426f + 3b2a6e0 commit 247f6dd

9 files changed

Lines changed: 126 additions & 121 deletions
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Generated by Django 5.1.15 on 2026-03-24 20:17
2+
3+
from django.db import migrations, models
4+
5+
6+
def migrate_manual_handling_status(apps, schema_editor):
7+
ComplianceReportVersion = apps.get_model('compliance', 'ComplianceReportVersion')
8+
ComplianceReportVersion.objects.filter(
9+
status="No obligation or earned credits",
10+
manual_handling_record__isnull=False,
11+
).update(status="Requires manual handling")
12+
13+
14+
def reverse_migrate_manual_handling_status(apps, schema_editor):
15+
ComplianceReportVersion = apps.get_model('compliance', 'ComplianceReportVersion')
16+
ComplianceReportVersion.objects.filter(
17+
status="Requires manual handling",
18+
manual_handling_record__isnull=False,
19+
).update(status="No obligation or earned credits")
20+
21+
22+
class Migration(migrations.Migration):
23+
24+
dependencies = [
25+
('compliance', '0039_V5_6_0'),
26+
]
27+
28+
operations = [
29+
migrations.AlterField(
30+
model_name='compliancereportversion',
31+
name='status',
32+
field=models.CharField(
33+
choices=[
34+
('Obligation not met', 'Obligation Not Met'),
35+
('Obligation fully met', 'Obligation Fully Met'),
36+
('Obligation pending invoice creation', 'Obligation Pending Invoice Creation'),
37+
('Earned credits', 'Earned Credits'),
38+
('No obligation or earned credits', 'No Obligation Or Earned Credits'),
39+
('Requires manual handling', 'Requires Manual Handling'),
40+
('Superceded', 'Superceded'),
41+
],
42+
db_comment='The status of this compliance report version. Options: [Obligation not met, Obligation fully met, Earned credits, No obligation or earned credits, Requires manual handling]',
43+
max_length=100,
44+
),
45+
),
46+
migrations.RunPython(migrate_manual_handling_status, reverse_migrate_manual_handling_status, elidable=True),
47+
]

bc_obps/compliance/models/compliance_report_version.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class ComplianceStatus(models.TextChoices):
1414
OBLIGATION_PENDING_INVOICE_CREATION = "Obligation pending invoice creation"
1515
EARNED_CREDITS = "Earned credits"
1616
NO_OBLIGATION_OR_EARNED_CREDITS = "No obligation or earned credits"
17+
REQUIRES_MANUAL_HANDLING = "Requires manual handling"
1718
SUPERCEDED = "Superceded"
1819

1920
compliance_report = models.ForeignKey(
@@ -47,7 +48,7 @@ class ComplianceStatus(models.TextChoices):
4748
status = models.CharField(
4849
max_length=100,
4950
choices=ComplianceStatus.choices,
50-
db_comment="The status of this compliance report version. Options: [Obligation not met, Obligation fully met, Earned credits, No obligation or earned credits]",
51+
db_comment="The status of this compliance report version. Options: [Obligation not met, Obligation fully met, Earned credits, No obligation or earned credits, Requires manual handling]",
5152
)
5253

5354
is_supplementary = models.BooleanField(

bc_obps/compliance/service/compliance_dashboard_service.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,10 @@ def _annotate_status(
333333
When(
334334
status=ComplianceReportVersion.ComplianceStatus.EARNED_CREDITS.value, then=Value("Earned credits")
335335
),
336+
When(
337+
status=ComplianceReportVersion.ComplianceStatus.REQUIRES_MANUAL_HANDLING.value,
338+
then=Value("Supplementary report - action required"),
339+
),
336340
default=F("status"),
337341
output_field=CharField(),
338342
),

bc_obps/compliance/service/supplementary_version_service.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ def handle(
4646

4747

4848
# Concrete strategy for:
49-
# - Detecting when a previous CRV is still under manual handling (pending), and
50-
# - Creating a new supplementary NO_OBLIG / earned-credits CRV
51-
# with an associated manual-handling record to carry that flag forward.
49+
# - Detecting when a previous CRV has a manual-handling record, and
50+
# - Creating a new supplementary REQUIRES_MANUAL_HANDLING CRV
51+
# carrying forward the handling_type/context from the previous record.
5252
class ManualHandler:
5353
@staticmethod
5454
def can_handle(
@@ -72,7 +72,7 @@ def handle(
7272
version_count: int,
7373
) -> Optional[ComplianceReportVersion]:
7474
"""
75-
Create a new supplementary NO_OBLIGATION_OR_EARNED_CREDITS CRV and an
75+
Create a new supplementary REQUIRES_MANUAL_HANDLING CRV and an
7676
associated manual-handling record when the previous CRV has a manual-handling record
7777
7878
Assumptions:
@@ -95,7 +95,7 @@ def handle(
9595
compliance_report_version = ComplianceReportVersion.objects.create(
9696
compliance_report=compliance_report,
9797
report_compliance_summary=new_summary,
98-
status=ComplianceReportVersion.ComplianceStatus.NO_OBLIGATION_OR_EARNED_CREDITS,
98+
status=ComplianceReportVersion.ComplianceStatus.REQUIRES_MANUAL_HANDLING,
9999
is_supplementary=True,
100100
previous_version=previous_compliance_version,
101101
)
@@ -815,7 +815,8 @@ def _record_manual_handling(
815815
compliance_report_version_id: int,
816816
) -> None:
817817
"""
818-
Create a related ComplianceReportVersionManualHandling record.
818+
Create a related ComplianceReportVersionManualHandling record and set the
819+
CRV status to REQUIRES_MANUAL_HANDLING.
819820
"""
820821
crv = ComplianceReportVersion.objects.get(id=compliance_report_version_id)
821822

@@ -826,6 +827,9 @@ def _record_manual_handling(
826827
context=ComplianceReportVersionManualHandling.Context.OBLIGATION_REFUND_POOL_CASH,
827828
)
828829

830+
crv.status = ComplianceReportVersion.ComplianceStatus.REQUIRES_MANUAL_HANDLING
831+
crv.save(update_fields=['status'])
832+
829833
@staticmethod
830834
def _create_earned_credits(compliance_report_version_id: int, tonnes: Decimal) -> None:
831835
"""

bc_obps/compliance/tests/integration/test_manual_handling_supplementary.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def test_decreased_obligation_paid_invoice_creates_manual_handling(
6565
→ obligation fully paid AND leftover refund > 0 AND has_cash → manual handling
6666
6767
Expected:
68-
- New CRV with NO_OBLIGATION_OR_EARNED_CREDITS status
68+
- New CRV with REQUIRES_MANUAL_HANDLING status
6969
- Previous CRV marked OBLIGATION_FULLY_MET
7070
- ManualHandling record created with handling_type=OBLIGATION,
7171
context=OBLIGATION_REFUND_POOL_CASH
@@ -102,7 +102,7 @@ def test_decreased_obligation_paid_invoice_creates_manual_handling(
102102
compliance_report=self.compliance_report,
103103
is_supplementary=True,
104104
)
105-
assert supp_crv.status == ComplianceReportVersion.ComplianceStatus.NO_OBLIGATION_OR_EARNED_CREDITS
105+
assert supp_crv.status == ComplianceReportVersion.ComplianceStatus.REQUIRES_MANUAL_HANDLING
106106
assert supp_crv.previous_version == self.initial_crv
107107

108108
manual_handling = ComplianceReportVersionManualHandling.objects.get(compliance_report_version=supp_crv)

bc_obps/compliance/tests/service/test_compliance_dashboard_service.py

Lines changed: 45 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -438,124 +438,58 @@ def test_get_compliance_report_versions_for_dashboard_sorts_and_filters(
438438
def test_get_compliance_report_versions_annotates_status_correctly(
439439
self,
440440
):
441-
# setup
442441
user_operator = make_recipe('registration.tests.utils.approved_user_operator')
443442

444-
# 1 - not met
445-
test_data_1 = ComplianceTestHelper.build_test_data(
446-
crv_status=ComplianceReportVersion.ComplianceStatus.OBLIGATION_NOT_MET
447-
)
448-
test_data_1.operation.operator = user_operator.operator
449-
test_data_1.report.operator = user_operator.operator
450-
test_data_1.operation.save()
451-
test_data_1.report.save()
452-
453-
# # 2 - met
454-
test_data_2 = ComplianceTestHelper.build_test_data(
455-
crv_status=ComplianceReportVersion.ComplianceStatus.OBLIGATION_FULLY_MET
456-
)
457-
test_data_2.operation.operator = user_operator.operator
458-
test_data_2.report.operator = user_operator.operator
459-
test_data_2.operation.save()
460-
test_data_2.report.save()
461-
462-
# # 3 - pending invoice creation
463-
test_data_3 = ComplianceTestHelper.build_test_data(
464-
crv_status=ComplianceReportVersion.ComplianceStatus.OBLIGATION_PENDING_INVOICE_CREATION
465-
)
466-
test_data_3.operation.operator = user_operator.operator
467-
test_data_3.report.operator = user_operator.operator
468-
test_data_3.operation.save()
469-
test_data_3.report.save()
470-
471-
# # 4 - not requested
472-
test_data_4 = ComplianceTestHelper.build_test_data(
473-
crv_status=ComplianceReportVersion.ComplianceStatus.EARNED_CREDITS
474-
)
475-
test_data_4.operation.operator = user_operator.operator
476-
test_data_4.report.operator = user_operator.operator
477-
test_data_4.compliance_earned_credit.issuance_status = ComplianceEarnedCredit.IssuanceStatus.CREDITS_NOT_ISSUED
478-
test_data_4.compliance_earned_credit.save()
479-
test_data_4.operation.save()
480-
test_data_4.report.save()
443+
def build(crv_status):
444+
td = ComplianceTestHelper.build_test_data(crv_status=crv_status)
445+
td.operation.operator = user_operator.operator
446+
td.report.operator = user_operator.operator
447+
td.operation.save()
448+
td.report.save()
449+
return td
450+
451+
# Obligation statuses
452+
td_not_met = build(ComplianceReportVersion.ComplianceStatus.OBLIGATION_NOT_MET)
453+
td_fully_met = build(ComplianceReportVersion.ComplianceStatus.OBLIGATION_FULLY_MET)
454+
td_pending_invoice = build(ComplianceReportVersion.ComplianceStatus.OBLIGATION_PENDING_INVOICE_CREATION)
455+
456+
# Earned credits — vary by issuance_status
457+
earned_credit_cases = [
458+
(ComplianceEarnedCredit.IssuanceStatus.CREDITS_NOT_ISSUED, "Earned credits - not requested"),
459+
(ComplianceEarnedCredit.IssuanceStatus.ISSUANCE_REQUESTED, "Earned credits - issuance requested"),
460+
(ComplianceEarnedCredit.IssuanceStatus.APPROVED, "Earned credits - approved"),
461+
(ComplianceEarnedCredit.IssuanceStatus.DECLINED, "Earned credits - declined"),
462+
(ComplianceEarnedCredit.IssuanceStatus.CHANGES_REQUIRED, "Earned credits - changes required"),
463+
]
464+
earned_credit_data = []
465+
for issuance_status, _ in earned_credit_cases:
466+
td = build(ComplianceReportVersion.ComplianceStatus.EARNED_CREDITS)
467+
td.compliance_earned_credit.issuance_status = issuance_status
468+
td.compliance_earned_credit.save()
469+
earned_credit_data.append(td)
470+
471+
# Earned credits — no issuance record
472+
td_no_credit_record = build(ComplianceReportVersion.ComplianceStatus.EARNED_CREDITS)
473+
td_no_credit_record.compliance_earned_credit.delete()
474+
475+
# Requires manual handling
476+
td_manual = build(ComplianceReportVersion.ComplianceStatus.REQUIRES_MANUAL_HANDLING)
481477

482-
# # 5 - issuance requested
483-
test_data_5 = ComplianceTestHelper.build_test_data(
484-
crv_status=ComplianceReportVersion.ComplianceStatus.EARNED_CREDITS
485-
)
486-
test_data_5.operation.operator = user_operator.operator
487-
test_data_5.report.operator = user_operator.operator
488-
test_data_5.compliance_earned_credit.issuance_status = ComplianceEarnedCredit.IssuanceStatus.ISSUANCE_REQUESTED
489-
test_data_5.compliance_earned_credit.save()
490-
test_data_5.operation.save()
491-
test_data_5.report.save()
492-
493-
# # 6 - approved
494-
test_data_6 = ComplianceTestHelper.build_test_data(
495-
crv_status=ComplianceReportVersion.ComplianceStatus.EARNED_CREDITS
496-
)
497-
test_data_6.operation.operator = user_operator.operator
498-
test_data_6.report.operator = user_operator.operator
499-
test_data_6.compliance_earned_credit.issuance_status = ComplianceEarnedCredit.IssuanceStatus.APPROVED
500-
test_data_6.compliance_earned_credit.save()
501-
test_data_6.operation.save()
502-
test_data_6.report.save()
503-
504-
# # 7 - declined
505-
test_data_7 = ComplianceTestHelper.build_test_data(
506-
crv_status=ComplianceReportVersion.ComplianceStatus.EARNED_CREDITS
507-
)
508-
test_data_7.operation.operator = user_operator.operator
509-
test_data_7.report.operator = user_operator.operator
510-
test_data_7.compliance_earned_credit.issuance_status = ComplianceEarnedCredit.IssuanceStatus.DECLINED
511-
test_data_7.compliance_earned_credit.save()
512-
test_data_7.operation.save()
513-
test_data_7.report.save()
514-
515-
# # 8 - changes required
516-
test_data_8 = ComplianceTestHelper.build_test_data(
517-
crv_status=ComplianceReportVersion.ComplianceStatus.EARNED_CREDITS
518-
)
519-
test_data_8.operation.operator = user_operator.operator
520-
test_data_8.report.operator = user_operator.operator
521-
test_data_8.compliance_earned_credit.issuance_status = ComplianceEarnedCredit.IssuanceStatus.CHANGES_REQUIRED
522-
test_data_8.compliance_earned_credit.save()
523-
test_data_8.operation.save()
524-
test_data_8.report.save()
525-
526-
# # 9 - earned credits
527-
test_data_9 = ComplianceTestHelper.build_test_data(
528-
crv_status=ComplianceReportVersion.ComplianceStatus.EARNED_CREDITS
529-
)
530-
test_data_9.operation.operator = user_operator.operator
531-
test_data_9.report.operator = user_operator.operator
532-
test_data_9.operation.save()
533-
test_data_9.report.save()
534-
test_data_9.compliance_earned_credit.delete()
535-
536-
# checks
537478
result = ComplianceDashboardService.get_compliance_report_versions_for_dashboard(
538479
user_guid=user_operator.user_id,
539480
sort_field="id",
540481
sort_order="asc",
541482
filters=_NoopFilters(),
542483
)
543484

544-
assert result[0].id == test_data_1.compliance_report_version.id
545-
assert result[0].display_status == "Obligation - not met"
546-
assert result[1].id == test_data_2.compliance_report_version.id
547-
assert result[1].display_status == "Obligation - met"
548-
assert result[2].id == test_data_3.compliance_report_version.id
549-
assert result[2].display_status == "Obligation - pending invoice creation"
550-
assert result[3].id == test_data_4.compliance_report_version.id
551-
assert result[3].display_status == "Earned credits - not requested"
552-
assert result[4].id == test_data_5.compliance_report_version.id
553-
assert result[4].display_status == "Earned credits - issuance requested"
554-
assert result[5].id == test_data_6.compliance_report_version.id
555-
assert result[5].display_status == "Earned credits - approved"
556-
assert result[6].id == test_data_7.compliance_report_version.id
557-
assert result[6].display_status == "Earned credits - declined"
558-
assert result[7].id == test_data_8.compliance_report_version.id
559-
assert result[7].display_status == "Earned credits - changes required"
560-
assert result[8].id == test_data_9.compliance_report_version.id
561-
assert result[8].display_status == "Earned credits"
485+
expected = [
486+
(td_not_met, "Obligation - not met"),
487+
(td_fully_met, "Obligation - met"),
488+
(td_pending_invoice, "Obligation - pending invoice creation"),
489+
*zip(earned_credit_data, [status for _, status in earned_credit_cases]),
490+
(td_no_credit_record, "Earned credits"),
491+
(td_manual, "Supplementary report - action required"),
492+
]
493+
for i, (td, expected_status) in enumerate(expected):
494+
assert result[i].id == td.compliance_report_version.id
495+
assert result[i].display_status == expected_status

bc_obps/compliance/tests/service/test_supplementary_version_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ def test_handle_creates_compliance_report_version(self):
732732
assert result.report_compliance_summary == new_summary
733733
assert result.is_supplementary is True
734734
assert result.previous_version == previous_crv
735-
assert result.status == ComplianceReportVersion.ComplianceStatus.NO_OBLIGATION_OR_EARNED_CREDITS
735+
assert result.status == ComplianceReportVersion.ComplianceStatus.REQUIRES_MANUAL_HANDLING
736736

737737
# And a new manual-handling record carrying forward handling_type/context
738738
new_manual = ComplianceReportVersionManualHandling.objects.get(compliance_report_version=result)

bc_obps/compliance/tests/utils/compliance_test_helper.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from registration.models import Operation
33
from reporting.models import ReportingYear, ReportVersion
44
from compliance.models import CompliancePeriod, ComplianceReportVersion
5+
from compliance.models.compliance_report_version_manual_handling import ComplianceReportVersionManualHandling
56

67
from model_bakery.baker import make_recipe
78

@@ -243,4 +244,17 @@ def build_test_data(
243244
t.compliance_report_version.status = ComplianceReportVersion.ComplianceStatus.SUPERCEDED
244245
t.compliance_report_version.save()
245246

247+
case ComplianceReportVersion.ComplianceStatus.REQUIRES_MANUAL_HANDLING:
248+
if previous_data:
249+
t = SupplementaryComplianceTestInfrastructure(previous_data)
250+
else:
251+
t = BaseComplianceTestInfrastructure(reporting_year)
252+
t.compliance_report_version.status = ComplianceReportVersion.ComplianceStatus.REQUIRES_MANUAL_HANDLING
253+
t.compliance_report_version.save()
254+
ComplianceReportVersionManualHandling.objects.create(
255+
compliance_report_version=t.compliance_report_version,
256+
handling_type=ComplianceReportVersionManualHandling.HandlingType.OBLIGATION,
257+
context=ComplianceReportVersionManualHandling.Context.OBLIGATION_REFUND_POOL_CASH,
258+
)
259+
246260
return t

bciers/libs/utils/src/enums.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export enum ComplianceSummaryStatus {
126126
OBLIGATION_PENDING_INVOICE_CREATION = "Obligation pending invoice creation",
127127
EARNED_CREDITS = "Earned credits",
128128
NO_OBLIGATION_OR_EARNED_CREDITS = "No obligation or earned credits",
129+
REQUIRES_MANUAL_HANDLING = "Requires manual handling",
129130
}
130131

131132
export enum PenaltyStatus {

0 commit comments

Comments
 (0)