Monday, August 24, 2026
22 changes · saas-19.3
Resolved issues and error corrections
Point of Sale returns now link back to the original sale so returned items use the correct historical cost. This prevents inventory valuation errors and accounting imbalances for products valued with average cost or FIFO methods.
Original PR description
Currently, returning a product via the PoS does not populate the `origin_returned_move_id` on the generated incoming stock move. For products using AVCO or FIFO valuation, this causes the stock valuation engine to fall back to the product's current standard price instead of using the historical cost of the original sale, resulting in stock valuation errors and accounting imbalances. This commit fixes the issue by updating `_prepare_stock_move_vals` to evaluate the `refunded_orderline_id`. It traces the refund back to the original PoS order and dynamically links the original completed outgoing stock move. This ensures the valuation waterfall correctly intercepts the return and applies the original historical cost. opw-6216531 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#273847
Odoo now handles empty or malformed uploaded attachments more safely when loading images. This prevents the Media Dialog and Chatter from crashing, so users can continue working even if an attachment upload was interrupted or invalid.
Original PR description
Problem: Interrupted uploads or malformed email payloads can create 0-byte binary `ir.attachment` records where the `checksum` is `False`. Accessing the computed `image_src` field on these records…
Problem:
Interrupted uploads or malformed email payloads can create 0-byte binary
`ir.attachment` records where the `checksum` is `False`. Accessing the
computed `image_src` field on these records triggers a
`TypeError: 'bool' object is not subscriptable` when attempting to slice
`attachment.checksum[:8]`. This crashes the Media Dialog and Chatter
across the framework.
Purpose:
Add a fallback boolean guard to `attachment.checksum` inside `_compute_image_src` so that empty attachments evaluate safely to a string ('0') instead of raising a traceback, allowing the UI to render gracefully.
Steps to Reproduce on Runbot:
1. Go to Settings > Technical > Database Structure > Attachments.
2. Create a new record: Name: `test.png`, Type: `File` (Binary), File Content: [leave empty], Is public document: Checked.
3. Open any record with a Chatter (e.g., Helpdesk or Sales Order) and click "Insert Image" to open the Media Dialog.
4. The system attempts to evaluate `image_src` and throws the `TypeError`.
Notes:
A regression test (`test_compute_image_src_empty_checksum`) was added to `test_ir_attachments.py` to ensure this framework guard remains active.
opw-6482674
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-prThe Inventory Valuation report now shows accounting differences for products that have no stock left, even when multiple valuation accounts are used. This helps finance teams see and balance all relevant inventory accounts without losing the prior performance benefit for zero-quantity products.
Original PR description
## Problem If multiple valuation/variation accounts are used, the Inventory Valuation report will not show the balance of accounts attached to products that have 0 quantity available if a different…
## Problem If multiple valuation/variation accounts are used, the Inventory Valuation report will not show the balance of accounts attached to products that have 0 quantity available if a different account has quantity. ## Solution In order to maintain the performance improvements intended by the commit that introduced the `qty_available != 0` filter, we will avoid calculating `total_value` for products with 0 quantity. We will still run `stock_accounting_value` on these products in order to capture interim accounting value on the Inventory Valuation report. ## Steps to Reproduce (Runbot v19) (defer to the test for more info) 1. Create an extra set of valuation/variation accounts 2. Create a product, avco perpetual accounting the default valuation/variation accounts 3. Create a second product, avco perpetual accounting the new valuation/variation accounts 4. Purchase 1 unit of each of the products and receive, bill both 5. Sell 1 unit of the product attached to the new valuation/variation 6. Go to Accounting > Review > Inventory Valuation, and note that the new valuation/variation accounts are not present. If you click Generate Entry, you will see that these accounts need to be balanced opw-6473319 Forward-Port-Of: odoo/odoo#282819
Message posting now cleans submitted data before a message is accepted, using the current user's permissions as the reference. This helps prevent invalid or inappropriate data from being posted in discussions and improves reliability of mail conversations.
Original PR description
This change sanitizes some post data before allowing the post, making sure the data received by `message_post` is clean based on the current user. part of task-6452761 Forward-Port-Of: odoo/odoo#283677 Forward-Port-Of: odoo/odoo#280894
Point of Sale receipts now show the correct cash rounding line even when a customer overpays, such as using a quick-add payment button. This keeps tickets and payment totals accurate and consistent with normal cash payments.
Original PR description
**Steps to reproduce:** - Make a rounding method, Nearest and 0.05 of rounding - Make a product that costs $4.99, don't set a tax - Go to the PoS - Order the product - Before paying click the +10…
**Steps to reproduce:** - Make a rounding method, Nearest and 0.05 of rounding - Make a product that costs $4.99, don't set a tax - Go to the PoS - Order the product - Before paying click the +10 button, then pay - The rounding line is not present on the ticket **Why the fix:** When making a normal rounded purchase, by just clicking the "Cash" button, the rounding line will be displayed. This is because we do not try to apply the rounding if the rounding of the remaining is not equal to zero. https://github.com/odoo/odoo/blob/995629db3231de944710751c3184bf1b8b1355c7/addons/point_of_sale/static/src/app/models/accounting/pos_order_accounting.js#L118-L123 When we try to over pay, the remaining will be negative by the amount we overpay, so the amount will be set to zero, and the rounding will not be set. We now take the amount we overpay into account, and deduct it from the amount we paid to then correctly compute the remaining amount without having to deal with the amount overpaid. Some tests were not taking the rounding as it was not working correctly, so it has now been changed now that it works as it should. opw-6025807 Forward-Port-Of: odoo/odoo#256117
Company-paid expenses created from a project are no longer mistakenly counted as positive revenue in project margin reports. This keeps Actual Margins accurate by excluding payment settlement lines while still recording the real expense cost.
Original PR description
Steps to reproduce --- 1. Install Sales (with Margins) and Project, and open a billable project linked to a sale order. 2. From the project's Expenses view, create an expense paid by the Company and…
Steps to reproduce --- 1. Install Sales (with Margins) and Project, and open a billable project linked to a sale order. 2. From the project's Expenses view, create an expense paid by the Company and post it. 3. Open the project's Actual Margins: the expense shows under Other Revenues as a positive amount. Issue --- Creating an expense from a project's Expenses view keeps `project_id` in the context until its journal entry is built. For a company-paid expense that entry is a payment, and `AccountMoveLine._compute_analytic_distribution` puts the project's analytic distribution on every line that is not receivable or payable, which also covers the Outstanding Payments liquidity line. An analytic amount is the opposite of the move line balance, so the negative liquidity balance becomes a positive analytic line, classified as Other Revenues, on top of the real cost already booked on the expense account. The receivable/payable filter from b6200026ecf1 narrowed the override introduced in ac1995ad6ddf but ignored the liquidity counterpart of a payment; since only profit and loss lines make up a project margin, restricting the distribution to `income` and `expense` accounts keeps the settlement line out of the report. https://github.com/odoo/odoo/blob/f037dead17eb6a74d1ea9c56b0861b285be17516/addons/sale_project/models/account_move_line.py#L10-L19 opw-6326551 Forward-Port-Of: odoo/odoo#273273
This fix ensures customer and pickup details entered during restaurant self-ordering are saved before customers are sent to the online payment page. It prevents draft orders from losing important information when a customer uses the browser Back button after payment navigation.
Original PR description
**Setup** * Increase the debounce time of `debouncedSynchronizeLocalDataInIndexedDB` to **5 seconds** to reproduce the issue deterministically. * Configure a restaurant with **Self Ordering** enabled…
**Setup** * Increase the debounce time of `debouncedSynchronizeLocalDataInIndexedDB` to **5 seconds** to reproduce the issue deterministically. * Configure a restaurant with **Self Ordering** enabled (`QR Menu + Ordering`). * Configure **Mollie** as the **only** online payment method. **Reproduction** 1. Place a **takeout** order through the mobile menu. 2. Select a pickup time, enter the required customer information (including a mobile number), and proceed to the payment page. 3. Verify from the backend that the draft order contains the expected data (customer/partner and `preset_time`). 4. Press the browser **Back** button to return from the payment page. 5. Check the draft order in the backend again. [video](https://drive.google.com/file/d/1kNWpYuo79mYMV3eMelwJ5IDFeWUc7zsD/view) **Observed result** * The draft order loses its previously synced information. In particular, the **partner/customer** data (and other synced fields such as `preset_time`) are removed. **Expected result** * Returning from the payment page should not modify the draft order. All previously synced data should remain intact. **Cause** - When there's only a single payment method, it's [auto-selected](https://github.com/odoo/odoo/blob/161715c850496d3683baa5d1600380470d0b5ff5/addons/pos_self_order/static/src/app/pages/payment_page/payment_page.js#L21-L22) and `checkAndOpenPaymentPage` immediately opens the payment page via[ window.open()](https://github.com/odoo/odoo/blob/161715c850496d3683baa5d1600380470d0b5ff5/addons/pos_online_payment_self_order/static/src/app/pages/payment_page/payment_page.js#L35). - The order's local data is saved to IndexedDB on a 300ms debounce. If the redirect fires before that debounce completes, the save is cancelled, leaving IndexedDB out of sync with the in-memory order **Fix** - Before opening the payment URL, explicitly flush the order to IndexedDB using the `synchronizeLocalDataInIndexedDB`, ensuring the local data is persisted before the page navigates away. opw-6231478 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281906 Forward-Port-Of: odoo/odoo#272724
Sales users with access limited to their own documents can now cancel sales orders that include loyalty program activity without hitting an access error. This prevents blocked cancellations while still cleaning up temporary loyalty point records correctly.
Original PR description
Steps to produce: --- - Install `sale_management` and `sale_loyalty` module without demo. - From sales > products > discounts & loyalty, create new loyalty card program and save. - Now create new…
Steps to produce: --- - Install `sale_management` and `sale_loyalty` module without demo. - From sales > products > discounts & loyalty, create new loyalty card program and save. - Now create new product of 100$. - Create a user which have sales rights as `user: own documents only`. - With that user, create new sale order with product and confirm. - Try to cancel the order. Issue: --- - It shows the access error: ```py You are not allowed to delete 'Sale Order Coupon Points - Keeps track of how a sale order impacts a coupon' (sale.order.coupon.points) records. This operation is allowed for the following groups: - Sales/Administrator Contact your administrator to request access if necessary. ``` Root cause: --- - Users with the `Sales: Own Documents Only` access right only have read permissions ([1]). When they cancel a Sales Order, the `_action_cancel` method attempts to clean up the temporary pending points allocated to the order by calling `self.coupon_point_ids.unlink()`. Because this call is executed without elevated privileges, the system blocks the deletion and raises an Access Error Solution: --- - Added `.sudo()` to the `unlink()` call for `coupon_point_ids` in the `_action_cancel` method. This ensures the pending point records are cleaned up with the necessary elevated privileges. [1]https://github.com/odoo/odoo/blob/23af2b443735c6d3a2f64e44f9ea5da45638b052/addons/sale_loyalty/security/ir.model.access.csv#L16 opw-6453016 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284046 Forward-Port-Of: odoo/odoo#281477
Safari users can now insert a soft line break with Shift+Enter in the HTML editor, such as when editing Knowledge articles. This prevents unintended paragraph splits and makes editing behavior consistent with other browsers.
Original PR description
**Steps to reproduce:** - Use a Mac with Safari - Install Knowledge app - Go to any article - Press Shift+Enter to try to enter a soft line break - Hard split is done instead **Issue:** Shift+Enter causes a `insertParagraph` event instead of `insertLineBreak` in Safari, which triggers the `SplitPlugin` instead of the `LineBreakPlugin`. **Fix:** Check if the browser is Safari and call `insertLineBreak` from the `SplitPlugin` (when needed) by listening to the "keydown" events. (note: I was not able to find any other key combination to properly trigger the `insertLineBreak` event in Safari) opw-6413507 Forward-Port-Of: odoo/odoo#281458
Users who manually replenish products will now see the expected notification when a purchase order is created. This helps purchasing and inventory teams confirm that their replenishment action succeeded without having to search for the new 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#283747 Forward-Port-Of: odoo/odoo#271993
This fixes an issue where translating text edited inside a related-record dialog could show old or empty content instead of the user's latest changes. Users can now open the translation dialog from these forms and see the current text, while existing editable-list behavior remains unchanged.
Original PR description
Clicking the translate button saves the form's root record before opening the translation dialog, since https://github.com/odoo/odoo/commit/9da52919a03dbcee5209430918195158c0652099. A record opened…
Clicking the translate button saves the form's root record before opening the translation dialog, since https://github.com/odoo/odoo/commit/9da52919a03dbcee5209430918195158c0652099. A record opened in an x2many form dialog keeps its changes for itself until the dialog is saved, see https://github.com/odoo/odoo/blob/242f6d3cf7288853f163ac6986a3b7aa4279efaf/addons/web/static/src/model/relational_model/static_list.js#L193. Its pending changes are not part of the root record changes, so saving the root sends nothing to the server, and the translation dialog then shows the stored terms instead of the current content, or no terms at all when the stored value is empty. The fix changes openTranslationDialog in translation_button.js, the place that decides which record to save. When the record keeps its changes for itself (record._noUpdateParent), the record is saved directly, like the button did before the commit above. The root record is still saved in the other cases, so the editable list case that commit fixed keeps working. Steps to reproduce: 1. Activate a second language in Settings > Translations > Languages 2. Open the Surveys app, open a survey and click a question in the Questions tab 3. In the Description tab, change the description 4. Click the EN button on the description field => the translation dialog shows the terms of the previous description, not the current one Ticket [link](https://www.odoo.com/odoo/project.task/6237291) opw-6237291 Forward-Port-Of: odoo/odoo#269507
Cancelling a payslip now correctly makes related time off available to be recalculated in the next payslip. This prevents approved time off from being accidentally excluded after payroll corrections, improving payroll accuracy.
Original PR description
How to reproduce: - Create a payslip for an employee and validate it - Create a new time off for said employee during the same period as the payslip and validate it - Go back to the payslip, cancel it and reset it to draft - The new time off is not included in the payslip Reason: When a payslip is cancelled, if there are time off during the same period as the payslip, their state is not reset to "to compute in next payslip" and instead stays in "to defer to next payslip", causing the issue How it was fixed: Now, when a payslip is cancelled, the new function "return_time_off_to_normal" will catch all leaves that are in the same time frame as the payslip to reset their state to "to compute in next payslip". Task ID: 6431576 Forward-Port-Of: odoo/enterprise#126868
Point of Sale bills no longer show the self-service invoicing QR code while an order is still in draft. This prevents customers from invoicing unpaid or unfinished orders, reducing payment and order reconciliation issues.
Original PR description
Step to reproduce: - install point_of_sale - have a pos, with `Early Receipt Printing` and `Self-service invoicing` enabled - open a pos ,select a product - from action button, click on "Bill" Observation: - We can see QR code in bill, using which a person can invoice itself, even when order is in draft state. - This cause a lot of anomoly like payment line not visible in pos order, even after successful payment Cause: - Prior to this version, `Qr` related data is shown only when `order.finalized` i.e. `status = draft` . https://github.com/odoo/odoo/blob/6f64942cbbbf2355f7328394a6d484f6828a80f1/addons/point_of_sale/static/src/app/components/receipt/order_receipt.xml#L76 - After commit https://github.com/odoo/odoo/commit/aeaca097ae39b293bff47458ae8af019585f9224 we removed this condition Fix: - The condition is brought back. opw-6427152 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279382
This fix prevents crashes when users drag unscheduled planning slots onto the calendar after enabling scheduling in Studio. It restores the information Planning needs to calculate slot end times and avoids a separate date-related error, making scheduling more reliable for affected users.
Original PR description
Steps to reproduce: ------------------------- 1. Install `sale_planning` and `web_studio` with demo data. 2. Open the Planning calendar view. 3. Enable the "Scheduling" option from Studio and close…
Steps to reproduce:
-------------------------
1. Install `sale_planning` and `web_studio` with demo data.
2. Open the Planning calendar view.
3. Enable the "Scheduling" option from Studio and close it.
4. Drag an unscheduled slot onto the calendar.
Issues:
-----------
**Issue 1:**
```python
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 142, in write
self.assign_slot(vals)
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 159, in assign_slot
new_vals, tmp_sale_order_slots_to_plan, resource = slot._get_sale_order_slots_to_plan(vals, slot_vals_list_per_employee)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 228, in _get_sale_order_slots_to_plan
)._get_resource_work_info(vals, slot_vals_list_per_resource)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 366, in _get_resource_work_info
assert self.env.context.get('default_end_datetime')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
```
**Issue 2:**
```python
UncaughtPromiseError > TypeError
Uncaught Promise > Cannot read properties of undefined (reading 'endOf')
TypeError: Cannot read properties of undefined (reading 'endOf')
```
Cause:
----------
Since commit 1ce0dc8, the scheduling/unscheduling logic has been moved to the generic calendar implementation. However, the generic scheduling flow does not provide the `default_end_datetime` context required by sale_planning. As a result, sale_planning raises an `AssertionError` while scheduling a slot.
Additionally, when no date is available, attempting to call `endOf()` raises a `TypeError`.
Solution:
------------
Introduce a generic scheduling context hook in the calendar model and override it in Planning to provide the `default_end_datetime context when scheduling a slot.
This restores the context expected by` sale_planning`, prevents the `AssertionError`, and avoids calling `endOf()` on an undefined date to resolve `TypeError`.
**NOTE:**
This issue has already been resolved in the later versions (saas-19.4) as part of the scheduling/unscheduling refactoring. This commit backports the minimal changes required to fix the issue in this version.
References: f0f7b34 & https://github.com/odoo-dev/enterprise/commit/f82d073d17ce61a2ff39496364d3dced814ee90a
Related enterprise pr: https://github.com/odoo/enterprise/pull/127196
opw-6442889
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#281178This fix prevents the incoming invoices journal from being cleared for companies that must receive Peppol documents as vendor bills rather than through the Documents app. It helps French electronic invoicing users keep required accounting settings intact and ensures incoming documents are routed correctly.
Original PR description
Peppol documents can be received in a journal or in the Documents app (peppol_reception_mode). Some companies cannot use Documents: _peppol_allows_document_reception() returns False and the journal…
Peppol documents can be received in a journal or in the Documents app (peppol_reception_mode). Some companies cannot use Documents: _peppol_allows_document_reception() returns False and the journal stays required. This is the case of French companies (via l10n_fr_pdp). The onchange of the settings and the import did not check this method. So on a French company with the mode set to 'documents': - the Settings cleared the journal on each opening, while it was still required - the incoming documents were saved in Documents instead of vendor bills Steps to reproduce: - Create a Belgian company, with a purchase journal, and register it on Peppol as receiver - Set the reception mode to "Receive in Documents" - Change the fiscal position to France, and install l10n_fr_pdp, the Peppol part is replaced by "French Electronic Invoicing", so the radio button is not visible anymore, but the company still has peppol_reception_mode == 'documents'. - Open the Settings again: the field "Incoming Invoices Journal" is empty. opw-6429691 Forward-Port-Of: odoo/enterprise#126462
This update fixes an issue where social media users encountered an access error when liking stream posts. Likes are now processed through the proper backend flow, improving the reliability of Facebook and Twitter social stream interactions.
Original PR description
Bug === When a social user like a stream post, an access error is raised because he has no write access on it. Task-6425391 Forward-Port-Of: odoo/enterprise#128780 Forward-Port-Of: odoo/enterprise#125973
Auto-planning for monthly schedules now correctly includes the last day of the selected month. This prevents missing planned work on valid working days, helping sales and planning teams produce complete schedules.
Original PR description
Steps to reproduce: --------------------------- 1. Install `sale_planning` with demo data. 2. Create a SO with a planning product, set the quantity to 100 hours, and confirm the SO. 3. Click the "To…
Steps to reproduce: --------------------------- 1. Install `sale_planning` with demo data. 2. Create a SO with a planning product, set the quantity to 100 hours, and confirm the SO. 3. Click the "To Plan" button, then click "Auto Plan". 4. Make sure the "Month" filter is selected in the scale options and observe the planned slots. Issue: -------- When auto planning slots for a month, the last day of the month is excluded. For example, slots are scheduled only until July 30th, even though July 31st is a working day. Cause: -------- While preparing the context, `stopDate` is set to July 31st at 00:00. It is then passed to [serializeDateTime()](https://github.com/odoo/odoo/blob/dacaad91bba8f959daf5d89a046c5a1c11e48eec/addons/web/static/src/core/l10n/dates.js#L553-L560), which converts the datetime to UTC. Depending on the user's timezone, this can shift the date to the previous day, causing the last day of the month to be excluded. Solution: ------------ Use `localEndOf()` to set `stopDate` to the local end of the selected range before passing it to `serializeDateTime()`. This ensures the last day of the month is preserved during UTC conversion. **NOTE:** Forward-port the solution from the 18.0 version, which was adapted to the publish shift use case in 18.3 and introduced this issue. Add a HOOT test case to prevent this regression in future versions. References: [18](https://github.com/odoo/enterprise/commit/bc3db24f83473d5646f7c2cfca8ed1c5b064ea2e) and [saas-18.3](https://github.com/odoo/enterprise/commit/c81fba31780869940f726b695ad46a87f69798fb) opw-6391495 Forward-Port-Of: odoo/enterprise#128501 Forward-Port-Of: odoo/enterprise#127950
This fix ensures users can create shortcuts for documents that are shared with a group. It also makes ownership access tracking more reliable when group-based document access is created, reducing access-related errors and confusion.
Original PR description
Reproduce: try creating a shortcut for a document shared with a group. \+ increase robustness of logging owner access when creating a documents with an access command regarding a group. Task-6344800
Belgian payroll now prorates fixed monthly salaries using the employee's expected working hours for the full payslip period, rather than a single weekly schedule value. This prevents incorrect salary deductions and ensures employees are paid correctly when they worked more or less than half of the period.
Original PR description
### Problem - Fixed salary payslips were not being prorated correctly. The 50% rule threshold was compared against `hours_per_week` (single week) instead of 50% of the theoretical hours for the…
### Problem
- Fixed salary payslips were not being prorated correctly. The 50% rule threshold was compared against
`hours_per_week` (single week) instead of 50% of the theoretical hours for the payslip period.
This led to incorrect salary deductions in all cases and the wrong computation path being taken when less than
50% of the month was worked.
### Solution
- Fix `_l10n_be_has_enough_paid_hours` to compare paid hours against
50% of theoretical hours instead of `hours_per_week` (which are calculated for the employee's `working schedule`)
### How it works
- For fixed salary (`wage_type = 'monthly'`), the quarterly hourly rule
is applied as follows:
- **Hourly rate** = `fixed_salary × 3 / 13 / theoretical_hours`
- **50% rule**:
- If more than 50% of theoretical hours were worked → deduct absences
from fixed wage
- If less than 50% of theoretical hours were worked → pay only the
hours worked
For variable salary (`wage_type = 'hourly'`), the employee is simply
paid for the number of hours worked during the period.
Task-6260378
Forward-Port-Of: odoo/enterprise#126600Fixed an issue that prevented PDF generation for Colombian electronic vendor bills after the reception and acceptance flow. Businesses using Colombian localization can now print these invoice PDFs without encountering a server error.
Original PR description
**Steps to reproduce:** * Install the **l10n_co_dian** module. * Go to **Settings** and, under **Colombian Electronic Invoicing**: * Disable **Testing Mode**. * Enable **DIAN Demo**. * Create a…
**Steps to reproduce:**
* Install the **l10n_co_dian** module.
* Go to **Settings** and, under **Colombian Electronic Invoicing**:
* Disable **Testing Mode**.
* Enable **DIAN Demo**.
* Create a vendor bill with a tax and confirm it.
* Click **Acknowledge Reception**.
* Click **Receive Goods**.
* Click **Accept**.
* From the gear menu, click **Print → Invoice PDF**.
**Observed behavior:**
* A server error is raised:
```
lxml.etree.XMLSyntaxError: Start tag expected, '<' not found, line 1, column 1
```
* The PDF cannot be generated.
**Cause (two-step):**
1. **ZIP not unwrapped:** The original code called `etree.fromstring(self.l10n_co_dian_attachment_id.raw)` directly for all move types. For vendor bills (`in_invoice`) the attachment is stored as a ZIP file, so `raw` is compressed binary data — not XML. Passing it to `etree.fromstring` directly produces the `XMLSyntaxError` above.
2. **AttachedDocument wrapper not unwrapped:** Once the ZIP is correctly decompressed with `xml_utils._unzip`, the resulting XML is an `AttachedDocument` wrapper, not a plain `Invoice`. The actual invoice XML is embedded as CDATA inside `cac:Attachment/cac:ExternalReference/cbc:Description`. `_get_qr_code_value` expects the inner document and searches for nodes like `cac:AccountingSupplierParty`, `cac:LegalMonetaryTotal`, and `sts:QRCode` — none of which exist on the outer wrapper, so the QR code was blank or the method crashed.
**Fix:**
* In `_l10n_co_dian_get_invoice_report_qr_code_value`, for vendor bills (`in_invoice`/`in_refund` without support document), unzip the attachment and immediately attempt to extract the inner invoice XML from `cbc:Description` using `findtext('.//{*}Description')` (lxml namespace wildcard). If the node is present, parse its text as the actual document; otherwise fall back to the unzipped bytes directly.
**Note:**
* A unit test for the `AttachedDocument` unwrapping path was not added because the test would require a zipped fixture file (the vendor bill attachment is stored as a ZIP) which is not appropriate to commit.
* A regression test was added in `test_accept_by_customer`: after the full commercial event flow the method is called inside a `try/except etree.XMLSyntaxError` block so that any XML parse failure surfaces as a proper test *failure* rather than an unhandled test *error*.
opw-6417422
Forward-Port-Of: odoo/enterprise#126463This fix prevents Planning from crashing when users drag unscheduled slots onto the calendar after enabling scheduling options. It restores the expected scheduling information and handles missing dates safely, so planners can continue assigning work without interruption.
Original PR description
Steps to reproduce: ------------------------- 1. Install `sale_planning` and `web_studio` with demo data. 2. Open the Planning calendar view. 3. Enable the "Scheduling" option from Studio and close…
Steps to reproduce:
-------------------------
1. Install `sale_planning` and `web_studio` with demo data.
2. Open the Planning calendar view.
3. Enable the "Scheduling" option from Studio and close it.
4. Drag an unscheduled slot onto the calendar.
Issues:
-----------
**Issue 1:**
```python
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 142, in write
self.assign_slot(vals)
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 159, in assign_slot
new_vals, tmp_sale_order_slots_to_plan, resource = slot._get_sale_order_slots_to_plan(vals, slot_vals_list_per_employee)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 228, in _get_sale_order_slots_to_plan
)._get_resource_work_info(vals, slot_vals_list_per_resource)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 366, in _get_resource_work_info
assert self.env.context.get('default_end_datetime')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
```
**Issue 2:**
```python
UncaughtPromiseError > TypeError
Uncaught Promise > Cannot read properties of undefined (reading 'endOf')
TypeError: Cannot read properties of undefined (reading 'endOf')
```
Cause:
----------
Since commit [1ce0dc8,](https://github.com/odoo/odoo/commit/1ce0dc86f8ae52b21a5a889aacd1efce0e27c722) the scheduling/unscheduling logic has been moved to the generic calendar implementation. However, the generic scheduling flow does not provide the `default_end_datetime` context required by sale_planning. As a result, sale_planning raises an `AssertionError` while scheduling a slot.
Additionally, when no date is available, attempting to call `endOf()` raises a `TypeError`.
Solution:
------------
Introduce a generic scheduling context hook in the calendar model and override it in Planning to provide the `default_end_datetime context when scheduling a slot.
This restores the context expected by` sale_planning`, prevents the `AssertionError`, and avoids calling `endOf()` on an undefined date to resolve `TypeError`.
**Note:**
This issue has already been resolved in the later versions (saas-19.4) as part of the scheduling/unscheduling refactoring. This commit backports the minimal changes required to fix the issue in this version.
References: f82d073 & https://github.com/odoo-dev/odoo/commit/f0f7b342895734d2151c0c106940006e37a5fd86
Related community pr: https://github.com/odoo/odoo/pull/281178
opw-6442889
Forward-Port-Of: odoo/enterprise#127196Projects linked to both standard sales orders and rental orders now show the complete list when users open the Sales button. This keeps the list aligned with the displayed total, preventing rental revenue documents from being overlooked.
Original PR description
Steps to Reproduce --- 1. Install sale_renting_project. 2. Create a Project linked to 1 standard Sales Order and 1 Rental Order. 3. Observe the "Sales" stat button counts 2 Sales. 4. Click the stat button. Only the standard Sales Order is displayed. Issue --- In saas-18.4, the project Sales stat button calls action_view_sos without the from_embedded_action context key. As a result, _get_sale_orders_domain applies the non-rental filter by default, causing rental orders to be excluded from the action even though they are included in the displayed counter. Expected Behavior --- The Sales stat button should display all orders linked to the project, including both standard and rental orders, matching its total counter. Fix --- Return the base project domain unmodified when from_embedded_action is not set in the context. task-6140201 Forward-Port-Of: odoo/enterprise#128468 Forward-Port-Of: odoo/enterprise#121449