[FIX] account_edi : search checks for identically named product first

1.
If the name of a product on an imported EDI document is a substring
of the name of a product on the DB, said product could be used instead
of the correct one, depending on their order (i.e. ID) in the DB.

Example: There are 2 products named "Placement" and "Displacement" in
a DB. A PDF is imported. It contains a product named "Placement". If
the "Displacement" product on the DB appears before the "Placement"
one, the product on the AML will be set to the "Displacement" one.

To fix this, we first check if there is a product identically named to
the one in the EDI document.

2.
In case a product from the DB is used, if possible, the UoM used should
be the one present on the EDI document. Thus there should be a check to
ensure that the UoM category on said document matches the one of the
product's UoM before assigning it, to avoid a compatibility error.

opw-3470969

closes odoo/odoo#143252

X-original-commit: 84911fe007b229b97081a6f5aa298e7b213d43be
Signed-off-by: Laurent Smet (las) <las@odoo.com>
Co-authored-by: Julien Van Roy <juvr@odoo.com>
This commit is contained in:
Soam (sold)
2023-12-04 11:58:12 +00:00
co-authored by Julien Van Roy
parent 15295e317f
commit 027e45596e
4 changed files with 123 additions and 16 deletions
+17 -13
View File
@@ -245,17 +245,21 @@ class ProductProduct(models.Model):
# cut Sales Description from the name
name = name.split('\n')[0]
domains = []
for value, domain in (
(name, ('name', 'ilike', name)),
(default_code, ('default_code', '=', default_code)),
(barcode, ('barcode', '=', barcode)),
):
if value is not None:
domains.append([domain])
if default_code:
domains.append([('default_code', '=', default_code)])
if barcode:
domains.append([('barcode', '=', barcode)])
domain = expression.AND([
expression.OR(domains),
self.env['product.product']._check_company_domain(company),
extra_domain,
])
return self.env['product.product'].search(domain, limit=1)
# Search for the product with the exact name, then ilike the name
name_domains = [('name', '=', name)], [('name', 'ilike', name)] if name else []
for name_domain in name_domains:
product = self.env['product.product'].search(
expression.AND([
expression.OR(domains + [name_domain]),
self.env['product.product']._check_company_domain(company),
]),
limit=1,
)
if product:
return product
return self.env['product.product']
@@ -685,10 +685,16 @@ class AccountEdiCommon(models.AbstractModel):
# Set the values on the line_form
invoice_line.quantity = inv_line_vals['quantity']
if inv_line_vals.get('product_uom_id'):
if not inv_line_vals.get('product_uom_id'):
logs.append(
_("Could not retrieve the unit of measure for line with label '%s'.", invoice_line.name))
elif not invoice_line.product_id:
# no product set on the line, no need to check uom compatibility
invoice_line.product_uom_id = inv_line_vals['product_uom_id']
else:
logs.append(_("Could not retrieve the unit of measure for line with label '%s'.", invoice_line.name))
elif inv_line_vals['product_uom_id'].category_id == invoice_line.product_id.product_tmpl_id.uom_id.category_id:
# needed to check that the uom is compatible with the category of the product
invoice_line.product_uom_id = inv_line_vals['product_uom_id']
invoice_line.price_unit = inv_line_vals['price_unit']
invoice_line.discount = inv_line_vals['discount']
invoice_line.tax_ids = inv_line_vals['taxes']
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from . import test_partner_peppol_fields
from . import test_ubl_cii
@@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from lxml import etree
from odoo import Command
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests import tagged
@tagged('post_install', '-at_install')
class TestAccountEdiUblCii(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
cls.uom_units = cls.env.ref('uom.product_uom_unit')
cls.uom_dozens = cls.env.ref('uom.product_uom_dozen')
cls.displace_prdct = cls.env['product.product'].create({
'name': 'Displacement',
'uom_id': cls.uom_units.id,
'standard_price': 90.0,
})
cls.place_prdct = cls.env['product.product'].create({
'name': 'Placement',
'uom_id': cls.uom_units.id,
'standard_price': 80.0,
})
def test_import_product(self):
line_vals = [
{
'product_id': self.place_prdct.id,
'product_uom_id': self.uom_units.id,
'tax_ids': [self.company_data_2['default_tax_sale'].id]
}, {
'product_id': self.displace_prdct.id,
'product_uom_id': self.uom_units.id,
'tax_ids': [self.company_data_2['default_tax_sale'].id]
}, {
'product_id': self.displace_prdct.id,
'product_uom_id': self.uom_units.id,
'tax_ids': [self.company_data_2['default_tax_sale'].id]
}, {
'product_id': self.displace_prdct.id,
'product_uom_id': self.uom_dozens.id,
'tax_ids': [self.company_data_2['default_tax_sale'].id]
}
]
company = self.company_data_2['company']
company.country_id = self.env['res.country'].search([('code', '=', 'FR')])
company.vat = 'FR23334175221'
company.email = 'company@site.ext'
company.phone = '+33499999999'
company.zip = '78440'
company.partner_id.ubl_cii_format = 'facturx'
company.partner_id.bank_ids = [Command.create({
'acc_number': '999999',
'partner_id': company.partner_id.id,
'acc_holder_name': 'The Chosen One'
})]
invoice = self.env['account.move'].create({
'company_id': company.id,
'partner_id': company.partner_id.id,
'move_type': 'out_invoice',
'journal_id': self.company_data_2['default_journal_sale'].id,
'invoice_line_ids': [Command.create(vals) for vals in line_vals],
})
invoice.action_post()
template = self.env.ref('account.email_template_edi_invoice', raise_if_not_found=False)
print_wiz = self.env['account.move.send'].create({
'move_ids': invoice.ids,
'mail_template_id': template.id
})
print_wiz.checkbox_download = False
print_wiz.checkbox_send_mail = False
print_wiz.checkbox_send_by_post = False
print_wiz.checkbox_ubl_cii_xml = True
print_wiz.action_send_and_print()
facturx_attachment = invoice.ubl_cii_xml_id
xml_tree = etree.fromstring(facturx_attachment.raw)
# Testing the case where a product on the invoice has a UoM with a different category than the one in the DB
wrong_uom_line = xml_tree.findall('./{*}SupplyChainTradeTransaction/{*}IncludedSupplyChainTradeLineItem')[1]
wrong_uom_line.find('./{*}SpecifiedLineTradeDelivery/{*}BilledQuantity').attrib['unitCode'] = 'HUR'
facturx_attachment.raw = etree.tostring(xml_tree)
new_invoice = invoice.journal_id._create_document_from_attachment(facturx_attachment.ids)
self.assertRecordValues(new_invoice.invoice_line_ids, line_vals)