@@ -88,7 +88,7 @@ def handle(
8888 # 3) Build a normalized strategy dict
8989 # The strategy object returned by the determination functions is the single contract used _process_adjustment_after_commit
9090 # invoices: list of InvoiceAdjustment entries, each with `version_id`, `applied`, `net_outstanding_after`, `mark_fully_met`, `should_void_invoice`.
91- # should_record_manual_handling: boolean flag .
91+ # manual_handling_context: the manual-handling context to record, or None .
9292 # earned_credits_tonnes: earned credits to create on the NEW CRV
9393 # should_create_earned_credits: whether to create an earned-credits record
9494
@@ -134,37 +134,43 @@ def _process_adjustment_after_commit(
134134 """
135135 Execute the "strategy" produced in handle():
136136
137- For each invoice in strategy["invoices"]:
138- - Post a signed negative adjustment (reduces outstanding).
137+ For each invoice in strategy.invoices:
138+ - Post a signed negative adjustment (reduces outstanding), dated to the fee's date so the
139+ reduction applies from the start of the fee's accrual window (works for regular and
140+ supplementary obligations, and eLicensing accepts it since it is never dated before the fee).
139141 - If net outstanding hits zero, mark that previous CRV as FULLY_MET.
140142 - If fully met AND no prior CASH payments, void the invoice.
141- - If fully met AND no prior CASH payments AND credited emissions, create earned credits
142143
144+ Then act on the single outcome of the strategy already decided for the new supplementary CRV:
145+ record manual handling (with the strategy's context) or create earned credits.
143146 """
144- for entry in strategy .invoices :
145- applied = entry .applied
147+ for invoice in strategy .invoices :
148+ applied = invoice .applied
146149 if applied != ZERO_DECIMAL :
147150 reason = (
148151 ElicensingAdjustment .Reason .SUPPLEMENTARY_REPORT_ADJUSTMENT_TO_VOID_INVOICE
149- if entry .should_void_invoice
152+ if invoice .should_void_invoice
150153 else ElicensingAdjustment .Reason .SUPPLEMENTARY_REPORT_ADJUSTMENT
151154 )
152155 ComplianceAdjustmentService .create_adjustment_for_target_version (
153- target_compliance_report_version_id = entry .version_id ,
156+ target_compliance_report_version_id = invoice .version_id ,
154157 adjustment_total = applied ,
155158 supplementary_compliance_report_version_id = compliance_report_version_id ,
156159 reason = reason ,
160+ date_adjustment_to_fee_date = True ,
157161 )
158162
159- if entry .mark_fully_met :
160- DecreasedObligationHandler ._mark_previous_version_fully_met (entry .version_id )
161- if entry .should_void_invoice :
162- DecreasedObligationHandler ._void_unpaid_invoices (entry .version_id )
163+ if invoice .mark_fully_met :
164+ DecreasedObligationHandler ._mark_previous_version_fully_met (invoice .version_id )
165+ if invoice .should_void_invoice :
166+ DecreasedObligationHandler ._void_unpaid_invoices (invoice .version_id )
163167
164- if strategy .should_record_manual_handling :
165- DecreasedObligationHandler ._record_manual_handling (compliance_report_version_id )
166-
167- if strategy .should_create_earned_credits and strategy .earned_credits_tonnes > ZERO_DECIMAL :
168+ # Act on the single outcome decided by the strategy builder.
169+ if strategy .manual_handling_context :
170+ DecreasedObligationHandler ._record_manual_handling (
171+ compliance_report_version_id , context = strategy .manual_handling_context
172+ )
173+ elif strategy .should_create_earned_credits and strategy .earned_credits_tonnes > ZERO_DECIMAL :
168174 DecreasedObligationHandler ._create_earned_credits (
169175 compliance_report_version_id ,
170176 strategy .earned_credits_tonnes ,
@@ -271,18 +277,17 @@ def _build_adjustment_strategy(
271277 total_refund_vs_anchor , invoices , anchor_crv_id
272278 )
273279
274- per_invoice , refund_pool = DecreasedObligationHandler ._adjust_refund_to_invoices (invoices , refund_pool )
280+ invoice_adjustments , refund_pool = DecreasedObligationHandler ._adjust_refund_to_invoices (invoices , refund_pool )
275281
276282 # --- Manual handling decision -------------------------------------------
277283 # Fully paid if no invoices OR all tracked invoices end at $0.
278- fully_paid_obligation = (len (per_invoice ) == 0 ) or all (
279- e .net_outstanding_after == ZERO_DECIMAL for e in per_invoice
284+ fully_paid_obligation = (len (invoice_adjustments ) == 0 ) or all (
285+ invoice_adjustment .net_outstanding_after == ZERO_DECIMAL for invoice_adjustment in invoice_adjustments
280286 )
281287
282288 has_cash = DecreasedObligationHandler ._determine_if_has_cash (
283289 fully_paid_obligation , refund_pool , over_corrected_tonnes , invoices , anchor_crv_id
284290 )
285- should_record_manual_handling = has_cash
286291
287292 # --- Earned credits decision -------------------------------------------
288293 # If the decrease over-corrects past zero AND there were no cash payments,
@@ -295,9 +300,22 @@ def _build_adjustment_strategy(
295300 should_create_earned_credits = True
296301 earned_credits_tonnes = over_corrected_tonnes
297302
303+ # --- Manual handling decision -------------------------------------------
304+ # Two situations require manual handling, differing only by the context:
305+ # - a refundable cash pool, or
306+ # - a decrease that pushed applied credits over the cap
307+ # An over-correction into earned credits takes precedence over the over-cap case.
308+ manual_handling_context : Optional [str ] = None
309+ if has_cash :
310+ manual_handling_context = ComplianceReportVersionManualHandling .Context .OBLIGATION_REFUND_POOL_CASH
311+ elif not should_create_earned_credits and DecreasedObligationHandler ._has_credit_usage_over_cap (
312+ invoice_adjustments
313+ ):
314+ manual_handling_context = ComplianceReportVersionManualHandling .Context .CREDITS_OVER_ALLOWED_PERCENTAGE
315+
298316 return AdjustmentStrategy (
299- invoices = per_invoice ,
300- should_record_manual_handling = should_record_manual_handling ,
317+ invoices = invoice_adjustments ,
318+ manual_handling_context = manual_handling_context ,
301319 should_create_earned_credits = should_create_earned_credits ,
302320 earned_credits_tonnes = earned_credits_tonnes ,
303321 )
@@ -653,6 +671,7 @@ def _mark_previous_version_fully_met(previous_version_id: int) -> None:
653671 @staticmethod
654672 def _record_manual_handling (
655673 compliance_report_version_id : int ,
674+ context : str = ComplianceReportVersionManualHandling .Context .OBLIGATION_REFUND_POOL_CASH ,
656675 ) -> None :
657676 """
658677 Create a related ComplianceReportVersionManualHandling record and set the
@@ -664,12 +683,31 @@ def _record_manual_handling(
664683 ComplianceReportVersionManualHandling .objects .create (
665684 compliance_report_version = crv ,
666685 handling_type = ComplianceReportVersionManualHandling .HandlingType .OBLIGATION ,
667- context = ComplianceReportVersionManualHandling . Context . OBLIGATION_REFUND_POOL_CASH ,
686+ context = context ,
668687 )
669688
670689 crv .status = ComplianceReportVersion .ComplianceStatus .REQUIRES_MANUAL_HANDLING
671690 crv .save (update_fields = ['status' ])
672691
692+ @staticmethod
693+ def _has_credit_usage_over_cap (invoice_adjustments : List [InvoiceAdjustment ]) -> bool :
694+ """
695+ Return True if the decrease pushes the compliance units already applied to any target obligation
696+ over the allowed cap (the decrease shrinks the cap). The invoice_adjustments decrease amount is passed as
697+ the pending supplementary adjustment, so the cap reflects the decrease before it is posted
698+ """
699+ from compliance .service .bc_carbon_registry .apply_compliance_units_service import ApplyComplianceUnitsService
700+
701+ return any (
702+ ApplyComplianceUnitsService .is_credit_usage_over_cap (
703+ invoice_adjustment .version_id ,
704+ pending_supplementary_adjustment = (
705+ ZERO_DECIMAL if invoice_adjustment .should_void_invoice else invoice_adjustment .applied
706+ ),
707+ )
708+ for invoice_adjustment in invoice_adjustments
709+ )
710+
673711 @staticmethod
674712 def _create_earned_credits (compliance_report_version_id : int , tonnes : Decimal ) -> None :
675713 """
0 commit comments