Wednesday, September 23, 2026
29 changes · 19.0
Resolved issues and error corrections
Dropship and similar inventory operations that only create lots no longer show the misleading "Pick From" popup when adding a detailed line. This ensures the lot number and expiration date entered by the user are the values actually used, reducing delivery mistakes for tracked products.
Original PR description
Steps to reproduce 1. In Inventory Settings, enable Lots & Serial Numbers and Expiration Dates. 2. Create a storable product tracked by lot with Expiration Date enabled. 3. Create and confirm a…
Steps to reproduce 1. In Inventory Settings, enable Lots & Serial Numbers and Expiration Dates. 2. Create a storable product tracked by lot with Expiration Date enabled. 3. Create and confirm a dropship of it, then open the move's Detailed Operations. 4. The pre-filled line works: typing a batch and an expiration date creates exactly that lot at validation. 5. To split the quantity, click "Add a line": it opens the "Pick From" popup, and choosing "Create" there creates the lot and attaches it to the line. 6. On that line, edit "Lot/Serial Number" and "Expiration Date", then Validate. -> Expected: the values entered on the line are used. -> Actual: they are ignored; the lot created through "Pick From" is delivered with its own expiration date, the name and date typed on the line have no effect. Issue --- On a create-lots-only non-incoming operation like `Dropship`, `show_quant` is derived from `picking_code` alone, so the "Pick From" quant picker is shown even though the operation only creates lots. Adding a line goes through "Pick From", which creates the lot and attaches its `lot_id` to the move line; from then on the line's `lot_name` and `expiration_date` stay editable but do nothing, since a set `lot_id` is used as-is at validation and the `expiration_date` is recomputed from the lot, so anything typed there is silently dropped. Such an operation has no existing stock to pick from, so `show_quant` is now gated on the lot settings too: "Pick From" is hidden and the line's `lot_name` and `expiration_date` become the only inputs, created into the lot at validation like receipts already do. https://github.com/odoo/odoo/blob/68dcb950df83d70ff2aea0e05c96cc9b57c1a8a9/addons/stock/models/stock_move.py#L646-L647 opw-6530563 Forward-Port-Of: odoo/odoo#287474
Hungarian electronic invoice reports sent to NAV now leave out cash rounding adjustment lines, aligning the reported invoice content with local legal requirements. This helps avoid treating settlement differences as taxable goods or services while keeping invoice totals calculated correctly.
Original PR description
Global cash rounding can be applied to customer invoices. Before this commit, the rounding would be included in the XML file sent to NAV. It would be included as a new invoice line (same as the products lines) and the ATK tax is applied on it. As stated in the legal Hungarian Documentation, an invoice line should always relate to the supply of a good or the service provided. In this case, a cash rounding (which is not a financial advantage or disadvantage) will be considered by the law as a settlement difference, that is not part of the invoice. So, this commit removes cash rounding lines from the NAV XML. Moreover, it uses base_lines for the amounts computation instead of line_ids. task-6527383 Forward-Port-Of: odoo/odoo#286258
Fixed an issue where selecting an autocomplete suggestion could cause the dropdown to reopen and stay visible. This improves form reliability and prevents related automated checks from timing out.
Original PR description
Before this commit, the autocomplete dropdown of a many2one could open again right after a suggestion was picked, and stay open. The step of mail_template_dynamic_placeholder_tour waiting for it to…
Before this commit, the autocomplete dropdown of a many2one could open again right after a suggestion was picked, and stay open. The step of mail_template_dynamic_placeholder_tour waiting for it to close then times out:
FAILED: [8/36] Tour mail_template_dynamic_placeholder_tour
Step Wait for the drop down to disappear
(trigger: div[name="model_id"] .o-autocomplete:not(:has(.ui-autocomplete)))
TIMEOUT step failed to complete within 10000 ms.
This happens because the search filling the dropdown runs 250ms after the last keystroke, so a suggestion of the previous search can be picked while the next search is still scheduled. Picking a suggestion only closes the dropdown. The scheduled search then runs, opens the dropdown on the selected value, and nothing closes it after that.
This commit fixes the issue by discarding the scheduled search when the dropdown closes.
https://runbot.odoo.com/odoo/error/233574
https://runbot.odoo.com/odoo/error/237744
https://runbot.odoo.com/odoo/error/944454
https://runbot.odoo.com/odoo/error/945517
https://runbot.odoo.com/odoo/error/947141
Forward-Port-Of: odoo/odoo#290216
Forward-Port-Of: odoo/odoo#287888The pickup location list now only shows stores that belong to the same company as the customer’s current cart. This prevents shoppers on a company-specific website from selecting an invalid pickup store and encountering an error during checkout.
Original PR description
# How to reproduce - Have two company A & B - Install the Sales + Click & Collect modules - Create a Warehouse for each Company - Enable both company A & B - Go to the Delivery Methods & select "Pick…
# How to reproduce - Have two company A & B - Install the Sales + Click & Collect modules - Create a Warehouse for each Company - Enable both company A & B - Go to the Delivery Methods & select "Pick up in store" - Add two Stores, one for each Company - Go to a website that is limited to Company A - Go to the page of a product with Click & Collect Available > Both Stores are available - Select the Store associated for Company B - Click on "Choose this location" # Issue We get an error notification saying : "Invalid Operation. Uh-oh! You’ve got some company inconsistencies here: ..." # Cause When displaying the available stores, we don't filter them using the website's company. When we select a store with a different company than the website, the write on `sale.order` will trigger a call to `_check_company`, that will detect an inconsistency between the SO's company and the warehouse's company # Proposed solution We limit the available locations to warehouses that have the same company as the cart's SO. opw-6417309
Searches for archived bills of materials now correctly find records even when the linked product is also archived. This helps manufacturing users locate historical or inactive BOM data without missing results due to related archived products being filtered out.
Original PR description
When searching for an archived bom (filer archived activated), if the main product is also archived, the search on the produt will not find the bom. Steps to reproduce: ------------------- * Create…
When searching for an archived bom (filer archived activated), if the main product is also archived, the search on the produt will not find the bom.
Steps to reproduce:
-------------------
* Create product AA
* Create a bom for product AA
* Archive product AA
* Products > bom
* Search for "Archived" and Product: "AA" -> It will not find the bom
Observation:
-------------
When doing the search, it will start a web_search_read ['&', ('active', '=', False), ('product_tmpl_id', 'ilike', 'AA')].
While in _search it will decide if we filter active elements: https://github.com/odoo/odoo/blob/60b5b99d569dbe183ef9c980217ccfe9bf243756/odoo/orm/models.py#L5363-L5369 In the first level since "active = False" is in the domain (for the bom) it will not add the filter.
The _search will optimize the domain query level by level by calling optimize_full.
First the outher layer :
optimize_full (Domain '[]')> _optimize (DomainNary '&')> _optimize_step (DomainNary '&')
Then the children [1: (active bom), 2: (product template display name)]:
_optimize>optimize_step> ...
While optimizing the second child the domain condition is: [('product_tmpl_id', 'any', [('display_name', 'ilike', 'AA')])]
Since it's a any operation, in optimize_step, it will use the _optimize_any_domain_at_level:
https://github.com/odoo/odoo/blob/16aaaafd7d314c04f39b1f633af3b5e15b46d78b/odoo/orm/domains.py#L970-L972
After doing some checks it will continue to optimize per level with the related comodel:
https://github.com/odoo/odoo/blob/60b5b99d569dbe183ef9c980217ccfe9bf243756/odoo/orm/domains.py#L1389-L1393
Since it's an "any" operator it need to respect the following guidelines : https://github.com/odoo/odoo/blob/60b5b99d569dbe183ef9c980217ccfe9bf243756/odoo/orm/domains.py#L98-L101
this is no done in `_optimize_any_domain_at_level` since it resolves a relational field's 'any' domain by looking up a fresh comodel recordset without adjusting its context.
Which means that for the following level, on the product template, the active_name is not on the domain anymore and the context was not passed corretly, so _search will automaticly add the filter ("active = True").
opw-6514762This fixes how Odoo handles non-stored related many-to-many fields so they no longer keep an unnecessary database relation. It prevents data from being mixed up between similar fields, reducing the risk of incorrect information appearing in records.
Original PR description
When we setup a many2many field which is not stored, it must not have a relation. This is already done for nonrelated fields, but we must do it also for related fields. Otherwise, we may have…
When we setup a many2many field which is not stored, it must not have a relation. This is already done for nonrelated fields, but we must do it also for related fields. Otherwise, we may have side-effects like finding sibling fields in 20.0: in the code below, reading categories would fill roles.
```py
model_id = self.env["ir.model"]._get("test_new_api.foo")
fields = [{
"name": "x_partner_id",
"ttype": "many2one",
"relation": "res.partner",
}, {
"name": "x_role_ids",
"ttype": "many2many",
"relation": "res.partner.category",
}, {
"name": "x_partner_category_ids",
"ttype": "many2many",
"relation": "res.partner.category",
"related": "x_partner_id.category_id",
"store": False,
"readonly": True
}]
for f in fields:
f["model_id"] = model_id.id
self.env["ir.model.fields"].create(fields)
env['test_new_api.foo']._fields['x_partner_category_ids'].relation # should be None
```
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#290067Italian invoices using the Import/Export fiscal position no longer apply two different 0% taxes to the same line by default. This prevents incorrect tax setup on invoices and helps businesses produce cleaner, more accurate accounting records.
Original PR description
Upon Import/Export, the 0% EX N7 tax is added by default on each tax excluded line, as it incorrectly shares the same default Import/Export fiscal position with the standard 0% EX tax. This causes both to be applied simultaneously to a single invoice line. 1. Install Accounting and `l10n_it` 2. Switch to IT company 3. Go to Invoices and create a new one 4. Select the Import/Export fiscal position 5. Add a line with a new product (so it's clean of custom product taxes) 6. Both `0% EX` and `0% EX N7` taxes are applied, instead of just `0% EX` Ticket [link](https://www.odoo.com/odoo/project.task/6518926) opw-6518926
This update fixes a timing issue that could make the website editor think a page preview was ready before it actually was. It improves reliability when editing website menus, reducing intermittent failures during resizing or loading of the editor preview.
Original PR description
**PROBLEM** waitForIframeReady() returns when the iframe is not ready. This can cause a race condition in `test_17_website_edit_menus` when the website editor sidebar resizes the iframe of the webpage. **CAUSE** waitForIframeReady() check if there is a `is-ready` on the body attribute, instead of checking its value. This was fixed in the observer if condition by https://github.com/odoo/odoo/commit/89994eb7a54ca606bab26b6d579d03e86c11ba60 but this is not the case for the first check. runbot-939929
The website project form handling was cleaned up and reorganized to make it more reliable and easier to update in the future. This is a minor maintenance fix with no expected disruption for users, but it helps support smoother form processing going forward.
Original PR description
Clean up and move some of the form processing logic for future updates opw-6560036 Forward-Port-Of: odoo/odoo#287697
Users can now download all files from a chatter message as a ZIP even when some attachments are stored in cloud storage. This fixes a download failure and makes cloud-stored attachments behave consistently with regular attachments in bulk downloads.
Original PR description
Clicking "Download Files" on a chatter message fails when one of its attachments is stored in the cloud. Cloud attachments normally return a stream containing a signed URL. For individual downloads, Odoo redirects the browser to that URL, letting the cloud provider serve the file directly. The ZIP controller instead needs the file's bytes to build the archive on the server. It calls `Stream.read()`, which cannot read URL streams and raises `ValueError: Cannot read an URL`. Enable a `cloud_storage_force_download` context flag on the attachments when the cloud controller delegates ZIP creation to the mail controller. With this flag, the cloud attachment model fetches the file through the provider's signed URL and returns a data stream that the existing ZIP controller can read. opw-6570065
Point of Sale now correctly loads newly added product option values even when the main option category was already cached. This prevents errors when selecting products and avoids requiring users to clear browser data or restart sessions to recover.
Original PR description
Steps to reproduce: - Open a PoS session so the browser caches the data (IndexedDB) - Add an attribute line on a product available in PoS, using an attribute that already existed and was not modified…
Steps to reproduce: - Open a PoS session so the browser caches the data (IndexedDB) - Add an attribute line on a product available in PoS, using an attribute that already existed and was not modified since - Reopen the PoS and click the product Issue: TypeError: undefined is not an object (evaluating 'values[0].is_custom') in openConfigurator. The attribute line is loaded but none of its values are; the variants have no attribute values on the frontend. Closing the session or reloading the browser does not help since the cache is kept. Cause: On an incremental load (pos_last_server_date in context), every model only returns the records written after the last sync. The domain of product.template.attribute.value restricts attribute_id to the ids in data['product.attribute'], which in that case only holds the attributes modified since the last sync. Values created on an older attribute are therefore excluded, and the frontend drops the ids it cannot resolve. Fix: Filter attribute_id with the domain of product.attribute itself instead of the ids of the records returned in the current load. A full load returns the same records as before. opw-6568808 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fix ensures an automated employee registration test opens the correct starting menu in community editions. It prevents false test failures, helping keep quality checks reliable without changing end-user functionality.
Original PR description
**Trigger:** Running `test_onsite_employee_registration` in a community database starts in Discuss instead of enterprise's usual home menu which crashes the test on the first step. This commit adds the `showAppsMenuItems` util to the test. To ensure the test starts in the correct view. runbot-946289
Point of Sale now correctly opens the weighing prompt when a cashier scans a product that must be weighed, unless the barcode already includes the weight. This prevents sales of weighed items from being added without capturing the required weight.
Original PR description
Since 3604790e1fee, `needToConfigure()` returns a real `false` for a product without attribute lines instead of `undefined`. The barcode handlers of the product screen pass its result as the `configure` argument of `addLineToCurrentOrder`, whose default value is `true`. The scale popup was gated on that argument, so it only opened on scan because the `undefined` fell back to the default. It now stays closed for every scanned weighed product. Weigh the product whenever it was added interactively or scanned with a barcode that does not already carry a weight. opw-6561067 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Fixed an issue where all-day calendar events created from approved time off could be shortened if their start time changed, such as during Google Calendar sync. This helps ensure multi-day leave remains accurately reflected in calendars.
Original PR description
**Problem:** When modifying the start time of an all-day Calendar Event created from the Validation of a Time Off Request, the end time will be shortened dramatically, no longer spanning the correct…
**Problem:** When modifying the start time of an all-day Calendar Event created from the Validation of a Time Off Request, the end time will be shortened dramatically, no longer spanning the correct timespan. In the case of the ticket referred to below, this occurs during Google Calendar sync. **Cause:** When a Calendar Event is created from a Time Off Request being Validated, its duration is set according to the amount of days the Time Off Request spans multiplied by the employee's daily work hours as defined on their contract, even if the Calendar Event will be an all-day event. When the start time of a Calendar Event is modified, the end time is recomputed according to the duration added to the new start time. As such, if the duration of the Event does not match the duration of the dates covered, it will be shortened dramatically. **Purpose:** Modify the values defined when creating a Calendar Event as a result of a Validated Time Off Request so that, if the created Event will be an all day Event, the duration is computed according to `calendar.event._get_duration`. **Steps to Reproduce in Runbot:** 1. Create and Validate a Time Off Request of the Paid Time Off leave type spanning multiple days. 2. Modify the start time of the Calendar Event that is created as a result of the validation. (This step will likely require a nonstandard flow as the start time field is normally hidden for all-day Calendar Events) opw-6426586 Forward-Port-Of: odoo/odoo#289152 Forward-Port-Of: odoo/odoo#284552
French companies can now select and send multiple invoices without the process crashing. This restores access to the batch sending wizard, helping teams process electronic invoices more reliably.
Original PR description
Sending several invoices at once from the list view of a French company crashes instead of opening the batch sending wizard. Steps to reproduce: - Activate French Electronic Invoicing - Create a French customer with a valid PDP endpoint - Post two invoices for that customer, select them in the invoice list and click 'Send' Issue: The batch sending wizard raises ``` AttributeError: 'account.move.send.batch.wizard' object has no attribute 'company_id' ``` opw-6558940 Forward-Port-Of: odoo/odoo#289547
This fixes a Point of Sale issue where gift card, eWallet, or coupon rewards could disappear after refreshing or reopening an order. Cashiers now keep the correct discounted total, reducing the risk of charging customers again for value already paid with a card.
Original PR description
Steps to reproduce: - Create a gift card program and a gift card with some balance - Open a PoS session, add a product to a new order and enter the gift card code: a reward line is added and the…
Steps to reproduce: - Create a gift card program and a gift card with some balance - Open a PoS session, add a product to a new order and enter the gift card code: a reward line is added and the total decreases - Refresh the page, or leave the order and take it back from the Orders screen Issue: The reward line disappears and the total goes back to the full price, while the server still holds the pos.order.line with `is_reward_line`, `coupon_id` and `points_cost`. Nothing warns the cashier, so the customer can be charged for a service already paid with the card. Cause: `_code_activated_coupon_ids` is a local field of pos.order: it is neither stored in IndexedDB nor sent to the server, so it is empty once the order is rebuilt on the client. `_updateRewardLines` deletes every reward line and only re-applies the ones whose coupon is found in `_code_activated_coupon_ids` or in `uiState.couponPointChanges`. `updatePrograms` only rebuilds the latter for programs detectable from the order content, so a gift card, eWallet or coupon card activated by code is never recognised again and its reward is dropped by the next `updateRewards` call (TicketScreen.setOrder, barcode scan, partner change, line added...). In 17.0 `codeActivatedCoupons` was exported and restored with the order; the field became local with the 18.0 models rewrite. Fix: Add `_restoreCodeActivatedCoupons` on pos.order and call it from `checkMissingCoupons`, which runs once per rebuilt order (`setup` flags it with `invalidCoupons`) before the programs and the reward lines are refreshed. It re-links every persisted coupon referenced by a reward line that is in neither structure. Nominative cards are excluded so that removing the partner still drops their reward. opw-6568849 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fix ensures Point of Sale receipt numbers are only reused after a deleted draft order is fully removed from local browser storage. It prevents two paid orders from receiving the same receipt or invoice reference, reducing confusion and avoiding problems with fiscal integrations that require unique invoice numbers.
Original PR description
Steps to reproduce: - Add a product to a draft order, do not pay - Open the same PoS in a second tab of the same browser (the first tab is sent to the backend), or reload while a draft is being…
Steps to reproduce: - Add a product to a draft order, do not pay - Open the same PoS in a second tab of the same browser (the first tab is sent to the backend), or reload while a draft is being removed - Pay the restored draft, then make and pay a new order Issue: Both orders carry the same pos_reference (e.g. 264-3-000701). The number is printed on both receipts and sent to fiscal integrations that use it as the unique invoice number. Cause: The receipt number is issued client side by DeviceIdentifierSequence, a per-device counter kept in localStorage that all tabs of a browser share. When a draft is removed, its number is pushed on `unsynced_number_stack` to be reused by the next order. `removeOrder` pushes the number synchronously, then deletes the order from IndexedDB without waiting for the transaction. If the page unloads before it commits (the tab is closed by another tab opening the same PoS, a redirect to the backend, a reload), the next load restores the draft from IndexedDB with its number while the stack hands that same number to the next new order. Both are then created on the server with different uuids and the same pos_reference: the server only deduplicates by uuid. Fix: Recycle the number only once the order is gone from IndexedDB. A new `recycleOrderNumber` helper waits on the deletion of the order before calling `saveUnusedNumber`; `removeOrder` uses it after `localDeleteCascade`. `deleteOrders` awaits the recycling so that a delete followed by a new order still reuses the freed number, as before. opw-6558992 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Cancelling several deliveries at once now sends each carrier only the shipment information that belongs to it. This prevents failed or incorrect cancellation requests and avoids duplicate cancellation attempts when multiple delivery carriers are involved.
Original PR description
When cancelling shipments for multiple pickings with different delivery carriers, passing the entire recordset (self) instead of the individual picking to the carrier's cancel_shipment method causes each carrier to receive tracking references that do not belong to it. This results in API errors or silent wrong cancellations at the carrier level, and each shipment being cancelled N times instead of once, where N is the total number of pickings being processed. Forward-Port-Of: odoo/odoo#289809
Opening a previously sent Sign template with no fields no longer triggers an access error. The system now avoids adding a placeholder signer when the template is already tied to a sent request, so users can view these templates normally.
Original PR description
Version: 19.0 Steps to reproduce: - Create a sign template with no sign fields - Send a sign request using that template - Open that template from Templates Issue: After a sign request is sent, record rules make the template read only for sign items (create/write only when there are no sign requests). On open, if the template has no signers, the UI auto creates a dummy signer. For an empty sent template, that create is denied by those rules and raises an Access Error. Fix: Guard the auto create signer flow with a sign requests check so that a sent template does not try to create a new signer on open. Task: 6591146
Fixed an issue where Planning could show an access error when scheduling shifts for users working with multiple companies. The overlap check now only considers shifts from companies the user can currently access, preventing crashes and avoiding exposure of restricted company data.
Original PR description
Steps to Reproduce --- 1. Log in as a user with access to multiple companies (e.g., Company A and Company B). 2. Ensure both companies are ticked in the top-right multi-company widget. 3. In the…
Steps to Reproduce --- 1. Log in as a user with access to multiple companies (e.g., Company A and Company B). 2. Ensure both companies are ticked in the top-right multi-company widget. 3. In the Planning app, create a shift for an employee from 09:00 to 17:00 in Company B. 4. Remove the access of Company B 5. In the Planning Gantt view, click the same empty cell to schedule the same employee at the same time for Company A. Issue --- The _compute_overlap_slot_count method uses raw SQL to find overlapping shifts. However, this raw SQL fetches overlapping slot IDs from shifts belonging to companies the user has currently deselected or does not have access to. Current behaviour --- The raw SQL populates the conflicting_slot_ids field with inaccessible record IDs (the shift from the deselected Company B). This triggers accessError. Expected behaviour --- The system should only calculate overlapping shifts for companies the user currently has active in their environment. It should not leak the existence of shifts in deselected/unauthorized companies, and it should open the new shift dialog without crashing. Fix --- Add company_id to both raw SQL queries (for existing records and new virtual records) inside _compute_overlap_slot_count. Pass company id to ensure the database only returns conflict IDs that are valid within the user's active multi-company context. task - 4554813 Forward-Port-Of: odoo/enterprise#109027
Demo data for Belgian payroll-related modules now loads with the correct Belgian company context. This prevents errors when users install or load the demo data through the interface, improving setup reliability.
Original PR description
Before the fix, loading the module's demo data through the UI triggered an error. task-6581013 Forward-Port-Of: odoo/enterprise#132358
The Bulgarian SAF-T reporting feature will no longer be installed automatically unless the advanced accounting setup is explicitly present. This prevents businesses from seeing or activating a specialized accounting report in environments that may not support it properly.
Original PR description
As the Bulgarian SAF-T Report is destined for advanced accounting, it should only be available and auto-installed when the 'accountant' module is as well. As the 'accountant' module is not part of the dependencies yet (it is from 20.0 on), we cannot be sure that it is already installed. It is then better to stop the auto installation altogether.
This fix prevents errors when adding users to payroll groups from the Groups screen in Australian Payroll. It also ensures both added and removed group members are properly audit-logged, improving reliability and compliance tracking.
Original PR description
#### Description of the issue/feature this PR addresses:
Adding a user to a group from the Groups form crashes on an Australian Payroll-API database, and removals from that form are never audit-logged.
#### Current behavior before PR:
The audit-logging mixin reads the changed users out of the raw write command with vals.get("user_ids")[0][2], which assumes a 3-element command tuple. The Groups form sends the 2-element (4, id) LINK command, so the write raises an IndexError; UNLINK commands are silently not logged for the same reason.
#### Desired behavior after PR is merged:
Group membership changes made from the Groups form are audit-logged for both additions and removals, regardless of the command used to write user_ids. The mixin reads the members before and after the write instead of interpreting the command. No field, model or method-signature change, so it is safe in stable.
opw-6397011Budgets now correctly account for purchase receipts when calculating committed amounts. This prevents the same purchase value from being counted twice, so budget totals reflect the expected cost rather than an inflated amount.
Original PR description
**PROBLEM** When using receipt with budget, the receipt are not taken into account for computing the `qty_invoiced_table`. This leads the budget committed amount to be false, because we take into account the invoiced qty 2 times, once for the receipt, and one for the purchase order line. **STEP TO REPRODUCE** 1. Install account_budget. 2. Create a budget, with a analytical account. 3. Create a purchase order with one line of 100$ and add the budget project (setting the analytical account on the line). 4. Create a bill from the purchase (receive the item if necessary) and set the move type to receipt. 5. Goes to the budget, and see the committed amount is not correct (you will see 200$ instead of the expected value of 100$). expected behavior: committed amount should be correct (ie, not repeating the purchase order amount that is already in the receipt). opw-6527042
This change removes unnecessary fallback logic in the accounting reports download flow. It makes the code clearer and helps avoid confusion around how closing behavior is handled after downloading a report.
Original PR description
`!something` is never nullish, so `?? true` is dead code. Probably the intention was `if (!(data.no_closing_after_download ?? true))`, but reviewer has the final say and I can change it.
Resetting an expense report to draft now also clears Studio approval records for its posting action. This prevents a previously approved posting step from being reused after changes, ensuring expenses require fresh approval when resubmitted.
Original PR description
Currently, when resetting to draft an expense sheet, approval steps added with studio are not reset, silently granting the change Steps to reproduce: - In Studio, add an approval rule on the "Post Journal Entries" button on expense sheet - Create an expense report, submit it and approve it - Click "Post Journal Entries" and approve approve it - Open the journal entry and reset it to draft - Back to the expense sheet, reset it to draft too Issue: The "Post Journal Entries" button is still approved, showing the previous approver with the same approval date. Analysis: Approval entries are dropped on state change by a base automation that Studio builds when the rule is created. However it is only done for sale order, account move and purchase order. opw-6530689 Forward-Port-Of: odoo/enterprise#130692
Vietnamese Balance Sheet and Profit & Loss reports are updated to match Circular 99/2025/TT-BTC requirements effective FY2026. The Profit & Loss statement now includes the new investment property gain/loss line, revised labels and numbering, and avoids double-counting related amounts while keeping period comparisons clear.
Original PR description
Circular 99/2025/TT-BTC (effective FY2026) replaces Circular 200/2014/TT-BTC. The Profit & Loss statement (Mẫu B02-DN) gains a new mandatory line, "Lãi/lỗ của hoạt động bán, thanh lý bất động sản đầu…
Circular 99/2025/TT-BTC (effective FY2026) replaces Circular 200/2014/TT-BTC. The Profit & Loss statement (Mẫu B02-DN) gains a new mandatory line, "Lãi/lỗ của hoạt động bán, thanh lý bất động sản đầu tư" (mã số 21), computed from the new 5117/6327 sub-accounts (see the paired l10n_vn commit). Financial income, financial expenses and the interest memo line shift to mã số 22/23/24 accordingly, the "Net profit from operating activities" formula is updated to include the new line, and all following line numbers/labels are renumbered to match the printed form. The interest memo line itself is renamed from "Chi phí lãi vay" to "Chi phí đi vay" (Borrowing costs), per the gazette text. Also, per revised guidance: - Revenue and cost of goods sold now exclude the new investment property sub-accounts (5117/6327), so those amounts aren't double-counted between the main lines and the new gain/loss line. - Other income is now computed from the "income_other" account type instead of a hardcoded account code, matching how the chart of accounts already classifies it. Both the Balance Sheet and Profit and Loss VN reports also declare an extra "Code" string column (for the printed-form mã số) alongside "Balance". Core's growth-comparison heuristic only turns on the auto growth-% column when there are exactly 2 total columns; with Code+Balance that becomes 4 once a comparison period is added, so the % never renders even though the underlying side-by-side comparison values are fine. account.report exposes filter_growth_comparison as a toggle independent from filter_period_comparison for exactly this case (see Trial Balance and the Deferred reports), so both reports set it to False: period comparison stays available, only the auto growth-% column is suppressed. Task-6518304 Forward-Port-Of: odoo/enterprise#131085
This fix prevents Ecuadorian invoice confirmation from failing when a company partner has a VAT number containing letters or other non-digit characters. Instead of a system error, users receive a clear validation message so the VAT can be corrected before invoicing.
Original PR description
When the VAT of an Ecuadorian company's partner contains characters other than decimal digits, confirming an invoice raises a traceback. Steps to reproduce the error: - Install ``l10n_ec_edi`` module…
When the VAT of an Ecuadorian company's partner contains characters other than decimal digits, confirming an invoice raises a traceback. Steps to reproduce the error: - Install ``l10n_ec_edi`` module and switch to EC Company - Open the partner of EC Company > Set the VAT to ``BE0477472701`` - Create a new invoice > fill the required fields > Confirm Traceback: ```py ValueError: invalid literal for int() with base 10: 'E' ``` After this [commit], companies outside the EU can use European VAT numbers. Consequently, an Ecuadorian partner can have a RUC number such as ``BE0477472701``, which is valid as a Belgian VAT number but not as a RUC. During invoice confirmation, ``_l10n_ec_set_authorization_number()`` method is called to generate the authorization number at [1], It builds the ``key_value`` using the vat of company's partner at [2]. The generated ``key_value`` is then passed to ``_l10n_ec_get_check_digit`` method, which converts each character of ``key_value`` to an integer using ``int()`` at below line. https://github.com/odoo/enterprise/blob/799e9e94efcc338a8f3a6b7ab85cf77f9898f005/l10n_ec_edi/models/account_move.py#L830 When the VAT contains a character that cannot be converted to an integer, It will raises the above traceback. [commit]: https://github.com/odoo/odoo/commit/a2afe3292e1cd0a4f339dc47707e469653d13ea0 [1]: https://github.com/odoo/enterprise/blob/799e9e94efcc338a8f3a6b7ab85cf77f9898f005/l10n_ec_edi/models/account_move.py#L806 [2]: https://github.com/odoo/enterprise/blob/799e9e94efcc338a8f3a6b7ab85cf77f9898f005/l10n_ec_edi/models/account_move.py#L825-L826 Solution: Raise the ValidationError when the vat contains non-digit character. sentry-7642961403 Related Enterprise PR: https://github.com/odoo/enterprise/pull/126519 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280047
Peruvian delivery guides for itinerant issuer transfers now omit a field that SUNAT does not allow for this transfer reason. This prevents SUNAT rejection errors and helps users successfully generate compliant delivery guides.
Original PR description
### Issue before this commit: When issuing a Delivery Guide for an "Itinerant issuer transfer CP", SUNAT rejects the document with error 3416 because the establishment code of the arrival point is…
### Issue before this commit: When issuing a Delivery Guide for an "Itinerant issuer transfer CP", SUNAT rejects the document with error 3416 because the establishment code of the arrival point is being reported. ### Steps to reproduce the issue: 1. Download Stock, l10n_pe_edi_stock and l10n_pe_reports_stock 2. Create one contact setting a valid RUC 3. Go to settings and insert Credentials for Sunat Delivery Guide API 4. Create a new warehouse 5. Go to deliveries and create a new one with: 1. Delivery Address: the contact you created 2. Transport Type: Public Transport 3. Reason for transfer: Itinerant issuer transfer CP 4. Operator (in the PE EDI tab): any 6. Validate the delivery 7. Generate the delivery guide 8. There was an error communicating with the SUNAT service. Details: 400 Client Error: Bad Request for url: https://api-seguridad.sunat.gob.pe/v1/clientessol/test/oauth2/token/ 9. If you download the generated XML, you will see that the node <cbc:AddressTypeCode> inside <cac:DeliveryAddress> is being generated with a default value of "0" but it should not be there ### Cause of the issue: The UBL template automatically forces the <cbc:AddressTypeCode> node with a default value of "0" for any delivery address associated with a RUC (identification type 6). However, SUNAT's validation rules strictly forbid this node when the transfer reason is 18. ### Reason to introduce the fix: Update the XML template to conditionally omit the <cbc:AddressTypeCode> tag inside <cac:DeliveryAddress> whenever the l10n_pe_edi_reason_for_transfer is '18'. This ensures compliance with SUNAT's validation rules and allows the successful generation of the delivery guide. opw-6493067