Friday, September 11, 2026
24 changes · saas-19.2
Resolved issues and error corrections
This fixes SEPA Credit Transfer exports so they no longer include an address field that some European banks reject. Businesses using Austrian, German, and other strict SEPA banks should be able to validate and submit batch payment XML files successfully again.
Original PR description
### Issue before this commit: Generating a SEPA Credit Transfer XML for certain European banks (e.g., Austrian and German banks) fails because the generated XML contains an unexpected `<CtrySubDvsn>`…
### Issue before this commit: Generating a SEPA Credit Transfer XML for certain European banks (e.g., Austrian and German banks) fails because the generated XML contains an unexpected `<CtrySubDvsn>` tag inside the `<PstlAdr>` node, leading to the rejection of the batch payment file. ### Steps to reproduce the issue: 1. Download Accounting and l10n_at 2. Go to settings and activate SEPA Credit Transfer / ISO20022 3. Go to Journals > Bank > set an account number 4. Create an austrian contact (ex. FK Austria Wien AG) and set in the invoicing tab a bank (ex. AT526000071856851733) and set it trusted 5. Go to Bills, create a new one with the contact created and confirm it 6. Then click 'PAY' and select SEPA Credit Transfer 7. Go to Vendors > Batch Payments 8. Create a new one with: 1. Bank as bank 2. SEPA Credit Transfer as Payment Method 3. Add the bill just created 9. Validate and download the XML 10. See that a wrong tag <CtrySubDvsn> is added. This makes some banks refuse it ### Cause of the issue: External commit 30b023d394a5e4de64873188aa1d5961fecccb10 introduced the `<CtrySubDvsn>` tag to support US and CA requirements. However, the change was incorrectly applied to the common `account_iso20022` file, making it leak into standard European SEPA exports where the tag is not compliant with certain strict banking validation rules. ### Reason to introduce the fix: Revert the generic addition of the `<CtrySubDvsn>` tag in the common ISO20022 XML generation and restrict it only to the specific localizations (US/CA) that require it. This brings the `<PstlAdr>` node back to compliance, allowing Austrian, German, and other European banks to successfully process the files. opw-6523035 Forward-Port-Of: odoo/enterprise#131156 Forward-Port-Of: odoo/enterprise#131090
Fixes an issue that caused the Peruvian Inventory and Balance General Ledger report export to fail. Users can now generate the report reliably while preserving the file format required by SUNAT.
Original PR description
### Description of the issue/feature this PR addresses: This PR fixes a server crash in the Peruvian localization (l10n_pe_reports_lib) that occurs when generating the "Inventory and Balance" General…
### Description of the issue/feature this PR addresses: This PR fixes a server crash in the Peruvian localization (l10n_pe_reports_lib) that occurs when generating the "Inventory and Balance" General Ledger report. The crash is triggered by strict validation rules within Python's csv module, which rejects the custom line terminator used to fulfill the SUNAT PLE formatting requirements. ### Current behavior before PR: When a user attempts to generate and export the "Inventory and Balance" report, the server crashes with a ValueError: bad delimiter or lineterminator value. This happens because the csv.DictWriter is initialized with lineterminator='|\n' to ensure every row ends with a pipe. Python's underlying csv implementation rejects this, as it expects standard line endings (\r, \n, or \r\n) and throws an error if the delimiter character (|) is included in the terminator string. ### Desired behavior after PR is merged: The "Inventory and Balance" report generates successfully without server errors. The code now uses the standard lineterminator='\n' to satisfy Python's validation rules. To maintain the mandatory trailing pipe (|) at the end of each row required by SUNAT, a dummy empty column (['']) is appended to the field names with restval=''. This prompts the writer to naturally append the final pipe as a column delimiter before the newline, resulting in the exact |\n output format required, safely and reliably. opw-6509674 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/enterprise#129636
The Contracts page no longer offers a Kanban view option that was not actually available. This avoids user confusion by only showing valid ways to view employee contract records.
Original PR description
We don't have a Kanban view for contracts, but we still allow users to select that view type. After discussion with the team, we've deemed that view unnecessary. Instead of implementing the Kanban view, we'll just remove that option from the "Employee Records" (Contracts) page. opw-6475797 Forward-Port-Of: odoo/odoo#284829
Odoo now handles empty or incomplete image attachments without crashing the media picker or chatter. This helps users continue working even when an upload is interrupted or a malformed email creates an invalid attachment.
Original PR description
Problem: Interrupted uploads or malformed email payloads can create 0-byte binary `ir.attachment` records where the `checksum` is `False`. Accessing the computed `image_src` field on these records…
Problem:
Interrupted uploads or malformed email payloads can create 0-byte binary `ir.attachment` records where the `checksum` is `False`. Accessing the computed `image_src` field on these records triggers a `TypeError: 'bool' object is not subscriptable` when attempting to slice `attachment.checksum[:8]`. This crashes the Media Dialog and Chatter.
Purpose:
Add a fallback boolean guard to `attachment.checksum` inside `_compute_image_src` so that empty attachments evaluate safely to a string ('0') instead of raising a traceback, allowing the UI to render gracefully.
Steps to Reproduce on Runbot:
1. Go to Settings > Technical > Database Structure > Attachments.
2. Create a new record: Name: `test.png`, Type: `File` (Binary), File Content: [leave empty], Is public document: Checked.
3. Open any record with a Chatter (e.g., Contact or CRM Lead) and click "Insert Image" to open the Media Dialog.
4. The system attempts to evaluate `image_src` and throws the `TypeError`.
Notes:
A test (`test_compute_image_src_empty_checksum`) was added to `test_ir_attachment.py`
opw-6482674
Forward-Port-Of: odoo/odoo#287639
Forward-Port-Of: odoo/odoo#287444Time off measured in hours is now calculated using only actual working periods from the employee schedule. This prevents non-working periods, such as a Friday afternoon marked unavailable, from being incorrectly counted and helps keep leave balances accurate.
Original PR description
When computing the amount of hours used by a time off, if a time off entry was set in the working schedule it would not be taken into account and the amount of hours used would be wrong. Steps to reproduce: ------------------- * Open any working schedule WS and make sure that the friday has morning set as working time and afternoon as non-working time * Make sure the time off type is configured to use hours and not days * Create a time off of 2 days that includes a friday > Observation: The amount of hours used is 16 hours instead of 12 hours Why the fix: ------------ When now filter out the non working period when computing the work intervals. opw-6469587 Forward-Port-Of: odoo/odoo#287505 Forward-Port-Of: odoo/odoo#285955
Image upload fields now apply the Android camera workaround only for Android Chromium browsers that need it. This avoids showing unnecessary document picker options in other environments, especially the native app, while preserving camera access where Android would otherwise hide it.
Original PR description
Since Android 14, Chromium sends a file input accepting only images straight to the photo picker, which has no "Camera" entry. The image fields work around it by appending `dummy/allowAndroidCamera`…
Since Android 14, Chromium sends a file input accepting only images straight to the photo picker, which has no "Camera" entry. The image fields work around it by appending `dummy/allowAndroidCamera` to their accept attribute: a mimetype which is not an image is enough to get the generic chooser, and its camera, back. https://issues.chromium.org/issues/40937303 That invalid mimetype was appended for everyone, while only the browsers based on Chromium on Android need it: - the issue is an Android one, the desktop file dialogs are not concerned - the native app builds its own file chooser out of the accept attribute, and the invalid mimetype makes it offer the document picker on a field which only accepts images - Firefox and Safari are not based on Chromium and are not affected The workaround is now limited to the browsers needing it, and the expression moved from the template to a getter, since it is no longer a simple concatenation. Code made by Claude Supervised by RFR --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286508 Forward-Port-Of: odoo/odoo#285643
The WhatsApp Disconnect button is now only shown to administrators for accounts set up through onboarding. This prevents manual WhatsApp accounts from being accidentally disconnected in Meta while still appearing configured in Odoo, and avoids access errors for regular internal users opening the account form.
Original PR description
`show_disconnect` was true for any account holding a token, so the Disconnect button showed on manually configured accounts as well. Pressing it unsubscribes the webhook on Meta first, then clears the token, and that write is rejected on a manual account since the credentials are required there. Odoo rolls back, Meta does not, so the account keeps looking configured while incoming messages stop. The compute also read `token`, restricted to WhatsApp administrators, while every internal user has read access on `whatsapp.account`, so opening the account form as a regular user raised an AccessError. The button is now shown only for administrators on onboarded accounts, and `button_disconnect` checks write access, since hiding a button does not stop an RPC call and the request to Meta happens before the write that would have been refused. Task-6544628 Forward-Port-Of: odoo/enterprise#130759
Validated time off that is shortened, such as when an employee leaves mid-leave, now keeps the employee calendar correctly updated. This prevents payroll from incorrectly counting approved time off days as worked attendance.
Original PR description
Problem ------- Fixes bug caused by PR odoo#249527. When a validated time off's dates are shortened while it stays validated (e.g. when the employee's departure date falls int he middle of the…
Problem ------- Fixes bug caused by PR odoo#249527. When a validated time off's dates are shortened while it stays validated (e.g. when the employee's departure date falls int he middle of the leave), the linked resource.calendar.leaves record was unconditionally unlinked. To reproduce: 1. Create and validate a time off request covering a whole month. 2. Register the employee's departure with a departure date in the middle of that time off. 3. Generate the employee's last payslip. The leave is correctly cut at the departure date, but since it never leaves the `validate` state, it never goes through `_validate_leave_request()` again, so its resource.calendar.leaves record is never recreated. The days that were covered by the deleted entry are no longer blocked in the employee's resource calendar, so the payslip's worked day lines (computed from resource.calendar.leaves) count them as attendance instead of time off. Cause ----- `hr.leave.write()` removed the resource.calendar.leaves record any time either the state changed away from `validate` or the leave's dates changed, regardless of whether the leave remained validated. Date-only changes on an already-validated leave never re-trigger validation, so the entry was not recreated. Solution -------- Only remove the resource.calendar.leaves record when the leave actually loses its validated state. When a validated leave's dates change but it stays validated, amend the existing resource.calendar.leaves record in place instead, falling back to creating one if none exists. Related PR: odoo#249527 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287624 Forward-Port-Of: odoo/odoo#286465
The project overview now shows the upcoming milestone with the earliest deadline, rather than the one that was created first. This keeps the project list consistent with milestone planning and helps teams see the correct next delivery point.
Original PR description
Issue: The project list could display the first created unreached milestone as the next milestone, even when another milestone had an earlier deadline. This made the project overview inconsistent…
Issue: The project list could display the first created unreached milestone as the next milestone, even when another milestone had an earlier deadline. This made the project overview inconsistent with the milestone list. Steps to reproduce: - Create a project with milestones enabled. - Create MS1, then MS2. - Give MS1 a later deadline than MS2. - Open the project list and display the Next Milestone column. Cause: `_compute_next_milestone_id()` aggregated unreached milestones as an `id:recordset` and selected its first element. The ORM orders that aggregate by database ID, so creation order was used instead of the milestone model's deadline order. https://github.com/odoo/odoo/blob/765174be270813442df6c497456fe2864517da0b/addons/project/models/project_project.py#L209-L218 https://github.com/odoo/odoo/blob/765174be270813442df6c497456fe2864517da0b/odoo/models.py#L364-L377 Solution: Retrieve unreached milestones through their normal ordered search before grouping them per project. This preserves batched computation while ensuring that the selected record follows the established milestone order. opw-6496938 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287355 Forward-Port-Of: odoo/odoo#285933
Users can now switch to another view through the AI assistant while a full email composer is open without seeing an unexpected error. The system avoids trying to refresh a page component that has already been closed, making navigation smoother and more reliable.
Original PR description
Asking the AI agent to navigate to a different view calls doAction with clearBreadcrumbs, which tears down the current form view (and its chatter) before the still-open full mail composer dialog is…
Asking the AI agent to navigate to a different view calls doAction with clearBreadcrumbs, which tears down the current form view (and its chatter) before the still-open full mail composer dialog is closed. Closing that dialog then calls the chatter's onCloseFullComposerCallback, which reloads the parent record through reloadParentView(). However at that point the chatter no longer exists, so the reload's RPC call gets rejected with "Component is destroyed", and nothing is left to catch it.
How to reproduce:
- Open "Ask AI" from the top bar.
- Open an opportunity in CRM, then open the mail composer (click either 'Send message' or 'Log Note'), then click the enlarge button so it opens as a full composer dialog.
- Ask the agent (the one you pre-opened) to open another view, e.g. "show me all my contacts in the US", "show me the contact view of Abigail Peterson"
Current behavior:
The requested view opens correctly, but an UncaughtPromiseError ("Component is destroyed") is raised.
Expected behavior:
Switching views while a full composer dialog is open should not raise any error. The chatter's parent record should simply not be reloaded if the chatter has already been destroyed by the time its full composer dialog closes.
task: 6365003
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#287125The quotation document form now clearly marks the attachment as required before saving. This helps users understand they must select a document first, avoiding confusing save errors caused by a hidden required name field.
Original PR description
When trying to save an empty quotation document from the form view, the save is blocked because the `name` field is required. However, `name` is readonly when no attachment has been selected. As a result, the form doesn't display the required-field decoration on that field, which is confusing. The attachment field should be marked as required in the view instead. This makes it clear that an attachment must be selected first, after which the `name` field becomes available and can be filled in. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287400
This fix prevents an error from appearing when a user asks the AI assistant to open another view while a full message composer is still open. The requested view still opens normally, and the system now skips refreshing a record that has already been closed, improving stability during AI-assisted navigation.
Original PR description
Asking the AI agent to navigate to a different view calls doAction with clearBreadcrumbs, which tears down the current form view (and its chatter) before the still-open full mail composer dialog is…
Asking the AI agent to navigate to a different view calls doAction with clearBreadcrumbs, which tears down the current form view (and its chatter) before the still-open full mail composer dialog is closed. Closing that dialog then calls the chatter's onCloseFullComposerCallback, which reloads the parent record through reloadParentView(). However at that point the chatter no longer exists, so the reload's RPC call gets rejected with "Component is destroyed", and nothing is left to catch it.
How to reproduce:
- Open "Ask AI" from the top bar.
- Open an opportunity in CRM, then open the mail composer (click either 'Send message' or 'Log Note'), then click the enlarge button so it opens as a full composer dialog.
- Ask the agent (the one you pre-opened) to open another view, e.g. "show me all my contacts in the US", "show me the contact view of Abigail Peterson"
Current behavior:
The requested view opens correctly, but an UncaughtPromiseError ("Component is destroyed") is raised.
Expected behavior:
Switching views while a full composer dialog is open should not raise any error. The chatter's parent record should simply not be reloaded if the chatter has already been destroyed by the time its full composer dialog closes.
community: https://github.com/odoo/odoo/pull/287125
task: 6365003
Forward-Port-Of: odoo/enterprise#131112Fixed an issue that could prevent German point-of-sale sessions from closing when an order used a customer address contact without its own name. The system now uses the displayed contact name, or a safe fallback if needed, so required compliance export data is still produced.
Original PR description
Steps to reproduce: - German PoS with Fiskaly TSS configured - Set a customer that is an address contact (type invoice/delivery/other) on an order; such a contact has no name of its own, it is…
Steps to reproduce: - German PoS with Fiskaly TSS configured - Set a customer that is an address contact (type invoice/delivery/other) on an order; such a contact has no name of its own, it is displayed with its parent's - Close the session Issue: Closing crashes with "TypeError: 'bool' object is not subscriptable" and the session cannot be closed at all. Cause: _get_dsfinvk_cash_point_closing_data builds the DSFinV-K buyer block from partner.name, which is only guarded by "if partner := o.partner_id". res.partner.name is not required: the res_partner_check_name constraint only enforces it for type='contact', so an address contact stores NULL there and partner.name reads as False, which cannot be sliced. Fix: Fall back on complete_name - what the UI displays for those contacts, "Parent Company, Delivery" - and on a literal when even that is empty, since the buyer name is mandatory in the export. Reading name first leaves the exported value untouched for every partner that has one. opw-6518336 Forward-Port-Of: odoo/enterprise#129709
Payslips now only include public holidays from the employee version's company, preventing holidays from another selected company from appearing in worked day lines. This improves payroll accuracy for multi-company setups, such as separate Belgian and French entities.
Original PR description
Issue: - Create a public holiday in France, and generate a payslip in Belgium, with both companies selected. - French public holidays will appear in the belgian worked day lines. Fix: replace `self.env.companies.ids` -which returns all selected companies- in `_get_leave_domain` with `self.company_id.ids` to match the company of the current version in the payslip. task-id: 6425963
French PDP Flow 10 responses from the public platform are now processed instead of only being stored. Rejected reports show the rejection reason, move to the correct status, and can be corrected and resent with a new transmission reference.
Original PR description
Flow 10 PPF responses were stored as attachments without being processed. Consequently, rejected reports remained marked as sent, the rejection reason was not shown to users, and corrected reports could not be submitted again. Process the PPF response codes, update the flow state, and log the returned details in the chatter. Keep response attachments separate from the outgoing payload and allow rejected reports to be manually resent with their original moves and a new transmission identifier. no task id Forward-Port-Of: odoo/odoo#287338
Fixes an issue where the HTML editor could keep outdated content after detecting a stale document. The editor now uses the latest content saved on the server, reducing the risk of users seeing or continuing to edit obsolete information.
Original PR description
Before this commit, an editor recovering from a stale document could stop on this error and keep the stale content:
```
Error: Concurency detected while recovering from a stale document. The
last history id of the server is different from the history id received
by the html_field_write event.
at CollaborationOdooPlugin.resetFromServerAndResyncWithPeers
```
This happens because the recovery compares the history id of the record with the one the html_field_write event carried. A write keeps only the last step id in the field, so an event handled after a later write names an id the record no longer holds. As a result, the recovery stops there and the document stays stale.
This commit fixes the issue by taking the history id read from the record as the new server reference, so the editor converges on the document the server holds.
https://runbot.odoo.com/odoo/error/944595
Forward-Port-Of: odoo/odoo#287408Files sent through Odoo Live Chat can now be downloaded reliably when the chat widget is embedded on an external website. This prevents customers or visitors from hitting browser download errors caused by cross-site restrictions, improving the support experience.
Original PR description
**Steps to reproduce:** - Install `im_livechat` module - Copy the code from the livechat channel's widget tab - Paste it in an external website `<head>` (e.g., local python webserver on `0.0.0.0`) -…
**Steps to reproduce:**
- Install `im_livechat` module
- Copy the code from the livechat channel's widget tab
- Paste it in an external website `<head>` (e.g., local python webserver on `0.0.0.0`)
- Start a conversation and send a file from odoo
- Try to download it from the external website
- POST request is sent for the download
- Download fails: `CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.`
- Also when the file is a PDF the preview won't open: `404 (File not found)`
**Issue:**
Mix of multiple issues:
- The download is triggered using a POST request instead of a GET request
(see similar issue for the file viewer [1])
- The file route does not provide the required CORS headers, so requests
originating from the external website are blocked by the browser
- PDF files are rooted to the related path of the `pdfjs` fileviewer
(e.g. `http://0.0.0.0:8000/web/static/lib/pdfjs/web/viewer.html?file=...`)
```xml
<!--
Template rendering all the scripts required to execute the Livechat from an external page (which not contain Odoo)
-->
<template id="external_loader" name="Livechat : external_script field of livechat channel">
<!-- the loader -->
<script defer="defer" t-attf-src="{{url}}/im_livechat/loader/{{channel_id}}" type="text/javascript"/>
<!-- js of all the required lib (internal and external) -->
<script defer="defer" t-attf-src="{{url}}/im_livechat/assets_embed.js" type="text/javascript" />
</template>
```
**Fix:**
Make the `downloadFile` helper handle cross-origin URLs by falling back to a native `<a download>` click (like before) when the target route has the same origin as the embedded script. Could use `session.origin` or `new URL(document.currentScript.src).origin` for this. This is done to avoid allowing CORS on the file content route.
The file viewer issue is handled separately by [1].
For the PDF issue we could manually add the origin to the full url everywhere (but we get some `SecurityError` error from the library due to the cross-origin iframe), add the libjs library in the `assets_embed` (not sure it's possible in `im_livechat.assets_embed_external`), or block external PDF preview for now.
[1] https://github.com/odoo/odoo/pull/281330
opw-6444167
Forward-Port-Of: odoo/odoo#287535
Forward-Port-Of: odoo/odoo#286180Changing the delivery address on an outgoing rental transfer no longer redirects the destination away from the rental location. This helps rental operations keep stock movements accurate and prevents confusion or manual corrections when customer delivery details change.
Original PR description
**Issue** Changing the `partner_id` of an outgoing rental transfer could reset its destination location to the partner customer location instead of the rental location. **Steps to reproduce** -…
**Issue** Changing the `partner_id` of an outgoing rental transfer could reset its destination location to the partner customer location instead of the rental location. **Steps to reproduce** - Enable Rental Transfers from Rental Configuration. - Create a Sales Order for a rental product. - Open the related delivery transfer. - Using Studio, make the Destination Location field visible. -> Current destination location is: Partner/Customer/Rental - Change the delivery address -> The destination location become: Partner/Customer **Cause** Changing the delivery address (i.e: the partner_id) triggers `_compute_location_id`: https://github.com/odoo/odoo/blob/85372b625a80ab50fe2d8a6bce49a890b2bf1665/addons/stock/models/stock_picking.py#L949-L950 Since commit https://github.com/odoo/odoo/commit/8c90fc1fd336ee872fd67d8d72473e4f6c55b2e0, not only draft picking are recomputed. As a result, `location_dest_id` is set as `picking.partner_id.property_stock_customer` https://github.com/odoo/odoo/blob/85372b625a80ab50fe2d8a6bce49a890b2bf1665/addons/stock/models/stock_picking.py#L959-L961 https://github.com/odoo/odoo/blob/85372b625a80ab50fe2d8a6bce49a890b2bf1665/addons/stock/models/stock_picking.py#L963 Regardless whether we are in rental setup opw-6237495 Forward-Port-Of: odoo/enterprise#126158 Forward-Port-Of: odoo/enterprise#123564
This fixes a mismatch between product identifiers sent to Google Analytics 4 and Google Merchant Center. It helps ensure product performance and advertising data line up correctly across Google reporting tools.
Original PR description
Commit d0bf183b053eb0133e6f7e5e04481ca07bff6bb3 fixes a mismatch between GA4 `item_id` and GMC `id` but was missing the fix in `_get_google_analytics_data` method opw-6443326 Forward-Port-Of: odoo/odoo#287590
This fix prevents automated tests from intermittently changing login sessions while password checks are being simplified for faster test runs. It makes test results more reliable without changing normal user-facing behavior.
Original PR description
The test harness patches the CryptContext object to spend less time hashing to decrease total test runtime. Because the hash parameters have changed, every first login (per transaction) per user will result in a hash rotation. The session_id is also rotated when the password hash rotates. When multiple requests are sent to the server while a session rotation is underway, the session datastore holds either a valid, or expired, or logged-out user session. This is a source of indeterminism in tests that can be prevented by always returning None value for replacement hash. REF Runbot; https://runbot.odoo.com/odoo/error/242811 REF Runbot; https://runbot.odoo.com/odoo/error/233722 Forward-Port-Of: odoo/odoo#287350 Forward-Port-Of: odoo/odoo#285710
Fixed an issue where creating or updating a time off request could incorrectly save a one-day request as zero days when an automation rule created an activity. This ensures automated activity notifications no longer interfere with time off duration calculations, improving reliability for HR workflows.
Original PR description
Problem: A time off request is saved with a duration of 0 days instead of 1 day as soon as an automation rule on Time Off has an action that creates an activity. Cause: `_compute_field_value` runs…
Problem: A time off request is saved with a duration of 0 days instead of 1 day as soon as an automation rule on Time Off has an action that creates an activity. Cause: `_compute_field_value` runs the actions while the fields of the computation it wraps are still protected and not written yet. On `hr.leave` the actions run from a computation nested in `_compute_date_from_to`, so `date_from` is empty when the activity notification reads `display_name`, and `_compute_duration` stores 0. Setting `date_from` afterwards does not mark `number_of_days` to compute again since it is protected. Solution: Extract the snapshot `_filter_pre` already does into `_keep_to_compute` and wrap the post filter and the actions with it, so the fields depending on the running computation stay to compute. Same treatment as 488419a5ca49 (odoo/odoo#243611) on the pre filter. Steps to reproduce: - Enable the developer mode. - Go to Settings > Technical > Automation Rules and create a rule on the Time Off model with the trigger On create and edit. - Add an action of type Create Activity, set its Responsible to another user, and save. - Go to Time Off > New, pick an employee and a time off type, and request one working day. - Observe that the request shows a duration of 0 days. Ticket [link](https://www.odoo.com/odoo/project.task/6498783) opw-6498783 Forward-Port-Of: odoo/odoo#287536 Forward-Port-Of: odoo/odoo#286791
Refund and credit note lines without a product now suggest the correct type of account based on whether the document is for a sale or a purchase. This prevents customer credit notes from using expense accounts and vendor credit notes from using income accounts, reducing accounting errors for contacts used as both customers and vendors.
Original PR description
Before this commit, when adding a line without a product to an invoice or credit note, `_get_most_frequent_account_for_partner` picked the partner's most-used account, filtered to an income or…
Before this commit, when adding a line without a product to an invoice or credit note, `_get_most_frequent_account_for_partner` picked the partner's most-used account, filtered to an income or expense account depending on `get_inbound_types` and `get_outbound_types`. Those helpers classify move types by cash-flow direction which is correct for choosing a receivable and payable account but wrong for choosing an income ro expense account: they group `in_refund` with `out_invoice` as "inbound", and `out_refund` with `in_invoice` as "outbound". As a result, a Vendor Credit Note line with no product would be filtered to income accounts instead of expense accounts, and a Customer Credit Note line to expense accounts instead of income accounts. This only surfaced for contacts who are both customer and vendor, since the query needs matching history to return a result; otherwise it silently falls back to the journal's default account, masking the bug for ordinary contacts. This commit uses `get_sale_types` and `get_purchase_types` instead, which classify by document side, sale vs. purchase rather than cash-flow direction, matching the classification already used for product-based lines `is_sale_document` and `is_purchase_document` opw-6373124 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#278794 Forward-Port-Of: odoo/odoo#276846
The Point of Sale product configurator now hides extra-price labels when a fixed pricelist means those extras will not actually be charged. This prevents staff from seeing misleading add-on prices and helps keep checkout pricing clear and consistent for customers.
Original PR description
Steps to reproduce: - Create a pricelist "TAKEAWAY" with a fixed price of 10.00 on a product whose sales price is 20.00 - Create a preset "TAKEOUT" and set its pricelist to "TAKEAWAY" - Add to that…
Steps to reproduce: - Create a pricelist "TAKEAWAY" with a fixed price of 10.00 on a product whose sales price is 20.00 - Create a preset "TAKEOUT" and set its pricelist to "TAKEAWAY" - Add to that product an attribute with variant creation "Never", with a value carrying an extra price of 1.00 - In the POS, switch to the preset "TAKEOUT" and click the product Issue: The configurator advertises the attribute value with a "+ $ 1.00" badge, but that extra is charged nowhere: the title of the popup and the resulting order line both stay at the 10.00 of the pricelist. Cause: A fixed pricelist rule replaces the whole price of the product, the attribute extra prices included: _compute_price on product.pricelist.item returns fixed_price and never reaches _compute_base_price, the only place where _get_attributes_extra_price is taken into account. getPrice is a faithful port of that and overwrites `basePrice + price_extra` with rule.fixed_price. The configurator, however, rendered its badges out of value.price_extra alone, without ever asking what the pricelist of the order does with it. Fix: Only advertise an extra price when the price of the product actually reflects it, the way website_sale already does with the show_extra_price of _get_additionnal_combination_info. Asking getPrice covers more than a fixed rule: a rule based on another pricelist recurses with no extra either, and a full discount leaves nothing of it. Combo items keep their badges, since computeComboItems adds their extras on top of the combo price, like _get_combo_item_display_price does. opw-6528187 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286475
Point of Sale cash closing messages now include cash in and cash out movements even when the closing user does not have accounting access. This prevents incorrect closing differences from being recorded, helping store managers trust register closing reports.
Original PR description
Steps to reproduce: - Give a user PoS Administrator rights and no Accounting rights. - As that user, open a session with cash control, sell something paid in cash and do a Cash Out. - Close the…
Steps to reproduce: - Give a user PoS Administrator rights and no Accounting rights. - As that user, open a session with cash control, sell something paid in cash and do a Cash Out. - Close the register. The closing popup shows the expected cash (opening + cash payments - cash out); enter exactly that amount, the popup shows no difference. - Open the closed session: its chatter says "Closing difference: -X", X being the cash out amount, and "Closing expected" is the amount before the cash out. Issue: The closing control data agrees with the user, but the closing message records a cash difference equal to the cash out. Cause: `_compute_cash_balance` sums `statement_line_ids` with the rights of the current user. Cash moves are `account.bank.statement.line` records, which `_inherits` `account.move`, so the account.move record rules apply to them too. For a PoS user the only such rule is `rule_invoice_pos_user` (`pos_order_ids != False`), and a cash move has no PoS order: unless the user is in `account.group_account_invoice`, their own cash moves are filtered out and `cash_register_balance_end` ignores them, while `get_closing_control_data` sums the lines in sudo. Since da8be203ec32 PoS managers can create cash moves without any accounting group, which made this visible: in 18.0 cash in/out required `account.group_account_invoice`, whose `account_move_see_all` rule makes every move readable. The values stored at closing are right by accident: `_validate_session` reads the lines in sudo just before, and the one2many cache is shared between the sudo and non-sudo environments of the same transaction. The message posted by `update_closing_control_state_session` runs in its own transaction and gets the filtered sum. Fix: Read the cash lines in sudo in `_compute_cash_balance`, as `get_closing_control_data`, `get_cash_in_out_list` and `_validate_session` already do. Users with accounting rights see all lines already, so nothing changes for them. opw-6521591 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286308