From 1ba6f64bb7c7bae3627252d2e7a826dfa66718aa Mon Sep 17 00:00:00 2001 From: Emanuel Cino Date: Thu, 16 Jul 2026 13:57:01 +0200 Subject: [PATCH 1/2] FIX invoice generation mindset - The next month should be generated if advance_billing_month is 1. --- recurring_contract/models/contract_group.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/recurring_contract/models/contract_group.py b/recurring_contract/models/contract_group.py index e4eea0b1..b33ab3c1 100644 --- a/recurring_contract/models/contract_group.py +++ b/recurring_contract/models/contract_group.py @@ -266,12 +266,14 @@ def _generate_invoices(self, invoicer): invoicing_date, starting_offset = group._calculate_start_date_and_offset() # Iterate through invoice offsets to generate invoices. - # Upper bound (exclusive) = starting_offset + advance_billing_months - # + month_interval - 1. The starting_offset term ensures the same - # number of periods is produced whether offset is 0 or 1: + # Upper bound = starting_offset + advance_billing_months + # + month_interval. This keeps the same invoice count + # when starting_offset is 1 (waiting contracts skipping current month). + # This ensures the correct number of invoices for all billing cycles: + # rec val => recurring_value / mo => months interval # # rec val | rec unit| advance | mo | offset| invoices - # 1 | month | 1 | 1 | 0 | 1 (curr month) + # 1 | month | 1 | 1 | 0 | 2 (curr + next month) # 1 | month | 1 | 1 | 1 | 1 (next month) # 1 | month | 12 | 1 | 0 | 12 (+11 months) # 1 | year | 12 | 12 | 0 | 2 (curr+next yr) @@ -282,8 +284,7 @@ def _generate_invoices(self, invoicer): starting_offset, starting_offset + group.advance_billing_months - + group.month_interval - - 1, + + group.month_interval, group.month_interval, ): # Calculate the current invoicing date for this offset From 5a8f406b47b189a18929d6794a91e741cf2c0e37 Mon Sep 17 00:00:00 2001 From: Daniel Palumbo Date: Thu, 16 Jul 2026 14:29:48 +0200 Subject: [PATCH 2/2] FIX invoice generation upper bound for waiting contracts The previous formula (starting_offset + advance_billing_months + month_interval) still added starting_offset to the upper bound, which changes the invoice count depending on whether the contract is waiting or active. For a waiting contract with advance_billing_months=3, it generated 4 invoices instead of 3, breaking test_generate_invoice_data_coherency. starting_offset should only control where the range starts (skip the current month for a waiting contract), not where it ends. --- recurring_contract/models/contract_group.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/recurring_contract/models/contract_group.py b/recurring_contract/models/contract_group.py index b33ab3c1..774d282b 100644 --- a/recurring_contract/models/contract_group.py +++ b/recurring_contract/models/contract_group.py @@ -266,25 +266,23 @@ def _generate_invoices(self, invoicer): invoicing_date, starting_offset = group._calculate_start_date_and_offset() # Iterate through invoice offsets to generate invoices. - # Upper bound = starting_offset + advance_billing_months - # + month_interval. This keeps the same invoice count - # when starting_offset is 1 (waiting contracts skipping current month). - # This ensures the correct number of invoices for all billing cycles: - # rec val => recurring_value / mo => months interval + # Upper bound (exclusive) = advance_billing_months + 1, independent + # of starting_offset: starting_offset only shifts where the range + # starts (0 = current month, 1 = skip to next month for a waiting + # contract), it must not also shift where it ends, or the number + # of invoices generated changes depending on offset. # # rec val | rec unit| advance | mo | offset| invoices # 1 | month | 1 | 1 | 0 | 2 (curr + next month) # 1 | month | 1 | 1 | 1 | 1 (next month) - # 1 | month | 12 | 1 | 0 | 12 (+11 months) + # 1 | month | 12 | 1 | 0 | 13 (curr + 12 more) # 1 | year | 12 | 12 | 0 | 2 (curr+next yr) # # _should_skip_invoice_generation will skip any period already covered # by an existing non-cancelled invoice, so re-running is always safe. for invoice_offset in range( starting_offset, - starting_offset - + group.advance_billing_months - + group.month_interval, + group.advance_billing_months + 1, group.month_interval, ): # Calculate the current invoicing date for this offset