Monday, September 21, 2026
17 changes · saas-19.2
Resolved issues and error corrections
The Source PO smart button now appears only when a picking is a true resupply for a subcontracted purchase order. This prevents unrelated purchase receipts from showing misleading links, helping users identify the right purchasing document in subcontracting flows.
Original PR description
After a recent change, the smart button for Source POs uses the reference_ids on a stock.picking. This altered the logic to fetch all orders from any picking that has the same reference. However,…
After a recent change, the smart button for Source POs uses the reference_ids on a stock.picking. This altered the logic to fetch all orders from any picking that has the same reference. However, `subcontracting_source_purchase_count` should specifically relate to the resupply of subcontractor picking to their subcontracted purchase order. **Steps to Reproduce:** 1. Enable multi-step routes and subcontracting 2. Unarchive the MTO route 3. Create 4 products Finished Product, A, B, C, and set MTO on both A and B 4. Put 10 units of C in stock 5. Create a BoM for Finished Product using A and B as components 6. Create a subcontracted BoM for B using C as a component 7. Set the purchase vendor for product A as the vendor 8. Set the purchase vendor for product B as the subcontractor 9. Create and confirm a manufacturing order for Finished Product 10. Confirm the POs The two POs are: 1. Purchase Product A from its vendor 2. Subcontract Product B from its subcontractor and use Product C as a component **Current Functionality:** - Product A's receipt has a **Source PO** smart button pointing to the subcontracting PO for Product B (BUG) - Product B's receipt has a **Source PO** smart button pointing to its own PO (BUG) - Product C's picking has a **Source PO** smart button pointing to its own PO **New functionality:** - Product A's receipt has no **Source PO** smart button - Product B's receipt has no **Source PO** smart button - Product C's receipt has a **Source PO** smart button pointing to its own PO opw-6420168 Forward-Port-Of: odoo/odoo#278908
Fixes an error that could interrupt website setup when enabling an online store through the configurator. This helps users complete website and eCommerce setup smoothly without encountering a technical crash during demo data loading.
Original PR description
Since [1], the registry is loaded without a request context, so a module installation now goes through the fallback of `get_current_website` that reads the url stored on the current thread. That…
Since [1], the registry is loaded without a request context, so a module installation now goes through the fallback of `get_current_website` that reads the url stored on the current thread. That value is a full url, not the `domain:port` the method expects, so IDNA-encoding it splits it on the dots of the path rather than on those of a host name and raises `UnicodeError` as soon as one of the resulting parts reaches 64 characters. The thread url is now reduced to its `domain:port` part. Steps to reproduce: - Start the server locally on a database with demo data, served on `http://localhost:8069`, with only `Website` installed - Create a new website - In the configurator, choose `I want an online store` - Go through each step - After the last step, a traceback appears while loading the accounting demo data: the configurator should apply without error [1]: https://github.com/odoo/odoo/commit/1ae80f9fb18c9c9e67fe0367b94f0d882c711570 Also reported here: https://github.com/odoo/odoo/issues/286432 Forward-Port-Of: odoo/odoo#288931
Fixed an issue where settling certain made-to-order sales orders in Point of Sale could fail at payment validation. This helps retailers complete checkout reliably when products require manufacturing before delivery.
Original PR description
Step to reproduce: ------------------ - Install `pos_sale_stock` and `mrp` module. - Go to the setting enable "Replenish on Order (MTO)" - Create a storable product with the MTO route - Create a Bill…
Step to reproduce:
------------------
- Install `pos_sale_stock` and `mrp` module.
- Go to the setting enable "Replenish on Order (MTO)"
- Create a storable product with the MTO route
- Create a Bill of Materials for the product with a storable component
- Now Create Sale order for the Product and confirm it.
- Open POS Store and settel Created sale order.
- Process toward payment and try to validate it.
Issue:
------
`AssertionError: Invalid falsy real id`
Cause:
------
When an MTO product with a manufacturing Bill of Materials is added to a Sale Order and the SO is confirmed, Odoo creates two distinct sets of stock.move records that share the same stock.reference group:
1. **Delivery moves** (picking_id → stock.picking) Created by the outgoing stock rule triggered by the MTO route. These moves are assigned to a delivery picking and have a valid picking_id.
2. **Manufacturing component moves** (picking_id = False) Created by the Manufacture route's procurement rule, which spawns an mrp.production. The raw-material moves inside an MO belong to the production order, not to any stock.picking. Their picking_id field is intentionally False by design.
Both sets of moves are linked to the same stock.reference record through the stock_reference_move_rel many2many table. This means that traversing:
so_line.move_ids → delivery move(s)
.reference_ids → shared stock.reference
.move_ids → ALL moves in the group (delivery + MRP)
every move in the reference group, including MRP component moves whose picking_id is False.
- Then In PosOrder.sync_from_ui(), the code collected the IDs of all related pickings into a set for later cancellation:
https://github.com/odoo/odoo/blob/a0cdf9c33ed464b4ec8bd520e4d0523afb8dbd28/addons/pos_sale/models/pos_order.py#L111
https://github.com/odoo/odoo/blob/a0cdf9c33ed464b4ec8bd520e4d0523afb8dbd28/addons/pos_sale/models/pos_order.py#L130-L133
waiting_picking_ids.add(move.picking_id.id) # ← adds False for MRP moves
Because MRP moves pass the state filter ('confirmed') but have picking_id = False, `move.picking_id.id` evaluates to False (the empty recordset's falsy id), which was silently added to the waiting_picking_ids set.
Issue occur from this [commit](https://github.com/odoo/odoo/commit/515eb5ba0892a759dde32a9b2923d8ecc1e4bf74?debug=1), the ORM assertion in browse() rejects falsy IDs:
https://github.com/odoo/odoo/blob/a0cdf9c33ed464b4ec8bd520e4d0523afb8dbd28/addons/pos_sale/models/pos_order.py#L139
https://github.com/odoo/odoo/blob/9e02922f821a3b4e891e90bf9bd18448a08e66fa/odoo/orm/models.py#L5297
Fix:
---
Add guards in `sync_from_ui()`
*Inner filtered() guard* — add `m.picking_id` as the first condition so
that MRP component moves (picking_id = False) are never iterated, preventing
`False` from ever entering the set
---
opw-6486681
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-prInvoice imports now better handle fixed taxes when quantities are greater than one and tiny rounding differences occur. This prevents valid fixed taxes from being missed during import, reducing manual corrections and improving accuracy for electronic invoices.
Original PR description
When importing an invoice with fixed taxes, if the line has a quantity of more than 1, sometimes, due to rounding errors, searching for the fixed tax fails to find it, as the values needed to exactly match, a new search method was added to give a margin of +/- 0.01 when searching for valid taxes. task-id-6307992 Forward-Port-Of: odoo/odoo#287948 Forward-Port-Of: odoo/odoo#271842
This fix ensures Irish balance sheet reports place current-year profit or loss in the correct section only. It prevents the same amount from appearing both as brought-forward profit and current-year profit, improving accuracy for Irish accounting reports.
Original PR description
Scenario: - install l10n_ie and switch to a company with irish accounting - create and validate a 2025 customer invoice with one line and value 50 - go to the balance sheet report and check values of year 2025 Result: the 50 amount is present in both "H.V. Profit or loss brought forward" and "H.VI. Profit or loss for the financial year" while it should only be present in "H.VI." Cause: Start of december 2025 e699a14a1922d6b8a5bc6b88ad9c02a411e4b073 was merged that requires adding force_date_scope in some case. End of december 2025 597f25a4faff914504d1dfa021e9839e39ece937 was merged that added a new balance sheet report but didn't take into account the recent change for the force_date_scope parameter. Fix: add the missing force_date_scope parameters. opw-6425317
The Timesheet Grid now only uses public holidays from the company currently being viewed. This prevents days from being incorrectly greyed out because of holidays configured in another company, making timesheet planning clearer for multi-company users.
Original PR description
Issue: - Public holidays from other companies were incorrectly included in the Timesheet Grid view of the current company. As a result, days corresponding to those holidays appeared greyed out.…
Issue: - Public holidays from other companies were incorrectly included in the Timesheet Grid view of the current company. As a result, days corresponding to those holidays appeared greyed out. Cause: - The `grid_unavailability` method relies on the `_get_valid_work_intervals` function to fetch all unavailability data at once. When fetching data for multiple employees, this function also retrieves public holidays from all companies. Fix: - The method no longer uses the data returned by `_get_valid_work_intervals` for company unavailable days. Instead, it now always makes a separate, direct call via the `get_company_unavailable_dates()` function. This ensures that only holidays relevant to the current company are considered in the grid view. The company is also passed in the domain of `_work_intervals_batch`, which otherwise returns the leaves of every company when no resource is given. Steps to reproduce: - 1. Create Company A and Company B. 2. In Company B, create a public holiday on Tuesday. 3. Switch back to Company A. 4. Open the All Timesheets Grid view from Company A. Expected behavior: - - The grid column for Tuesday should not be grey for Company A users. Current behavior: - - The grid column for Tuesday is grey, incorrectly showing it as a time-off day. task:4492966 Forward-Port-Of: odoo/enterprise#132030 Forward-Port-Of: odoo/enterprise#88495
Appointment bookings made from the backend Gantt view now avoid creating negative resource capacity values when shared resources are involved. This prevents website appointment bookings from failing after a resource was accidentally overbooked, improving booking reliability for staff and customers.
Original PR description
**Steps to reproduce:** - Install Appointment app - Create an appointment type based on resources, auto-assigned, with two shareable resources linked together - Set the first resource's capacity to 3…
**Steps to reproduce:**
- Install Appointment app
- Create an appointment type based on resources, auto-assigned, with two shareable resources linked together
- Set the first resource's capacity to 3 and the second resource's capacity to 4
- From the backend Gantt view, create a booking for 2 people on the first resource
- Create a second booking for 2 people on the same resource, at the same date and time
- First resource is now overbooked with reserved capacity of 4 out of 3
- Try to create an appointment from the website
- Error: "The capacity reserved should be positive."
**Issue:**
When bookings are created from the backend gantt view, the selected resource can be overbooked even if another linked resource still has available capacity.
Then when trying to book an appointment the new booking lines will trigger this constraint:
```py
_check_capacity_reserved = models.Constraint(
'CHECK(capacity_reserved >= 0)',
"The capacity reserved should be positive.",
)
```
This is caused by the negative values in:
```py
booking_line_values = []
if appointment_type.schedule_based_on == 'resources':
capacity_to_assign = asked_capacity
for resource in resources:
resource_remaining_capacity = resources_remaining_capacity.get(resource)
new_capacity_reserved = min(resource_remaining_capacity, capacity_to_assign, resource.capacity)
capacity_to_assign -= new_capacity_reserved
booking_line_values.append({
'appointment_resource_id': resource.id,
'capacity_reserved': new_capacity_reserved,
'capacity_used': new_capacity_reserved if resource.shareable and appointment_type.resource_manage_capacity else resource.capacity,
})
```
**Fix:**
Avoid negative remaining value in resource booking when computing available slots.
Note: Tried to take the capacity already used by overlapping bookings into account when assigning resource booking lines from the backend gantt view. And also force linked_resources booking when trying to book more
than the total capacity to properly dispatch as many slots as possible. But it was breaking `appointment_google_reserve` tests.
opw-6503147
Forward-Port-Of: odoo/enterprise#131752
Forward-Port-Of: odoo/enterprise#130520This fix ensures AvaTax taxes get the correct accounting accounts when a new US company is created without demo data. It prevents invoices using AvaTax from generating tax lines with missing accounts, reducing accounting setup errors for affected companies.
Original PR description
Steps to reproduce: - Create a DB and Install `Accounting` without demo data - In Companies > Create New Company with `US` Localization - Switch to US Company > Add `Avatax` Credentials - Create a…
Steps to reproduce: - Create a DB and Install `Accounting` without demo data - In Companies > Create New Company with `US` Localization - Switch to US Company > Add `Avatax` Credentials - Create a Product with `Avatax Category` - Create a Invoice > In Other Info `Fiscal Position` - `Automatic Tax Mapping (AvaTax)` > `Compute Taxes` - In Taxes check newly created tax does not have a account ID Issue: AvaTax fiscal position created from chart templates have empty `avatax_invoice_account_id` and `avatax_refund_account_id` fields on a fresh database without demo data. Problem: Both fields use [defaults] based on `company.account_sale_tax_id`. During initial CoA loading, `_load_data()` creates the AvaTax fiscal position before `_post_load_data()` sets `company.account_sale_tax_id`. The defaults therefore resolve to an empty recordset and no tax account is assigned. This issue does not occur in databases with demo data because the demo company is already loaded, so `company.account_sale_tax_id` is already set when the AvaTax fiscal position is created. Solution: Set the invoice and refund accounts explicitly in the AvaTax fiscal position template to ensure they are correctly assigned during CoA loading. [defaults]: https://github.com/odoo/enterprise/blob/de17c107651394025e9a3d5cbed8d81bcfc2e76d/account_avatax/models/account_fiscal_position.py#L9-L13 opw-6347128 Forward-Port-Of: odoo/enterprise#132005 Forward-Port-Of: odoo/enterprise#126604
The online shop now blocks combo products from being added to the cart unless every required choice has been selected. This prevents customers from checking out with incomplete combo orders, improving order accuracy and reducing fulfillment issues.
Original PR description
Steps to reproduce: =================== 1. Publish a combo product whose choices each offer more than one item, so the combo configurator is shown. 2. On the shop, open the product and click "Add to…
Steps to reproduce: =================== 1. Publish a combo product whose choices each offer more than one item, so the combo configurator is shown. 2. On the shop, open the product and click "Add to Cart". 3. Select an item for one combo choice only. The dialog's "Add to cart" button stays disabled. 4. Remove the `disabled` attribute from that button with the browser developer tools and click it. => The combo is added to the cart with one of its choices unanswered, and the order can be paid in that state. Root cause: =========== The incomplete selection is only prevented on the client side, by disabling the button until every choice has been answered. Server side, `/website_sale/combo_configurator/update_cart` only rejects a completely empty selection, never that the selected items cover every choice. Fix: ==== Reject the request unless the selected combo items cover exactly the combo choices of the product. Comparing the set of choices rather than counting the items also rejects a selection answering the same choice twice while leaving another one unanswered. opw-6478837 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286989 Forward-Port-Of: odoo/odoo#283465
Products with multiple variants are now marked sold out only when every variant is unavailable. This keeps the add-to-cart option visible when at least one variant can still be purchased, preventing missed sales caused by variant ordering.
Original PR description
Steps to reproduce: =================== 1. Create a storable product with two variants and untick "Continue selling when out-of-stock". 2. Leave the first variant at 0 in stock, put some stock on the…
Steps to reproduce: =================== 1. Create a storable product with two variants and untick "Continue selling when out-of-stock". 2. Leave the first variant at 0 in stock, put some stock on the second one. 3. try the "Add to Cart" option of the shop page. => The product has no add to cart button, as if it were sold out, while its second variant can be bought. Reordering the variants so that the one in stock comes first brings the button back. Root cause: =========== `product.template._is_sold_out()` delegates to `product_variant_id`, which is `product_variant_ids[:1]`, so a whole template is declared sold out on the sole basis of its first variant. `_website_show_quick_add()` then hides the button for the template, whatever the stock of the other variants. The helper has looked at that single variant since it was written: - [1] added it to hide the button of sold out products. Fix: ==== Consider the template sold out only when all of its variants are. The check stops at the first variant in stock, so a product that can be bought still costs a single stock lookup. [1]: https://github.com/odoo/odoo/commit/4ae197cc770cec2058e1f113209c4f93a4a0f4b2 opw-6520052 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287882 Forward-Port-Of: odoo/odoo#286781
Employees without HR access can now see one-off work location changes in the calendar, just like recurring work locations. This prevents missing or misleading location information when teams coordinate schedules.
Original PR description
**Steps to reproduce** - With a user having HR rights, create an exceptional work location for a user (click on the top bar of one of the days in the calendar, where work locations are displayed, and do not check "repeat every". - Open the calendar app with a user having no HR rights, in the sidebar, add the user with a non-recurrent work location. Notice that the work location is not visible, unlike recurring ones. **Cause** Recurring work locations are defined on the public employee (`*_location_id` type fields) and are readable by all users. Non-recurring work locations are `hr.employee.location` records and the `homeworking_own_rule` rule restricts read operations for non-HR users. opw-6190464 Forward-Port-Of: odoo/odoo#266380
This change ensures purchase order email templates are updated correctly during upgrades. It prevents an error that could appear after migration when a confirmed purchase order line has its price changed without changing the quantity.
Original PR description
In this commit: eadf127 the `track_po_line_template` template was added in a `noupdate` file. The commit specifies: “put their declaration in no update when not done if template has no technical code or complex dependency on underlying code;” However, this is not actually the case for the two templates in this file. This did not cause any error in v17, but errors started appearing later because of #254602, which modified this code and the related Python code, leading to an error when no product quantity is changed. Steps to reproduce in a 19.3 database migrated from v19: - Create a purchase order with one product line and confirm it. - Only change the unit price of that line. - Error: "Error rendering template: ..." While this can also be considered a migration issue, I think the simpler and more logical solution is to remove the noupdate from this file. opw-6518729 Forward-Port-Of: odoo/odoo#288463
The Time Off Ledger now shows expected hours based on each employee's actual weekday schedule instead of using a weekly average. This makes reports accurate for employees whose working hours vary by day, such as shorter Fridays.
Original PR description
The Time Off Ledger report shows the same "Expected Hours" value for every working day of an employee, even when their working schedule assigns different hours to different weekdays. ### Steps to…
The Time Off Ledger report shows the same "Expected Hours" value for every working day of an employee, even when their working schedule assigns different hours to different weekdays. ### Steps to reproduce: 1) Install hr_holidays_attendance. 2) Create/assign a working schedule where daily hours vary across the week (e.g. Monday-Thursday 8.5h, Friday 5h). 3) Go to Attendance > Reporting > Time Off Ledger, filter on that employee. ### Observed behavior: "Expected Hours" is identical on every day (the schedule's average hours/day), regardless of which weekday the row is for. ### Expected behavior: "Expected Hours" should match the hours actually scheduled for that specific weekday, so a shorter Friday shows less than a full Monday. ### Root cause: `Time off Ledger` is a SQL view. Its `_select()` builds `expected_hours` from `rc.hours_per_day` at [1], i.e. `resource.calendar.hours_per_day`, a field explicitly labelled **"Average Hour per Day"** and is computed via [_get_hours_per_day] as `hours_per_week / days_per_week`, a single flat value for the whole calendar. The per-weekday hours are available in `resource.calendar.attendance` it stores `dayofweek`, `hour_from`/`hour_to` and a computed `duration_hours` per line, and can legitimately differ per weekday (e.g. Monday 8h vs Friday 4h) at [2]. The report's own [_cte_cal_workday()] CTE already reads this table to decide whether a weekday is a working day (`cal_workday`, joined as `cw` in `_from()`), but discarded the actual hours, keeping only `(calendar_id, dayofweek)`. `_select()` then fell back to the calendar-wide average via `rc.hours_per_day` instead of the per-day value. Example: calendar with Monday 8h and Tuesday 4h -> `hours_per_day` averages to 6h; the report showed 6h on both Monday and Tuesday instead of 8h and 4h respectively. [1]- https://github.com/odoo/odoo/blob/5568f6e472e2e53bc2931e744421015b0f0f3550/addons/hr_holidays_attendance/report/hr_leave_attendance_report.py#L256 [_get_hours_per_day]: https://github.com/odoo/odoo/blob/5568f6e472e2e53bc2931e744421015b0f0f3550/addons/resource/models/resource_calendar.py#L703-L707 [2]- https://github.com/odoo/odoo/blob/5568f6e472e2e53bc2931e744421015b0f0f3550/addons/resource/models/resource_calendar_attendance.py#L15-L31 [_cte_cal_workday()]: https://github.com/odoo/odoo/blob/5568f6e472e2e53bc2931e744421015b0f0f3550/addons/hr_holidays_attendance/report/hr_leave_attendance_report.py#L82-L89 ### Fix: `_cte_cal_workday()` now aggregates `SUM(duration_hours)` per `(calendar_id, dayofweek)` from `resource_calendar_attendance`, instead of just selecting the distinct keys. `_select()` reads this per-weekday value (`cw.hours_per_day`) instead of the calendar-wide `rc.hours_per_day` for both `expected_hours` and `difference_hours`. **opw-6425167** Forward-Port-Of: odoo/odoo#280664
The HTML editor now refreshes the font size field after formatting is removed, so users see the actual size applied to their text. It also handles default heading and font size styles more reliably, preventing confusing toolbar states and unnecessary nested formatting.
Original PR description
### Steps To Reproduce: - Add this content in the HTML editor: `<h2 class=display-3-fs>heading 2</h2>` - Select the heading text. - Click Remove Format. - Check the font-size input in the toolbar.…
### Steps To Reproduce: - Add this content in the HTML editor: `<h2 class=display-3-fs>heading 2</h2>` - Select the heading text. - Click Remove Format. - Check the font-size input in the toolbar. ### Description of the issue/feature this PR addresses: - The font-size class was removed from the block element, but the font-size input still displayed the value associated with the removed class. - The toolbar did not show the correct font size for default block classes (like `o_default_font_size`). - Applying a new font size inside default block classes created nested spans instead of splitting them. - The "Remove format" button remained enabled even when selection had no custom formatting (only default block classes). ### Desired behavior after PR is merged: - The font-size input is updated after removing the font-size class and correctly displays the font size of the resulting block element. - Update the `getFontSizeDisplayValue` utility to find correct CSS variable dynamically to also find the font size of default block classes. - Add default font size classes (like `o_default_font_size` and headings) to `format_class_predicates` resource in `font_plugin.js`. This allows them to be split and replaced instead of creating nested spans. task-6321166 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#272023
Fixes a mobile checkout issue where the cart summary could cover address fields when the on-screen keyboard was open on Android. This makes it easier for shoppers to complete checkout on mobile devices without fields being hidden.
Original PR description
The mobile summary element hides the address form on Android Steps to reproduce: 1. Install eCommerce 2. On android (or using browserstack) and without being logged in, go to the eCommerce 3. Add any…
The mobile summary element hides the address form on Android Steps to reproduce: 1. Install eCommerce 2. On android (or using browserstack) and without being logged in, go to the eCommerce 3. Add any product to the cart and go to checkout 4. Click on "Checkout" to enter the address details 5. Open the keyboard by clicking inside the first input 6. The virtual keyboard opens, enter a name and click on "Next" in the virtual keyboard 7. Do this a couple of times: the cart summary element doesn't scroll and the input field is hidden behind it Similar problem happens when you scroll a bit after clicking in the first input: the cart summary element displayed at the bottom is still visible (on top of the virtual keyboard) which hides part of the page and makes it difficult to navigate the page and fill in the details Issue: `sticky-bottom` keeps the element at the bottom of the screen Solution: Remove class `sticky-bottom` from `o_mobile_summary` when we click in one of the input opw-6500503 Forward-Port-Of: odoo/odoo#287464
HR responsible users can now see and approve or refuse time off requests when the time off type requires approval by a Time Off Officer. This removes blocked approval buttons and validation errors, helping assigned HR contacts process employee leave without needing broader Time Off or Employee permissions.
Original PR description
Steps to reproduce: ---------------------------------------- - Have a Time Off Type with approval "By Time Off Officer" - Have two users with employees and without any rights on Employee or Time Off…
Steps to reproduce: ---------------------------------------- - Have a Time Off Type with approval "By Time Off Officer" - Have two users with employees and without any rights on Employee or Time Off - Set the second user as time off approver and Hr responsible on the first one - As the first user create a Leave request with the thime off type of the first step **First issue:** - As the second user, open Time Off > Management > Time Off - The request appear in "Waiting For Me" but we cannot approve it, the buttons aren't there - Same on the form view **Second issue:** - As the second user, open Time Off > Overview - Remove the "My Team" filter to see the leave request - Click on it, then click "Approve" - `Validation Error: You are not allowed to approve this leave request.` Cause: ---------------------------------------- **First issue:** The field `can_approve` is computed through `_check_approval_update()` which calls `_get_next_states_by_state()` to see which states are accessible. In the `if` section of `is_time_off_manager` we prevent the 'hr' validated time off type leaves from being refused and validated. So only Time Off officers can approve or refuse them. **Second issue:** `action_approve()` in `HrLeaveReportCalendar` prevents the aprobation if `leave_validation_type` is not `manager` or `both`. Solution: ---------------------------------------- **First issue:** We also add the possible state `validate` and `refuse` for `hr` validated time off type leaves if the current user is the HR responsible of the employee on the leave. **Second issue:** Also allow `hr` for `leave_validation_type` in `action_approve()` in `HrLeaveReportCalendar`. To make everything work, we also add a condition on `employee_id.hr_responsible_id` in the record rules allowing the HR responsible to see the leaves. And we adapt the `write()` on `hr.employee` and `_clean_leave_responsible_users()` to automatically add/remove HR responsibles from the group `hr_holidays.group_hr_holidays_responsible`. opw-6545508
Credit limit warnings now account for confirmed sales orders even when products have not yet been delivered. This helps businesses avoid approving additional orders for customers who have already committed beyond their credit limit.
Original PR description
### Steps to reproduce Set a credit limit of 1.000 on a customer, then: 1. Create a Sale Order for a product with Invoice Policy on **Delivered Quantities**, over that amount → the warning shows.…
### Steps to reproduce
Set a credit limit of 1.000 on a customer, then:
1. Create a Sale Order for a product with Invoice Policy on **Delivered Quantities**, over that amount → the warning shows. Good.
2. Confirm it, and deliver nothing.
3. Create a second order for the same customer → the warning never shows, even though the customer is already over the limit.
### Solution
The ordered quantity is what the customer committed to, delivered or not. Using `qty_to_invoice` instead of `uom_qty_to_consider` or `qty_delivered`
Introducing invoice_status in sales domain for `_compute_credit_to_invoice`: `untaxed_amount_to_invoice` still follows the invoicing policy, while `amount_to_invoice` no longer does. On a confirmed order for a delivery-based product with nothing delivered:
```
line.untaxed_amount_to_invoice = 0 # still gated by qty_delivered
line.invoice_status = 'no'
order.amount_to_invoice = 2000 # fixed by this PR
```
The domain filters on the first one, so the order is dropped from the search before its amount is ever read and `credit_to_invoice` stays at 0.`'no'` is the stored marker for a confirmed line that is not invoiceable yet, which is exactly what the first clause misses.
ticket: [6480211](https://www.odoo.com/odoo/project/967/tasks/6480211)
Forward-Port-Of: odoo/odoo#288659
Forward-Port-Of: odoo/odoo#285578