Friday, September 18, 2026
20 changes · 19.0
Resolved issues and error corrections
Fixed an issue where sending a Peppol invoice with a blank invoice line could fail with a technical error instead of showing the normal validation message. Users now receive a clear warning about missing item information, helping them correct the invoice and continue the sending process.
Original PR description
### Steps to Reproduce: 1. Activate Peppol 2. Create invoice for customer with Peppol 3. Add line without product name or label 4. Send invoice through Peppol and observe the Traceback error ###…
### Steps to Reproduce: 1. Activate Peppol 2. Create invoice for customer with Peppol 3. Add line without product name or label 4. Send invoice through Peppol and observe the Traceback error ### Issue: When generating a Peppol invoice, there will be a Traceback error if an invoice line is missing both a product and a label. The XML builder evaluates the missing `cbc:Name` element as `None` instead of an empty dictionary. Therefore, attempting to directly subscript `['_text']` on it throws a TypeError traceback. This prevents the user from seeing the standard validation warning about the missing item data. ### Solution: We can update the document node constraint check to safely access the dictionary keys using `.get()` with an empty dictionary fallback. This prevents the traceback and ensures that the system will correctly display the intended validation error to the user. opw-6577441 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#288713
Fixes an issue where dropship valuation entries could count the same analytic allocation twice when a sale and purchase were linked to the same stock move. This keeps cost reporting accurate for dropshipped sales that use analytic distribution rules.
Original PR description
Steps to reproduce: - Set the product category to Automated (perpetual) Inventory Valuation. - Set up an Analytic Distribution Model for a vendor. - Confirm a sale order linked to a project, with a…
Steps to reproduce: - Set the product category to Automated (perpetual) Inventory Valuation. - Set up an Analytic Distribution Model for a vendor. - Confirm a sale order linked to a project, with a line using the Dropshipping route for that vendor, then validate the dropship delivery. Cause of the issue: A dropship move is weird: the same stock move is linked to both a purchase line and a sale line at once (one move covers both sides). When we build the stock valuation entry for that move, two different modules each add their own analytic distribution on top: - purchase_stock adds the purchase line's distribution (vendor's ADM, already combined with the project account by project_purchase) - sale_stock also adds the sale line's distribution, which already has the project account in it too Both just do a plain dict union, no normalization. On a normal delivery or a normal receipt only one of these ever kicks in, but on a dropship move both fire on the same line, so the project account's share gets counted twice (100% + 100% = 200%). Solution: Don't fall back to the sale line's distribution when the move already has a purchase line attached (that's the dropship case) — the purchase line's distribution is already the full, correct one for that move. The test uses two Analytic Distribution Models (one on the vendor, one on the customer) instead of the project app, since stock_dropshipping doesn't depend on project. opw-6383779 Forward-Port-Of: odoo/odoo#282751
This update refreshes Odoo's spreadsheet component and fixes an issue where pivot table running totals could be incorrect when some dimensions were collapsed or hidden. It also improves spreadsheet demo behavior, helping ensure more reliable spreadsheet reporting and examples for users.
Original PR description
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/493acaf76d [REL] 19.0.51 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0)…
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/493acaf76d [REL] 19.0.51 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0) https://github.com/odoo/o-spreadsheet/commit/dbe0b34980 [IMP] demo: debounce xml template build [Task: 6574005](https://www.odoo.com/odoo/2328/tasks/6574005) https://github.com/odoo/o-spreadsheet/commit/54b5a6f9a5 [FIX] demo: fix scorecard demo definition [Task: 6573109](https://www.odoo.com/odoo/2328/tasks/6573109) https://github.com/odoo/o-spreadsheet/commit/fbfc45aaaa [FIX] pivot: wrong running total with collapsed/hidden dimensions [Task: 6569607](https://www.odoo.com/odoo/2328/tasks/6569607) Co-authored-by: Florian Damhaut (flda) <flda@odoo.com> Co-authored-by: Anthony Hendrickx (anhe) <anhe@odoo.com> Co-authored-by: Alexis Lacroix (laa) <laa@odoo.com> Co-authored-by: Lucas Lefèvre (lul) <lul@odoo.com> Co-authored-by: Adrien Minne (adrm) <adrm@odoo.com> Co-authored-by: Ronak Mukeshbhai Bharadiya (rmbh) <rmbh@odoo.com> Co-authored-by: Dhrutik Patel (dhrp) <dhrp@odoo.com> Co-authored-by: Rémi Rahir (rar) <rar@odoo.com> Co-authored-by: Pierre Rousseau (pro) <pro@odoo.com> Co-authored-by: Vincent Schippefilt (vsc) <vsc@odoo.com> Co-authored-by: Marceline Thomas (matho) <matho@odoo.com>
Invoice imports with fixed taxes now better handle small rounding differences when quantities are greater than one. This prevents valid fixed taxes from being missed during import, reducing incorrect tax matching issues and manual corrections.
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#271842
This fixes an issue where migrated databases could show an error when a confirmed purchase order line had only its unit price changed. Updating the purchase email template data ensures purchase order changes can be processed normally without template rendering failures.
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
Italian Split Payment taxes are now configured so VAT report amounts are calculated on the correct base values and shown with the correct sign. Invoice tax labels now clearly identify Split Payment rates, reducing reporting mistakes and customer confusion.
Original PR description
The`SP` (Split Payment) taxes have incorrect configurations: - **Tax Grid Tags on `SP` Taxes**: The ve38 grid tag was incorrectly assigned to the tax line instead of the base line. - **`VE38` VAT…
The`SP` (Split Payment) taxes have incorrect configurations: - **Tax Grid Tags on `SP` Taxes**: The ve38 grid tag was incorrectly assigned to the tax line instead of the base line. - **`VE38` VAT Report Formula**: In Tax Report > VAT Report, the line `VE38 - Transactions with parties referred to in Article 17-ter` was using a negative formula `-ve38` and should be `ve38` - **Tax Group Invoice Label**: Updated the default label on invoices for the SP tax group to display 22% SP instead of 22%. Steps to reproduce: - Install `account` and `l10n_it` - Switch to IT company - Go to taxes and filter for `SP` taxes - The tax grid is wrong for the negative taxes since `ve38` should be in the base line instead of in the tax line - Go in the Tax report > VE VAT Report - The line `VE38 - Transactions with parties referred to in Article 17-ter` has a negative formula `-ve38`, should be positive `ve38` - The label on invoices of the tax group should be `4% SP`, `5% SP`, `10% SP` not `4%` etc. References: https://www.informazionefiscale.it/IMG/pdf/dichiarazione_modello_iva_2026_agenzia_delle_entrate.pdf <img width="1413" height="141" alt="immagine" src="https://github.com/user-attachments/assets/7fa15859-beaa-4345-bf81-fedc3f0af2fa" /> https://fiscomania.com/quadro-ve-della-dichiarazione-iva/ <img width="705" height="154" alt="immagine" src="https://github.com/user-attachments/assets/4f23b407-0e4d-4104-9695-f9891ccfd2f2" /> Ticket [link](https://www.odoo.com/odoo/project.task/6543520) opw-6543520
This fix prevents manufacturing costs in one company from accidentally using purchase cost history from another company. It improves the accuracy of FIFO product costing in multi-company setups and helps keep each company's inventory valuation separate.
Original PR description
**Problem**: In a multi-company environment, while manufacturing a product, if the component of the product is visible for both company and there is no last_in stock move for the component in the current company, then the last_in stock move of the other company is used to compute the cost of the component. This only happens when the costing method of the product is FIFO **Steps to reproduce:** 1. Create company A and company B. 1. Create a product A and set FIFO costing method to it. 2. Make a purchase order of product A in company A and receive it. 3. Create a product B with product A as its BOM material. 4. Create a MO of product B in company B and produce it. 5. The unit cost of product A in company B is using the unit cost from the purchase order of product A in company A. **Fix**: Add a company domain to the last_in stock move search to prevent cross-company last_in stock move search. opw-6411216
Fixes an issue in the HTML editor where the font size shown in the toolbar could remain outdated after removing formatting. This makes editing clearer and prevents unnecessary formatting markup when users change font sizes on headings or default text styles.
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
Leave for flexible employees now correctly marks every full day as unavailable, including the first and last days. This prevents planning and attendance views from showing partial or missing leave days, giving managers a more accurate view of employee availability.
Original PR description
### Steps to reproduce: - Create a flexible employee - Create a leave from a Monday to a Friday for this employee - Navigate to Planning gantt view and check the week where the created leave exists - Notice only 3 days are greyed-out ### Cause: This is happening because in planning we normally exclude days that are not fully unavailable and since flexible doesn't have a specific start and end hours for the working days the start and end days for the flexible leaves are excluded from the unavailabiliy. This is also happening in `hr_attendance` for example but in a different way where the start and end days of the leave will be shown half greyed-out ### Fix: For _unavailable_intervals_batch we make flexible leaves cover the whole days of the leave opw-6142094 Forward-Port-Of: odoo/odoo#261597
On mobile checkout, the order summary will no longer stay fixed over address fields when shoppers open the on-screen keyboard. This makes it easier for Android users to complete checkout 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
Customers' credit usage now includes confirmed sales orders even when products have not yet been delivered. This prevents additional orders from being accepted without a warning when the customer has 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#285578Bank synchronization no longer removes payment options that users already configured on a bank journal. This prevents custom incoming and outgoing payment setup from being lost when connecting a bank feed, while still adding any missing default options.
Original PR description
**Steps to reproduce:** - Install Accounting - Configure "Bank" journal: * Add several incoming payments * Add several outgoing payments - From Accounting dashboard, connect Bank (e.g. Odoo Bank Sync Demo) **Issue:** After bank sync, all the incoming/outgoing payments added on the Bank journal are removed. Only the default ones are re-created. **Solution:** Keep all the existing incoming/outgoing payments (i.e. payment method lines) and only create the default ones for the payment method types that don't exist. opw-6424407 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281658
Fixed an issue where opening customer orders in Point of Sale could show a blank screen when an order belonged to a delivery address without its own name. The system now uses the related company name as a fallback, so staff can still find and view those orders normally.
Original PR description
Steps to reproduce: - Create a company contact with a delivery address that has no name - Make a PoS order for the delivery address and pay it - Open the PoS, open the customer list, and click…
Steps to reproduce: - Create a company contact with a delivery address that has no name - Make a PoS order for the delivery address and pay it - Open the PoS, open the customer list, and click "Orders" on the company Issue: The screen goes blank. Cause: The ticket screen is opened with the company name as search term. The server domain searches on `partner_id.complete_name`, so the orders of the address contact are returned too. They are then fuzzy matched on `getPartnerName()`, which returns `false` for a partner without a name, and `fuzzyLookup` calls a string method on it. The error is raised during rendering, so the whole app is destroyed. Fix: Make `getPartnerName()` always return a string, and fall back on the parent name, like the complete name does. This way the orders of the address contact are still listed when searching on the company name, instead of being filtered out by an empty name. opw-6578541 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fix prevents appointment resources from being treated as having negative available capacity when bookings are made from the Gantt view. It helps avoid failed website bookings and keeps capacity handling consistent for shareable appointment resources.
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#130520Mexican electronic payment records now use the payment method configured on the selected bank journal instead of always defaulting to bank transfer. This helps ensure payment complements sent to the tax authority reflect the actual payment method, reducing manual corrections and compliance risk.
Original PR description
Currently, payment way configured on a bank journal is not taken into account, payments will default to "03 Transferencia electrónica de fondos". Steps to reproduce: - Set the "Payment Way" of a bank journal to "04 Tarjeta de Crédito". - Manually create a customer payment in that journal - Look at the "Payment Way" of the payment, then send the payment complement (REP) to the SAT. Issue: The payment way is "03 Transferencia electrónica de fondos" and the REP carries FormaDePagoP="03" The one set on the journal is ignored. Analysis: We should evaluate the journal before the transferencia default so a payment inherits the payment way, and keep transferencia as the last possible value. opw-6493514 Forward-Port-Of: odoo/enterprise#130863 Forward-Port-Of: odoo/enterprise#130133
Flexible employee leave now blocks the full duration in Planning, including the first and last day. This prevents managers from seeing employees as partially available when they are actually on leave, improving schedule accuracy.
Original PR description
### Steps to reproduce: - Create a flexible employee - Create a leave from a Monday to a Friday for this employee - Navigate to Planning gantt view and check the week where the created leave exists - Notice only 3 days are greyed-out ### Cause: This is happening because in planning we normally exclude days that are not fully unavailable and since flexible doesn't have a specific start and end hours for the working days the start and end days for the flexible leaves are excluded from the unavailability. This is also happening in `hr_attendance` for example but in a different way where the start and end days of the leave will be shown half greyed-out ### Fix: For _unavailable_intervals_batch we make flexible leaves cover the whole days of the leave opw-6142094 Forward-Port-Of: odoo/enterprise#115485
The Ecuadorian ATS tax export now consolidates transactions from a company and its branches when they share the same RUC. This ensures the exported XML matches the consolidated tax return totals and avoids missing branch sales or tax data.
Original PR description
In Ecuador, a company and its branches file under a single RUC, so the ATS is expected to consolidate all of them. However, currently the system exports only the active company report. Steps to…
In Ecuador, a company and its branches file under a single RUC, so the ATS is expected to consolidate all of them. However, currently the system exports only the active company report. Steps to reproduce: - Install the Ecuadorian localization - Create a parent company and a branch, assign the RUC of the parent to the branch, and set the legal name of the branch in Settings - Post a customer invoice with taxes in the parent (e.g. 750) and another one in the branch (e.g. 250) - Activate both companies in the company selector - Go to Accounting > Reporting > Tax return - Select "Report: 104 (EC)", the dashboard shows the consolidated values - Click on the gear icon next to "Tax Return" and select ATS Issue: The exported XML only contains the documents and the totals of the parent company (750). The branch (250) is omitted. Analysis: Currently the ATS export use self.env.company for all searches, which only retrieve the data of the first company set opw-6469807 Forward-Port-Of: odoo/enterprise#128430
Users with attendance access can now open the Attendances Gantt even when it includes a colleague's approved time off on a flexible schedule. This prevents an access error while still only showing the unavailability information already needed for the schedule view.
Original PR description
### Problem Attendances Gantt raises an AccessError for a user who can see attendances but cannot read a colleague's time off, as soon as the window covers a day off of a flexible-schedule employee. ### Cause `_handle_flexible_leave_interval` reads the linked `hr.leave` (request unit, half-day period, hours) to size the grayed-out interval. It reads it as the current user, so the `hr.leave` record rule blocks any leave the user does not for the same reason; the `resource_calendar` override (commit 8e12ae9486) missed it. ### Fix Read `holiday_id` through sudo. Those values size the unavailability interval the Gantt shows to this user regardless, so sudo exposes no restricted data. `hr_holidays_gantt/models/hr_leave.py` sudoes the same fields for this reason; the `resource_calendar` override (commit 8e12ae9486) missed it.
Fixed an issue where planning shifts created from overnight templates could incorrectly span one additional day. This helps schedules reflect the intended shift duration, avoiding confusion and incorrect staffing plans.
Original PR description
This is a backport of this [commit](https://github.com/odoo/enterprise/commit/c02b8eccd72758127cc7cdc51ab8ec461a2ec45e) Issue: ---------------------------------------- Creating a night shift from a…
This is a backport of this [commit](https://github.com/odoo/enterprise/commit/c02b8eccd72758127cc7cdc51ab8ec461a2ec45e) Issue: ---------------------------------------- Creating a night shift from a template produces a shift spanning over one additional day. Steps to reproduce: ---------------------------------------- - Create a planning shift template form 23h to 1h the next day (2h) - It must have a span over 2 working days - Create a shift and use this template - The shift spans over one more day Cause: ---------------------------------------- In `_calculate_start_end_dates()`, we call `plan_days()` with `start` having the hours specified. So in `plan_days()` when retrieving the worked days, the first day is ignored because the resource is not supposed to be working from 23h to 1h. Then we count two days and so the end date is offset by one day. Solution: ---------------------------------------- We should call `plan_days()` without the hour specified so we make sure the first day is included in the count. opw-6535758 Forward-Port-Of: odoo/enterprise#131140
AI live chat now only shows the option to ask for a human when a human operator is actually available. This prevents visitors from clicking a support option that can only lead to an unavailable-agent message, creating a clearer chat experience.
Original PR description
## Issue In `ai_livechat`, the "Ask a Human" button shown below the chat composer when an AI agent is the operator does not check whether a human operator is actually available on the livechat…
## Issue In `ai_livechat`, the "Ask a Human" button shown below the chat composer when an AI agent is the operator does not check whether a human operator is actually available on the livechat channel. With a channel configured for AI-only (no users assigned, rule with `chatbot_enabled_condition = only_if_no_operator` and an `ai_agent_id`), clicking the button always returns the notification *"There is no human agent available at the moment. Please try again later."* — a dead-end UX. ## Steps to reproduce 1. Create an `im_livechat.channel` with no users assigned. 2. Add a rule on that channel with: - `chatbot_enabled_condition = only_if_no_operator` - `ai_agent_id` set (any non-system AI agent). 3. Open the public livechat URL as a visitor. 4. Observe the *Ask a Human* button below the composer; clicking it produces the no-human-available warning. ## Cause `ChatWindow.showForwardOperatorButton` (in `ai_livechat/static/src/discuss/core/common/chat_window_patch.js`) only checks `thread.channel_type === 'livechat' && thread.ai_agent_id`. It does not consider whether the parent livechat channel currently has any available human operator. ## Fix - Expose a new `ai_livechat_has_human_operator` attribute on the `discuss.channel` Store payload via `Store.Attr`. The value is computed on the fly from the already-existing `im_livechat.channel.available_operator_ids` field — **no new stored field is introduced** (relevant for stable). The attribute is only emitted for livechat channels that have an AI agent set, so the extra cost is paid only by AI-backed sessions. - The web client hides the *Ask a Human* button when this flag is `false`. When the attribute is missing (older payloads or non-AI threads), behavior is unchanged. ## After the fix The *Ask a Human* button is hidden when the livechat channel has no available human operator, matching the "pure AI livechat" setup intent. Configurations with operators behind the AI continue to show the fallback button as before. ## Tests `ai_livechat/tests/test_im_livechat_channel.py::test_store_exposes_human_operator_availability_for_ai_livechat` covers the Store payload for an AI-backed livechat session without operators.