Skip to content

Commit c772345

Browse files
test: add manual handling scenarios integration tests
1 parent 65f49a6 commit c772345

1 file changed

Lines changed: 169 additions & 0 deletions

File tree

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import pytest
2+
from decimal import Decimal
3+
from unittest.mock import patch
4+
from compliance.models import (
5+
ComplianceEarnedCredit,
6+
ComplianceReportVersion,
7+
ComplianceReportVersionManualHandling,
8+
ElicensingLineItem,
9+
ElicensingPayment,
10+
)
11+
from compliance.tests.integration.utils import (
12+
ComplianceIntegrationTestBase,
13+
HANDLE_OBLIGATION_INTEGRATION,
14+
REFRESH_DATA,
15+
SEND_OBLIGATION_EMAIL,
16+
SEND_EARNED_CREDITS_EMAIL,
17+
SEND_NO_OBLIGATION_EMAIL,
18+
)
19+
20+
ON_COMMIT_PATH = "django.db.transaction.on_commit"
21+
CREATE_ADJUSTMENT_PATH = (
22+
"compliance.service.compliance_adjustment_service"
23+
".ComplianceAdjustmentService.create_adjustment_for_target_version"
24+
)
25+
26+
pytestmark = pytest.mark.django_db
27+
28+
29+
class TestManualHandlingSupplementary(ComplianceIntegrationTestBase):
30+
REPORTING_YEAR = 2024
31+
32+
def _approve_initial_earned_credits(self):
33+
"""Simulate credit approval."""
34+
self.initial_earned_credit.issuance_status = ComplianceEarnedCredit.IssuanceStatus.APPROVED
35+
self.initial_earned_credit.bccr_trading_name = "Test Trading Name"
36+
self.initial_earned_credit.bccr_holding_account_id = "TEST-001"
37+
self.initial_earned_credit.save(
38+
update_fields=["issuance_status", "bccr_trading_name", "bccr_holding_account_id"]
39+
)
40+
41+
# ------------------------------------------------------------------
42+
# Scenario 1: Excess emissions decrease after invoice has been paid
43+
# ------------------------------------------------------------------
44+
45+
@patch(SEND_NO_OBLIGATION_EMAIL)
46+
@patch(SEND_OBLIGATION_EMAIL)
47+
@patch(CREATE_ADJUSTMENT_PATH)
48+
@patch(ON_COMMIT_PATH)
49+
@patch(REFRESH_DATA)
50+
@patch(HANDLE_OBLIGATION_INTEGRATION)
51+
def test_decreased_obligation_paid_invoice_creates_manual_handling(
52+
self, mock_elicensing, mock_refresh, mock_on_commit, _mock_create_adj, _mock_obl_email, _mock_no_obl_email
53+
):
54+
"""Excess emissions decrease when invoice has been partially paid with cash.
55+
56+
Routing:
57+
- ManualHandler: no manual handling on initial CRV → can't handle
58+
- SupercedeVersionHandler: invoice exists (not pre-invoice) → can't handle
59+
- DecreasedObligationHandler: excess decreased + unpaid invoice + cash paid → CAN handle
60+
61+
Setup:
62+
- Initial: 10 excess emissions, invoice created with outstanding = full fee
63+
- Half the fee is paid in cash; outstanding_balance is reduced to half
64+
- Supplementary: 0 excess → refund_pool = full fee > outstanding (half fee)
65+
→ obligation fully paid AND leftover refund > 0 AND has_cash → manual handling
66+
67+
Expected:
68+
- New CRV with NO_OBLIGATION_OR_EARNED_CREDITS status
69+
- Previous CRV marked OBLIGATION_FULLY_MET
70+
- ManualHandling record created with handling_type=OBLIGATION,
71+
context=OBLIGATION_REFUND_POOL_CASH
72+
"""
73+
mock_elicensing.side_effect = self.fake_handle_obligation_post_invoice
74+
mock_refresh.side_effect = self.fake_refresh
75+
mock_on_commit.side_effect = lambda fn: fn()
76+
77+
self._create_base_infrastructure()
78+
self._create_initial_report_with_obligation_post_invoice(Decimal("10"))
79+
80+
invoice = self.initial_obligation.elicensing_invoice
81+
fee = invoice.outstanding_balance
82+
partial_payment = (fee / 2).quantize(Decimal("0.01"))
83+
invoice.outstanding_balance = fee - partial_payment
84+
invoice.save(update_fields=["outstanding_balance"])
85+
86+
fee_line_item = ElicensingLineItem.objects.get(
87+
elicensing_invoice=invoice,
88+
line_item_type=ElicensingLineItem.LineItemType.FEE,
89+
)
90+
ElicensingPayment.objects.create(
91+
payment_object_id=self.initial_obligation.id,
92+
elicensing_line_item=fee_line_item,
93+
amount=partial_payment,
94+
)
95+
96+
self._submit_supplementary_report(
97+
excess_emissions=Decimal("0"),
98+
previous_report_version=self.initial_report_version,
99+
)
100+
101+
supp_crv = ComplianceReportVersion.objects.get(
102+
compliance_report=self.compliance_report,
103+
is_supplementary=True,
104+
)
105+
assert supp_crv.status == ComplianceReportVersion.ComplianceStatus.NO_OBLIGATION_OR_EARNED_CREDITS
106+
assert supp_crv.previous_version == self.initial_crv
107+
108+
manual_handling = ComplianceReportVersionManualHandling.objects.get(compliance_report_version=supp_crv)
109+
assert manual_handling.handling_type == ComplianceReportVersionManualHandling.HandlingType.OBLIGATION
110+
assert manual_handling.context == ComplianceReportVersionManualHandling.Context.OBLIGATION_REFUND_POOL_CASH
111+
assert manual_handling.director_decision == (
112+
ComplianceReportVersionManualHandling.DirectorDecision.PENDING_MANUAL_HANDLING
113+
)
114+
115+
self.initial_crv.refresh_from_db()
116+
assert self.initial_crv.status == ComplianceReportVersion.ComplianceStatus.OBLIGATION_FULLY_MET
117+
118+
# ------------------------------------------------------------------
119+
# Scenario 2: Credited emissions decrease after credits have been approved
120+
# ------------------------------------------------------------------
121+
122+
@patch(SEND_NO_OBLIGATION_EMAIL)
123+
@patch(SEND_EARNED_CREDITS_EMAIL)
124+
@patch(SEND_OBLIGATION_EMAIL)
125+
@patch(HANDLE_OBLIGATION_INTEGRATION)
126+
def test_decreased_credits_approved_creates_manual_handling(
127+
self, _mock_elicensing, _mock_obl_email, _mock_ec_email, _mock_no_obl_email
128+
):
129+
"""Credited emissions decrease when credits are APPROVED.
130+
131+
Routing:
132+
- ManualHandler: no manual handling on initial CRV → can't handle
133+
- SupercedeVersionHandler: issuance_status is APPROVED (not CREDITS_NOT_ISSUED) → can't handle
134+
- DecreasedCreditHandler: credited decreased + previous credit exists → CAN handle (APPROVED branch)
135+
136+
Expected:
137+
- New CRV with EARNED_CREDITS status
138+
- ManualHandling record created with handling_type=EARNED_CREDITS,
139+
context=EARNED_CREDITS_PREVIOUSLY_APPROVED
140+
- Previous earned credit NOT modified (stays APPROVED)
141+
"""
142+
self._create_base_infrastructure()
143+
self._create_initial_report_with_earned_credits(Decimal("100"))
144+
self._approve_initial_earned_credits()
145+
146+
self._submit_supplementary_report(
147+
excess_emissions=Decimal("0"),
148+
credited_emissions=Decimal("50"),
149+
previous_report_version=self.initial_report_version,
150+
)
151+
152+
supp_crv = ComplianceReportVersion.objects.get(
153+
compliance_report=self.compliance_report,
154+
is_supplementary=True,
155+
)
156+
assert supp_crv.status == ComplianceReportVersion.ComplianceStatus.EARNED_CREDITS
157+
assert supp_crv.previous_version == self.initial_crv
158+
159+
manual_handling = ComplianceReportVersionManualHandling.objects.get(compliance_report_version=supp_crv)
160+
assert manual_handling.handling_type == ComplianceReportVersionManualHandling.HandlingType.EARNED_CREDITS
161+
assert (
162+
manual_handling.context == ComplianceReportVersionManualHandling.Context.EARNED_CREDITS_PREVIOUSLY_APPROVED
163+
)
164+
assert manual_handling.director_decision == (
165+
ComplianceReportVersionManualHandling.DirectorDecision.PENDING_MANUAL_HANDLING
166+
)
167+
168+
self.initial_earned_credit.refresh_from_db()
169+
assert self.initial_earned_credit.issuance_status == ComplianceEarnedCredit.IssuanceStatus.APPROVED

0 commit comments

Comments
 (0)