Thursday, July 2, 2026
12 changes · 19.0
New functionality added to Odoo
This update establishes the initial payroll framework for Oman, including a local working schedule, demo company and employee data, and a first version of the salary structure. It helps businesses test and prepare payroll processes that match local working patterns and compensation rules.
Enhancements to existing features
Odoo can now use the Central Bank of Azerbaijan as a source for daily currency rates. This helps companies with multi-currency accounting and taxable transactions convert amounts into Azerbaijani manat (AZN) using the official rate for the transaction day.
Original PR description
This commit adds the Central Bank of Azerbaijan (CBA) as a supported service provider for automatic currency rate updates. Purpose: To ensure multi-currency accounting entries and taxable transactions are accurately translated into the national currency (AZN) using the official exchange rate defined by the CBA for the transaction day. Functionality: -Enables fetching official daily exchange rates directly from CBA via XML. -Automatically handles rates defined for different nominal quantities (e.g., rates quoted per 100 units instead of 1 unit). Related Community PR: https://github.com/odoo/odoo/pull/262471 Related Upgrade PR: https://github.com/odoo/upgrade/pull/10107 task-6112867
Resolved issues and error corrections
The planning preview now displays shifts exactly as they were created, instead of splitting them into smaller parts based on working schedules. It also avoids timezone-related visual offsets, so users see the correct shift times in the preview.
Original PR description
Steps to reproduce: ------------------------- 1. Install Planning. 2. Create a full day shift (8h) for an employee with a fully fixed schedule. 3. Publish the shift. 4. Go to Actions > Preview and…
Steps to reproduce: ------------------------- 1. Install Planning. 2. Create a full day shift (8h) for an employee with a fully fixed schedule. 3. Publish the shift. 4. Go to Actions > Preview and observe the shift. Current behavior and issue: ------------------------------------ **Shift Splitting:** The system fetches [attendance_intervals](https://github.com/odoo/enterprise/blob/6318e5cc392324ed43f3d34cf3f1282f055a5b3e/planning/controllers/main.py#L122) based on the employee's working schedule and cuts the shift into multiple segments (e.g., splitting a full-day shift to exclude lunch time). **Incorrect Start/End Time:** The preview displays shift starting times based on the working schedule rather than the specific times defined during shift creation. **Timezone Discrepancy:** When a normal shift (e.g., 11 AM to 12 PM) is created, it is stored in UTC based on the current user's timezone. However, the preview calculation attempts to convert this value to the [employee's timezone](https://github.com/odoo/enterprise/blob/2a97fd904d618b123500920c082e8e5537d27e56/planning/controllers/main.py#L58), leading to a visual offset if the user and employee are in different regions. Expected behavior: ------------------------ 1. Full-day shifts should be displayed as a single continuous block and should not be cut based on work intervals/attendance. 2. The previewed shift time should exactly match the created shift time. opw-5913944 **Before fix:** <img width="1372" height="738" alt="image" src="https://github.com/user-attachments/assets/7dbbf161-85c3-440d-bd4f-9d481656e016" /> **After fix:** <img width="1458" height="875" alt="image" src="https://github.com/user-attachments/assets/6e058468-aebc-438a-a7c3-3fb9b5c2eda8" />
This fix ensures that when capacity management is turned off, multiple bookings for the same time slot are all assigned to the first matching resource as expected. It restores the previous scheduling behavior so customers can book simultaneous appointments without bookings being spread across different resources.
Original PR description
Steps to reproduce: 1. Install Appointments, create an Appointment Type scheduled based on Resources and add 3 resources. 2. Uncheck "Manage Capacities" on the Appointment configuration. 3. Set 3…
Steps to reproduce: 1. Install Appointments, create an Appointment Type scheduled based on Resources and add 3 resources. 2. Uncheck "Manage Capacities" on the Appointment configuration. 3. Set 3 simultaneous appointments per resource. 4. Click on Share and open the given link in a new tab. 5. Attempt to book 3 appointments for the same slot in the portal. Issue: - If you click the Appointments smart button in the Appointment form, you will find each booking assgined to one of the resources Expected: - All 3 bookings should be assigned to the first resource Why this happens: - A recent performance optimization (9a48e80a3382bb4895013ae6b9b28e2827ac90aa) introduced batch resource checking via `_slot_available_resources`. - Inside its filter logic, the block evaluating unshareable resources was compressed into `not (self.manage_capacity and resource.shareable)`. - When `manage_capacity` is False, the resulting evaluation of that block of code marks the resource as unavailable, so the next resource was picked for the following appointment. - Before the optimization, the equivalent block explicitly returned True if `manage_capacity` was disabled opw-6344712
Payslips for Mexican employees now count only the days that fall within the selected payslip period. This prevents off-cycle payslips from showing too many worked days when the pay schedule is bi-weekly but the period is shorter.
Original PR description
## Issue With a Mexican company, creating a payslip for a duration of one week for an employee with a bi-weekly `schedule_pay`, the payslip will account for 15 days. ## Steps to reproduce 1. Install…
## Issue
With a Mexican company, creating a payslip for a duration of one week for an employee with a bi-weekly `schedule_pay`, the payslip will account for 15 days.
## Steps to reproduce
1. Install *"Mexico - Payroll"* (`l10n_mx_hr_payroll`)
2. Using a Mexican company, create a new Employee E:
- Set a contract (e.g., January 1st to Indefinite)
- Wage: $1,500.00 / Bi-weekly
3. In Payroll > Payslips > Payslips, create a new Off-Cycle
- Employee E
- Period: Any month, from the 8th to the 15th of the month
4. **In the _Worked Days_ tab, 15 days are counted, even though that's more than the period covers.**
## Cause
In the `_get_worked_day_lines` method, the amount of days is taken from `_get_schedule_days` [[1](https://github.com/odoo/enterprise/blob/7092dd2cc3578c3f22d1fbc82d958d57e2f93553/l10n_mx_hr_payroll/models/hr_payslip.py#L121)], which uses the `l10n_mx_schedule_table` [[2](https://github.com/odoo/enterprise/blob/7092dd2cc3578c3f22d1fbc82d958d57e2f93553/l10n_mx_hr_payroll/models/hr_payslip.py#L98-L102)] and returns `15` days for a bi-weekly schedule [[3](https://github.com/odoo/enterprise/blob/7092dd2cc3578c3f22d1fbc82d958d57e2f93553/l10n_mx_hr_payroll/data/hr_rule_parameters_data.xml#L850-L858)]. This does not take into account the period (`date_from`/`date_to`) of the payslip.
For the cases where the period is shorter than the default period (which usually either goes from the 1st to the 15th of the month, or the 16th to the end of the month), we should only consider the days within that period.
\[1]: https://github.com/odoo/enterprise/blob/7092dd2cc3578c3f22d1fbc82d958d57e2f93553/l10n_mx_hr_payroll/models/hr_payslip.py#L121
[2]: https://github.com/odoo/enterprise/blob/7092dd2cc3578c3f22d1fbc82d958d57e2f93553/l10n_mx_hr_payroll/models/hr_payslip.py#L98-L102
[3]: https://github.com/odoo/enterprise/blob/7092dd2cc3578c3f22d1fbc82d958d57e2f93553/l10n_mx_hr_payroll/data/hr_rule_parameters_data.xml#L850-L858
opw-6192639When users export an audit report to PDF, images inserted with the /file command will now appear correctly instead of being left out. This makes the exported document match what users see in the report and improves the readability of shared PDFs.
Original PR description
Currently, when a user uses the `/file` command to insert an image into an audit report and exports the report to PDF, the image is omitted from the generated PDF. To improve the support of those blocks, we will pre-process the document and replace the embedded files that correspond to images with standard image elements before PDF generation. This will ensure that images are correctly rendered and displayed within the document's text flow in the exported PDF. Task [link](https://www.odoo.com/odoo/project.task/5115280) task-5115280
When processing a package during barcode operations, the system now correctly recognizes it as the destination package instead of wrongly blocking it for containing other products. This prevents unnecessary errors during warehouse picking and lets operators complete transfers smoothly.
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
This fix ensures that product barcodes are recognized properly even when they could also be interpreted as a GS1 barcode format. As a result, scanning the same product multiple times will update the delivery quantity correctly instead of creating an incorrect extra line with a huge quantity.
Original PR description
In certain cases the barcode of a product could be a valid standalone GS1 sequence. In that case it needs to be be correctly interpreted as a product scan. ### Steps to reproduce: - In the settings…
In certain cases the barcode of a product could be a valid standalone GS1 sequence. In that case it needs to be be correctly interpreted as a product scan. ### Steps to reproduce: - In the settings enable "Default GS1 Nomenclature" - Create a storable product P with the barcode 3701762412212 - Create and confirm a delivery for 2 units of P and set the qty to 2 - Go to the barcode app and open your delivery - Scan 3701762412212 > The line of P is now selected with a quantity of 1/2 - Scan 3701762412212 #### > A new line is created for 1762411 units ### Cause of the issue: According to the GS1 nomenclature, the barcode 3701762412212 matches the scan of a quantity of "1762412" units of the lot name "2". As the scan of the of the product match a pattern for the GS1 nomenclature before matching a product, its barcode data is expected to be reset by these lines: https://github.com/odoo/enterprise/blob/7cceddaf086d849b8e2121e1023ef3479397534f/stock_barcode/static/src/models/barcode_model.js#L1320-L1324 In order to bypass the GS1 parser and to add 1 unit of the product. This is what happen on the first scan. However, performing the first scan also selects the associated line and, hence on the second scan the lines just above this check do set the product to match the product of the current line: https://github.com/odoo/enterprise/blob/7cceddaf086d849b8e2121e1023ef3479397534f/stock_barcode/static/src/models/barcode_model.js#L1294-L1320 In particular, we do not bypass the result provided by the GS1 parser and add `1762412` units of the product. opw-6175621 Forward-Port-Of: odoo/enterprise#122256 Forward-Port-Of: odoo/enterprise#120035
When users put products into a package from the barcode app, the system will now show the package type selection when that option is enabled. This makes the barcode flow behave consistently with the standard warehouse process and prevents users from missing an important packaging choice.
Original PR description
### Steps to reproduce: - In the settings enable: Packages - On the operation type `Delivery Order` set "Set Package Type" - Create and confirm a delivery for 1 unit of a product P + reserve it - Got…
### Steps to reproduce: - In the settings enable: Packages - On the operation type `Delivery Order` set "Set Package Type" - Create and confirm a delivery for 1 unit of a product P + reserve it - Got to the barcode app to process the delivery - Scan your product and click "put in pack" #### > The put in pack wizard allowing you to set a package type on the new package does not pop up. ### Cause of the issue: As a general rule of thumb the wizard is suppose to be displayed when the option is enabled and when a package/package type is not already provided to the call: https://github.com/odoo/odoo/blob/5dbc448d336c7ff22803ae91d5014eb6d0a07234/addons/stock/models/stock_package.py#L332-L341 https://github.com/odoo/odoo/blob/5dbc448d336c7ff22803ae91d5014eb6d0a07234/addons/stock/models/stock_move_line.py#L1236-L1238 However an override was added to the barcode module so that the wizard is never displayed when the action is launched from the barcode app: https://github.com/odoo/enterprise/blob/673d449f38cd3eff27c44270c8f7edf91d0ecd02/stock_barcode/models/stock_move_line.py#L193-L196 The idea behind this override was that you could provide the package type id via scans and hence that is was not necessary. However, if you click directly on the put in pack button, the wizard still make sense and should therefore be displayed under the same conditions. opw-6325092
Bookings made through Reserve with Google will now create contacts using the customer’s actual name when it is provided. This prevents new bookings from showing an email address instead of the person’s name in the contact card, calendar event, and attendee details.
Original PR description
When a customer books through Reserve with Google, the createBooking payload carries the booker given_name and family_name next to the email, but the handler passed only the normalized email to…
When a customer books through Reserve with Google, the createBooking payload carries the booker given_name and family_name next to the email, but the handler passed only the normalized email to _mail_find_partner_from_emails. The new res.partner was therefore created with its name falling back to the email, see https://github.com/odoo/odoo/blob/aa7b5921191a0ff53ef1cc32af99fe458c45c0da/addons/mail/models/res_partner.py#L177 That name then flows into the calendar.event name, the attendee common_name and the contact details, all showing the email instead of the customer name. The module has read neither field since it was added in https://github.com/odoo/enterprise/commit/2e855b910173b56e8501d0ebe9ee6f83ac5845bc. Build the booker name from given_name and family_name and pass it with the email through formataddr in google_reserve_booking_create, so a newly created partner is named after the customer. A partner matched on an existing email keeps its current name. Steps to reproduce: 1. Enable Reserve with Google on an appointment type. 2. Book a slot from Google Maps with given name John and family name Doe. 3. Open the created booking and its contact in Odoo. => the contact name is the email instead of John Doe Ticket [link](https://www.odoo.com/odoo/project/49/tasks/6232318) opw-6232318 Forward-Port-Of: odoo/enterprise#120604
This update cleans up data before it is sent to the Belgian POS blackbox, reducing the risk of rejected or inconsistent records. It also makes cashier clock-in and clock-out behavior more reliable, especially when the register is idle or multiple actions happen at once.
Original PR description
Trim strings data before sending to the blackbox. Also make clock-in/out more robust: - call handleClockInOut through a Mutex to avoid concurrent races - clock the cashier out when the idle SaverScreen is shown FW of this PR: https://github.com/odoo/enterprise/pull/122471
This update checks whether an invoice was already received by DIAN before sending it again. If DIAN already has the document, Odoo now marks it as accepted instead of retrying and getting stuck on a duplicate-processing error. This helps prevent invoices from being blocked when the first response was delayed or lost.
Original PR description
When a customer invoice is sent to the DIAN. This call can time out the DIAN is frequently slow or momentarily unavailable while the DIAN has in fact already received, validated and stored the…
When a customer invoice is sent to the DIAN. This call can time out the DIAN is frequently slow or momentarily unavailable while the DIAN has in fact already received, validated and stored the document. The response never reaches Odoo, the exchange is recorded as `invoice_sending_failed`, and the accepted CUFE is never linked back to the move. On the next attempt Odoo re-POSTs the same invoice number. The DIAN validates only one transmission per document and rejects the second with rule 90, "Documento procesado anteriormente" (document already processed). The invoice is then stuck: the DIAN considers it accepted, Odoo considers it failed, and it can never be moved forward because every resend reproduces the same rejection. Instead of blindly re-transmitting, we now check whether the document already exists at the DIAN. When the move already carries a document in `invoice_sending_failed`, `_send_to_dian()` extracts the CUFE from the signed XML and calls the new `_get_status_by_cufe()` helper, which queries the `GetStatus` web service for that CUFE. If the DIAN confirmed it, the document is reconciled to `invoice_accepted` storing the DIAN message and the confirmed CUFE and no second transmission is made. In every other case the helper returns None and the existing `SendBillSync` flow proceeds unchanged. Keying the lookup on the CUFE makes this safe against false positives. A genuine lost acknowledgement yields the very same CUFE, so a matching valid CUFE at the DIAN is by construction the same document and can be accepted without any field-by-field comparison.