@@ -27,6 +27,15 @@ def call
2727 pending_cycles . group_by { |cycle | invoice_key ( cycle ) } . each_value do |cycles |
2828 result . invoices << build_invoice ( cycles )
2929 end
30+
31+ # Finalize inline (invoice numbering) in the same job — one job per invoice, like
32+ # the legacy BillSubscriptionJob, so no extra Sidekiq hop. Each finalize runs in
33+ # its OWN short transaction (invoice.finalized!), so the per-billing_entity
34+ # numbering lock is held briefly and a race raises a clean SequenceError that the
35+ # ProcessJob retries. Retry-safe: a failed finalize leaves the invoice `generating`,
36+ # and this reconcile re-finalizes it on the retry (it re-queries generating
37+ # invoices, so it doesn't re-create — the cycles are already `done`).
38+ finalize_generating_invoices
3039 end
3140
3241 result
@@ -108,13 +117,24 @@ def build_invoice(cycles)
108117 cycles . each { |cycle | cycle . update! ( status : :done , invoice :) }
109118 end
110119
111- # Finalize (invoice numbering) in a separate, retry-protected job. Numbering
112- # serialises per billing_entity on a 10s advisory lock, so keeping it OUT of this
113- # parallel per-customer transaction means the contention can't crash the fan-out —
114- # a numbering race just retries the finalize on the already-persisted invoice.
115- BillingCycles ::FinalizeInvoiceJob . perform_later ( invoice )
116-
117120 invoice
118121 end
122+
123+ # Finalize every still-generating invoice this customer's cycles produced — this run's
124+ # plus any left generating by a failed finalize on a previous attempt. Re-querying
125+ # (not the built array) is what makes a job retry recover orphans without re-creating.
126+ def finalize_generating_invoices
127+ invoice_ids = BillingCycle
128+ . where ( customer_id : customer . id , status : :done )
129+ . where . not ( invoice_id : nil )
130+ . joins ( :invoice )
131+ . where ( invoices : { status : :generating } )
132+ . distinct
133+ . pluck ( :invoice_id )
134+
135+ Invoice . where ( id : invoice_ids ) . find_each do |invoice |
136+ Invoices ::TransitionToFinalStatusService . call ( invoice :)
137+ end
138+ end
119139 end
120140end
0 commit comments