Friday, September 11, 2026
23 changes · 19.0
Enhancements to existing features
Invoice sections that hide their detailed composition now handle taxes consistently across printed PDFs and exported XML files. This improves accuracy and alignment with Sales behavior by grouping collapsed sections by tax and keeping tax details clearer when prices are hidden.
Original PR description
Before this commit, collapsing composition in Accounting did not behave the same as in Sales. In Accounting PDFs, when a section was created and its composition was hidden, taxes were removed from…
Before this commit, collapsing composition in Accounting did not behave the same as in Sales. In Accounting PDFs, when a section was created and its composition was hidden, taxes were removed from individual lines and displayed on the section line as a comma-separated list. This commit aligns the behavior with the Sales app: - When collapsing composition, sections are grouped by tax. - When hiding prices, taxes are removed from the section line and kept on each individual line. Also, hiding composition in sections wasn't taken into account while exporting the XML. This commit introduces this behaviour for the XML invoices : - when we export section that hides the composition, we first group the sections by tax, then we create an invoice line with the section's name, with the right tax and amounts calculations. NOTE: This commit does not introduce price hiding in XML exports. task-6101592 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
External tax calculations now avoid an unnecessary clearing step before applying updated tax values. This can significantly reduce processing time for large invoices using external tax services, improving responsiveness without changing the tax calculation outcome.
Original PR description
[PERF] account_external_tax: avoid redundant tax clearing Avoid unnecessarily clearing `tax_ids` before setting the externally computed taxes, as the field is immediately replaced with the new tax…
[PERF] account_external_tax: avoid redundant tax clearing Avoid unnecessarily clearing `tax_ids` before setting the externally computed taxes, as the field is immediately replaced with the new tax values. Performance testing was performed using an invoice containing 200 lines with externally computed AvaTax taxes. Performance testing: | Metric | Before | After | |--------------------------------|--------|---------| | `_set_external_taxes()` | ~1:13 | ~43.77s | | Total external tax calculation | ~1:16 | ~46.66s | This reduces the execution time of `_set_external_taxes()` by approximately 40% for a 200-line invoice, while preserving the existing functional flow. The optimization is not specific to AvaTax and benefits the common `account.external.tax.mixin` flow when setting externally computed taxes on multiple lines. Profiler comparison: Before: <img width="1548" height="442" alt="image" src="https://github.com/user-attachments/assets/a5f2553c-a926-4324-88b2-743ed9214bf5" /> After: <img width="1429" height="440" alt="image" src="https://github.com/user-attachments/assets/c9c9c673-4654-4e11-b5f2-ffa4ecc87038" /> **opw-6472692**
Point of Sale session closing now limits how much customer credit history it reviews when reconciling pay-later balances. This prevents very large customer account histories from causing excessive memory use or blocking session closure, while keeping the resulting payments and balances the same.
Original PR description
At session close, the pay later receivable lines are reconciled with every unreconciled posted line of the same partners in the PoS journal and PoS invoices, searched with no bound. A customer buying…
At session close, the pay later receivable lines are reconciled with every unreconciled posted line of the same partners in the PoS journal and PoS invoices, searched with no bound. A customer buying on credit accumulates one open line per order and none of them is ever matched until a settlement comes in, so this set only grows. On a database with 48k open items for a single partner, the close hands `_reconcile_plan` 55k lines over 28k moves; `_sync_dynamic_lines` then evaluates `m.line_ids.tax_ids` on every move of the container, prefetching the 262k lines of those moves. That is about 1 GiB: the worker is killed and the session cannot be closed. All of this to reconcile nothing most of the time: reconciliation only pairs opposite signs, and a session that merely adds charges brings no credit to allocate. When there is one, the engine consumes the open items oldest first (`date_maturity` or `date`) and stops when the credit is used up, so anything past that point is loaded for nothing. Look the open items up per partner, account and currency, and only call `_reconcile_plan` when the session's lines or the customer's open credits give something to allocate. Take the open debits in the order the engine consumes them - the PoS lines have no `date_maturity`, so the ordering is done in Python on `date_maturity or date` - and hand over only as many as the credits cover. Both lookups are capped at 2000 lines: a settlement larger than the oldest 2000 open items leaves its remainder as an open credit, allocated by the next close. The partials created are the same as before, only the size of the batch given to `_reconcile_plan` changes. opw-6529792 Forward-Port-Of: odoo/enterprise#130210
Resolved issues and error corrections
Fixes a crash when users generate the Peruvian “Inventory and Balance” General Ledger report. The export now keeps the required SUNAT file format while avoiding the server error, so affected reports can be produced reliably.
Original PR description
### Description of the issue/feature this PR addresses: This PR fixes a server crash in the Peruvian localization (l10n_pe_reports_lib) that occurs when generating the "Inventory and Balance" General…
### Description of the issue/feature this PR addresses: This PR fixes a server crash in the Peruvian localization (l10n_pe_reports_lib) that occurs when generating the "Inventory and Balance" General Ledger report. The crash is triggered by strict validation rules within Python's csv module, which rejects the custom line terminator used to fulfill the SUNAT PLE formatting requirements. ### Current behavior before PR: When a user attempts to generate and export the "Inventory and Balance" report, the server crashes with a ValueError: bad delimiter or lineterminator value. This happens because the csv.DictWriter is initialized with lineterminator='|\n' to ensure every row ends with a pipe. Python's underlying csv implementation rejects this, as it expects standard line endings (\r, \n, or \r\n) and throws an error if the delimiter character (|) is included in the terminator string. ### Desired behavior after PR is merged: The "Inventory and Balance" report generates successfully without server errors. The code now uses the standard lineterminator='\n' to satisfy Python's validation rules. To maintain the mandatory trailing pipe (|) at the end of each row required by SUNAT, a dummy empty column (['']) is appended to the field names with restval=''. This prompts the writer to naturally append the final pipe as a column delimiter before the newline, resulting in the exact |\n output format required, safely and reliably. opw-6509674 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/enterprise#129636
Employee records now show the correct contract type for the selected historical timeline version instead of always showing the current active value. This helps HR teams review and update past employee contract information accurately without accidentally affecting the current employee record.
Original PR description
### Description of the issue/feature this PR addresses: When navigating through historical employee records on the employee form view timeline, the contract_type_id persistently displays the active…
### Description of the issue/feature this PR addresses: When navigating through historical employee records on the employee form view timeline, the contract_type_id persistently displays the active contract type rather than the correct historical value for the selected version. ### Current behavior before PR: Selecting a historical version on the Employee timeline continues to display the current active contract_type_id. The server payload for web_read returns the active employee contract type instead of the record stored on the associated hr.version. ### Desired behavior after PR is merged: Selecting any timeline version on the Employee form view dynamically displays that version's specific historical contract_type_id. The field stays reliably populated and reactive when switching back and forth between timeline nodes. Editing the contract_type_id while on a historical timeline node cleanly writes the update to hr.version.contract_type_id via the inverse method. opw-6518153 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
The Google Merchant Center feed now excludes video thumbnails from additional product images and avoids loading full image files just to check them. This prevents misleading product image listings and reduces the risk of feed generation failing on large catalogs due to memory usage.
Original PR description
Description of the issue/feature this PR addresses: The Google Merchant Center feed builds the extra image URL list of each product, and must skip the `product.image` records holding a video, as…
Description of the issue/feature this PR addresses:
The Google Merchant Center feed builds the extra image URL list of each product, and must skip the `product.image` records holding a video, as their image is only a thumbnail of that video. It filters those out by accessing `image_128`, which is wrong on two counts:
- A video record does have an `image_128`: the thumbnail, fetched from the video URL or uploaded by the user. Video thumbnails were therefore listed in the feed as if they were product images.
- Binary fields are read and base64-encoded in full by default, so the binary content of every extra product image was loaded into memory just to evaluate a truthiness check. On a database with ~4261 products with images, rendering the feed exhausts the worker memory limit and raises an out-of-memory error.
Current behavior before PR:
`_get_extra_image_1920_urls()` keeps every extra image whose `image_128` is set, including video thumbnails, and forces the ORM to fetch and base64-encode the full content of every extra image attachment.
<img width="2038" height="1462" alt="image" src="https://github.com/user-attachments/assets/e3b375e5-ac17-4b5c-b0c3-59264d7e36f7"/>
Desired behavior after PR is merged:
The records are filtered on `video_url`, the field that actually flags a video, so videos are properly excluded and no image binary is read at all.
opw-6513194Fixed an issue where removing a coupon reward in Point of Sale could prevent the customer list from opening. The system now ignores deleted coupon records so cashiers can continue selecting customers without interruption.
Original PR description
Steps to reproduce: - A coupon program with a coupon assigned to a customer - In the PoS, set that customer on the order - Enter the coupon code, its reward line is added - Remove the reward line -…
Steps to reproduce: - A coupon program with a coupon assigned to a customer - In the PoS, set that customer on the order - Enter the coupon code, its reward line is added - Remove the reward line - Click the customer button Issue: The customer list does not open. The PoS crashes with "TypeError: Cannot read properties of undefined (reading 'id')" raised while rendering PartnerLine. Cause: `partnerId2CouponIds` maps a partner to the ids of its `loyalty.card` records. It is filled at boot and on every `loyalty.card` create, but nothing ever removes an id from it: the models only trigger a create event. Removing the reward line of a code activated coupon deletes that card from the local models (`_setValue` in the order summary), so its id stays in the map while the record is gone. `getLoyaltyCards` pushed `models["loyalty.card"].get(id)` unconditionally, hence an `undefined` entry in the list the PartnerLine template iterates over with `t-key="_loyaltyCard.id"`. Fix: Skip the ids whose record no longer exists. The partner keeps its remaining cards, and the path where no card was deleted is unchanged. The stale id is not pruned from the map: it is reached through the reactive store proxy, and mutating it there would notify subscribers in the middle of a render. opw-6517564 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286971
Restaurant POS orders are now synced when staff print the bill, reducing the risk of losing an order if the system reloads afterward. This helps keep order data reliable during a common restaurant workflow.
Original PR description
We now sync the order when printing bill, to avoid loosing it in case of reload data. task-6527207
Fixed an issue where package information could be overwritten when multiple warehouse packing steps fed into the same final delivery. This keeps each package correctly represented on the delivery, improving shipment accuracy and reducing confusion during fulfillment.
Original PR description
Steps to reproduce --- 1. Set the warehouse to deliver in 3 steps. 2. Create a delivery pick not tied to a procurement group (not from a sale order) with a consumable product, and validate it. 3. On…
Steps to reproduce --- 1. Set the warehouse to deliver in 3 steps. 2. Create a delivery pick not tied to a procurement group (not from a sale order) with a consumable product, and validate it. 3. On the pack transfer, put the goods in a package and validate. 4. Duplicate the pick, validate it, put its goods in a second package on the pack transfer, and validate. 5. Open the final delivery: both lines sit in the same package instead of one line per package. Issue --- The two picks share no procurement group, so their delivery moves merge onto a single move keyed by partner. Validating the second pack tops up that already partially reserved move: for a consumable, `_action_assign` builds its lines from `_get_available_move_lines`, which reports the availability of every upstream package without discounting what the move already reserved. https://github.com/odoo/odoo/blob/05af9e6877fd0f044bb46c983fe7300eb1db9307/addons/stock/models/stock_move.py#L1907-L1909 The package already reserved on the first line is therefore offered again and reused, collapsing both lines onto one package. The reserved-product branch below already subtracts the move's own reservation before allocating; doing the same here leaves each package on its own line. opw-6498532 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284956
Fully booked time slots in Point of Sale self-ordering are now still shown to customers, but they cannot be selected. This makes availability clearer and avoids confusion when expected time options disappear from the ordering flow.
Original PR description
Before this commit: = * Slots that reached their maximum capacity for a given time frame were hidden from the `pos_self_order` slot selection dialog. After this commit: = * Slots remain visible but are disabled when they reach their maximum capacity. task-6340956
Portal users can now submit product reviews with file attachments when reviews are enabled on a website. This prevents a confusing "Not Found" error and keeps the review experience working consistently whether or not a file is attached.
Original PR description
A portal user cannot add an attachment when posting a review on a product page: submitting the review with a file attached fails with a generic "Not Found" error, while posting the exact same review…
A portal user cannot add an attachment when posting a review on a product page: submitting the review with a file attached fails with a generic "Not Found" error, while posting the exact same review without an attachment works fine.
Steps to reproduce:
-------------------
* Enable "Discussion and Rating" on a product page (Website > Customize)
* Log in as a portal user and open that product page
* Write a review, attach a file, then submit
> Observation:
"Not Found
The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again."
Why the fix:
------------
`product.template._get_mail_message_access()` demotes a portal user's create access to 'write' whenever
`env['website'].is_view_active('website_sale.product_comment')` is False, since 'write' access is a deliberate way to block posting when the feature is toggled off for the website. `is_view_active` only picks the correct, website-specific view when `website_id` is present in `self.env.context`; that key is injected by the website frontend only for routes declared `website=True`.
`/mail/attachment/upload` is not `website=True` (attachments used to go through the dedicated, `website=True` `/portal/attachment/add` route, removed when portal's attachment uploader was unified with mail's), so `website_id` is missing from context on that route, `is_view_active` falls back to the generic, shipped-`active="False"` view, and always reports the feature as disabled - even when it is actually enabled for the current website. Create access is then wrongly demoted to 'write', which a portal user never has on `product.template`, so the thread lookup fails and the controller raises `NotFound()`.
Resolve the current website from the request itself (`env['website'].get_current_website()`) and inject its id into context before checking `is_view_active`, instead of relying on `website_id` already being in context. This makes the check accurate regardless of which route triggered it.
opw-6539184
Forward-Port-Of: odoo/odoo#287345Fixed an issue in the Project app where opening a saved task filter in Pivot view could fail when personal stages were involved. This ensures users can analyze their tasks reliably without encountering an error.
Original PR description
Since the introduction of `read_grouping_sets` for Pivot views, opening the Pivot view from a saved favorite filter can crash. ### **Steps to reproduce:** - Install Project. - Open My Tasks. - Save…
Since the introduction of `read_grouping_sets` for Pivot views, opening the Pivot view from a saved favorite filter can crash. ### **Steps to reproduce:** - Install Project. - Open My Tasks. - Save the current filter as a favorite. - Switch to the Pivot view. ### **Error:** ``` ValueError: Cannot convert project.task.personal_stage_id to SQL because it is not stored. ``` ### **Root Cause:** Since [commit](https://github.com/odoo/odoo/pull/194413/changes/166a546ec52784c413f5b5d7d29af89d618d6519), Pivot views use `_read_grouping_sets` instead of `_read_group`. `project.task` only remaps `personal_stage_type_id` to the stored `personal_stage_type_ids` in [_read_group](https://github.com/odoo/odoo/blob/5f6fb63d5d7585805642c702d096b2f882e73761/addons/project/models/project_task.py#L2169-L2179), so the remapping is bypassed for Pivot views. The ORM then attempts to group by the non-stored `personal_stage_type_id` relation, leading to the SQL conversion error. ### **Fix:** Mirror the remapping logic in `_read_grouping_sets` so Pivot views use `personal_stage_type_ids` before the ORM generates the SQL query. **opw-6306389**
This fixes a Point of Sale issue where selling multiple physical gift cards with the same value could combine them into one order line, preventing staff from entering a separate code for each card. Orders now keep these gift card, eWallet, and discount lines separate when required, avoiding validation failures and unsynced sales.
Original PR description
Steps to reproduce: - Create a gift card program (several programs sharing the same gift card product show the same issue) - In the PoS, sell a physical gift card: click the gift card product and set…
Steps to reproduce:
- Create a gift card program (several programs sharing the same gift card product show the same issue)
- In the PoS, sell a physical gift card: click the gift card product and set a code through "Sell physical gift card?"
- Click the gift card product again to sell a second physical card of the same value
- Validate the order
Issue:
The second unit is merged into the already coded orderline (one line, qty 2, one code), so the "Sell physical gift card?" link is no longer displayed and the second code cannot be entered. Validating the order then fails with "The operation cannot be completed: A coupon/loyalty card must have a unique code." and the order stays unsynced: the qty 2 line is split into two point entries both carrying the same gift_code, so two loyalty.card records are created with the same code.
Cause:
_setupGiftCardOptions() (and setupEWalletOptions()) pass merge=false so that a gift card line is never merged, and until 17.0 add_product() honored it ("options.merge !== false"). The 18.0 store refactoring dropped it: addLineToOrder() decides merging from a local variable that is only set to false when a price_unit is given in vals, and never reads opts.merge. The option became dead code, in point_of_sale's addLineToOrder as well as for the pos_discount caller.
Fix:
Honor opts.merge === false in addLineToOrder(). Callers that do not pass the option are unaffected, so the default merging behaviour is byte-for-byte the same; only the callers explicitly forbidding a merge (gift card, ewallet and discount lines) get their pre-18.0 behaviour back.
opw-6466324
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#286237This fix stops Odoo from automatically saving and closing an editable list row when a user opens the mobile file picker. It prevents in-progress edits, such as adding resources to eLearning course content, from being silently discarded.
Original PR description
The form view autosaves on 'visibilitychange' (e.g. when the user switches tab/app) to avoid losing unsaved changes. On mobile, opening the native file picker for a binary field also fires 'visibilitychange', which triggered this autosave. When that binary field was part of an editable x2many list, the autosave forced the row out of edition before the file could be selected, silently discarding the edition in progress. Skip the autosave when a x2many field of the root record currently has a row in edition. Can be reproduced in eLearning > course > content > Additional Resources, on mobile devices (must force "desktop mode" in the browser), and on tablets. opw~6517735 Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merged: --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287581
The HTML editor now recovers from outdated editing sessions by using the latest version stored on the server. This prevents users from remaining stuck with stale content after another save has occurred, improving reliability during collaborative or interrupted editing.
Original PR description
Before this commit, an editor recovering from a stale document could stop on this error and keep the stale content:
```
Error: Concurency detected while recovering from a stale document. The
last history id of the server is different from the history id received
by the html_field_write event.
at CollaborationOdooPlugin.resetFromServerAndResyncWithPeers
```
This happens because the recovery compares the history id of the record with the one the html_field_write event carried. A write keeps only the last step id in the field, so an event handled after a later write names an id the record no longer holds. As a result, the recovery stops there and the document stays stale.
This commit fixes the issue by taking the history id read from the record as the new server reference, so the editor converges on the document the server holds.
https://runbot.odoo.com/odoo/error/944595
Forward-Port-Of: odoo/odoo#287408This fix prevents attendance creation or updates from failing when an employee has overlapping Planning slots with different allocation percentages. It keeps schedule calculations stable so overtime and attendance records can be processed, while any real planning conflicts can still be handled separately.
Original PR description
### Analysis Planning schedule computation may merge intervals coming from two different sources. Fully allocated Planning slots reuse intervals from the employee working schedule, whose payload is a…
### Analysis
Planning schedule computation may merge intervals coming from two different sources.
Fully allocated Planning slots reuse intervals from the employee working schedule, whose payload is a `resource.calendar.attendance` recordset. Partial allocations, however, create synthetic intervals using a `resource.calendar` recordset.
When such Planning slots overlap, these intervals are merged into the same `Intervals` instance. This can raise a `TypeError` while sorting or merging the interval payloads, since recordsets from different models cannot be combined.
This can surface during attendance creation or deletion when overtime recomputation requests the employee Planning schedule.
### Steps to reproduce:
1. Install the following applications/modules:
- Attendances
- Planning
- Payroll / Work Entries
- `hr_work_entry_planning_attendance`
2. Create an employee with:
- Work Entry Source: Planning
- A flexible working schedule
- An employee version covering the test date
3. Create two published Planning slots for the same employee on the same day
and with overlapping times:
- Slot 1: 08:00–17:00, Allocated Percentage = 100%
- Slot 2: 08:00–17:00, Allocated Percentage = 99%
4. Create a completed attendance for the same employee on that day, for
example:
- Check In: 08:00
- Check Out: 17:00
5. Save the attendance.
Current behavior:
Attendance creation crashes while recomputing overtime/schedules with a
TypeError caused by mixing `resource.calendar` and
`resource.calendar.attendance` recordsets inside an `Intervals` instance.
Depending on the exact interval boundaries, the error can be:
```
TypeError: '<' not supported between instances of
'resource.calendar.attendance' and 'resource.calendar'
```
or:
```
TypeError: inconsistent models in:
resource.calendar() | resource.calendar.attendance(...)
```
Expected behavior:
The attendance should be created successfully. Conflicting Planning slots
may still be reported as a Planning conflict, but attendance schedule
recomputation must not crash with an internal TypeError.
### Fix
This commit creates the partial-allocation intervals with an empty `resource.calendar.attendance` recordset instead. This keeps the interval payload model consistent with the working schedule intervals.
opw-6511903This update fixes an issue in the German point-of-sale certification flow. It helps ensure certified POS operations continue to work reliably for businesses using German localization requirements.
Original PR description
Long description Steps to reproduce: ------------------- * * > Observation: Why the fix: ------------ opw-6524526
This fixes an issue where some custom header or footer PDF fields could lose their values when generating a sales PDF quote. Businesses using PDF Quote Builder with structured form fields will now see the expected information preserved in generated quotations.
Original PR description
Issue: --- When a quotation header/footer PDF has a hierarchical AcroForm field, the value set for that field is silently dropped from the generated PDF Quote when running on pypdf 5.4.0. Steps to…
Issue: --- When a quotation header/footer PDF has a hierarchical AcroForm field, the value set for that field is silently dropped from the generated PDF Quote when running on pypdf 5.4.0. Steps to reproduce: 1- Using a python 3.13 env, install requirements.txt (or otherwise run with pypdf==5.4.0 instead of PyPDF2). 2- Upload a header/footer PDF whose form field is a hierarchical field (`/T`/`/FT`/`/V` on the parent, not on the widget itself). The attachment from the ticket can be used as a sample to reproduce the bug. 3- Create a SO and select that document in the Quote Builder tab. 4- Print -> PDF Quote. 5- The value bound to that field does not appear in the printed PDF. Cause: --- After https://github.com/odoo/odoo/commit/4b02fbd717f62dd5345dad3ffb8d428c1c180007 `_add_pages_to_writer` renames the parent `/Field` object's `/T` when the widget itself has none. PyPDF2's `addPage` inserted the reader's page as is, keeping the widget's `/Parent`. pypdf 5.4.0 deep clones the page instead and ignores `/Parent` at every depth, so the widget loses the link to that field. It ends up with neither `/T` nor `/FT`, hence nothing matches the value mapping. Fix: --- Merge the field into its widget annotations instead: copy the prefixed `/T` and the inheritable keys onto each of them, then drop `/Parent`. The field's own `/T` is left untouched, so a field owning several widgets is filled on all of them. opw-6508728 Forward-Port-Of: odoo/odoo#286186
French PDP Flow 10 responses from the public platform are now processed instead of only being stored. Rejected reports are marked correctly, users can see the rejection reason, and corrected reports can be resent with a new transmission reference.
Original PR description
Flow 10 PPF responses were stored as attachments without being processed. Consequently, rejected reports remained marked as sent, the rejection reason was not shown to users, and corrected reports could not be submitted again. Process the PPF response codes, update the flow state, and log the returned details in the chatter. Keep response attachments separate from the outgoing payload and allow rejected reports to be manually resent with their original moves and a new transmission identifier. no task id Forward-Port-Of: odoo/odoo#287338
SEPA Credit Transfer XML files no longer include an address field that some European banks reject. This restores compatibility for batch payments, especially for Austrian and German banks, while keeping the field only where it is required.
Original PR description
### Issue before this commit: Generating a SEPA Credit Transfer XML for certain European banks (e.g., Austrian and German banks) fails because the generated XML contains an unexpected `<CtrySubDvsn>`…
### Issue before this commit: Generating a SEPA Credit Transfer XML for certain European banks (e.g., Austrian and German banks) fails because the generated XML contains an unexpected `<CtrySubDvsn>` tag inside the `<PstlAdr>` node, leading to the rejection of the batch payment file. ### Steps to reproduce the issue: 1. Download Accounting and l10n_at 2. Go to settings and activate SEPA Credit Transfer / ISO20022 3. Go to Journals > Bank > set an account number 4. Create an austrian contact (ex. FK Austria Wien AG) and set in the invoicing tab a bank (ex. AT526000071856851733) and set it trusted 5. Go to Bills, create a new one with the contact created and confirm it 6. Then click 'PAY' and select SEPA Credit Transfer 7. Go to Vendors > Batch Payments 8. Create a new one with: 1. Bank as bank 2. SEPA Credit Transfer as Payment Method 3. Add the bill just created 9. Validate and download the XML 10. See that a wrong tag <CtrySubDvsn> is added. This makes some banks refuse it ### Cause of the issue: External commit 30b023d394a5e4de64873188aa1d5961fecccb10 introduced the `<CtrySubDvsn>` tag to support US and CA requirements. However, the change was incorrectly applied to the common `account_iso20022` file, making it leak into standard European SEPA exports where the tag is not compliant with certain strict banking validation rules. ### Reason to introduce the fix: Revert the generic addition of the `<CtrySubDvsn>` tag in the common ISO20022 XML generation and restrict it only to the specific localizations (US/CA) that require it. This brings the `<PstlAdr>` node back to compliance, allowing Austrian, German, and other European banks to successfully process the files. opw-6523035 Forward-Port-Of: odoo/enterprise#131090
Kiosk self-ordering now correctly marks kitchen tickets as already sent before the order reaches the cashier screen. This prevents restaurant staff from receiving and preparing the same kiosk order twice when the cashier later opens the order in PoS.
Original PR description
Steps to reproduce: - Restaurant PoS with a kitchen printer, self-ordering in kiosk mode - Place an order on the kiosk: the kiosk prints the kitchen ticket - Open the same order in the PoS and press "Order" Issue: The PoS prints every line of the order again as a new one, so the kitchen prepares the order twice. Cause: The kiosk prints its own kitchen tickets (printKioskChanges) but never records that in last_order_preparation_change, the state the PoS diffs against to find the lines that still have to be sent. The order thus reaches the PoS with all its lines unsent. Fix: Update last_order_preparation_change before the kiosk submits the order to the server, as 19.0 already does since 555df96ac87a. Only in kiosk mode: in 18.0 a mobile order is still sent to the kitchen by the cashier from the PoS, so it has to stay unsent. opw-6520435 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285992
This fix prevents certain Taiwanese B2B e-invoices with down payment deductions from being rejected by ECPay due to small tax rounding differences. The e-invoice now uses the tax amounts already recorded on the invoice, helping ensure accepted submissions and consistent tax reporting.
Original PR description
Current behavior: -- Sending a B2B invoice that deducts a down payment is rejected by ECPay with "(item tax discrepancy exceeds 1 NT$)", so the invoice cannot be issued at all. When it is accepted,…
Current behavior: -- Sending a B2B invoice that deducts a down payment is rejected by ECPay with "(item tax discrepancy exceeds 1 NT$)", so the invoice cannot be issued at all. When it is accepted, the tax on the e-invoice can still differ from the tax the invoice books. Expected behavior: -- The invoice is accepted, and the tax reported on the e-invoice is the one the invoice booked. Steps to reproduce: -- - Set a company up in Taiwan (TWD) with the ECPay credentials filled in - Create a sale order of 190,630 for a customer with a VAT number - Invoice a 50% down payment through the down payment wizard, then a 30% one, and post both - Invoice the remainder and post it - Send the final invoice to ECPay Cause of the issue: -- _l10n_tw_edi_prepare_item_list rebuilt the tax from the raw amount of every line and rounded that total once. Both steps also disagree with the invoice. Negative lines resulting from downpayments seem to be more strictly checked on ECPay and taxes cannot be re-derived easily by ECPay's system. There is a also a relevant but slightly different issue where the invoice amount is calculated differently but the ECPay page will show a different value. e.g. the invoice rounds each computation key on its own: 50.00 - 16.50 = 33.50 rounds to 34 where the invoice books 50 - 17 = 33. No issue was detected for purely positive lines (a transaction with different products but no downpayment) Fix: -- Use the tax amount the line carries when it has one, and fall back to the raw amount otherwise, so the payload reports what the invoice booked. Send the tax of each line in the ItemTax field. Let ECPay derive its own per-item tax and checks it against the declared total for purely positive lines, and recalculate them for negative so the rounding difference between them is spread one unit at a time across the lines, the way ECPay distributes it. opw-6424237 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Spanish amounts written in words now use the grammatically correct form "un" instead of "uno" before thousands and large-number terms. This improves the wording shown on invoices, CFDI documents, and other Spanish-language reports where totals are displayed in text.
Original PR description
#### Description of the issue/feature this PR addresses: Spanish amounts in words render "uno" where the grammar requires the apocopated "un" — on invoice PDFs, CFDI amounts, and anywhere num2words…
#### Description of the issue/feature this PR addresses: Spanish amounts in words render "uno" where the grammar requires the apocopated "un" — on invoice PDFs, CFDI amounts, and anywhere num2words is called with a Spanish language. #### Current behavior before PR: "DOS MILLONES TRESCIENTOS UNO MIL CUATROCIENTOS TREINTA Y NUEVE PESOS 88/100 M.N." num2words applies the apocope only in to_currency(), never in to_cardinal(), and Odoo renders the plain cardinal then appends the currency label itself. Present in both versions pinned in requirements.txt (0.5.10, 0.5.13). #### Desired behavior after PR is merged: "DOS MILLONES TRESCIENTOS UN MIL CUATROCIENTOS TREINTA Y NUEVE PESOS 88/100 M.N." The apocope is applied in to_cardinal() through the num2words monkey patches, for es, es_CO and es_VE. Other languages are untouched. Note: the cardinal is now always apocopated, so a standalone count reads "un" rather than "uno". opw-6375677 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285558 Forward-Port-Of: odoo/odoo#277919