T2860 - FIX recurring invoice generation for annual contracts - #276
Conversation
Two bugs in _generate_invoices / _should_skip_invoice_generation:
1. Loop range used `advance_billing_months + 1` as upper bound, causing
an off-by-one that generated too many invoices. Fixed to
`advance_billing_months + month_interval - 1` so the number of
generated periods is consistent across all billing cycles:
- monthly/advance=1 → 1 invoice, monthly/advance=12 → 12 invoices
- annual/advance=12 → 2 invoices (current + next year)
2. Skip check did not exclude cancelled invoices, so a super-deleted
(cancelled) invoice for a future period would permanently block
regeneration of that period. Fixed by adding
`("state", "not in", ["cancel"])` to the search domain.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request reformats code, adjusts contributor list spacing, and updates the invoice generation logic to ignore cancelled invoices. It also modifies the offset range calculation in _generate_invoices. However, the new range calculation is missing starting_offset in its upper bound, which can result in an empty range and prevent invoices from being generated when there are waiting contracts.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Confidence Score: 5/5The changes are narrowly scoped to recurring invoice period generation and duplicate-period checks. No correctness or security issues were identified in the reviewed changes, and the implementation matches the described contract for advance billing and cancelled invoices.
What T-Rex did
Reviews (2): Last reviewed commit: "FIX loop range truncation when starting_..." | Re-trigger Greptile |
| open_invoices = self.env["account.move"].search( | ||
| [ | ||
| ("invoice_date", "=", invoicing_date), | ||
| ("partner_id", "=", self.partner_id.id), | ||
| ("move_type", "=", "out_invoice"), | ||
| ("state", "not in", ["cancel"]), | ||
| ("line_ids.contract_id", "in", contracts.ids), | ||
| ("line_ids.product_id", "in", contracts.mapped("product_ids").ids), | ||
| ] | ||
| ) |
There was a problem hiding this comment.
This base search now ignores cancelled moves, but the coupled sponsorship override has a separate gift-invoice lookup that still counts cancelled invoice lines: sponsorship_compassion/models/contract_group.py in compassion-modules. When a future gift invoice is cancelled, that override can still return has_all_gifts = True, so gift periods remain blocked even though regular contract periods can regenerate.
Artifacts
Repro: focused harness importing the real base and sponsorship override methods
- Contains supporting evidence from the run (text/x-python; charset=utf-8).
Repro: harness output showing cancelled gift line counted and skip result True
- Keeps the command output available without making the summary code-heavy.
When _calculate_start_date_and_offset returns starting_offset=1 (waiting contracts or block-day logic), the previous upper bound `advance_billing_months + month_interval - 1` did not include the offset, causing range(1, 1, 1) to be empty for monthly contracts with advance_billing_months=1 — no invoices generated at all. Fix: upper bound is now `starting_offset + advance_billing_months + month_interval - 1` so the number of generated periods is identical regardless of the offset. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs in _generate_invoices / _should_skip_invoice_generation:
Loop range used
advance_billing_months + 1as upper bound, causing an off-by-one that generated too many invoices. Fixed toadvance_billing_months + month_interval - 1so the number of generated periods is consistent across all billing cycles:Skip check did not exclude cancelled invoices, so a super-deleted (cancelled) invoice for a future period would permanently block regeneration of that period. Fixed by adding
("state", "not in", ["cancel"])to the search domain.