diff --git a/l10n_fr_einvoicing/models/account_move.py b/l10n_fr_einvoicing/models/account_move.py index dea1cc8..d72df08 100644 --- a/l10n_fr_einvoicing/models/account_move.py +++ b/l10n_fr_einvoicing/models/account_move.py @@ -184,10 +184,25 @@ def _compute_fr_directory_line_id(self): fr_directory_line_id = dir_lines.id move.fr_directory_line_id = fr_directory_line_id + # fr_directory_company_entity_type is deliberately NOT a dependency here. + # + # It is a related on company_id.partner_id.fr_directory_entity_type, so a + # single value would drive this stored field on EVERY invoice of the + # company. Setting the issuer's entity type — a one-off, done when the + # reform is switched on — then invalidates the whole history in one + # transaction: on a production-sized database (877k journal entries, 497k + # customer invoices on the one this was found on) the worker dies in + # MemoryError before writing anything. + # + # The issuer's entity type only changes at activation time, so the refresh + # is triggered explicitly and in batches by + # res.company._fr_ctc_recompute_einvoicing_required(). Same approach as + # company_fr_directory_line_id, which depends on company_id alone and comes + # with _fr_ctc_compute_invoice_company_dir_line(). @api.depends( - "fr_directory_company_entity_type", "fr_directory_partner_entity_type", "move_type", + "company_id", "company_id.fr_ctc_send_out_invoice", ) def _compute_einvoicing_required(self): diff --git a/l10n_fr_einvoicing/models/res_company.py b/l10n_fr_einvoicing/models/res_company.py index 7796f79..dfb9aa8 100644 --- a/l10n_fr_einvoicing/models/res_company.py +++ b/l10n_fr_einvoicing/models/res_company.py @@ -568,6 +568,49 @@ def _fr_ctc_authorization_code_redirect(self): } return action + def _fr_ctc_recompute_einvoicing_required(self, batch_size=2000): + """Refresh fr_einvoicing_required after the issuer's entity type changed. + + That stored field depends on the issuer's entity type, which only + changes once — when the reform is switched on. Keeping it in the + @api.depends would recompute the company's whole history at flush time + and exhaust the worker's memory on a production database, so it is + refreshed here instead, in committed batches. + + Deliberately not atomic: the computation is idempotent, so a batch + replayed after a failure yields the same result. + """ + self.ensure_one() + domain = [ + ("move_type", "in", ("out_invoice", "out_refund")), + ("company_id", "=", self.id), + ] + if self.hard_lock_date: + domain.append(("date", ">", self.hard_lock_date)) + move_ids = self.env["account.move"].search(domain).ids + logger.info( + "Recomputing fr_einvoicing_required on %d invoices in company %s", + len(move_ids), + self.display_name, + ) + for offset in range(0, len(move_ids), batch_size): + batch = self.env["account.move"].browse( + move_ids[offset:offset + batch_size] + ) + batch._compute_einvoicing_required() + self.env.flush_all() + self.env.cr.commit() + self.env.invalidate_all() + logger.info( + "fr_einvoicing_required: %d/%d invoices processed", + min(offset + batch_size, len(move_ids)), + len(move_ids), + ) + logger.info( + "Recomputation of fr_einvoicing_required in company %s finished", + self.display_name, + ) + def _fr_ctc_compute_invoice_company_dir_line(self): self.ensure_one() domain = [