Wednesday, August 26, 2026
25 changes · 19.0
Resolved issues and error corrections
This update improves a confusing error shown when sending a French credit note through e-invoicing. Users now receive clearer guidance when EDI document generation cannot proceed, reducing confusion and support effort.
Original PR description
Steps to reproduce: - Install `l10n_fr_pdp` module > Switch to `FR Company` - Activate `French e-invoicing` (Demo mode) - Create a New `Credit Note` with `FR Customer` > Send Issue: The system currently displays a confusing error message during EDI document generation. We are making the error message clearer and more user-friendly. opw-6412521 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284189
Restaurant point-of-sale orders now remember when the number of guests has already been entered on another device. This avoids repeatedly asking staff for the same guest count when multiple terminals access the same table, keeping service smoother and reducing duplicate prompts.
Original PR description
Steps to reproduce: - Enable presets on a restaurant PoS and tick "Amount of Guests" on the preset used for tables - On device A, open a table and enter the number of guests - On device B, open the…
Steps to reproduce: - Enable presets on a restaurant PoS and tick "Amount of Guests" on the preset used for tables - On device A, open a table and enter the number of guests - On device B, open the same table Issue: Device B pops the guest count numpad again, even though the guest count was already entered on device A. Cause: ensureGuestCustomerCount guarded the popup on order.uiState.guestSetted. uiState is only serialized to IndexedDB (SERIALIZED_UI_STATE_PROP, used by serializeForIndexedDB); it is never sent to the server, so the flag is local to one browser and a second device always considers the guest count as not yet asked. customer_count is synced and could carry that information, but PosStore createNewOrder pre-filled it with the table seats, so it was never 0 for a table order and could not tell "not asked" from "answered". Fix: Stop storing the seats default on the record and expose it from getCustomerCount() instead, so customer_count == 0 means "no guest count entered yet". ensureGuestCustomerCount now guards on that synced value, so an order whose guest count was entered on another device is not asked for it again. Every display goes through getCustomerCount(), so the values shown on the Guests button, the receipt and the preparation ticket are unchanged. opw-6470180 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Fixes purchase catalog bulk suggestions so products use the supplier's configured unit of measure instead of defaulting to the product unit. This helps purchase orders reflect vendor agreements accurately, including suggested quantities.
Original PR description
Issue ----- When adding all of the catalog's suggestions at once, the uom specified on the product's vendor lines is not respected. Steps to reproduce ----- - Create a product - Add a vendor line in…
Issue ----- When adding all of the catalog's suggestions at once, the uom specified on the product's vendor lines is not respected. Steps to reproduce ----- - Create a product - Add a vendor line in a different uom - Create a past outgoing shipment of 100 of the product - Create a PO (same vendor as vendor line) - Open the Catalog - Click "Add All" in the suggestion section on the left - Go back to the PO > The product is in units instead of the different uom Cause ----- Clicking the button calls `action_purchase_order_suggest` in Python directly https://github.com/odoo/odoo/blob/3aec1c317aad547b1ad0c85a21cc53f735c5cbfd/addons/purchase_stock/models/purchase_order.py#L131-L134 whereas clicking on a product will go through `addProduct` in JS https://github.com/odoo/odoo/blob/3aec1c317aad547b1ad0c85a21cc53f735c5cbfd/addons/purchase_stock/static/src/product_catalog/record/kanban_record.js#L20-L28 Which leads to `_update_order_line_info` where the purchase line is created https://github.com/odoo/odoo/blob/f59783d43f891fea14d03f67d7c017d7ffbbe5a7/addons/purchase/models/purchase_order.py#L1322-L1328 The uom is then retrieved in the create call through `_suggest_quantity` https://github.com/odoo/odoo/blob/f59783d43f891fea14d03f67d7c017d7ffbbe5a7/addons/purchase/models/purchase_order_line.py#L547-L549 Other issue ----- The quantity of the product also doesn't match the suggestion since the product `suggested_qty` is in the product's uom and not the vendor ones. ----- Ticket: opw-6417665
Copying an image that is already attached to another record now reuses the existing file instead of creating an unnecessary duplicate. This helps keep stored files cleaner and avoids redundant attachments without changing the user workflow.
Original PR description
Copying an image attachment already linked to another record could leave a redundant duplicate behind instead of reusing the existing one. opw-6463012 Forward-Port-Of: odoo/odoo#283221 Forward-Port-Of: odoo/odoo#282287
Point of Sale now asks the browser to keep its offline data safe from automatic deletion when device storage is low. This reduces the risk of losing unsynced orders, helping avoid missing sales, closing differences, and accounting issues.
Original PR description
All PoS offline data, including orders not yet synced to the server, lives in indexedDB. By default this storage is "best-effort": when the device runs low on disk space, the browser is allowed to silently evict an entire origin's data. On a PoS terminal holding unsynced orders, such an eviction permanently loses those orders (sales missing from the session, "Difference at closing", unbalanced accounting entries). This PR requests persistent storage with `navigator.storage.persist()` when the data service initializes indexedDB. When granted, the origin's storage is exempted from automatic eviction; data can then only be removed by an explicit user action. The request is best-effort: Chrome grants or denies it silently based on engagement heuristics (it is always granted for an installed PWA), Firefox prompts the user once. The call is therefore not awaited in the startup path and its outcome is only logged. See https://developer.mozilla.org/en-US/docs/Web/API/StorageManager/persist
This fixes an issue where the first day of a multi-day absence could show incorrect negative hours after matching it with an approved leave. The change improves timezone handling so absence and overtime records are found and cleared correctly, keeping attendance balances accurate.
Original PR description
## Steps to reproduce: - Install hr_attendance_holidays - Activate 'Absence Management' for the company - Run the cron on multiple days to have an employee who is absent for a week - Create a leave…
## Steps to reproduce: - Install hr_attendance_holidays - Activate 'Absence Management' for the company - Run the cron on multiple days to have an employee who is absent for a week - Create a leave for the same days of absence - Notice all attendances which was created for absence have 0 hours now except the very first day has -8h ## Cause: When fetching the attendances that overlaps with the leave since the absence attendance being created for midnight of the absence day and the leave starts at 8AM then the first day's attendance will be ignored in the search. Also when fetching the hr.attendance.overtime.line to unlink them we use but that seems to fetch LMT+ tz when use with which mis-localize the date which then ignore the first day's attendance and it keeps its hr.attendance.overtime.line ## Fix: We adjust dates when fetching the attendance at first to make sure we cover the time of the absence attendances' time as well. Use ZoneInfo instead of to keep getting UTC+ tz opw-6410506
This fixes an inventory issue where choosing a contact on a delivery transfer could replace a custom destination location with the generic Customers location. Businesses using specific customer stock locations can now rely on their operation type settings staying intact, reducing incorrect routing and manual corrections.
Original PR description
### Steps to reproduce: - In the settings: Enable Storage Locations - Create a customer location "Customer stock" with "Customers" as its parent location - Create a delivery operation type "Deliver…
### Steps to reproduce: - In the settings: Enable Storage Locations - Create a customer location "Customer stock" with "Customers" as its parent location - Create a delivery operation type "Deliver Super Customer" and set its default destination location to "Customer stock" - Go to Inventory > Overview > Deliver Super Customer > New - Set a contact on the transfer #### > The destination location switches from "Customer stock" to "Customers" ### Cause of the issue: The `location_dest_id` of `stock.picking` depends on its `partner_id`. So that changing the partner recomputes the locations of the transfer. However, as soon as the destination of the operation type has a `customer` usage, the `property_stock_customer` of the contact replaces it unconditionally: https://github.com/odoo/odoo/blob/04f3a7bca99d0144a4ea871be9625db368b196ca/addons/stock/models/stock_picking.py#L949-L963 However, the `property_stock_customer` falls back to an `ir.default` pointing at the default `Customers` location when nothing is set on the contact: https://github.com/odoo/odoo/blob/1c40fab04b71def8f3645c4c4bb0c1441057f307/addons/stock/data/stock_data.xml#L71-L72vs The override comes from 8a0775aa1dd9, which replaced an `elif` fallback on the contact by an "unconditional" substitution as this fallback had become unreachable once `default_location_src_id` and `default_location_dest_id` were made required: https://github.com/odoo/odoo/blob/04f3a7bca99d0144a4ea871be9625db368b196ca/addons/stock/models/stock_picking.py#L34-L41 opw-6421090 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283691 Forward-Port-Of: odoo/odoo#280016
Fixes an issue where confirming a sale order again after cancellation could fail to create the needed event registration. This ensures customers who reconfirm orders through normal flows or the portal get the correct event registration records.
Original PR description
**Steps to reproduce:** - Create a SO and add the Event Registration - Standard product and specify an event - Confirm the SO then select "Create/Update registrations" - Cancel the SO, then "Set to…
**Steps to reproduce:** - Create a SO and add the Event Registration - Standard product and specify an event - Confirm the SO then select "Create/Update registrations" - Cancel the SO, then "Set to Quotation" - Select "Preview" and confirm the Sale Order again - There will not be any new registration created when there should be one. **Behavior:** Usually when a sale order is confirmed the `action_sale_order_event_registration` form will be opened which when filled correctly creates registrations. However in certain cases: confirming from the customer portal, or simply closing the form when it is opened, will not trigger `action_make_registration` which creates registrations if it is not already the case `action_confirm()` should be creating the registrations correctly on its own anyway by calling ´init_registrations()´ : https://github.com/odoo/odoo/blob/beed378cde592bc96c1e79a976ac775264b843ed/addons/event_sale/models/sale_order_line.py#L49-L66 This function tries to create each missing registrations by looking at the amount in the so_line and deducting the already created registrations, however since some of them can be cancelled, this computation is wrong. And leads to registration not being created when they should. opw-6444127
Argentinian companies can now create invoices for foreign customers even when export sales journals are unavailable or archived. Instead of stopping the workflow with an error, Odoo falls back to a standard Invoice B document so billing can continue.
Original PR description
### Issue before this commit: Before this commit, users were completely blocked from creating an invoice for a foreign partner (e.g., "Cliente del Exterior") if all exportation journals were archived…
### Issue before this commit: Before this commit, users were completely blocked from creating an invoice for a foreign partner (e.g., "Cliente del Exterior") if all exportation journals were archived or unavailable, as the system would immediately trigger a RedirectWarning error. ### Steps to reproduce the issue: 1. Download Accounting and l10n_ar 2. Go to contacts and create a new one with: 1. Country as United States 2. VAT number ex. 55000002126 3. AFIP Responsibility Type as Cliente del Exterior 3. Go to Journals, filter for sales journals and archive: 1. Electronic Exportation Invoice (FEX) 2. Expo Sales Journal 4. Go to invoices and create a new one for the client you just created 5. As soon as you insert the client you will receive the error: You are trying to create an invoice for foreign partner but you don't have an exportation journal ### Cause of the issue: https://github.com/odoo/odoo/blob/014d58e3204d17db6dcba3c8ab7d8ad35003300e/addons/l10n_ar/models/account_move.py#L186-L189 The _onchange_partner_journal method rigidly enforced the use of an exportation journal for foreign AFIP responsibility types (codes 8, 9, and 10). If the query failed to find an active export journal, the code intentionally threw a hard error instead of providing a fallback mechanism. ### Reason to introduce the fix: This fix is introduced to prevent unnecessary workflow blocks. By catching the missing journal and defaulting the document type to "Invoice B" (code 6), the user can now successfully generate the invoice using a standard domestic sales journal without being forced to configure an exportation journal. opw-6442501 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282971
The translation endpoint is no longer handled as a full website page because it already receives the requested language directly. This prevents unexpected language redirects and cookie conflicts, improving reliability for website translation loading.
Original PR description
/website/translations does not require request.website or language redirection logic as `lang` is passed explicitly. Drop `website=True` to prevent unexpected language redirects and cookie conflicts. Backport of 4faddd8b44 (odoo/odoo#269325). runbot-231758 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Credit notes created from existing Turkish customer invoices now use the sales return account configured on the sales journal, matching manually created credit notes. This keeps sales and sales returns recorded separately as required by the Turkish chart of accounts, while cancellation reversals still mirror the original invoice exactly.
Original PR description
The Turkish chart of accounts keeps sales and sales returns on separate accounts, and the sales journal carries the account to use for returns. A credit note typed in by hand already lands on it, but one created from an existing customer invoice did not. Reversing an invoice copies `account_id` over from the invoice line, and since that field is a stored compute without depends, nothing ever recomputes it, so the return kept the sales account. Set the journal account on the copied product lines instead. Reversals made to cancel an entry are left alone, as those have to mirror the original move exactly for the two to net out, and a plain duplicate is untouched. Task-6438412 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282858
Fixed an issue where pressing Enter in a bullet list item that also contained a table would not create a new bullet when the cursor was outside the table. This makes editing lists with embedded tables behave more consistently and reduces formatting frustration for users.
Original PR description
### Steps to reproduce: - Insert a bullet list - Inside of the list, insert a table - Write before and/or after the table (in the same list item) - Press enter before and/or after the inserted text - Notice that the bullet is not split like in a normal list ### Root Cause: - On Enter, list plugin checked whether the list item contained unsplittable element. Since the table was inside the list item, it always treated the list item as unsplittable, even when the cursor was outside the table. As a result, the list item could never be split. ### Solution: - Instead of checking the whole list item, walk up from the split target to the list item and look for an unsplittable element along the way. This allows the list item to split normally when the cursor is outside the unsplittable. task-6449843 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Intrastat reports now only include rental orders when their rental duration is at least two years. This prevents shorter rentals from being reported incorrectly and improves compliance reporting accuracy.
Original PR description
Problem: Some rental orders are showing in Intrastat reports when they should not be showing. Only rental orders with duration of 2 years or more should be shown in Intrastat reports. However, all rental orders are being shown. <img width="783" height="768" alt="intrastat_leasing" src="https://github.com/user-attachments/assets/7419e3dc-7b3e-4234-809f-6973fef93fc1" /> Cause: When querying the lines to show in the Intrastat report, there is no condition that checks for the duration of rental orders. opw-6351456
The Turkish reporting journal form now places the return from sales account field in the right position. This prevents labels and values from appearing under the wrong captions, reducing confusion when configuring journals.
Original PR description
The journal form renders `default_account_id` as six standalone labels followed by two `nolabel="1"` fields, one for bank, cash and credit journals and one for sale, purchase and general ones. The xpath matched the first of those two fields, so the return from sales account was inserted between them. Its own label then landed in the middle of the label run, shifting the group grid: both labels rendered side by side with their values underneath, each next to the wrong caption. Anchor on the second field instead, so the new field follows the whole label and field run. Task-6438412
Fixes an error that occurred when users selected multiple appraisals and marked them as done from the list view. Each appraisal now gets its completion message and notification correctly, so HR teams can process appraisals in bulk without interruption.
Original PR description
Steps to reproduce: - select multiple appraisals and try to mark as done from list view. Issue: - The completion notification uses an appraisal variable assigned by a previous loop, raising an UnboundLocalError. Furthermore, message_notify() requires a singleton. Fix: - notify and post the completion message for each appraisal explicitly. task-6479018 Forward-Port-Of: odoo/enterprise#128207
Fixes an issue where reconciling journal items could fail when users had both a parent company and a branch company selected. The reconciliation process now uses the relevant company from the journal item, preventing currency conversion errors in multi-company accounting workflows.
Original PR description
When having multiple companies selected at the same time, _get_conversion_rate returns: File "/data/build/odoo/odoo/orm/fields_misc.py", line 114, in get raise ValueError("Expected singleton: %s" %…
When having multiple companies selected at the same time, _get_conversion_rate returns:
File "/data/build/odoo/odoo/orm/fields_misc.py", line 114, in get
raise ValueError("Expected singleton: %s" % record)
1 - Create a new company with currency EUR.
2 - Create a branch company underneath the main company.
3 - In Accounting, install fiscal localization, e.g. Belgian Companies on the company configuration settings.
4 - Select an account like 600000 Raw Materials, and enable Allow Reconciliation on this account. The exact account isn't important, only that we can make credits / debits to it to be reconciled.
5 - With only the top level company selected, make a debit of 100 USD, e.g. Vendor Bill, set in currency USD to the account 600000.
6 - Now with only the branch level company selected, make a credit of 100EUR, e.g. Customers Invoices, set in currency EUR to the same account with an amount equal to the credit in step 5. (if 1USD == 1EUR, 1-1), so that there is no residual amount, i.e. credit == debit.
7 - Now select both the top level company and the sub branch company in the company context.
8 - In Journal Items, reconcile the unreconciled journal items for the Account 600000.
With this commit we select the first company of the aml instead of every companies on the amls.
opw-6290703
Forward-Port-Of: odoo/enterprise#123774The accounting app now prevents creating fiscal years that fully contain an existing fiscal year. This helps avoid conflicting financial periods and reduces the risk of reporting or closing-process errors.
Original PR description
Before this commit: - The current constraint for overlap check allows if we define a new, larger fiscal year that completely swallows an existing smaller one (e.g., creating Aug 2025 - Nov 2026 when Sept 2025 - Oct 2026 already exists). After this commit: - The constrain domain was changed to consider the above missed case. no task Forward-Port-Of: odoo/enterprise#128942
UPS shipping validation now follows UPS rules by allowing phone numbers from 1 to 15 digits instead of incorrectly requiring at least 10. This prevents valid customers in countries with shorter phone numbers, such as Luxembourg, from being blocked when getting shipping rates.
Original PR description
**Steps to reproduce:** - Create a contact with a phone number that has 9 characters - Setup an UPS carrier, a configuration that works is UPS Saver as Service Type and UPS Package/customer supplied…
**Steps to reproduce:** - Create a contact with a phone number that has 9 characters - Setup an UPS carrier, a configuration that works is UPS Saver as Service Type and UPS Package/customer supplied as a package type - Create a quotation, put the created contact as a client - Try adding a shipping and getting the rates - An User Error appears, the phone number is too short **Why the fix:** Before this commit, any phone number that was less than 10 characters would raise an User Error, but some countries, such as Luxembourg, use phone numbers that are nine characters long or even less. If we check the official UPS documentation (https://developer.ups.com/tag/Shipping?loc=en_EN#operation/Shipment), we can see in the Ship_to/Phone section, that the phone number should be a number between 1 and 15, not saying it should be 10 characters or more. <img width="495" height="473" alt="image" src="https://github.com/user-attachments/assets/fed82987-ffb8-4b84-b282-6c3d3b4f304e" /> After this commit, we adapt the way we prevent the user from inputing phone numbers to fit the official UPS documentation. opw-6307577 Forward-Port-Of: odoo/enterprise#128632 Forward-Port-Of: odoo/enterprise#122831
This fixes an internal sales timesheet test so it is not affected by unrelated pricing rules installed by other modules. The change helps ensure profitability calculations are validated consistently and avoids false test failures during development and upgrades.
Original PR description
The project profitability test assumes that the service product is sold at its list price. However, some modules such as `pos_pricer` add a globally applicable pricelist in their data. In 18.0, this pricelist can be selected for the test partner even when the pricelist feature is disabled, causing a discount to be applied and the expected profitability amount to differ. The fix is to remove existing pricelists in the test, as done in other tests affected by the same issue, so that the sale order price is deterministic. [error-944526](https://runbot.odoo.com/odoo/error/944526) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282712
This fixes incorrect and unclear labels for Japanese fiscal positions, including a wrong translation for domestic partners. The change helps users select the right tax-related setting with clearer wording in English and Japanese.
Original PR description
Japanese translation "海外取引先" for domestic was clearly wrong.
Also fixed the misspelling ("Oversea" -> "Overseas") and removed the unnecessary "Customer" context from the name.
@qrtl
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-prThe Point of Sale screen will no longer crash when staff open a paid order that has a related refund order cancelled in the backend. This keeps order lookup usable and avoids disruption during refund and return workflows.
Original PR description
When a return order is created from the front end and later cancelled without being deleted from the backend, opening the original order from the POS ticket screen causes the UI to crash. The issue…
When a return order is created from the front end and later cancelled without being deleted from the backend, opening the original order from the POS ticket screen causes the UI to crash. The issue occurs because the refundedQty getter in `addons/point_of_sale/static/src/app/models/pos_order_line.js` assumes that every refunded order line has a valid order_id. For cancelled return orders, line.order_id is no longer available, resulting in the following runtime error: `TypeError: can't access property 'state', line.order_id is undefined` As a result, selecting the original paid order from the Ticket Screen breaks the POS interface. **Steps to Reproduce** 1. Create a normal sale in the POS. 2. Keep the POS session open. 3. In the backend, open the POS order and click Return Products. 4. Cancel the generated refund order (do not delete it). 5. In the POS, navigate to Ticket Screen → Orders → Paid Orders. 6. Select the original order. Desired behavior after PR is merged: The original order should open normally without any errors, even if a related refund order has been cancelled. Issue Ticket: https://github.com/odoo/odoo/issues/260079 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
The lunch ordering test now waits for the correct product to load after changing locations before placing an order. This prevents intermittent test failures caused by ordering an outdated demo product, improving reliability without changing user-facing lunch features.
Original PR description
The lunch order tour selects `Farm 1` before ordering a product. However, it only waits for the location input to be updated before clicking the first kanban record. With demo data installed, a product from the previous location can still be displayed while the product model is being reloaded. The tour can therefore order a demo product instead of the product created by the test. This notably fails during weekends when the corresponding demo vendor is unavailable. To fix we need to wait for the product created by the test before clicking it. Besides selecting the intended product, this also ensures that the product reload following the location change has completed. [error-181572 ](https://runbot.odoo.com/odoo/error/181572) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281753
This change makes an automated website test more stable by waiting for the page editor sidebar to finish updating before checking popup visibility. It reduces random test failures in the development pipeline without changing customer-facing website behavior.
Original PR description
The test `undoing something on a target outside s_popup closes it` had a few fails in CI: the `fa-eye-slash` was not set as expected. This commit adds a `waitSidebarUpdated` call just before to ensure owl has no pending rendering when checking the eye. The fix is similar to aaf0f54d1feda60becb0bfbad578b366715c0172 which is about a similar failure in another test. runbot-938967 Forward-Port-Of: odoo/odoo#284381
This fixes stock quantities when a product is changed to be storable after stock movements already occurred. It helps keep on-hand inventory, stock accounting, forecasts, and historical inventory reports aligned with actual movements.
Original PR description
Since commit 680488cca2344a90d2a3864d26a03ade21ace268, it is now possible to change 'is_storable' after having validated stock moves. This means that if you validate moves while `is_storable = False`, then enable `is_storable`, the On Hand quantity will no longer correspond to the sum of the moved quantities. This means that the following features (that assume that the quantities between the moves and quants match) will no longer be reliable: - Stock Accounting (_run_avco / _run_fifo) - Forecast Report (past values) - Inventory At Date This commit is just a POC. TODO: - Add a warning on 'is_storable' change to let users know about the impact of the change. - Add tests --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fix prevents Odoo from crashing when users discard a newly created loyalty program after viewing reward details. It makes the web interface handle missing related records safely, so users return to the list view without errors.
Original PR description
Description of the issue/feature this PR addresses: This PR fixes a client-side crash in Odoo's web client that occurs when discarding a brand-new loyalty program record after opening and discarding…
Description of the issue/feature this PR addresses: This PR fixes a client-side crash in Odoo's web client that occurs when discarding a brand-new loyalty program record after opening and discarding its sub-reward popup (Odoo Issue #277155). We resolve this by adding defensive checks to: 1. `formatX2many` to prevent reading `.currentIds` on undefined values. 2. `getRawValue` to prevent checking `.count` on undefined relational values. Current behavior before PR: Discarding a new unsaved record completely wipes local changes, leaving relational fields (like `reward_ids` or `rule_ids` on the new `loyalty.program` model) as `undefined` in `record.data` (since they are not present in the record's initial values). During the post-discard render cycle, the view calls `getFormattedRecord` to format all active fields. This passes `undefined` to `formatX2many` and `getRawValue`, throwing JavaScript `TypeErrors` and crashing the interface. Desired behavior after PR is merged: The relational formatters and getters safely handle undefined/falsy relational values (returning "No records" or an empty array), allowing the new loyalty program page to discard cleanly back to the list view without any client errors.