Monday, May 11, 2026
17 changes · saas-19.1
New functionality added to Odoo
This update incorporates the required 5% IGIC (Value Added Tax) for Spanish businesses within the Odoo system. It also corrects previous errors where incorrect tax percentages (3%) or incorrect tax groups were being applied. This ensures accurate tax calculations and compliance with Spanish regulations.
Original PR description
- Fix also some errors on the 5% taxes, where a 3 percent was applied or the group was not the right onw @jco-odoo --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#263101 Forward-Port-Of: odoo/odoo#231836
Resolved issues and error corrections
This update fixes a previous issue where helpdesk returns weren't available for orders shipped through dropshipping. Now, users can process returns for dropshipped transfers directly from the helpdesk ticket, streamlining the returns process and improving customer service. This ensures consistent return functionality regardless of order fulfillment method.
Original PR description
### Steps to Reproduce: - Enable dropshipping in Inventory settings - Create a product with inventory tracking enabled - Enable dropship under the Inventory tab for the product - Add a vendor and…
### Steps to Reproduce: - Enable dropshipping in Inventory settings - Create a product with inventory tracking enabled - Enable dropship under the Inventory tab for the product - Add a vendor and quantity under the Purchase tab - Create a sale order for the product - Go to the Purchase stat button and confirm the order - Click on the Dropship stat button and validate the transfer - Open Helpdesk and create a new ticket for the same partner ### Issue: The "Returns" stat button is not visible for dropshipped deliveries. ### Current behaviour: - The helpdesk ticket allows returns of customer orders only if the order is outgoing. However, this does not cover the usecase where the order was dropshipped and still needs to be returned to the vendor. - With the current behavior, the user needs to find the customer's order to return the transfer as it is not possible to do from the ticket. ### Expected behaviour: Helpdesk tickets should also allow returns of dropshipped transfers (done and linked to the SO). ### Fix: The helpdesk return logic was limited to only 'outgoing' pickings. This commit extends the 'return' button should be visible if there is at least one delivery or dropship order linked to the partner of the ticket Issue:https://github.com/odoo/enterprise/pull/81378 task-4881338 Forward-Port-Of: odoo/enterprise#91402
This update ensures that a unique transaction ID, provided by the Italian tax authority (SDI), is consistently saved for vendor bills. Previously, this ID was lost during the import process. This enhancement improves the traceability and accuracy of financial records related to Italian VAT compliance.
Original PR description
A unique transaction id is provided by the SDI for every document. This transaction was saved on document sending, but discarded for received one. --- For each unwrapped attachment, the _unwrap_attachments method extend the origin filename with `_<number>`. This mechanism allow finding what transaction id does each move correspond to. opw-6111186 Forward-Port-Of: odoo/odoo#261970
This update fixes an issue where changing the delivery date for Hungarian invoices caused incorrect journal line calculations and unbalanced entries. The fix ensures that exchange rates are correctly applied when the delivery date is modified, resolving discrepancies in tax calculations and improving financial accuracy.
Original PR description
### Issue: When changing the delivery date (used as the Hungarian exchange rate date), some journal lines could be recomputed incorrectly, leading to unbalanced entries ### Cause:…
### Issue: When changing the delivery date (used as the Hungarian exchange rate date), some journal lines could be recomputed incorrectly, leading to unbalanced entries ### Cause: `expected_currency_rate` was recomputed when `delivery_date` changed, but the new value was never automatically applied In addition, after https://github.com/odoo/odoo/pull/225407, `_sync_tax_lines` partially updated the lines: https://github.com/odoo/odoo/blob/f5501e5c8dcf60444077912db4c87e7a3f2654a6/addons/account/models/account_move.py#L3029-L3031 https://github.com/odoo/odoo/blob/f5501e5c8dcf60444077912db4c87e7a3f2654a6/addons/account/models/account_move.py#L1633-L1637 These methods reapply the previous tax rate, causing base and tax lines to be updated inconsistently As a result, when the base amount increases, the tax amount decreases, and vice versa ### Steps to reproduce: - Install `l10n_hu_edi` and `accountant` with demo data, then switch to the `HU company` - Go to Currencies → USD and add two rates: April 5: HUF per Unit = 100 April 6: HUF per Unit = 150 - Create an Invoice: (Any customer, Currency: USD, Line: Price = 1000, Tax = 27%) - Open the Journal Items and duplicate the browser tab for comparison - In the duplicated tab, change the Delivery Date to April 5 and save - Change the Delivery Date back to today and compare both tabs ### Before the fix: The values differ between both tabs because the tax lines keeps the old exchange rate opw-5801126 Forward-Port-Of: odoo/odoo#262915 Forward-Port-Of: odoo/odoo#258310
This update resolves an issue where non-administrator users were unable to update their profiles due to a change in how user records were accessed. The fix restores the necessary sudo access, ensuring all users can correctly manage their profile information. An additional check was added to maintain proper access controls.
Original PR description
Following commit [1], sudo access was removed when fetching the user record. As a result, non-admin users encounter an access error when updating their profile. Previously, admin users were handled via an explicit `browse(user_id)`, while other users relied on `request.env.user`, which is always sudoed. This commit restores the expected behavior by explicitly sudoing the user. To prevent unintended privilege escalation, an explicit access check is added to ensure the linked partner can still be edited by the current customer. [1]:https://github.com/odoo/odoo/commit/a816d151ae16afc62d7980cedc492942bc181884 Task-5407884
This update resolves an issue where the 'Add a line' button was unresponsive in the mobile version of grid views (like Timesheets). The fix adjusts how elements are sized on mobile devices, ensuring the button is always clickable, regardless of the scroll position. This improves the user experience for mobile users.
Original PR description
**Steps to reproduce** On mobile: - Open a grid view (e.g. Timesheets > All timesheets) - Try to click on "Add a line" for the first employee - Issue: nothing happens. Notice that by scrolling down the list to employees at the bottom, it becomes possible to click on "Add a line". **Cause** `o_grid_cell_overlay` elements (with `h-100`) were taking more than the expected height in mobile, because the `o_grid_section_title` divs only have `position: sticky` on larger viewports. With the default `position: static`, the child element's height was exceeding its parent's height. opw-5853489 Forward-Port-Of: odoo/enterprise#113400
A recent update intended to improve product name display in order forms has unexpectedly broken the functionality of manually editing product descriptions. To avoid further disruption, the change has been reverted. This impacts how users can describe products within purchase orders and sales orders.
Original PR description
**Steps to reproduce:** 1- Create a new PO/SO/Invoice. 2- Add a new order line and select a product. 3- Click on the UI dropdown to hide the Product column/field. 4- In the Description column (stored…
**Steps to reproduce:**
1- Create a new PO/SO/Invoice.
2- Add a new order line and select a product.
3- Click on the UI dropdown to hide the Product column/field.
4- In the Description column (stored as name in the model), the product name appears by default. Delete it and write a manual description.
5- Enable the Product column once again.
**Issue:**
The description no longer appears under the product name in the form view
**Why this happens:**
Commit https://github.com/odoo/odoo/commit/bd5b86e1596e0058ff8f46a79b7c1a918f1d4adc introduces the new logic below:
`this.currentProductName = this.productName ? label.split("\n")[0] : "";`
which assumed the label will always come from the backend in the form [Product Name] + [Product Translation]
So when the following condition was checked, it evaluated to true and the description was truncated:
`else if(this.currentProductName && label.startsWith(this.currentProductName))`
However, a customer can manually edit the description like mentioned above, removing the product name from the description (`name`) column.
Additionally, the fix affected another flow. When you create a product with a Vendor Product Name/Code for vendor X and then create a RFQ for vendor X with that product, the Vendor Product Name/Code no longer appears in the description.
**Resolution:**
Since the fix for the initial problem is causing significant side effects across multiple flows, we decided to revert
the fix.
opw-6189252
Forward-Port-Of: odoo/odoo#263305This update resolves a problem where setting a maximum package weight in Sendcloud prevented accurate shipping rate calculations. The fix ensures that package splitting is handled correctly, allowing rates to be generated even when order weights exceed the maximum deliverable weight. This improves the reliability of shipping estimates.
Original PR description
Issue ----- Putting a max weight on a package type causes getting a rate with Sendcloud to fail. Steps to reproduce ----- - Setup Mondial Relay using Sendcloud - Set a default package type with max…
Issue ----- Putting a max weight on a package type causes getting a rate with Sendcloud to fail. Steps to reproduce ----- - Setup Mondial Relay using Sendcloud - Set a default package type with max weight 2kg - Create a product with a 500g weight - Create a SO with the product - Add delivery - Sendcloud Mondial Relay - Get rate > Impossible to get a rate Cause ----- When retrieving the shipping method to use when retrieving a rate, we use the real weight of the order. https://github.com/odoo/enterprise/blob/cca1433f5a064673b8e007530e20e8a9fe72949b/delivery_sendcloud/models/sendcloud_service.py#L67 https://github.com/odoo/enterprise/blob/cca1433f5a064673b8e007530e20e8a9fe72949b/delivery_sendcloud/models/sendcloud_service.py#L81 However, when making the rate call, we use the value returned by `_split_shipping` https://github.com/odoo/enterprise/blob/cca1433f5a064673b8e007530e20e8a9fe72949b/delivery_sendcloud/models/sendcloud_service.py#L91 which is equal to the maximum weight of the package. This is blocking in some cases, like if - the real weight is 750g - the package max is 2kg - Sendcloud returns a shipping method for [500g;1kg] Asking a rate for this method & a 2kg package will fail (rightfully so). Solution ----- The shipment should be split into packages before retrieving the shipping methods. Otherwise the problem might be the other way around where we retrieve a shipping method for the whole order, only to split it into multiple packages because they don't fit in one. Also, the `shipping_weight` returned by `_split_shipping` should only be different from the order's total weight if it is higher than the maximum deliverable weight. ----- Ticket: opw-5947199 Forward-Port-Of: odoo/enterprise#108315
This update resolves an issue where the gross salary line was missing in the salary configurator when the user interface was set to a non-English language (like French). The fix ensures that the correct translated category name is used consistently, displaying the gross salary accurately for all users regardless of their language settings.
Original PR description
**Problem:** On a Belgian company with the UI set to French (or any non English language), the gross line never appears in the salary configurator sidebar when opening an offer. **Steps to…
**Problem:**
On a Belgian company with the UI set to French (or any non English language), the gross line never appears in the salary configurator sidebar when opening an offer.
**Steps to reproduce:**
1. Create a Belgian company.
2. Install French and set the admin user to French.
3. Go to an applicant (e.g Laurie Poiret), create a salary offer, save.
4. Open the offer link (salary configurator).
**Cause:**
The base `_get_compute_results` uses the translated `category_id.name` ("Salaire mensuel" in french) as the dictionary key when writing entries into `resume_lines_mapped`. The payroll override function `_get_period_name`, which for monthly schedules returned the hard coded english string `"Monthly Salary"` instead of the translated category name. This caused a key mismatch: the gross line was stored under the translated key, while the override rebuilt `resume_categories` with the english key so when the template iterates over categories and looks up `lines[category]`, the whole "Monthly Salary" bucket was invisible in every non english language.
**Solution:**
We should now return the `category_id.name` directly (the translated name coming from the record itself). This keeps all keys consistent between `resume_categories` and `resume_lines_mapped` regardless of the language used.
also because in https://github.com/odoo/enterprise/blob/1845042ff388593c4cdf547d47c018f42bd02c7c/l10n_be_hr_contract_salary/controllers/main.py#L450
We use `resume = result['resume_lines_mapped']['Monthly Salary']`
We need to re-design this by using the actual translated names, and building `result` keys based on the language selected (the same should be applied for "Yearly benefits").
opw-6009711
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/enterprise#110676This update fixes an issue where users were incorrectly directed to a standard form view when opening documents linked through a Studio-added Many2One field. Now, users can directly access the document's Kanban or List view, allowing them to preview and navigate the document content as intended. This enhances the user experience for document management.
Original PR description
Problem: When opening a linked `documents.document` record from a Many2One field added via Studio, the user is redirected to the standard form view. This is problematic because the form view does not allow the user to preview the actual document or navigate into it if the record is a folder. Solution: override `get_formview_action` to open the Kanban/List/Activity views. task-6068437 Forward-Port-Of: odoo/enterprise#116425 Forward-Port-Of: odoo/enterprise#113149
This update resolves an issue where self-billing invoices were incorrectly processed as standard invoices, impacting Peppol compliance. The fix ensures the correct document type ('credit_note') is used when generating UBL invoices for self-billing transactions, improving accuracy and adherence to PEPPOL standards. A demo setup has also been added for testing.
Original PR description
To reproduce: - Activate Peppol - Activate selfbilling on your purchase journal - Create a Vendor Refund - Generate the UBL => The InvoiceTypeCode is 389, meaning it's considered a selfbilling invoice, not a selfbilling credit note. The issue is that we never put the document type of credit_note for selfbilling documents as it wasn't expected. invoice was, due to a else encompassing invoices and bills. Also add a handle demo to be able to create selfbilling documents in demo mode. opw-6132226 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#263128 Forward-Port-Of: odoo/odoo#260941
This update resolves an error that occurred when calculating overtime deductions for employees with specific filing statuses (other than 'single' or 'jointly'). The fix ensures the system correctly handles a wider range of employee statuses, preventing unexpected crashes. It also includes minor improvements to testing.
Original PR description
Issue: ---------------------------------------- When having an employee with `l10n_us_filing_status` not in `['single', 'jointly']` and evaluating the rule parameter…
Issue: ---------------------------------------- When having an employee with `l10n_us_filing_status` not in `['single', 'jointly']` and evaluating the rule parameter `l10n_us_qualified_overtime_deduction_cap` an error occurs. Cause: ---------------------------------------- `l10n_us_filing_status` can have 5 values: `['single', 'jointly', 'separately', 'head', 'survivor']` But only `['single', 'jointly']` are defined for `l10n_us_qualified_overtime_deduction_cap` ([src](https://github.com/odoo/enterprise/blob/2d2056766441157dc45ebc37b677841c44e5c513/l10n_us_hr_payroll/data/hr_rule_parameters_data.xml#L48)). When running the rule "Qualified Overtime", the custom Python crashes because we read a key that is not there: https://github.com/odoo/enterprise/blob/2d2056766441157dc45ebc37b677841c44e5c513/l10n_us_hr_payroll/data/hr_salary_rule_data.xml#L56 Solution: ---------------------------------------- In the custom Python condition, we first check if the key is there. The custom Python computation also tries to read the key, but it is run only if the condition is validated. So we don't need to change it. Also fixed indentation of test 069. opw-6129657 Forward-Port-Of: odoo/enterprise#115754
This update fixes an issue where the total row in the Planning Gantt view wasn't displaying accurate working hours data when the view wasn't grouped by resources. The change ensures that all working hour information is correctly calculated and displayed, regardless of grouping settings, improving planning accuracy.
Original PR description
Issue: ---------------------------------------- In the Planning Gantt view when we don't group by resources, the total row is not considering the working hours. Steps to reproduce:…
Issue: ---------------------------------------- In the Planning Gantt view when we don't group by resources, the total row is not considering the working hours. Steps to reproduce: ---------------------------------------- - Open Planning - Remove the default group by resources - Have at least a planning slot for a non-flexible employee - The total row doesn't take the working schedule into account Cause: ---------------------------------------- Since [an improvement,](https://github.com/odoo/enterprise/commit/cc35e1a4729453e4f788034a94402ab048eadfcb) the working hours data in given to the `PlanningGanttRenderer` through the progress bars data. This is an issue because the progress bars are only there if we group by resources. ([src](https://github.com/odoo/enterprise/blob/423ab064847dd41d36778a812390e3bec53ba4dc/planning/models/planning_slot.py#L2669-L2678)) Solution: ---------------------------------------- In this commit we partially revert the commit adding the working intervals in the progress bars. Instead of doing it in `_gantt_progress_bar_resource_id()` we create a new method `_get_gantt_planning_data()` which is called directly in `get_gantt_data()` and returns useful information even when there are no progress bars. opw-5507063 Forward-Port-Of: odoo/enterprise#112522
This update resolves an issue where the automatic checkout feature would fail when an employee had multiple overtime entries on the same day, particularly when one entry lacked a defined checkout time. The fix ensures accurate calculation of overtime durations, preventing validation errors and ensuring correct checkout times are applied.
Original PR description
__ ## Short functional explanation of the error When an employee has multiple overtimes for the same day, including one that doesn't have a check-out date. When running the scheduled action…
__ ## Short functional explanation of the error When an employee has multiple overtimes for the same day, including one that doesn't have a check-out date. When running the scheduled action `Attendance: Automatically check-out employees`, an error occurs: `odoo.exceptions.ValidationError: Duration must be positive and cannot exceed 24 hours.` ## Reproduction Steps 1. Create an employee. Set their timezone to UTC and the Overtime Ruleset to Default Ruleset in the settings tab. In the Payroll tab, set a contract start date. 2. Set their Working Hours to a fixed 40 hours/week. Set the timezone of the Working Schedule to UTC. 3. Go to attendances and create 2 attendances on a Sunday: one from 06:00 to 06:01 and a second that starts at 06:02 but that doesn't have a checkout date. 4. Go to Settings and enable Automatic Checkout. Leave the Tolerance to 2 hours. 5. Enable debug mode and go to Scheduled Actions. Look for `Attendance: Automatically check-out employees` and run it manually. ### Expected behavior As the tolerance is 2 hours, the second attendance check-out time should be set at 08:01. ### Unexpected behavior An error occurs: `odoo.exceptions.ValidationError: Duration must be positive and cannot exceed 24 hours.` ## Origin of the issue We retrieve overtime intervals with the domain: https://github.com/odoo/enterprise/blob/64f813dab727d76286c1ff6c80c08cb6a6737b49/hr_work_entry_attendance/models/hr_version.py#L34-L42 This will make us retrieve all the overtimes for a given day, even if overtimes belong to different attendances. However, this means that we will retrieve several times the same overtimes, as this piece of code is executed in this context: https://github.com/odoo/enterprise/blob/64f813dab727d76286c1ff6c80c08cb6a6737b49/hr_work_entry_attendance/models/hr_attendance.py#L35 https://github.com/odoo/enterprise/blob/64f813dab727d76286c1ff6c80c08cb6a6737b49/hr_work_entry_attendance/models/hr_attendance.py#L43 This results in the generation of intervals containing multiple times the same overtimes. We then sum their duration to later create work entries: https://github.com/odoo/enterprise/blob/64f813dab727d76286c1ff6c80c08cb6a6737b49/hr_work_entry_attendance/models/hr_attendance.py#L47 As we try to create a work entry with too much overtime, that exceeds 24 hours, it raises a validation error. Moreover, as the overtime hours are always stored in UTC, it makes sense to always keep the timezone as UTC when performing a `_read_group` . __ opw-6036064 Forward-Port-Of: odoo/enterprise#116194
This fix ensures that website product searches accurately reflect the company a user is logged into. Previously, users could see products restricted to a different company, leading to potential issues with sales orders. The change enforces the website's company setting during searches, resolving this inconsistency.
Original PR description
# Setup Have 2 companies : A & B # How to reproduce - Set your website's company to Company B - Create product X : - Company : Company A - Published - Name : xyz - Go to Users > Any User > Acces…
# Setup
Have 2 companies : A & B
# How to reproduce
- Set your website's company to Company B
- Create product X :
- Company : Company A
- Published
- Name : xyz
- Go to Users > Any User > Acces Rights > Allowed Companies => leave only Company A
- Connect as that user on the website
- Go to the Shop tab and search xyz
# The problem
The product X is displayed, even though we currently use the company B's website and the product is limited to company A.
This causes problem later when Sales Order are created using that product.
If you set the Allowed Companies of the user to both Company A and Company B, then the product is correctly hidden
# Why
When you search something in the search bar, the server does a `_search_with_fuzzy()` that ends up calling a simple `model.search()`.
In our case, this search should not return product X because there is an `ir.rule` that hides product not in the current company :
https://github.com/odoo/odoo/blob/0bb5ac6c1a87367c1ebb343ad6e6e6e56188cf13/addons/product/security/product_security.xml#L34-L38
But the `website` module has some particular rule about setting the current company :
https://github.com/odoo/odoo/blob/0bb5ac6c1a87367c1ebb343ad6e6e6e56188cf13/addons/website/models/ir_http.py#L249-L261
So, in our case, since the user does not have company B in its allowed companies, then
`allowed_company_ids` = Company A. So `('company_id', 'parent_of', company_ids)` is trucy and the product is displayed
# Proposed solution
Doing the search with `with_company` raise an AccessError because the company is not present in the allowed_companies. Chaging the allowed companies logic seems risky because it
may lead to unintended side effects.
We instead enforce the website's company in the search's domain
opw-6115647
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#262366
Forward-Port-Of: odoo/odoo#260138This update resolves a bug that caused Purchase Orders to fail when the quantity ordered was less than the vendor's minimum quantity requirement. The fix ensures a valid supplier is always selected, preventing crashes and allowing for accurate price calculations, even with small order volumes.
Original PR description
FIX] purchase_stock: handle missing seller during PO line update (min_qty) **Steps to Reproduce:** - Install Sale, Inventory, Manufacturing, and Purchase. - Enable MTO, Units of Measure, and Routes.…
FIX] purchase_stock: handle missing seller during PO line update (min_qty)
**Steps to Reproduce:**
- Install Sale, Inventory, Manufacturing, and Purchase.
- Enable MTO, Units of Measure, and Routes.
- Create a product:
Set a vendor price with min_qty = 1.0.
Enable MTO route.
Add a BoM with a component product.
Set quantity to 0.1 (less than vendor min_qty).
- Create a Sale Order with the same product added twice.
- Confirm the Sale Order.
**Issue:**
During procurement:
- First procurement correctly fetches the supplier.
- On PO line update (_update_purchase_order_line), seller is recomputed.
Due to min_qty filtering, no seller is returned when quantity is low.
This results in: Missing seller, Missing product_uom, Invalid price
computation, And finally causes a crash when confirming the Purchase Order,
in _get_stock_move_price_unit: ZeroDivisionErroR
Root Cause:
- _select_seller filters suppliers using min_qty.
During merge/update flow, recomputed quantity may not satisfy min_qty.
Existing valid supplier (from initial procurement) is ignored.
No fallback handling in _update_purchase_order_line.
**Solution:**
- Add fallback logic when _select_seller returns no result: Use
_prepare_sellers() to fetch a valid supplier ignoring min_qty.
- Ensure a supplier is always available for: UoM resolution, Price computation
Prevents crash and ensures consistent PO line updates.
**Result:**
- No traceback when quantity < vendor min_qty
Supplier, UoM, and price are properly set
**OPW-6106487**
Forward-Port-Of: odoo/odoo#259894This update resolves an issue where newly created analytic distribution records would disappear after a page reload. The fix ensures the widget's changes are properly saved to the database, preventing data loss when updating distribution models. This improves data integrity and reliability for users managing analytic accounts.
Original PR description
Steps to reproduce 1. Go to Accounting → Configuration → Analytic Distribution Models 2. Create a new model, name it, and in the distribution column pick an analytic account 3. Click outside the row…
Steps to reproduce 1. Go to Accounting → Configuration → Analytic Distribution Models 2. Create a new model, name it, and in the distribution column pick an analytic account 3. Click outside the row and reload the page Issue The newly created record vanishes because `web_save` received `analytic_distribution: false`. The single click that closes the popover also triggers the editable list's `leaveEditMode`, which calls `record.save()`. That save runs before the widget has flushed the user's pick into `record.data`, so the write goes out with stale/empty data. This became reliably reproducible after [37d78a47bb20], which moved the list renderer's outside-click listener from `document` (bubble) to `window` (capture). Because the list is mounted before the widget, its capture-phase listener now fires first: `leaveEditMode` → `record.save()` is already in flight by the time the widget's own window click handler runs, so the widget's commit loses the race. Solution `record.save()` awaits `_askChanges()` before writing: https://github.com/odoo/odoo/blob/cdf8aaec82ee387c8f29b8327efbc95fd17e2cb8/addons/web/static/src/model/relational_model/record.js#L268-L271 `_askChanges()` triggers `NEED_LOCAL_CHANGES` on the model bus and awaits any proms handlers push onto it: https://github.com/odoo/odoo/blob/cdf8aaec82ee387c8f29b8327efbc95fd17e2cb8/addons/web/static/src/model/relational_model/relational_model.js#L249-L253 This is the framework's standard hook for widgets that hold uncommitted local state; `ace_field` and `domain_field` already use it. Subscribe the analytic distribution widget to the same event and, when the dropdown is open, push a `commitChanges()` prom that awaits the existing `save()`. Because `record.save()` awaits these proms before running `_save`, the widget's pending distribution is guaranteed to be on `record.data` by the time the write payload is built, regardless of click-listener ordering. opw-6106309