Wednesday, April 1, 2026
3 changes · 17.0
Resolved issues and error corrections
This fix corrects the indicators used when exporting Spanish tax declarations (Modelo 347) to the AEAT tax authority. Previously, the system incorrectly used 'X' for both substitutive and complementary declarations, causing AEAT to reject the files. Now it properly uses 'C' for complementary and 'S' for substitutive declarations, ensuring tax reports are correctly recognized by the Spanish tax authority.
Original PR description
Currently, the BOE export for `Mod 347` uses incorrect indicators for `Substitutive` and `Complementary declarations`. **Steps to reproduce:** - Install the `l10n_es_reports` module and switch to the…
Currently, the BOE export for `Mod 347` uses incorrect indicators for `Substitutive` and `Complementary declarations`. **Steps to reproduce:** - Install the `l10n_es_reports` module and switch to the `ES company` - Navigate to Accounting > Reporting > Tax Report - From the smart button, select `Report: Tax Report (Mod 347) (ES)` - Download the BOE file using the dropdown. - In the wizard: - Enable `Substitutive Declaration` or `Complementary Declaration` - Set `Previous Report Number` (e.g., 123456789) - Click `Generate BOE` - Upload the generated .txt file to the `AEAT portal`. (AEAT credentials are required) **Observation:** AEAT does not recognize 'X' as a valid indicator for substitutive or complementary declarations and interprets the file as a standard return. **Root Cause:** At [1], the BOE Mod 347 generation writes 'X' for both substitute and complementary declarations. **Fix:** This commit ensures the file contains correct indicators: - 'C' for `complementary declarations` - 'S' for `substitute declarations` This aligns Modelo 347 with AEAT specifications and ensures consistency with the implementation of Modelo 349 at [2]. Ref: https://sede.agenciatributaria.gob.es/Sede/en_gb/ayuda/consultas-informaticas/declaraciones-informativas-ayuda-tecnica/modificar-declaracion-informativa-mediante-fichero.html [1]: https://github.com/odoo/enterprise/blob/c5332bef593cc3fa1b5013a0dac56ccd67e4da14/l10n_es_reports/models/aeat_tax_reports.py#L1061-L1062 [2]: https://github.com/odoo/enterprise/blob/c5332bef593cc3fa1b5013a0dac56ccd67e4da14/l10n_es_reports/models/aeat_tax_reports.py#L1490-L1491 opw-6048711
Odoo was incorrectly generating separate tax blocks for multiple taxes with the same rate on invoices, causing Peppol validation to fail. This fix consolidates all taxes of the same rate into a single block, ensuring invoices comply with Peppol standards and are accepted by tax authorities.
Original PR description
Peppol invoices are rejected with error `[BR-S-08]` when multiple taxes with the same rate (e.g. 21%) are used on the same invoice. Odoo generates separate `<cac:TaxSubtotal>`` blocks for them due to…
Peppol invoices are rejected with error `[BR-S-08]` when multiple taxes with the same rate (e.g. 21%) are used on the same invoice. Odoo generates separate `<cac:TaxSubtotal>`` blocks for them due to technical differences (like `include_base_amount`), but the standard requires a single consolidated block per rate. Simplify the grouping key used during UBL export by removing `include_base_amount`. This forces all taxes of the same category and rate to be merged into a single subtotal block, ensuring mathematical consistency and satisfying Peppol validation rules. Peppol error: ``` The XML document did not pass the Schematron validation. Schematron Validation failed with error: Fatal: [BR-S-08]-For each different value of VAT category rate (BT-119) where the VAT category code (BT-118) is "Standard rated", the VAT category taxable amount (BT-116) in a VAT breakdown (BG-23) shall equal the sum of Invoice line net amounts (BT-131) plus the sum of document level charge amounts (BT-99) minus the sum of document level allowance amounts (BT-92) where the VAT category code (BT-151, BT-102, BT-95) is "Standard rated" and the VAT rate (BT-152, BT-103, BT-96) equals the VAT category rate (BT-119). ``` opw-5891685
This fix corrects how the system calculates paid time off balances when employees request leave retroactively (for dates in the past). Previously, backdated leave requests would incorrectly reduce the current year's balance even when the leave should have been covered by surplus days from the previous year. The fix recalculates balances properly by replaying the accrual history when a backdated leave crosses a year boundary.
Original PR description
Steps to reproduce 1. Create a multi-level accrual plan (e.g., Level 1: 2 days/month, 5-day carryover cap). 2. Accrue for a full year (24 days earned) and pass the carryover date (Jan 1st). 3.…
Steps to reproduce 1. Create a multi-level accrual plan (e.g., Level 1: 2 days/month, 5-day carryover cap). 2. Accrue for a full year (24 days earned) and pass the carryover date (Jan 1st). 3. Observe the balance for the new year is 8.0 (5.0 carried over + 3.0 new grant). 4. Backdate a leave request to November of the previous year (12 days) and validate it. 5. Observe the remaining balance for the current year. 6. Result: The balance drops incorrectly. It fails to recognize that the 12-day leave should have been absorbed by the 19 surplus days (24 earned - 5 cap) discarded during the reset. Issue When a leave is backdated into a previous accrual period (before the most recent carryover), the system uses the current post-carryover allocation balance to evaluate and deduct the leave. It does not account for the fact that the previous period may have had a much higher balance, and that the leave's real impact is only on the carried-over amount — not the full duration. Solution When a backdated leave crossing a carryover boundary is detected, the accrual plan is replayed from scratch to recompute the correct allocation balance. 1. Detection (_get_accrual_allocations_across_carryover in hr.leave): Checks whether a leave's date falls before a carryover boundary that has already been processed (leave.date_from < carryover_date <= alloc.lastcall). This ensures the recompute only triggers for truly backdated leaves crossing a carryover. 2. Replay (_recompute_accrual_allocations in hr.leave.allocation): Creates a temporary allocation and replays _process_accrual_plans from the allocation's start date up to lastcall. The replay uses include_pending_leaves context to count not-yet-validated leaves via virtual_leaves_taken, so the carryover calculation correctly accounts for them at create time. 3. Lifecycle coverage: The recompute is triggered on all leave state changes that affect the allocation balance: create (_check_validity), refuse (action_refuse), delete (unlink), and reset to draft (action_draft). 4. The normal accrual cron (_update_accrual / _process_accrual_plans) continues to use leaves_taken (validated only), preserving existing behavior for forward-looking accrual processing. opw-5870858