From 22d2ae3d830c45fecd557966dcd470619e035691 Mon Sep 17 00:00:00 2001 From: ??? Date: Fri, 24 Apr 2026 12:13:57 +0200 Subject: [PATCH 1/2] [FIX] connector_sapb1: enable allow_commit on export_record job functions bind_export in connector_extension performs cr.commit() to persist the binding immediately after the SAP HTTP PATCH succeeds. This is intentional: it prevents duplicate addresses being created in SAP on job retry (when the adapter's -2035 counter would increment AddressName to "(2)", "(3)" etc. for the same partner on each retry attempt). Since queue_job 16.0.2.13.2 (OCA/queue#892), the _prevent_commit guard raises RuntimeError on any cr.commit() inside a job. Setting allow_commit =True on the Job Function makes perform() run in a temporary env with a separate DB cursor: the commit is real but does not release the main cursor's queue_job row-lock, so there is no double execution risk. Post-migration updates the existing auto-created queue.job.function records for: - sapb1.binding - sapb1.res.partner - sapb1.sale.order - sapb1.sale.order.line - sapb1.product.product Refs: https://github.com/OCA/queue/wiki/Upgrade-warning:-commits-inside-jobs Co-Authored-By: Claude Opus 4.7 (1M context) --- connector_sapb1/README.rst | 6 +- connector_sapb1/__manifest__.py | 2 +- .../data/queue_job_function_data.xml | 56 ++++++++++++++++++ .../migrations/16.0.1.0.1/pre-migration.py | 59 +++++++++++++++++++ connector_sapb1/static/description/index.html | 32 ++++++---- 5 files changed, 140 insertions(+), 15 deletions(-) create mode 100644 connector_sapb1/migrations/16.0.1.0.1/pre-migration.py diff --git a/connector_sapb1/README.rst b/connector_sapb1/README.rst index d11a32361..655cfe4ac 100644 --- a/connector_sapb1/README.rst +++ b/connector_sapb1/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ================ Connector SAP B1 ================ @@ -13,7 +17,7 @@ Connector SAP B1 .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-nuobit%2Fodoo--addons-lightgray.png?logo=github diff --git a/connector_sapb1/__manifest__.py b/connector_sapb1/__manifest__.py index c641ccf28..e780bcbe5 100644 --- a/connector_sapb1/__manifest__.py +++ b/connector_sapb1/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Connector SAP B1", - "version": "16.0.1.0.0", + "version": "16.0.1.0.1", "author": "NuoBiT Solutions SL", "license": "AGPL-3", "category": "Connector", diff --git a/connector_sapb1/data/queue_job_function_data.xml b/connector_sapb1/data/queue_job_function_data.xml index c78e149d2..8802e8c3f 100644 --- a/connector_sapb1/data/queue_job_function_data.xml +++ b/connector_sapb1/data/queue_job_function_data.xml @@ -17,4 +17,60 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)--> + + + + export_record + + + + + + + export_record + + + + + + + export_record + + + + + + + export_record + + + + + + + export_record + + + + diff --git a/connector_sapb1/migrations/16.0.1.0.1/pre-migration.py b/connector_sapb1/migrations/16.0.1.0.1/pre-migration.py new file mode 100644 index 000000000..85defa303 --- /dev/null +++ b/connector_sapb1/migrations/16.0.1.0.1/pre-migration.py @@ -0,0 +1,59 @@ +"""Pre-migration: claim auto-created queue.job.function records. + +Existing installs may have queue.job.function records for export_record on +sapb1 binding models that were auto-created and have no ir_model_data entry +(thus no xml_id). When the new XML definition loads with noupdate="1", +Odoo would try to CREATE records with our new xml_ids, which fails due to +the (model_id, method) unique constraint. + +Solution: insert the ir_model_data entries BEFORE the XML loads, so each +xml_id is already linked to the existing record. Also set allow_commit=True +directly (since noupdate="1" prevents the XML from updating fields). + +See: https://github.com/OCA/queue/wiki/Upgrade-warning:-commits-inside-jobs +""" + +JOB_FUNCTIONS = [ + ("sapb1_binding_method_export_record_job_function", "sapb1.binding"), + ("sapb1_res_partner_export_record_job_function", "sapb1.res.partner"), + ("sapb1_sale_order_export_record_job_function", "sapb1.sale.order"), + ("sapb1_sale_order_line_export_record_job_function", "sapb1.sale.order.line"), + ("sapb1_product_product_export_record_job_function", "sapb1.product.product"), +] + + +def migrate(cr, version): + if not version: + return + for xml_id, model_name in JOB_FUNCTIONS: + cr.execute( + """ + SELECT qjf.id + FROM queue_job_function qjf + JOIN ir_model im ON im.id = qjf.model_id + WHERE qjf.method = 'export_record' AND im.model = %s + """, + (model_name,), + ) + row = cr.fetchone() + if not row: + continue + qjf_id = row[0] + cr.execute( + """ + INSERT INTO ir_model_data + (module, name, model, res_id, noupdate) + VALUES + ('connector_sapb1', %s, 'queue.job.function', %s, TRUE) + ON CONFLICT (module, name) DO NOTHING + """, + (xml_id, qjf_id), + ) + cr.execute( + """ + UPDATE queue_job_function + SET allow_commit = TRUE + WHERE id = %s + """, + (qjf_id,), + ) diff --git a/connector_sapb1/static/description/index.html b/connector_sapb1/static/description/index.html index 9c2813944..f441c98d5 100644 --- a/connector_sapb1/static/description/index.html +++ b/connector_sapb1/static/description/index.html @@ -1,18 +1,18 @@ - -Connector SAP B1 +README.rst -
-

Connector SAP B1

+
+ + +Odoo Community Association + +
+

Connector SAP B1

-

Beta License: AGPL-3 nuobit/odoo-addons

+

Beta License: AGPL-3 nuobit/odoo-addons

SAP Business One connector

Table of contents

@@ -384,7 +389,7 @@

Connector SAP B1

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -392,15 +397,15 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • NuoBiT Solutions SL
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is part of the nuobit/odoo-addons project on GitHub.

You are welcome to contribute.

+
From 623c7809859fe36ecc379905b6b526ab47f0c146 Mon Sep 17 00:00:00 2001 From: Eric Antones Date: Tue, 28 Apr 2026 09:13:23 +0200 Subject: [PATCH 2/2] [FIX] connector_extension: propagate temporary cursor to record args in export entry-points MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a queue.job.function has allow_commit=True, queue_job 16.0's Job.in_temporary_env() opens a new DB cursor and switches self.recordset.env to it. However, args passed to the job method (backend_record, relation) keep the outer cursor — they were deserialized from the queue.job's args field with the runner's env, and queue_job does NOT re-bind them to the temporary env. When binding.export_record(backend_record, relation) calls backend_record.work_on(self._name), the resulting WorkContext.env returns self.collection.env (component/core.py:271-277) — i.e. the OUTER cursor that has the _prevent_commit monkey-patch applied. binder.bind_export() then commits via self.env.cr.commit() and hits the forbidden_commit guard, so allow_commit=True alone does NOT take effect for export entry-points whose flow ends in bind_export. Fix: rebind backend_record (and relation) to a new env that copies the arg's ORIGINAL env (preserving uid, su, context) and only swaps cr to self.env.cr — the temporary cursor opened by Job.in_temporary_env(). This is the same pattern queue_job itself uses internally (env(cr=new_cr)) and is minimally invasive: it only changes the cursor, leaving the user/su/context that were captured at enqueue time intact. Idempotent when self.env.cr already equals the arg's cr (calls outside of a queue job). Scope: only export entry-points need this. Imports do not commit — bind_import does not call cr.commit() (was removed in 22c9fb20) — so they never hit the forbidden_commit guard and don't need allow_commit=True nor the cursor propagation. Verified against Novolux production task #2539: queue.job 9434462 (.export_record, allow_commit=true) was still failing with RuntimeError("Commit is forbidden in queue jobs") at binder.py:315 because backend_record arrived on the outer cursor. --- connector_extension/models/binding/binding.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/connector_extension/models/binding/binding.py b/connector_extension/models/binding/binding.py index 53de6e30e..0ac681242 100644 --- a/connector_extension/models/binding/binding.py +++ b/connector_extension/models/binding/binding.py @@ -38,6 +38,27 @@ def import_data(self, backend_record, domain=None, delayed=True): @api.model def export_data(self, backend_record, domain=None, delayed=True): """Prepare the batch export records to Channel""" + # Cursor-rebind workaround for OCA/queue queue_job (16.0+). + # `Job.in_temporary_env()` (added by OCA/queue#910 / commit f2bfda90) + # only rebinds `self.recordset` to the new cursor opened when + # `allow_commit=True`; it does NOT rebind args. Without this manual + # rebind, `backend_record.work_on()` builds a `WorkContext` whose + # `env` is the OUTER cursor (with `_prevent_commit` patched on + # `cr.commit`), so `binder.bind_export()` at `binder.py:315` still + # raises RuntimeError("Commit is forbidden in queue jobs") despite + # `allow_commit=True` being set. Pattern (cursor-only swap, preserves + # uid/su/context captured at `_job_prepare_context_before_enqueue`) + # matches what queue_job uses internally for `self.recordset`, and + # what @guewen (queue_job maintainer) himself suggested in + # OCA/queue#889 ("such export_record and such are implementation + # specific and need to be fixed in many places"). The OCA wiki page + # https://github.com/OCA/queue/wiki/Upgrade-warning:-commits-inside-jobs + # does NOT document this args-rebinding limitation. + # Same fix applied below in export_batch, export_record, + # export_delete_record. Imports do not need it because bind_import + # does not commit. + # Refs: OCA/queue#889, OCA/queue#910, OCA/connector#522. + backend_record = backend_record.with_env(backend_record.env(cr=self.env.cr)) if delayed: model = self.with_delay() return model.export_batch( @@ -61,6 +82,8 @@ def import_batch(self, backend_record, domain=None, delayed=True, use_data=True) @api.model def export_batch(self, backend_record, domain=None, delayed=True): """Prepare the batch export of records modified on Odoo""" + # Cursor-rebind workaround for queue_job; see export_data for rationale. + backend_record = backend_record.with_env(backend_record.env(cr=self.env.cr)) if not domain: domain = [] with backend_record.work_on(self._name) as work: @@ -102,6 +125,9 @@ def import_record(self, backend_record, external_id, sync_date, external_data=No @api.model def export_record(self, backend_record, relation): """Export Odoo record""" + # Cursor-rebind workaround for queue_job; see export_data for rationale. + backend_record = backend_record.with_env(backend_record.env(cr=self.env.cr)) + relation = relation.with_env(relation.env(cr=self.env.cr)) with backend_record.work_on(self._name) as work: exporter = work.component(usage="record.direct.exporter") return exporter.run(relation) @@ -109,6 +135,9 @@ def export_record(self, backend_record, relation): @api.model def export_delete_record(self, backend_record, relation): """Export Odoo record""" + # Cursor-rebind workaround for queue_job; see export_data for rationale. + backend_record = backend_record.with_env(backend_record.env(cr=self.env.cr)) + relation = relation.with_env(relation.env(cr=self.env.cr)) with backend_record.work_on(self._name) as work: deleter = work.component(usage="record.direct.export.deleter") return deleter.run(relation)