From 7fa454eafcc07ca9e1e5f3517b9f097a69360764 Mon Sep 17 00:00:00 2001 From: "Levi Siuzdak (sile)" Date: Mon, 10 Mar 2025 17:07:37 +0100 Subject: [PATCH 001/180] [FIX] sale: remove empty line from SOL description Versions -------- - 16.0+ Steps ----- 1. Have a product with a "No variant" or customer attribute; 2. add product to a sales order. Issue ----- There is a blank line between the product name & attribute descriptor. Cause ----- The `_get_sale_order_line_multiline_description_variants` method returns either an empty string, or a string that starts with 2 newlines. Solution -------- Start with only 1 newline. opw-4585174 closes odoo/odoo#201011 Signed-off-by: Levi Siuzdak --- addons/sale/models/sale_order_line.py | 2 +- addons/sale_purchase/tests/test_sale_purchase.py | 2 +- .../static/tests/tours/product_matrix_tour.js | 6 +++--- .../tests/test_website_sale_configurator.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/sale/models/sale_order_line.py b/addons/sale/models/sale_order_line.py index a829a33c9a71f..99d50fbb9b4f5 100644 --- a/addons/sale/models/sale_order_line.py +++ b/addons/sale/models/sale_order_line.py @@ -346,7 +346,7 @@ def _get_sale_order_line_multiline_description_variants(self): if not self.product_custom_attribute_value_ids and not self.product_no_variant_attribute_value_ids: return "" - name = "\n" + name = "" custom_ptavs = self.product_custom_attribute_value_ids.custom_product_template_attribute_value_id no_variant_ptavs = self.product_no_variant_attribute_value_ids._origin diff --git a/addons/sale_purchase/tests/test_sale_purchase.py b/addons/sale_purchase/tests/test_sale_purchase.py index fdb9645566df6..b8613ce894939 100644 --- a/addons/sale_purchase/tests/test_sale_purchase.py +++ b/addons/sale_purchase/tests/test_sale_purchase.py @@ -304,7 +304,7 @@ def test_pol_custom_attribute(self): }) sale_order.action_confirm() pol = sale_order._get_purchase_orders().order_line - self.assertEqual(pol.name, f"{self.service_purchase_1.display_name}\n\n{product_attribute.name}: {product_attribute_value.name}: {custom_value}") + self.assertEqual(pol.name, f"{self.service_purchase_1.display_name}\n{product_attribute.name}: {product_attribute_value.name}: {custom_value}") def test_service_to_purchase_multi_company(self): """Test the service to purchase in a multi-company environment diff --git a/addons/test_sale_product_configurators/static/tests/tours/product_matrix_tour.js b/addons/test_sale_product_configurators/static/tests/tours/product_matrix_tour.js index 64e5e7acfb9b3..b71a2d59a4fdf 100644 --- a/addons/test_sale_product_configurators/static/tests/tours/product_matrix_tour.js +++ b/addons/test_sale_product_configurators/static/tests/tours/product_matrix_tour.js @@ -51,7 +51,7 @@ tour.register('sale_matrix_tour', { // wait for qty to be 1 => check the total to be sure all qties are set to 1 extra_trigger: '.oe_subtotal_footer_separator:contains("248.40")', }, { - trigger: 'span:contains("Matrix (PAV11, PAV22, PAV31)\n\nPA4: PAV41")', + trigger: 'span:contains("Matrix (PAV11, PAV22, PAV31)\nPA4: PAV41")', extra_trigger: '.o_form_editable', }, { trigger: '[name=product_template_id] button.fa-pencil', // edit the matrix @@ -80,7 +80,7 @@ tour.register('sale_matrix_tour', { // wait for qty to be 3 => check the total to be sure all qties are set to 3 extra_trigger: '.oe_subtotal_footer_separator:contains("745.20")', }, { - trigger: 'span:contains("Matrix (PAV11, PAV22, PAV31)\n\nPA4: PAV41")', + trigger: 'span:contains("Matrix (PAV11, PAV22, PAV31)\nPA4: PAV41")', extra_trigger: '.o_form_editable', }, { trigger: '[name=product_template_id] button.fa-pencil', // edit the matrix @@ -101,7 +101,7 @@ tour.register('sale_matrix_tour', { }, // Open the matrix through the pencil button next to the product in line edit mode. { - trigger: 'span:contains("Matrix (PAV11, PAV22, PAV31)\n\nPA4: PAV41")', + trigger: 'span:contains("Matrix (PAV11, PAV22, PAV31)\nPA4: PAV41")', extra_trigger: '.o_form_status_indicator_buttons.invisible', // wait for save to be finished }, { trigger: '[name=product_template_id] button.fa-pencil', // edit the matrix diff --git a/addons/website_sale_product_configurator/tests/test_website_sale_configurator.py b/addons/website_sale_product_configurator/tests/test_website_sale_configurator.py index 430b9c59a41fc..a2dcc1e98884c 100644 --- a/addons/website_sale_product_configurator/tests/test_website_sale_configurator.py +++ b/addons/website_sale_product_configurator/tests/test_website_sale_configurator.py @@ -159,4 +159,4 @@ def test_02_variants_modal_window(self): # Check the name of the created sale order line new_sale_order = self.env['sale.order'].search([]) - old_sale_order new_order_line = new_sale_order.order_line - self.assertEqual(new_order_line.name, 'Short (TEST) (M always, M dynamic)\n\nNever attribute size: M never\nNever attribute size custom: Yes never custom: TEST') + self.assertEqual(new_order_line.name, 'Short (TEST) (M always, M dynamic)\nNever attribute size: M never\nNever attribute size custom: Yes never custom: TEST') From 22785ff5b32320421bbbddc79ff898fd80986909 Mon Sep 17 00:00:00 2001 From: "Pieter Claeys (clpi)" Date: Thu, 13 Mar 2025 09:34:50 +0100 Subject: [PATCH 002/180] [FIX] delivery: estimated delivery cost on SO line not rounded To reproduce: - Install delivery_fedex (for example) and sale_management. - Open Fedex US shipping method and set invoicing to real cost, margin on rate to 93.47 (for example). - New SO to Azure Interior, 1x product 5555, Add shipping Fedex US and get the rate before adding to the SO. Current behaviour: Estimated cost on SO line description is not rounded according to the currency conventions. Expected behaviour: Estimated cost on SO line description is rounded, according to the currency conventions. opw-4543605 closes odoo/odoo#201510 Signed-off-by: Tiffany Chang (tic) --- addons/delivery/models/sale_order.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/delivery/models/sale_order.py b/addons/delivery/models/sale_order.py index 3ab7e2b25fb45..dfb72577eb7b3 100644 --- a/addons/delivery/models/sale_order.py +++ b/addons/delivery/models/sale_order.py @@ -123,7 +123,7 @@ def _prepare_delivery_line_vals(self, carrier, price_unit): } if carrier.invoice_policy == 'real': values['price_unit'] = 0 - values['name'] += _(' (Estimated Cost: %s )', self._format_currency_amount(price_unit)) + values['name'] += _(' (Estimated Cost: %s )', self.currency_id.format(price_unit)) else: values['price_unit'] = price_unit if carrier.free_over and self.currency_id.is_zero(price_unit) : @@ -137,6 +137,7 @@ def _create_delivery_line(self, carrier, price_unit): values = self._prepare_delivery_line_vals(carrier, price_unit) return self.env['sale.order.line'].sudo().create(values) + # to remove in master def _format_currency_amount(self, amount): pre = post = u'' if self.currency_id.position == 'before': From 95a488c4cef4332f6f743f5e55fb753c85fa4b4e Mon Sep 17 00:00:00 2001 From: "Nicolas Viseur (vin)" Date: Thu, 13 Mar 2025 10:08:29 +0800 Subject: [PATCH 003/180] [IMP] l10n_ph: BIR 2307 fix The export is completely wrong when it comes to multiple taxes affecting each others. It would recompute the withholding taxes one by one based on the price subtotal instead of taking into account all other taxes as you would expect. task-4641930 closes odoo/odoo#201489 Signed-off-by: Quentin De Paoli (qdp) --- addons/l10n_ph/tests/__init__.py | 3 + addons/l10n_ph/tests/test_bir_2307.py | 123 ++++++++++++++++++ addons/l10n_ph/wizard/generate_2307_wizard.py | 13 +- 3 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 addons/l10n_ph/tests/__init__.py create mode 100644 addons/l10n_ph/tests/test_bir_2307.py diff --git a/addons/l10n_ph/tests/__init__.py b/addons/l10n_ph/tests/__init__.py new file mode 100644 index 0000000000000..8cce5898b70d3 --- /dev/null +++ b/addons/l10n_ph/tests/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_bir_2307 diff --git a/addons/l10n_ph/tests/test_bir_2307.py b/addons/l10n_ph/tests/test_bir_2307.py new file mode 100644 index 0000000000000..ea6e37904c4b7 --- /dev/null +++ b/addons/l10n_ph/tests/test_bir_2307.py @@ -0,0 +1,123 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged + +import xlrd +import io +import base64 + + +@tagged("post_install_l10n", "post_install", "-at_install") +class TestBir2037(AccountTestInvoicingCommon): + + @classmethod + def setUpClass(cls, chart_template_ref="l10n_ph.l10n_ph_chart_template"): + super().setUpClass(chart_template_ref=chart_template_ref) + + cls.partner = cls.env['res.partner'].create({ + 'vat': '123-456-789-001', + 'branch_code': '001', + 'name': 'Jose Mangahas Cuyegkeng', + 'first_name': 'Jose', + 'middle_name': 'Mangahas', + 'last_name': 'Cuyegkeng', + 'street': "250 Amorsolo Street", + 'city': "Manila", + 'country_id': cls.env.ref('base.ph').id, + 'zip': "+900–1-096", + }) + + def test_01_no_atc(self): + """ Ensure that generating the file on a document where no taxes has an ATC set will work, although gives an empty file. """ + tax = self._create_tax('10% VAT', 10) + bill = self._create_invoice( + move_type='in_invoice', + invoice_amount=100, + taxes=tax, + ) + bill.action_post() + wizard = self.env['l10n_ph_2307.wizard'].with_context(default_moves_to_export=bill.ids).create({}) + wizard.action_generate() + report_file = io.BytesIO(base64.b64decode(wizard.generate_xls_file)) + xl = xlrd.open_workbook(file_contents=report_file.read()) + sheet = xl.sheet_by_index(0) + + result = [] + for row in range(1, sheet.nrows): + result.append(sheet.row_values(row)) + self.assertEqual(result, []) + + def test_02_simple_atc(self): + """ Ensure that generating the file on a document with a single ATC tax and check the results. """ + tax = self._create_tax('10% ATC', -10, l10n_ph_atc='WI010') + bill = self._create_invoice( + move_type='in_invoice', + invoice_amount=1000, + taxes=tax, + partner_id=self.partner.id, + date_invoice='2025-01-01', + ) + bill.action_post() + wizard = self.env['l10n_ph_2307.wizard'].with_context(default_moves_to_export=bill.ids).create({}) + wizard.action_generate() + report_file = io.BytesIO(base64.b64decode(wizard.generate_xls_file)) + xl = xlrd.open_workbook(file_contents=report_file.read()) + sheet = xl.sheet_by_index(0) + + result = [] + for row in range(1, sheet.nrows): + result.append(sheet.row_values(row)) + self.assertEqual(result, [ + ['01/01/2025', '123456789', '001', 'Jose Mangahas Cuyegkeng', 'Cuyegkeng', 'Jose', 'Mangahas', '250 Amorsolo Street, Manila, Philippines', 'product that cost 1000', 'WI010', 1000.0, -10.0, -100.0] + ]) + + def test_03_atc_affected_by_vat(self): + """ Ensure that generating the file on a document where the ATC tax is affected works as expected. """ + vat = self._create_tax('15% VAT', 15, include_base_amount=True) + atc = self._create_tax('10% ATC', -10, l10n_ph_atc='WI010', is_base_affected=True) + bill = self._create_invoice( + move_type='in_invoice', + invoice_amount=1000, + taxes=(vat | atc), + partner_id=self.partner.id, + date_invoice='2025-01-01', + ) + bill.action_post() + wizard = self.env['l10n_ph_2307.wizard'].with_context(default_moves_to_export=bill.ids).create({}) + wizard.action_generate() + report_file = io.BytesIO(base64.b64decode(wizard.generate_xls_file)) + xl = xlrd.open_workbook(file_contents=report_file.read()) + sheet = xl.sheet_by_index(0) + + result = [] + for row in range(1, sheet.nrows): + result.append(sheet.row_values(row)) + self.assertEqual(result, [ + ['01/01/2025', '123456789', '001', 'Jose Mangahas Cuyegkeng', 'Cuyegkeng', 'Jose', 'Mangahas', '250 Amorsolo Street, Manila, Philippines', 'product that cost 1000', 'WI010', 1150.0, -10.0, -115.0] + ]) + + def test_04_multi_currency(self): + """ Ensure that generating the file on a document of another currency than the company's gives the correct result. """ + tax = self._create_tax('10% ATC', -10, l10n_ph_atc='WI010') + bill = self._create_invoice( + move_type='in_invoice', + invoice_amount=2000, + taxes=tax, + partner_id=self.partner.id, + date_invoice='2025-01-01', + currency_id=self.currency_data['currency'].id, + ) + bill.action_post() + wizard = self.env['l10n_ph_2307.wizard'].with_context(default_moves_to_export=bill.ids).create({}) + wizard.action_generate() + report_file = io.BytesIO(base64.b64decode(wizard.generate_xls_file)) + xl = xlrd.open_workbook(file_contents=report_file.read()) + sheet = xl.sheet_by_index(0) + + result = [] + for row in range(1, sheet.nrows): + result.append(sheet.row_values(row)) + # We expect the values in company currency in the file. + self.assertEqual(result, [ + ['01/01/2025', '123456789', '001', 'Jose Mangahas Cuyegkeng', 'Cuyegkeng', 'Jose', 'Mangahas', '250 Amorsolo Street, Manila, Philippines', 'product that cost 2000', 'WI010', 1000.0, -10.0, -100.0] + ]) diff --git a/addons/l10n_ph/wizard/generate_2307_wizard.py b/addons/l10n_ph/wizard/generate_2307_wizard.py index 225e3efe4c81a..0c5cf79a4db30 100644 --- a/addons/l10n_ph/wizard/generate_2307_wizard.py +++ b/addons/l10n_ph/wizard/generate_2307_wizard.py @@ -55,14 +55,19 @@ def _write_rows(self, worksheet, moves): 'last_name': partner.last_name or '', 'address': ', '.join([val for val in partner_address_info if val]) } - for invoice_line in move.invoice_line_ids.filtered(lambda l: l.display_type not in ('line_note', 'line_section')): - for tax in invoice_line.tax_ids.filtered(lambda x: x.l10n_ph_atc): + aggregated_taxes = move._prepare_invoice_aggregated_taxes() + for invoice_line, tax_details_for_line in aggregated_taxes['tax_details_per_record'].items(): + for tax_detail in tax_details_for_line['tax_details'].values(): + tax = tax_detail['tax'] + if not tax.l10n_ph_atc: + continue + product_name = invoice_line.product_id.name or invoice_line.name values['product_name'] = re.sub(r'[\(\)]', '', product_name) if product_name else "" values['atc'] = tax.l10n_ph_atc - values['price_subtotal'] = invoice_line.price_subtotal + values['price_subtotal'] = tax_detail['base_amount'] values['amount'] = tax.amount - values['tax_amount'] = tax._compute_amount(invoice_line.price_subtotal, invoice_line.price_unit) + values['tax_amount'] = tax_detail['tax_amount'] self._write_single_row(worksheet, worksheet_row, values) worksheet_row += 1 From 74ddb3dd79811474b434959bce195cfff35fa6f9 Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Wed, 12 Mar 2025 14:52:28 +0100 Subject: [PATCH 004/180] [FIX] web_editor: keep background on border outlook fix Scenario: - create marketing email in 17.0 or above - use the Cover widget - change the cover image to get a base64 endoded image - send the mail Result: no image is sent Cause: commit 1605b81b12e0dee78905e6eb3526fdb7e4908050 could have issue when parsing the CSS because of having inside a CSS value eg. url("data:image/webp;base64,..."), then when the CSS was modified, the background image would be broken (with a :undefined after the value). Fix: instead of replacing all the style to change the border-style, update it with regex. opw-4613524 closes odoo/odoo#201394 Signed-off-by: Nicolas Lempereur (nle) --- .../static/src/js/backend/convert_inline.js | 13 ++++++++---- .../static/tests/convert_inline_tests.js | 20 ++++++++++++++++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/addons/web_editor/static/src/js/backend/convert_inline.js b/addons/web_editor/static/src/js/backend/convert_inline.js index c4a4d422145a6..cafa053173db9 100644 --- a/addons/web_editor/static/src/js/backend/convert_inline.js +++ b/addons/web_editor/static/src/js/backend/convert_inline.js @@ -1750,12 +1750,17 @@ function correctBorderAttributes(style) { }, 0); if (totalBorderWidth === 0) { - stylesObject["border-style"] = "none"; + let correctedStyle = style.trim(); + if (correctedStyle.slice(-1) != ';') { + correctedStyle += ';'; + } + correctedStyle = correctedStyle.replace( + /(;|^)\s*border-style\s*:[^;]*(;|$)|$/, '$1border-style:none$2' + ); + return correctedStyle; } - return Object.entries(stylesObject) - .map(([attribute, value]) => `${attribute}:${value}`) - .join(";"); + return style; } export default { diff --git a/addons/web_editor/static/tests/convert_inline_tests.js b/addons/web_editor/static/tests/convert_inline_tests.js index 331404e3def4f..7ab68d0a7b81f 100644 --- a/addons/web_editor/static/tests/convert_inline_tests.js +++ b/addons/web_editor/static/tests/convert_inline_tests.js @@ -1068,7 +1068,7 @@ QUnit.module('convert_inline', {}, function () { }); QUnit.test('Correct border attributes for outlook', async function (assert) { - assert.expect(2); + assert.expect(3); const $styleSheet = $('