Monday, August 24, 2026
22 changes · master
Resolved issues and error corrections
This fix prevents the timesheet assistant from accidentally changing event data and resolves errors that could appear when removing or viewing certain timesheets. Users should experience fewer interruptions and more dependable timesheet handling.
Original PR description
## [FIX] timesheet_grid: avoid altering consumedEvents Before this commit, the objects inside consumedEvents attribute are altered because we keep the reference of those objects. This commit avoid altering the consumed events. ## [FIX] timesheet_grid: use recordsByStart instead of records Forward-Port-Of: odoo/enterprise#128036 Forward-Port-Of: odoo/enterprise#127698
Timesheet suggestions now handle away-from-keyboard periods more reliably, so breaks and inactive time are less likely to be missed or overwritten. This helps employees and managers get a more accurate view of working time when using ActivityWatch-based timesheet assistance.
Original PR description
## Previous Behavior Before this Commit 1. When key and non‑key events were merged to build the final suggestion timeline, key events always took priority over AFK events, even when the key event was…
## Previous Behavior Before this Commit 1. When key and non‑key events were merged to build the final suggestion timeline, key events always took priority over AFK events, even when the key event was not “always active.” This caused AFK events to be incorrectly overridden. 2. During event normalization, certain events were lost entirely, resulting in important events not being counted. 3. When merging two event timelines, zero‑duration gaps were treated as valid, preventing proper merging of surrounding events. 4. ActivityWatch sometimes produced empty gaps instead of AFK events, causing breaks to go unrecorded. ## New Expected Behavior After this Commit 1. Events now follow the updated priority system: a. Always‑active key events b. Always‑active non‑key events c. Non‑key AFK events d. Other key events e. Other non‑key events 2. Events are now shortened or split so that the latest event always has priority, while minimizing unnecessary event removal. 3. Zero‑duration gaps are skipped when merging event lists. 4. Any gap larger than 3 minutes, between the first and last event and containing no events is automatically filled with an AFK event. ## Additional Notes Because point 4 introduces additional AFK events, several tests were updated to reflect the new behavior. task-[6455412](https://www.odoo.com/odoo/project/4105/tasks/6455412) Forward-Port-Of: odoo/enterprise#128039 Forward-Port-Of: odoo/enterprise#127811
Inventory move lines now keep the transfer's original scheduled date after validation instead of switching to the processing date. This makes Moves History filtering, grouping, and sorting by Scheduled Date match the date shown on the transfer, improving reporting accuracy.
Original PR description
### Problem `stock.move.line.scheduled_date` is related to `move_id.date`, and `stock.move.date` only holds the scheduled date **until the move is done** — at validation it is overwritten with the…
### Problem `stock.move.line.scheduled_date` is related to `move_id.date`, and `stock.move.date` only holds the scheduled date **until the move is done** — at validation it is overwritten with the processing date, as its own help states. So on a done move line the field returns the effective date under a "Scheduled Date" label. It shows up in Moves History (Inventory > Reporting), which lists done lines by default: filtering, grouping or sorting by "Scheduled Date" silently uses the effective date, and it contradicts the "Scheduled Date" displayed on the transfer, which does survive the validation (`stock.picking._compute_scheduled_date` ignores done moves). **Steps to reproduce** 1. Create a receipt scheduled tomorrow and validate it today. 2. Inventory > Reporting > Moves History, group by "Scheduled Date". 3. The line falls under today instead of tomorrow, while the transfer form still shows tomorrow. ### Fix Follow `picking_id.scheduled_date`, which keeps the scheduled date once the transfer is done. The field is only displayed in `stock.view_move_line_tree_detailed`, and every action using that view (`stock.action_get_picking_type_operations`, the Prepare Wave actions and the Add to Wave wizard) already restricts the lines to those belonging to a transfer, so no view loses a value it used to show. I hereby agree to the terms of the CLA available at: https://www.odoo.com/cla
Users who manually replenish stock will now see the expected notification when a purchase order is created. This makes the replenishment process clearer and helps teams confirm that their order action was successful without having to search for the purchase order manually.
Original PR description
Currently when the user does manual replenishment no notification is displayed. ## Steps to produce: - Install Inventory and Purchase - Create a product `Chocolate Icecream` and Enable `Track…
Currently when the user does manual replenishment no notification is displayed. ## Steps to produce: - Install Inventory and Purchase - Create a product `Chocolate Icecream` and Enable `Track Inventory` - Purchase > Add a Vendor `Ice cream man` - Reordering rules > Create a new reordering rule and save: - Trigger: Manual - Min: 5 - Max:10 - Press the `Order` button ## Observed Behavior: No notification is displayed about the newly created purchase order. ## Root cause: When the Order button is pressed, the `action_replenish` method is called. This method invokes `_procure_orderpoint_confirm` at [1]. The `_procure_orderpoint_confirm` function is responsible for creating procurements from orderpoints. During this process, it retrieves the procurement values using `_prepare_procurement_values` that are later used at [2]. However, `_prepare_procurement_values` only includes the orderpoint in the procurement values when the orderpoint's trigger is set to automatic, and not when it is manual, as shown at [3]. These procurement values are then used by `_run_buy` to create a purchase order and purchase order line at [4]. Since the orderpoint is not linked to the purchase order line in this case, no matching order is found at [5], which leads to the reported issue. **Which commit caused this unintentional behavior?** This behavior was unintentionally introduced by this [commit](https://github.com/odoo/odoo/commit/2a0d2c64d0027f540101447289b4c1a10cb3ecdf) . That commit fixed an issue where purchase order lines were not being merged for temporary manual orderpoints that are created dynamically based on product demand. [1]- https://github.com/odoo/odoo/blob/c076281dedeed2e25844c43c49ec17511434e3fc/addons/stock/models/stock_orderpoint.py#L342-L349 [2]- https://github.com/odoo/odoo/blob/c076281dedeed2e25844c43c49ec17511434e3fc/addons/stock/models/stock_orderpoint.py#L737-L741 [3]- https://github.com/odoo/odoo/blob/c076281dedeed2e25844c43c49ec17511434e3fc/addons/stock/models/stock_orderpoint.py#L687-L701 [4]- https://github.com/odoo/odoo/blob/c076281dedeed2e25844c43c49ec17511434e3fc/addons/purchase_stock/models/stock_rule.py#L156-L165 [5]- https://github.com/odoo/odoo/blob/c076281dedeed2e25844c43c49ec17511434e3fc/addons/purchase_stock/models/stock.py#L276-L296 [6]- https://github.com/odoo/odoo/blob/c076281dedeed2e25844c43c49ec17511434e3fc/addons/stock/models/stock_orderpoint.py#L365 [7]- https://github.com/odoo/odoo/blob/6a84d3e519892be333552e2e0ebf8da87e0a760c/addons/purchase_stock/models/purchase_order_line.py#L380-L384 ## Solution: Instead of removing the orderpoint ID from the procurement values, we reuse the same conditions used to identify temporary orderpoints for cleanup at [6]. Based on this, we determine how purchase order lines should be merged in the `_run_buy` method. With the previous implementation, no orderpoint was included in the procurement values. As a result, the condition at [7] checking for orderpoints always evaluated to True, causing the system to identify an existing purchase order line for the same product as a merge candidate. This solution allows us to retain that fix as well as avoid the error of notifications not showing up. opw-6311520 Forward-Port-Of: odoo/odoo#283853 Forward-Port-Of: odoo/odoo#271993
Fixes an error that occurred when users clicked the Recorded button on field service interventions. Users can now reliably view the timesheets logged for an intervention without needing an extra project planning integration installed.
Original PR description
**Problem:** Clicking the "Recorded" smart button on a field service intervention raises an AttributeError instead of listing the timesheets logged on it. **Steps to reproduce:** 1. Install the field…
**Problem:** Clicking the "Recorded" smart button on a field service intervention raises an AttributeError instead of listing the timesheets logged on it. **Steps to reproduce:** 1. Install the field service billing feature (Planning > Configuration > Settings > Field Service > "Invoice your time and materials to your customers") without installing Project's planning integration 2. Set the project in Planning > Configuration > Settings > Field Service 3. Create an intervention and assign a resource to it 4. Click the "Recorded" smart button **Current behavior:** 'super' object has no attribute 'action_open_timesheets' **Expected behavior:** The timesheets logged on the intervention are listed. **Cause of the issue:** `action_open_timesheets` on `planning.slot` is defined in two modules only: `project_timesheet_forecast` provides the base implementation, and `planning_field_service_sale_timesheet` extends it through `super()`. Since `59a04091c403` dropped `project_timesheet_forecast_sale` from the dependencies of `planning_field_service_sale_timesheet`, nothing guarantees the base implementation is part of the model's inheritance chain anymore: `project_timesheet_forecast` is auto installed only together with `project_forecast`, which no module of the field service stack depends on. The smart button is rendered by `planning_field_service_sale_timesheet` itself, so it stays visible in such an installation while the `super()` call has nothing to resolve to. **Fix:** That same commit already made the field service module self-sufficient for `_get_timesheetable_project`, `_compute_allow_timesheets` and `_compute_allow_billable`, leaving the project forecast flavour to the `project_timesheet_forecast_field_service_sale` bridge, and `action_open_timesheets` is the only method that was left relying on the other stack. Building the action from the module's own hooks preserves that separation, whereas depending on `project_timesheet_forecast` again would pull Project back into every field service installation, which is precisely what that commit set out to avoid. opw-6391972 Forward-Port-Of: odoo/enterprise#126489
Odoo now correctly detects when an internal stock transfer leaves a replenished source location short of stock. This ensures the Replenishment report creates the needed reordering suggestion, helping businesses avoid missed restocking actions after moving inventory between internal locations.
Original PR description
Steps to reproduce: ------------------- 1. Install `stock` module. 1. Enable the "Storage Locations" from setting. 2. Create an internal location and enable `replenish_location` on it and set…
Steps to reproduce:
-------------------
1. Install `stock` module.
1. Enable the "Storage Locations" from setting.
2. Create an internal location and enable `replenish_location` on it and set warehouse(WH) as Parent.
3. Create a storable product track by quantity with no on-hand quantity.
4. Create an internal transfer from the warehouse stock location to the new internal location.
5. Confirm the transfer.
6. Open the Replenishment report.
Issue:
------
No manual reordering rule is created for the product at the source location, although
the confirmed transfer makes its forecasted quantity negative.
If `replenish_location` is disabled on the destination, the expected reordering rule is created.
Cause:
------
When both the source and destination have `replenish_location=True`, Odoo considers both locations
inside the same replenishment area. Therefore, the internal transfer is not counted as either
incoming or outgoing, and no replenishment is created for the source location.
Code Flow:
Opening the Replenishment report calls
`stock.warehouse.orderpoint.action_open_orderpoints()`, which delegates the report preparation to `_get_orderpoint_action()`:
https://github.com/odoo/odoo/blob/12a66c6931d81e1cce18f676836aca0b32090d16/addons/stock/models/stock_orderpoint.py#L324-L326
`_get_orderpoint_action()` first obtains every location that must be monitored for replenishment through `_get_orderpoint_locations()`:
https://github.com/odoo/odoo/blob/12a66c6931d81e1cce18f676836aca0b32090d16/addons/stock/models/stock_orderpoint.py#L520
`_get_orderpoint_locations()` returns all locations whose `replenish_location` field is enabled:
https://github.com/odoo/odoo/blob/12a66c6931d81e1cce18f676836aca0b32090d16/addons/stock/models/stock_orderpoint.py#L799-L800
All these locations are passed together to
`product.product._get_domain_locations_new()`:
https://github.com/odoo/odoo/blob/12a66c6931d81e1cce18f676836aca0b32090d16/addons/stock/models/product.py#L396-L464
This method treats the provided locations and their descendants as a set. Its move domains are equivalent to:
- incoming: destination is inside the set and source is outside;
- outgoing: source is inside the set and destination is outside.
Consider the following sibling locations:
WH
├── Stock
└── Replenish Location
and the transfer:
Stock -- 3 units --> Replenish Location
When `replenish_location` is disabled on the destination, the considered location set contains only `Stock`:
considered location set = {Stock}
The source is inside the set and the destination is outside:
source inside = True
destination outside = True
The transfer therefore matches the outgoing domain:
source inside AND destination outside
True AND True
= True
It is included in `moves_out`, and the preliminary forecast for `Stock` becomes:
0 on hand + 0 incoming - 3 outgoing = -3
https://github.com/odoo/odoo/blob/12a66c6931d81e1cce18f676836aca0b32090d16/addons/stock/models/stock_orderpoint.py#L552
The negative quantity is detected and a manual orderpoint is created.
When `replenish_location` is enabled on the destination, both sibling locations belong to the considered location set:
considered location set = {Stock, Replenish Location}
Both ends of the transfer are now inside:
source inside = True
destination inside = True
destination outside = False
source outside = False
The transfer matches neither aggregate domain:
incoming:
destination inside AND source outside
True AND False
= False
outgoing:
source inside AND destination outside
True AND False
= False
The transfer is considered internal to the considered location set and is therefore absent from both `moves_in` and `moves_out`:
https://github.com/odoo/odoo/blob/12a66c6931d81e1cce18f676836aca0b32090d16/addons/stock/models/stock_orderpoint.py#L530-L540
This aggregate treatment conflicts with the next step, where `_get_orderpoint_action()` computes the quantity separately for each replenishment location:
https://github.com/odoo/odoo/blob/12a66c6931d81e1cce18f676836aca0b32090d16/addons/stock/models/stock_orderpoint.py#L549-L552
When `Stock` is evaluated separately, the internal transfer has already been discarded. Its calculated outgoing quantity is consequently zero:
0 on hand + 0 incoming - 0 outgoing = 0
As the quantity is not negative, the product is not scheduled for the final `virtual_available` computation and no manual orderpoint is created.
Fix:
----
Also retrieve moves whose source and destination are both inside the aggregate replenishment-location set.
Include these internal moves in both the incoming and outgoing grouped queries. The existing per-location path filtering then assigns each side correctly:
- the source replenishment location counts the move as outgoing;
- the destination replenishment location counts it as incoming.
For the reported transfer, this produces:
Stock:
0 on hand + 0 incoming - 3 outgoing = -3
Replenish Location:
0 on hand + 3 incoming - 0 outgoing = 3
This is correct because the report creates replenishment propositions per location, even though the initial move query is performed for all replenishment locations together.
---
opw-6462608
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#282168The accounting app now correctly blocks fiscal year periods that fully contain an existing fiscal year. This helps prevent duplicate or conflicting accounting periods, reducing the risk of reporting and closing errors.
Original PR description
Before this commit: - The current constraint for overlap check allows if we define a new, larger fiscal year that completely swallows an existing smaller one (e.g., creating Aug 2025 - Nov 2026 when Sept 2025 - Oct 2026 already exists). After this commit: - The constrain domain was changed to consider the above missed case.
Customers can now redeem more than one eligible reward from the same loyalty coupon when they have enough points. This removes an incorrect block that prevented combining rewards such as a discount and a free product, improving checkout flexibility and loyalty campaign value.
Original PR description
Steps to produce: --- - Install the `website_sale_loyalty` module. - Navigate to Discount & Loyalty and create a new coupon program. - Keep the existing `10% Discount reward` and add a second reward…
Steps to produce: --- - Install the `website_sale_loyalty` module. - Navigate to Discount & Loyalty and create a new coupon program. - Keep the existing `10% Discount reward` and add a second reward of type `Free Product`. Select a product for the free product reward. - Generate a coupon and set its balance to `2` coupon points. - Open the website and add a product to the shopping cart. - Apply the generated coupon code. - Attempt to redeem both rewards (the 10% discount and the free product reward) using the same coupon. Issue: --- - When trying to apply the second reward, it shows `This program is already applied to this order.` Root cause: --- - Once a program was applied to the order, subsequent rewards from the same program were blocked, regardless of whether the customer had enough points to claim them. Solution: --- - If a reward from an already-applied program is still claimable (i.e, the customer has sufficient points), allow it to be applied to the order. opw-6247206 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Employees on flexible schedules without a set average daily hour value will now have full-day leave recorded correctly. This prevents leave requests from being saved as zero-duration, improving accuracy in absence tracking and payroll-related records.
Original PR description
…duration leave When a flexible schedule has no average_hour_per_day set (hours_per_day=0), _get_hours_for_date computed [12-0, 12, 12+0] = (12.0, 12.0), producing a zero-duration leave (date_from == date_to). Treat hours_per_day=0 like a fully flexible employee and return (0, 24) so the leave spans the full day. Task-6488608
Belgian payroll now correctly recognizes a full working day when an employee follows a schedule where some days have fewer contracted hours than others. This prevents payslips from incorrectly showing full days as half-day attendances, improving payroll accuracy for affected work schedules.
Original PR description
Bug: - Create a working sched were one day has less working hours than the rest. - when generating a payslip for an employee linked to that working sched , you'll have half-day attendances even…
Bug: - Create a working sched were one day has less working hours than the rest. - when generating a payslip for an employee linked to that working sched , you'll have half-day attendances even though he worked all his days Reason : - in _get_work_hours_split_half , when building work_data, we compare the duration worked by the employee that day not against the contractual amount of that that but instead we use self.resource_calendar_id.hours_per_day or the max hours per day on that working day , so the day with the least contractual hours on that working sched for exemple will fail this condition " float_compare(duration_sum, number_of_hours_full_day, 2) != -1" even if the employee worked that whole day. Fix: - for fixed scheds : we builed hours_per_dayofweek where we store the hours for each day of the week , later on , we check the weekday and retreive the corresponding official working hours on that day. - for variable scheds: because in this case each day can have its own officil working hours , we fetch the official hours of a certain date right before the logic that determines if it's a full or half a day. task - 6377396 Forward-Port-Of: odoo/enterprise#124100
This fixes an issue where AI assistants could attach request details to the wrong part of a resumed conversation. It prevents confusing or blank replies in Discuss and helps both Google Gemini and OpenAI produce responses based on the right user message.
Original PR description
`_append_context_part` puts the context of the current request on the last entry of the history, whatever that entry happens to be. That is the user's message on a normal turn, but a turn resumed from a `user_input_request` carries the answer back as a tool response, so the context landed on that instead: Google then received a turn mixing a `functionResponse` with a text part, which Gemini answers with a lone newline, posted as the agent's reply and shown in Discuss as a removed message. OpenAI appended it to the output of the tool call instead, making the model read the current context as part of what the tool returned. Both providers now walk back to the last message of the user and attach the context there, leaving tool responses untouched. A normal turn is unchanged, that message being the last entry already.
Shopfloor users can now finish the final work order in continuous production after entering produced quantities. The update also corrects displayed produced quantities when assigning serial numbers, reducing confusion and preventing blocked manufacturing steps.
Original PR description
Previously there was a limitation for continuous production in shopfloor, that blocked the user from marking a workorder as done after registering a quantity. This commit fixes it by assigning the production's `quantity_producing` to the work order's `qty_produced` if it is the final work order. This unblocks the user and allows them to complete the work order. Forward-Port-Of: odoo/enterprise#125787
Manufacturing change orders can now be created successfully from incoming emails sent to configured ECO aliases, even when no product is included in the email. This prevents bounced messages and ensures teams can capture change requests through email as intended.
Original PR description
Steps to reproduce --- 1. In Settings > Technical > Email > Alias Domains, create an alias domain (e.g. `example.com`). 2. Open the ECO Types configuration, pick a type (e.g. "BOM Updates"), and set…
Steps to reproduce --- 1. In Settings > Technical > Email > Alias Domains, create an alias domain (e.g. `example.com`). 2. Open the ECO Types configuration, pick a type (e.g. "BOM Updates"), and set its Email Alias to `bom-updates`. 3. Send an email to `bom-updates@example.com` with any subject and body. 4. The gateway fails to open the change order: the sender receives a bounce and no `mrp.eco` appears under that ECO type. Issue --- `product_tmpl_id` on `mrp.eco` is defined with `required=True`: https://github.com/odoo/enterprise/blob/79f9ff2ccea3153c457e286bbbca27794ccfdb57/mrp_plm/models/mrp_eco.py#L261-L266 That constraint was added by ac635694 ([IMP] mrp_plm: ux improvements), which tightened the form UX but did not account for the mail gateway, which opens change orders without a product. A change order can be created from an incoming email: the alias declared on `mrp.eco.type` routes the message to `mrp.eco`, and `_alias_get_creation_values` injects only `type_id` into the creation values: https://github.com/odoo/enterprise/blob/79f9ff2ccea3153c457e286bbbca27794ccfdb57/mrp_plm/models/mrp_eco.py#L55-L61 The gateway then builds the record with `message_new`, called from `_message_route_process`: https://github.com/odoo/odoo/blob/c6ef99510adcb079f34657aa66dcf191928e3355/addons/mail/models/mail_thread.py#L1553-L1575 https://github.com/odoo/odoo/blob/c6ef99510adcb079f34657aa66dcf191928e3355/addons/mail/models/mail_thread.py#L1402-L1413 An email carries no product, so those creation values have no `product_tmpl_id`. `required=True` is enforced only by the database `NOT NULL` constraint, so on any database where that constraint is present the gateway `create` raises and the incoming request is lost. A product is not actually needed until the change order starts its revision, where `product_tmpl_id` is first read to copy the BoM and gather its documents: https://github.com/odoo/enterprise/blob/79f9ff2ccea3153c457e286bbbca27794ccfdb57/mrp_plm/models/mrp_eco.py#L755-L777 opw-6351438
This fixes how Indian localization classifies purchase entries for imported goods and services, especially credit and debit notes. It helps keep GST reporting aligned with the correct treatment for reverse charge services and moves invalid import-without-RCM cases out of scope.
Original PR description
Previously:
1.`purchase_cdnur_regular` section was assigned to credit/debit notes of:
- import of goods
- import of services without RCM However:
- import of goods should be handled through bill of supply
- import of services without RCM is not possible Therefore, with this commit, such journal items are moved to `purchase_out_of_scope`.
2.`purchase_imp_services` section included import of services both with and
without RCM. Since import of services without RCM is not possible, those
journal items are now moved to `purchase_out_of_scope`.
3.Credit/debit notes of import of services with RCM were previously moved to
`purchase_out_of_scope`, which was incorrect. With this commit, they are now
correctly moved to `purchase_imp_services`.
task-6330737
Forward-Port-Of: odoo/odoo#276721
Forward-Port-Of: odoo/odoo#272453Restaurant point-of-sale orders now keep combo meal items assigned to the correct course when a combo is split into individual items. Courses are also cleaned up automatically when all related items are removed, helping staff keep orders clearer and reducing kitchen confusion.
Original PR description
Following this commit: ==== - When a combo is broken down, its items are assigned to their respective courses. - Remove a course when all its items are deleted from the cart. task-6121521 Forward-Port-Of: odoo/odoo#283143 Forward-Port-Of: odoo/odoo#260276
Indian GST purchase reports now better reflect legal reporting requirements for imported goods and services. Imports of services are no longer shown in GSTR-2B, and GSTR-3B reporting has been adjusted to match the latest section rules.
Original PR description
As per the law, import of services is not required to be shown in GSTR-2B. Therefore, the related report lines are removed in this commit. Additionally, GSTR-3B reporting is now handled according to the updated section changes for import of goods and services. task-6330737 Forward-Port-Of: odoo/enterprise#124550 Forward-Port-Of: odoo/enterprise#121925
WhatsApp messages using templates with many mixed placeholders now send each value to the correct spot. This prevents customers from receiving messages with mismatched details when templates include 10 or more variables.
Original PR description
**Issue**:
Sending a WhatsApp template with 10 or more variables can assign values to the wrong placeholders when the body contains mixed variable types, such as free text, field, or user name variables.
Templates containing only free-text variables are not affected.
**Reason**:
Meta consumes template parameters positionally, but for mixed variable types, Odoo built the parameter list using the template variable recordset order.
That order can differ from the numeric placeholder order, notably placing {{10}}, {{11}}, {{12}}... before {{1}}
when sending the message, as the payload parameters are not ordered by their numeric placeholder index.
**Fix:**
Sort body variables by their numeric placeholder index before preparing the Meta payload.
Task-6401501
Forward-Port-Of: odoo/enterprise#128492
Forward-Port-Of: odoo/enterprise#125671German DATEV exports now handle bank settlements involving three different currencies by splitting them into compliant accounting legs. This prevents export issues and ensures each DATEV line uses only one foreign currency while leaving normal transactions unchanged.
Original PR description
Issue: - DATEV does not support multiple foreign currencies on a single journal line. - This can occur when reconciling a bank transaction where: - the payer uses one currency (C1), - the bank…
Issue: - DATEV does not support multiple foreign currencies on a single journal line. - This can occur when reconciling a bank transaction where: - the payer uses one currency (C1), - the bank journal is held in another currency (C2), - the company uses a third currency (C3). - The existing export logic could not represent the bank liquidity and foreign AR/AP currencies separately in such cases. Fix: - Detect 3-currency cases from bank statement transactions and their liquidity line. - Use the DATEV clearing account (1360 SKR03 / 1460 SKR04) to split the transaction into two logical legs: - Bank → Clearing (bank journal currency) - AR/AP → Clearing (payer currency) - Emit the liquidity leg only once when multiple foreign AR/AP lines are reconciled against the same bank transaction. - Keep regular 1- and 2-currency transactions on the existing export path. Impact: - Correctly represents 3-currency bank settlements in DATEV. - Keeps each exported line in a single foreign currency. - Leaves manual entries and payment transactions outside this specific handling, as the scenario is specific to the bank liquidity line. taskID-5457547 Forward-Port-Of: odoo/enterprise#128876 Forward-Port-Of: odoo/enterprise#109010
Barcode deliveries now use the real storage location of a scanned serial number when it was not already reserved. This prevents inventory from being deducted from the wrong parent location, keeping stock counts accurate across warehouse sublocations.
Original PR description
Steps to reproduce --- 1. Enable Storage Locations and Lots/Serial Numbers. 2. Set the delivery operation type's "Source Location" to "Do not scan". 3. Create a serial-tracked product with a serial…
Steps to reproduce --- 1. Enable Storage Locations and Lots/Serial Numbers. 2. Set the delivery operation type's "Source Location" to "Do not scan". 3. Create a serial-tracked product with a serial stored in a sublocation (e.g. WH/Stock/Section 2). 4. Confirm a sale order for it, open the delivery in Barcode, and scan an unreserved serial. Issue --- Scanning the unreserved serial creates a new move line that falls back to _defaultLocation() because the decoded scan carries no source location (the operation type does not require scanning one) and never carries the serial's quant location. https://github.com/odoo/enterprise/blob/f42cfa7265ce32bd95f7f805cf4fb54d8b79cce1/stock_barcode/static/src/models/barcode_model.js#L937-L944 For a delivery, that default resolves to the picking's own source location (the parent WH/Stock), so the line is sourced from the parent instead of the sublocation where the serial physically sits. https://github.com/odoo/enterprise/blob/f42cfa7265ce32bd95f7f805cf4fb54d8b79cce1/stock_barcode/static/src/models/barcode_picking_model.js#L1542-L1544 On validation the unit is deducted from the parent location instead of the sublocation, leaving a stale quant of the serial in the sublocation and a negative quant at the parent. opw-5864414 Forward-Port-Of: odoo/enterprise#128523 Forward-Port-Of: odoo/enterprise#121375
Point of Sale now correctly validates negative orders that were manually paid with standard methods such as card payments. This prevents users from being wrongly blocked by a customer selection popup when the first payment option is Customer Account.
Original PR description
Validating a negative order manually paid with a standard method (e.g., Card) mistakenly triggers the fast payment fallback. The system wrongly assumed any single negative payment line was an auto-generated unsettled due. As a result, if the first configured payment method is "Customer Account", validation is incorrectly blocked by a popup asking for a customer. This commit restricts the fast payment trigger for negative amounts to only apply when the existing line is specifically a 'pay_later' type. task-6443347 Forward-Port-Of: odoo/odoo#282844 Forward-Port-Of: odoo/odoo#281238
Users on the Android mobile app can now download images and other files opened from Odoo without seeing an unsupported download error. The change sends eligible file downloads through the mobile app's supported download path, improving a visible workflow in Discuss and file viewing.
Original PR description
Steps to reproduce: - send an image in a Discuss channel - click the image to open the file viewer - click the download button => Android shows "The Odoo Mobile Apps only supports file downloads…
Steps to reproduce: - send an image in a Discuss channel - click the image to open the file viewer - click the download button => Android shows "The Odoo Mobile Apps only supports file downloads using the HTTP protocol." downloadFile()'s GET-by-URL case fetches the URL via XHR, then saves the Blob response by clicking a hidden <a download> anchor on a blob: URL. Android's DownloadManager only accepts http(s) URLs, so it rejects that blob: URL instead of downloading anything. Patch downloadFile._download to hand the URL directly to a new mobile.methods.saveFile bridge method when available, the same way download._download already delegates to mobile.methods.downloadFile. Blob/string content downloads aren't handled here — the only such call site (spreadsheet JSON export) is debug-mode only, so this is left as a console.warn for now. Related to odoo/odoo@e83fd8c08c879f5e262d39f24edcb3f81238ea82 Code made by Claude Changes supervised by HUVW Forward-Port-Of: odoo/enterprise#128471 Forward-Port-Of: odoo/enterprise#127693
Products with a base price of zero but paid attribute options are now correctly recognized as having a sellable price. This prevents shoppers from being blocked when adding these configured products to their website cart while zero-price sale prevention is enabled.
Original PR description
Steps to reproduce: --- - Install `website_sale` module. - Enable `Product Variants` and `Prevent Sale of Zero Priced Product` in settings. - Create new attribute > set `Variant Creation` as `Never`…
Steps to reproduce: --- - Install `website_sale` module. - Enable `Product Variants` and `Prevent Sale of Zero Priced Product` in settings. - Create new attribute > set `Variant Creation` as `Never` and also add value with extra price. - Create a product with sales price = 0, assign the attribute, and publish it. - As a public user (incognito), try to add the product to the cart. Issue: --- - In terminal error `The given product does not exist therefore it cannot be added to cart` is raised. Root cause: --- - In `_is_add_to_cart_allowed()`[1], the method calls `_get_contextual_price()` [2] to check if the product's price is zero when `prevent_zero_price_sale` is enabled. - However, `_get_contextual_price()` is called without the no-variant attribute values in the context, so it does not account for their `price_extra`. For a product with list price as 0 and attribute with extra price, the price is incorrectly computed as 0, causing `_is_add_to_cart_allowed()` to return `False`. Solution: --- - Before calling `_is_add_to_cart_allowed()`, set the product's context with the no-variant attribute values via `_get_product_price_context()`, so that `_get_contextual_price()` correctly includes the price extra in its computation. [1]https://github.com/odoo/odoo/blob/bbafbbd8950ec7123ab652851ede5479484eee26/addons/website_sale/controllers/cart.py#L117-L120 [2]https://github.com/odoo/odoo/blob/bbafbbd8950ec7123ab652851ede5479484eee26/addons/website_sale/models/product_product.py#L149-L150 opw-6365566 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283491 Forward-Port-Of: odoo/odoo#278620