Daily updates from Odoo
Monday, July 6, 2026
28 changes · master
New functionality added to Odoo
Adds Belgian payroll reporting for forms 274.20 and 281.20, covering remuneration and withholding tax declarations for company executives. This helps businesses meet Belgian payroll compliance requirements more directly within Odoo.
Enhancements to existing features
Belgian payroll reporting now includes pool cars in both vehicle lists and related employer contribution calculations. This helps companies keep fleet-related payroll declarations more complete and aligned with Belgian reporting requirements.
Original PR description
. Add pool cars in vehicles list . Add pool cars in vehicles contribution task-6147532
Australian payroll data has been reorganized to make setup and leave handling clearer, with updated demo employees and contracts for easier demonstrations. Payroll and reporting wording has also been adjusted to better match Australian business terminology, improving usability for local teams.
Original PR description
## [IMP] l10n_au_hr_payroll{_{account,api}}: Revamp tooltips, master and demo data This revamp ensures AU-specific payroll and leave handling is more organized and demo-ready. Update…
## [IMP] l10n_au_hr_payroll{_{account,api}}: Revamp tooltips, master and demo data
This revamp ensures AU-specific payroll and leave handling is more organized and demo-ready.
Update hr_work_entry_type_data:
- Group work entry types by global/AU categories.
- Set default working schedule for AU companies to 38-hour resource calendar.
- Add time off types and convert paid time off to annual leave (avoid delete/create; update in place).
Demo data enhancements:
- Add employee images.
- Include country_id (AU) in hr_employee_demo.
- Revamped demo employee and contract data (9 employees in total)
Additional fixes and improvements:
- Add tooltips for improved UX in payroll/leave interfaces.
task-[5112796](https://www.odoo.com/odoo/all-tasks/5112796)
odoo/odoo#233335
odoo/upgrade#10302
## [I18N] account_reports, hr_payroll: revamp AU translations
We update some translations to be more aligned with local lingo.
task-[5112796](https://www.odoo.com/odoo/all-tasks/5112796)
odoo/odoo#233335
odoo/upgrade#10302Saudi payroll now supports a 30-day pay schedule so salary calculations can follow local legal requirements more accurately. The update also improves unpaid leave, GOSI contribution, allowance, and deferred amount calculations, helping reduce payroll discrepancies across different month lengths.
Original PR description
Purpose: Calculations in SA localization are done based on 30-day month basis accourding to the law. So, in this task we add 30 day pay schedule to allow 3 options: - Wroking days => Set schedule pay…
Purpose:
Calculations in SA localization are done based on 30-day month basis accourding to the law.
So, in this task we add 30 day pay schedule to allow 3 options:
- Wroking days => Set schedule pay as monthly and calendar as a normal one
- Calendar days => Set schedule pay as monthly and calendar as a full week one
- 30 days => Set schedule pay as 30_monthly and calendar as a full week one
Current behavior:
- changed the hardcoded value of 30 in salary rules to use number of days in the month accourding to the schedule pay
- corrected the computation of gosi rules to include all unpaid days in calculation
- changed some rules to use the correct category of `SA_ALW` instead of all allowances
- added a rule for deferred amount for the cases where the the employee took a full unpaid month in a non 30-day month
- added tests for 30-day pay schedule
Note that changes in tests are mainly because we are using working days instead of hardcoded 30 days per month
task-id: 6102084The UrbanPiper POS test order wizard can now validate discounts applied to individual products, not just the full order. Test order data also includes product taxes, making trial orders better match real customer orders and reducing the chance of configuration issues going unnoticed.
Original PR description
### Before this commit: - Test order wizard only supported order-level discount. - No way to validate line discount behavior from the test order flow. - Product Taxes were not included in the test order payload. ### After this commit: - Added `line_discount` field in the test order wizard. - Updated test order payload to include item-level discounts. - Added taxes in the test order payload based on the product taxes. - Updated test case to use the new `line_discount` field instead of passing it through context. Task:5240346
The timesheet timer now opens with a likely project already selected based on the employee's recent timesheets, while still prioritizing the project or task currently being viewed. It also avoids preselecting archived or template records, reducing mistakes and making time logging faster.
Original PR description
When opening the timesheet systray, the timer is now prefilled with the project to which the employee's three most recent timesheets are all linked, since they are most likely to keep logging time on it. The currently viewed project or task takes precedence over the favorite project, and viewing a project form now prefills the timer as well, just like task views already do. task-6290859 Forward-Port-Of: odoo/enterprise#120028
Payment users can now start individual or batch payments directly from the payment list, with clearer grouping, clickable batches, and visible Payment Initiation Service status. This reduces navigation and helps users understand which payments will be included before launching a bulk payment.
Original PR description
This commit makes the payment initiation workflow more intuitive
and accessible with the following UX improvements:
- Group payments by batch by default (since the batch list view
is restricted to debug mode).
- Add inline "Pay" buttons directly to standalone payment rows
and batch group-by headers.
- Make batches clickable directly from the list view.
- Display the PIS (Payment Initiation Service) status directly
in the list view for better visibility.
- Allow users to initiate a bulk payment from any single payment
within a batch. This includes a confirmation screen showing
all related payments in the batch before execution.
PS: Adding conditional buttons in the groupby batch_payment_id will
raise a traceback for empty lists if sample data is enabled. Turning
it off here to avoid the traceback. Will be reverted after the JS
team makes a fix.
task-6123868Budget reporting now uses the same profitability setting as analytic profitability views when selecting analytic lines. This helps ensure budget figures are consistent with profitability analysis, reducing confusion when comparing reports.
Original PR description
Use the new field analytic_profitability in the conditions of the query to get the account analytic lines of the budget report task-4959636 Forward-Port-Of: odoo/enterprise#121760
Online delivery order cancellations are now recorded in the point-of-sale order history with who cancelled them and why. This gives teams clearer visibility into cancellation patterns and makes support or operational investigations easier.
Original PR description
Before this commit: ------------------- - Online delivery order cancellations were not logged, making it difficult to determine the cancellation source and reason. After this commit: ------------------ - Added logs for online delivery order cancellations in the PoS order chatter. - The logs now capture both the cancellation source (Odoo POS or UrbanPiper) and the cancellation reason, improving traceability and debugging. Task-6244499
Planning reports now show time-based figures in hours and use clearer business-friendly labels such as Actual Time, Actual Revenue, and Time Variance. Report names and empty-state guidance were also improved, making it easier for users to understand planned versus actual work and project progress.
Original PR description
* _ = project_timesheet_forecast, project_timesheet_forecast_sale - Format all time-based measures in hours for better readability and consistency - Rename measures for clarity: | Initial Value | New…
* _ = project_timesheet_forecast, project_timesheet_forecast_sale - Format all time-based measures in hours for better readability and consistency - Rename measures for clarity: | Initial Value | New Value | |-----------------------------|-----------------------------------| | Effective Billable Time | Actual Billable Time | | Effective Margin | Actual Margin | | Effective Non-Billable Time | Actual Non-Billable Time | | Effective Revenues | Actual Revenue | | Effective Time | Actual Time | | Effective Costs | Actual Costs | | Planned Revenues | Planned Revenue | | Time Remaining | Time Variance (Planned − Actual) | | Time Difference | Time Variance (Planned − Actual) | - Rename reports: | Initial Value | New Value | |--------------------------------|-----------------------------------| | planning / timesheets analysis | Planning & Timesheets Analysis | | planning / attendance analysis | Planning & Attendance Analysis | - Improve empty state helper message: `No data yet! Track your projects’ progress by comparing planned hours with actual hours recorded` Task: 6042556
French fiscal declaration reports now let users choose a year with a date picker instead of typing it as plain text. This reduces entry mistakes and makes the reporting workflow clearer for users completing the declaration.
Original PR description
For the french fiscal declaration, a line of the report needed a year to be enter by the user. Instead of using a string, we think it would be better to use a datetime picker with the precision of the year. This commit will introduce that. task-6159852
Resolved issues and error corrections
Refunded point-of-sale orders that fully cancel out matching pay-later customer account payments are now hidden from the Settle Orders list. This reduces clutter and prevents staff from seeing orders that no longer require settlement, making customer account balances clearer.
Original PR description
Currently, if you refund an order made on the customer account with the customer account as well, you can still see the order in the list of orders to settle. Steps to reproduce: -------------------…
Currently, if you refund an order made on the customer account with the customer account as well, you can still see the order in the list of orders to settle. Steps to reproduce: ------------------- * Open shop * Make an order using the customer account for a customer, don't invoice it * Refund one of the order using the customer account, don't invoice it * Make a new order using the customer account * In the customer list, find the customer used and select "Settle Orders" > The 2 orders are present in the list Why the fix: ------------ Originally the list would only show the orders for chich the customers have due (>0). https://github.com/odoo/enterprise/commit/bf4b6043b999b4a081b1afa73fc4113bf4db28f8 But recently the code we also see the refunds in the list as well. https://github.com/odoo/enterprise/commit/12af23d5382e972facfaa999e4c5ab30c97e8d1f However this new behavior is not visible if, with the refund, the customer account temporarily falls to 0. So currently we have some refunds that impact the amount to settle and some that don't. Originally we were thinking that either we should show all refunds in that list (given they use the customer account) or we shouldn't show any as it was previously. Both solutions are not ideal. * Showing them all would get the list bigger than it is and would require the customer to select the order and its refund(s) and settle them together. Since refunds are not usually done right after the order they would not be close it that list. However this solution would enable the option to remove the orders from the list requiring a few step from the customer. * Showing none isn't idea either with this use case as it means that we still see orders that were cancelled out by their refunds. To remove to order the customer has two options. Either going backend and searching the order and its refund(s) and invoice them, either settling the order but that means that now there's money deposited on the customer account. Any of the two option isn't perfect a it still requires manual intervention from the customer and wouldn't work on previous data. Creating a server action to correct those data wouldn't have been feasible either. Instead, the approach we're taking is the following: When loading the list of order to settle we want to remove the orders and the potential refunds were the customer account is evened out. We only need to look at the orders of the partners that contains refunds for which the customer account was used. If the sum of the transactions made on the customer account is 0 we can say that the order and its refunds have cancelled out each other (in terms of customer account) and we don't show them if the list of orders remaining to settle. opw-6170830 Forward-Port-Of: odoo/enterprise#117725
This update adjusts how rental product availability is handled in the online store. It helps ensure customers see more accurate stock information when browsing or booking rental products, reducing confusion and potential order issues.
Original PR description
task-4826932 See also: - https://github.com/odoo/odoo/pull/265605
The Belgian payroll dashboard now opens reliably when checking the "Employees Under Minimum Wage" warning for companies with multiple CP200 employees. This prevents an error that could block payroll teams from reviewing wage compliance alerts.
Original PR description
Opening the Payroll dashboard may crashes with an error: - Have a Belgian company with 2 or more CP200 employees having active contracts - Create or modify a contract for at least 2 of them (this marks l10n_be_computed_seniority_years as dirty for the batch) - Open the Payroll dashboard, the "Employees Under Minimum Wage" warning evaluation crashes In _compute_l10n_be_computed_seniority, the for version in cp200_versions loop was incorrectly referencing self instead of version. Since self is the full batch recordset, self.employee_id returns a multi-record set, causing ensure_one() to fail inside _get_first_version_date. task-6358718
Belgian payroll now calculates bike reimbursements using the correct rates for CP200 and CP302 employees, instead of applying an incorrect daily cap. This helps ensure payslips reflect the legally expected reimbursement amounts and reduces payroll correction work.
Original PR description
Steps to reproduce: 1. Employee Setup: CP200 or CP302, Bike 18 km. 2. Action: Compute 05/2026 payslip (21 worked days) and close pay. For CP200 - Got 226.80€ (stuck at 10.80€/day cap) -> Expected 204.12€ (18 km × 2 × 21 days × 0.27€). Solution: - Add missing rule parameters and update existing. - Update Python logic to use rates based on Joint Committee code with safe national fallback. Task: 6334739
This fixes an issue that prevented some Point of Sale users from opening the Preparation Display app due to an access rights check. The app now opens directly through the correct link, allowing authorized users with read access to use it as expected.
Original PR description
A recent fix in the base module requires users to have "write" access on ao model to execute its server actions. commit: odoo/odoo@846eb51a02baaf2e4f6e6780f8d0ceb23322c38b Previously, the Preparation Display was opened through the server action `action_pos_preparation_display_kitchen_display`, which then redirected to the URL action `action_pos_preparation_display_bar_restaurant_filter_link`. However, PS users only have read access on `pos.prep.display`. As a result, executing the server action triggers an access error when opening the Preparation Display. This commit bypasses the intermediate server action and opens the URL action directly. Task-6311320 Forward-Port-Of: odoo/enterprise#121293
Users responsible for rental pickups can now create and process rental orders even when they do not have broader inventory permissions. This removes an access error that blocked rental workflows while keeping the permission change limited to the rental transfer role.
Original PR description
Issue: --- It's not possible to create rental orders without stock.lot access. Steps to reproduce: 1- Change demo user access: - All inventory accesses: No 2- Enable `Rental Transfers`. 3- Login Demo user. 4- Create a rental order. You will get access error. Cause and Fix: --- `stock.lot` model is in only accessed by `group_stock_user`. As a result fields such as `reserved_lot_ids` will be problematic when we don't have stock access. We initially tried to fix the issue by limiting the problematic fields to group stock user. However that limits the user from rental pickup. Instead we are giving the required access to group rental picking user. opw-6281154 Forward-Port-Of: odoo/enterprise#122507 Forward-Port-Of: odoo/enterprise#120670
Contract versions now correctly receive additional values from contract templates, including hourly wage, analytic distribution, Monster integration, and attendance-based settings. This reduces manual corrections and helps ensure employee contract data stays consistent when templates are used.
Original PR description
[IMP] hr: adjustment in contract template loading Some fields were not loading from contract template to the versions properly. In standard modules (not localization ones), I have detected the fields that are in contract template form view but that is not loaded to versions. I have found that hourly_wage, analytic_distribution, monster_id, attendance_based are the missing ones. There are other some missing ones but they are computed so they are already computed from other things so they shouldn't be loaded directly. task - 6344952
Overtime durations are now stored with higher precision, reducing rounding errors that could slightly affect payroll amounts. This helps ensure employees are paid more accurately for overtime worked.
Original PR description
Overtime duration computed as fractional hours was rounded to 3 decimal places before being stored on the overtime line. Since 1 decimal hour = 3600 seconds, this gives only 3.6 seconds of precision and the rounding can go in the wrong direction due to floating-point representation. The fix consists in replacing the duration rounding to 4 decimals when building overtime work entries so stored durations keep sub-second precision needed for money computation. task-6212231 Forward-Port-Of: odoo/enterprise#122227 Forward-Port-Of: odoo/enterprise#119721
The My Timesheets view now uses the employee's own working schedule when showing unavailable days. This prevents users from accidentally logging time on days that were removed from their personal schedule.
Original PR description
To reproduce: ============= - modify Mitchel Admin's working schedule and remove a day of work - open timesheet app as Mitchel Admin - the removed day is not grayed out as unavailable Porblem: ======== the method `get_unavailabily` was handling only the case when calling it with `groupby=employee_id` otherwise it returns the company's unvailability Solution: ========= when the "My Timesheet" action is opened, the method `get_unavailabily` is now called with a specific context key, allowing to return the current user's unavailability instead of the company's one. opw-5949236 Forward-Port-Of: odoo/enterprise#122936 Forward-Port-Of: odoo/enterprise#113984
Fixed an issue where scanning a package during a warehouse picking could be incorrectly blocked when extra products were not allowed. The barcode app now correctly treats the scanned package as the destination package when appropriate, avoiding unnecessary errors and keeping warehouse workflows moving.
Original PR description
### Steps to reproduce: - In the settings Enable: Multi-steps Routes, Packages - Put your warehouse in delivery in 2-steps - On the Pick operation type in the barcode tab disable: "Allow extra…
### Steps to reproduce: - In the settings Enable: Multi-steps Routes, Packages - Put your warehouse in delivery in 2-steps - On the Pick operation type in the barcode tab disable: "Allow extra products" - Create two storable products P1 and P2 - On P2 > On hand > Update Quantity > New - Create a new line in WH/Output with a package POOK for 1 unit - Create a new internal transfer for 1 unit of P1 using the pick operation type so that the picking goes WH/Stock -> WH/Output - Set the quantity of the move to 1 unit and go to the barcode app - Open the Pick > Scan WH-STOCK > Scan P1 > Scan POOK #### > An error is raised: This package contains extra products and extra products are not allowed on this operation. #### Expected behavior: The package should be set as result package. ### Cause of the issue: In the `_processPackage`, a check that is done to ensure that the package scan will not add extraproduct to the picking if this operation is not allowed: https://github.com/odoo/enterprise/blob/5e4c8ecb0c644e21755570ed59cd8f6e9f618c8a/stock_barcode/static/src/models/barcode_picking_model.js#L2024-L2035 Unfortunately, this check is done just before a possible usage of the package as package dest. And, in that case, since we do not try to add any product to the picking the check is irrelevant anyway. opw-6303969 Forward-Port-Of: odoo/enterprise#122782 Forward-Port-Of: odoo/enterprise#121789
When a signing request is completed, the person who sent it now receives the expected notification in their Odoo inbox if they use the "Handle in Odoo" preference. This helps users track completed documents without relying on email notifications.
Original PR description
## Issue When a user sets their notification to "Handle in Odoo" (`inbox`) and a sign requested is completed, they do not receive the expected notification. ## Steps to reproduce 1. Install *Sign*…
## Issue When a user sets their notification to "Handle in Odoo" (`inbox`) and a sign requested is completed, they do not receive the expected notification. ## Steps to reproduce 1. Install *Sign* (`sign`) 2. Sets the current user's notification preference to "Handle in Odoo" (`inbox`) 3. Create a sign request and send it to Marc Demo 4. As Marc Demo, sign the request 5. **The user who sent the sign request did not receive a notification to notify them that the request was signed.** ## Fix This is a partial backport of both https://github.com/odoo/enterprise/commit/463d6a2aae536356e6dee6b902f2e881dbc4fbda (saas-18.2) and a related fix https://github.com/odoo/enterprise/commit/41395dba4fd31222f5fd9fc94a84c977cf9334f9 (19.0). Before the first commit, users would not receive inbox notification when sign requests would be completed. ## Note to reviewer The issue only occurs in 18.0, as it is fixed by https://github.com/odoo/enterprise/commit/463d6a2aae536356e6dee6b902f2e881dbc4fbda in 18.2, but we can backport the fix from https://github.com/odoo/enterprise/commit/41395dba4fd31222f5fd9fc94a84c977cf9334f9 from 18.2 to 18.4 if desired. opw-6251702 Forward-Port-Of: odoo/enterprise#122740 Forward-Port-Of: odoo/enterprise#120740
Fixes an error that could occur when shortening the deadline of a standalone task in the Project Gantt view. Users can now adjust a single task's deadline without triggering a server error, making project scheduling more reliable.
Original PR description
## Current behavior: In the Project's app, switch to Gantt chart's view, when changing the deadline of a single task by shrinking its right edge, the server throws `ValueError: max() iterable…
## Current behavior: In the Project's app, switch to Gantt chart's view, when changing the deadline of a single task by shrinking its right edge, the server throws `ValueError: max() iterable argument`` is empty when calling end_date = max(candidates.mapped(stop_date_field_name)). ## Steps to reproduce: 1. In version 19.0 and above, install Project app 2. Create a project and only 1 single task 3. Switch to Gantt chart view 4. Try changing the deadline of a task by dragging its right edge 5. Observe that extending the task's deadline by dragging to the right works fine, but shrinking the deadline by dragging to the left will cause server to throw RPC_ERROR: Odoo Server Error and ValueError: max() iterable argument is empty. ## Cause of the issue: - A task with NO successors will cause candidates gathered via dependency_inverted_field_name to be empty. - The empty candidates recordset then get called by max(candidates.mapped(stop_date_field_name)), which is the reason causing error message ValueError: max() iterable argument is empty. opw-6283566 Forward-Port-Of: odoo/enterprise#121517 Forward-Port-Of: odoo/enterprise#120375
The Belgian salary package calculation now follows the legal mobility budget rules more accurately. It excludes holiday allowance and warrant-paid 13th month amounts where required, while including relevant commissions from the last 12 months, helping employers avoid incorrect budget caps.
Original PR description
https://lebudgetmobilite.be/fr/6-quel-est-le-montant-du-budget-mobilite#remunerationtotalebrute Simple and double holiday allowance should not be not accounted in the total brut remuneration for the cap of 20% for the mobility budget. 13th month should not be included if it is paid in warrants. This commit fixes the max mobility budhet amount computation by multipling the wage by 12.08 instead of 13, as we remove the simple holiday allowance. Ratio = 12 months + 13th month (except if paid in warrant) - Simple holiday allowance (0.92 month) Also, Commissions should included: Sum the commissions on payslips of the last 12 months for this employee. MB_Budget = monthly_wage * ratio / 5 + commissions Task-5948733
This fix makes USPS package dimensions display their unit of measure so users know whether they are entering inches or feet. It also ensures USPS shipping quotes reflect the selected package/service type instead of returning the same rate across different options.
Original PR description
Issue ----- There are 2 issues with USPS rest: 1. USPS packagings do not have their size UOM displayed. This leads to confusion as users input in inches but the dimensions are treated as feet. 2.…
Issue ----- There are 2 issues with USPS rest: 1. USPS packagings do not have their size UOM displayed. This leads to confusion as users input in inches but the dimensions are treated as feet. 2. USPS returns the same rate regardless of the package type. Steps to reproduce ----- - Set USPS up - Open the Package Type form > go to its' Dimensions tab > Issue 1 - Set USPS up (domestic) - Select a `Domestic Rating Indicator` (eg LF - Flat Rate Box) - Create a SO with some product - Open the delivery widget and add a rate with USPS - Discard the changes - Go to the delivery method and change the rating (eg SP - Single Piece) - Go back to the SO - Open the delivery widget and add a rate with USPS > Issue 2, rate is the same as before Issue 1 ----- By default, there is no displayed UOM on the form because of https://github.com/odoo/odoo/blob/38c737c2a4cc29b48235a100cfa9d6152af73826/addons/stock_delivery/models/stock_package_type.py#L20-L33 We can change this behaviour for USPS specifically as done in Envia https://github.com/odoo/enterprise/blob/20cc61e69aa3f6a59de1e962b25ce11fa402bf22/delivery_envia/models/stock_package_type.py#L37-L46 Issue 2 ----- In `usps_rest_rate_shipment`, we request rates for every package of the delivery, which we receive as lists. We then iterate over the list to find the rate matching the `mail_class`. The problem is that this only filters over whether the delivery is domestic or international. We don't filter based on the actual service selected on the carrier (`usps_domestic_rating_indicator` for domestic and `usps_international_rating_indicator` for international). https://github.com/odoo/enterprise/blob/20cc61e69aa3f6a59de1e962b25ce11fa402bf22/delivery_usps_rest/models/delivery_usps.py#L224-L236 ----- Ticket: opw-6224918 Forward-Port-Of: odoo/enterprise#120789 Forward-Port-Of: odoo/enterprise#120594
Planning now calculates multi-day shift templates correctly on round-the-clock schedules. This prevents an extra minute from appearing in allocated time, keeping planning reports accurate for affected customers.
Original PR description
**Problem:** On a round-the-clock (0h-24h) working schedule, shifts created from a shift template that spans more than one day get one extra minute added to their allocated time: an 8-hour shift…
**Problem:** On a round-the-clock (0h-24h) working schedule, shifts created from a shift template that spans more than one day get one extra minute added to their allocated time: an 8-hour shift shows 08:01. **Steps to reproduce:** 1. Create a working schedule with a 00:00 -> 24:00 attendance for every day (24h/day, "Full Day"). 2. Assign an employee to that schedule. 3. Create a multi-day-span shift template (e.g. 16:00 -> 00:00, 2 days). 4. Plan a shift for that employee using the template. 5. Observe the Allocated Time shows one minute more than expected (08:01). **Current behavior:** Allocated Time is one minute too long (e.g. 08:01 instead of 08:00), which throws off the customer's planning reports. **Expected behavior:** Allocated Time matches the template duration exactly (08:00). **Cause of the issue:** In `_calculate_start_end_dates`, the end of a multi-day-span shift is computed with `resource.calendar_id.plan_days(...)`. On a 0h-24h calendar each day ends at `time.max` (23:59:59.999999), so `plan_days` returns an end datetime carrying those stale seconds. The following `end.replace(hour=..., minute=...)` overwrites only the hour and minute, leaving `second=59, microsecond=999999`. The slot is therefore ~1 minute longer than intended, and `allocated_hours` rounds that up to 08:01. **Fix:** Resetting seconds and microseconds when rebuilding the end datetime keeps the slot aligned to the template's whole-minute boundary, regardless of how the underlying calendar represents the end of day. The hour/minute already come from the template, so the leftover sub-minute precision from `plan_days` is never meaningful and is what produces the drift. opw-6265238 Forward-Port-Of: odoo/enterprise#122157 Forward-Port-Of: odoo/enterprise#120219
PDF form fields are now locked during the signing process instead of being flattened in a way that could change how documents look. This keeps signed documents visually consistent for users while preventing fields from being edited during signing.
Original PR description
Currently, we flatten fields in a naive way which does not handle many edge cases and can alter the PDF appearance for users. We could use pypdf to handle production-grade flattening, but Odoo's `pypdf` dependency (5.4.0) does not support native form field flattening (which was introduced in 5.8.0). To resolve this, rather than flattening, we lock the interactive fields so they are no longer editable while signing, which perfectly maintains the original appearance. In the future, when we support higher pypdf versions, we can truly flatten the PDF to provide a better user experience. task-6037759 Forward-Port-Of: odoo/enterprise#122966 Forward-Port-Of: odoo/enterprise#112351
Batch bank reconciliations now combine duplicate early payment discount entries when multiple invoices share the same tax. This prevents the tax return report from overstating the discount base, helping accounting teams rely on accurate tax figures.
Original PR description
Steps to reproduce 1. Create two customer invoices that share the same VAT tax, with a payment term granting an early payment discount. 2. Create one bank statement line whose amount equals the sum…
Steps to reproduce 1. Create two customer invoices that share the same VAT tax, with a payment term granting an early payment discount. 2. Create one bank statement line whose amount equals the sum of both invoices' discounted totals. 3. From the bank reconciliation widget, select both invoices and validate in a single batch reconciliation. 4. Open Accounting > Reporting > Tax Return, switch the variant to "Group by: Account > Tax". Issue The cash-discount expense row shows a Net base column equal to twice the real discount base. The Tax column is correct. The bank-statement reconciliation paths (set_line_bank_statement_line, set_batch_payment_bank_statement_line, _reconcile_payments) loop over each invoice and call _apply_early_payment_discount one invoice at a time. Each call writes one discount base line and one discount tax line on the resulting bank entry, so when two invoices share the same tax the bank entry ends up with two pairs carrying the same (account, partner, currency, tax_repartition_line_id, tax_ids). The SQL that feeds the tax report at https://github.com/odoo/odoo/blob/d7d0efd39a65bfb6fee307b661cd2523a6b8231d/addons/account/models/account_move_line_tax_details.py#L100 matches every base line of a tax with every tax line of that tax inside the same move. With two pairs sharing one tax that turns two rows into four, and SUM(base_amount) doubles. The Tax column does not double because the same SQL redistributes each tax line's recorded amount across its matched rows so the totals still add back to the original tax. The payment register flow does not have this problem because it calls _get_invoice_counterpart_amls_for_early_payment_discount once with every invoice, and that helper already collapses duplicates with the merge key at https://github.com/odoo/odoo/blob/f3b317310b84edb073009f7d15d7fec002f3ccf0/addons/account/models/account_move.py#L5082-L5093 opw-6199906 Forward-Port-Of: odoo/enterprise#122747 Forward-Port-Of: odoo/enterprise#117743