[IMP] account_edi_ubl_cii: create partner at import if not retrieved

When importing a vendor bill/invoice, a matching partner is searched. If
no match is found, a new partner will created.

task-3141337

closes odoo/odoo#117168

X-original-commit: 08a6bbd92276f92f215d3225f3f14ae8a908f034
Signed-off-by: Laurent Smet <las@odoo.com>
Signed-off-by: Julien Van Roy <juvr@odoo.com>
This commit is contained in:
Julien Van Roy
2023-03-30 16:23:13 +02:00
parent 9e949bb0bf
commit 209da20bda
8 changed files with 333 additions and 25 deletions
@@ -327,6 +327,16 @@ class AccountEdiCommon(models.AbstractModel):
return True
def _import_retrieve_and_fill_partner(self, invoice, name, phone, mail, vat):
""" Retrieve the partner, if no matching partner is found, create it
"""
invoice.partner_id = self.env['res.partner']._retrieve_partner(name=name, phone=phone, mail=mail, vat=vat)
if not invoice.partner_id and name:
invoice.partner_id = self.env['res.partner'].create({'name': name, 'email': mail, 'phone': phone})
country_code = invoice.partner_id.commercial_partner_id.country_code
if vat and self.env['res.partner']._run_vat_test(vat, country_code, invoice.partner_id.is_company):
invoice.partner_id.vat = vat
def _import_fill_invoice_allowance_charge(self, tree, invoice, qty_factor):
logs = []
if '{urn:oasis:names:specification:ubl:schema:xsd' in tree.tag:
@@ -220,15 +220,12 @@ class AccountEdiXmlCII(models.AbstractModel):
# ==== partner_id ====
partner_type = 'SellerTradeParty' if invoice.journal_id.type == 'purchase' else 'BuyerTradeParty'
invoice.partner_id = self.env['res.partner']._retrieve_partner(
name=_find_value(f"//ram:{partner_type}/ram:Name"),
mail=_find_value(f"//ram:{partner_type}//ram:URIID[@schemeID='SMTP']"),
vat=_find_value(f"//ram:{partner_type}/ram:SpecifiedTaxRegistration/ram:ID"),
company=invoice.company_id,
)
if not invoice.partner_id:
logs.append(_("Could not retrieve the %s.", _("customer") if invoice.is_sale_document() else _("vendor")))
role = invoice.journal_id.type == 'purchase' and 'SellerTradeParty' or 'BuyerTradeParty'
name = _find_value(f"//ram:{role}/ram:Name")
mail = _find_value(f"//ram:{role}//ram:URIID[@schemeID='SMTP']")
vat = _find_value(f"//ram:{role}/ram:SpecifiedTaxRegistration/ram:ID")
phone = _find_value(f"//ram:{role}/ram:DefinedTradeContact/ram:TelephoneUniversalCommunication/ram:CompleteNumber")
self._import_retrieve_and_fill_partner(invoice, name=name, phone=phone, mail=mail, vat=vat)
# ==== currency_id ====
@@ -2,7 +2,7 @@
from odoo import models, _
from odoo.osv import expression
from odoo.tools import html2plaintext, cleanup_xml_node
from odoo.tools import html2plaintext, cleanup_xml_node, find_xml_value
from lxml import etree
@@ -466,6 +466,12 @@ class AccountEdiXmlUBL20(models.AbstractModel):
# -------------------------------------------------------------------------
def _import_fill_invoice_form(self, invoice, tree, qty_factor):
def _find_value(xpath, element=tree):
# avoid 'TypeError: empty namespace prefix is not supported in XPath'
nsmap = {k: v for k, v in tree.nsmap.items() if k is not None}
return find_xml_value(xpath, element, nsmap)
logs = []
if qty_factor == -1:
@@ -473,11 +479,12 @@ class AccountEdiXmlUBL20(models.AbstractModel):
# ==== partner_id ====
partner = self._import_retrieve_partner(tree, invoice)
if partner:
invoice.partner_id = partner
else:
logs.append(_("Could not retrieve the %s.", _("customer") if invoice.is_sale_document() else _("vendor")))
role = "Customer" if invoice.journal_id.type == 'sale' else "Supplier"
vat = _find_value(f'//cac:Accounting{role}Party/cac:Party//cbc:CompanyID')
phone = _find_value(f'//cac:Accounting{role}Party/cac:Party//cbc:Telephone')
mail = _find_value(f'//cac:Accounting{role}Party/cac:Party//cbc:ElectronicMail')
name = _find_value(f'//cac:Accounting{role}Party/cac:Party//cbc:Name')
self._import_retrieve_and_fill_partner(invoice, name=name, phone=phone, mail=mail, vat=vat)
# ==== currency_id ====
@@ -95,6 +95,19 @@ class TestUBLCommon(AccountTestInvoicingCommon):
self.assertTrue(new_invoice)
self.assert_same_invoice(invoice, new_invoice)
def _update_invoice_from_file(self, module_name, subfolder, filename, invoice):
""" Create an attachment from a file and post it on the invoice
"""
file_path = get_module_resource(module_name, subfolder, filename)
with misc.file_open(file_path, 'rb') as file:
attachment = self.env['ir.attachment'].create({
'name': filename,
'datas': base64.encodebytes(file.read()),
'res_id': invoice.id,
'res_model': 'account.move',
})
invoice.message_post(attachment_ids=[attachment.id])
def _assert_imported_invoice_from_file(self, subfolder, filename, amount_total, amount_tax, list_line_subtotals,
list_line_price_unit=None, list_line_discount=None, list_line_taxes=None,
move_type='in_invoice', currency_id=None):
@@ -116,16 +129,12 @@ class TestUBLCommon(AccountTestInvoicingCommon):
invoice_count = len(self.env['account.move'].search([]))
# Import the file to fill the empty invoice
file_path = get_module_resource('l10n_account_edi_ubl_cii_tests', subfolder, filename)
with misc.file_open(file_path, 'rb') as file:
attachment = self.env['ir.attachment'].create({
'name': filename,
'datas': base64.encodebytes(file.read()),
'res_id': invoice.id,
'res_model': 'account.move',
})
invoice.message_post(attachment_ids=[attachment.id])
self._update_invoice_from_file(
module_name='l10n_account_edi_ubl_cii_tests',
subfolder=subfolder,
filename=filename,
invoice=invoice,
)
# Checks
self.assertEqual(len(self.env['account.move'].search([])), invoice_count)
@@ -0,0 +1,127 @@
<?xml version='1.0' encoding='UTF-8'?>
<rsm:CrossIndustryInvoice xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
<rsm:ExchangedDocumentContext>
<ram:GuidelineSpecifiedDocumentContextParameter>
<ram:ID>urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended</ram:ID>
</ram:GuidelineSpecifiedDocumentContextParameter>
</rsm:ExchangedDocumentContext>
<rsm:ExchangedDocument>
<ram:ID>INV/2017/01/0001</ram:ID>
<ram:TypeCode>380</ram:TypeCode>
<ram:IssueDateTime>
<udt:DateTimeString format="102">20170101</udt:DateTimeString>
</ram:IssueDateTime>
<ram:IncludedNote>
<ram:Content>test narration</ram:Content>
</ram:IncludedNote>
</rsm:ExchangedDocument>
<rsm:SupplyChainTradeTransaction>
<ram:IncludedSupplyChainTradeLineItem>
<ram:AssociatedDocumentLineDocument>
<ram:LineID>1</ram:LineID>
</ram:AssociatedDocumentLineDocument>
<ram:SpecifiedTradeProduct>
<ram:Name>product_a</ram:Name>
</ram:SpecifiedTradeProduct>
<ram:SpecifiedLineTradeAgreement>
<ram:GrossPriceProductTradePrice>
<ram:ChargeAmount>0.00</ram:ChargeAmount>
</ram:GrossPriceProductTradePrice>
<ram:NetPriceProductTradePrice>
<ram:ChargeAmount>0.00</ram:ChargeAmount>
</ram:NetPriceProductTradePrice>
</ram:SpecifiedLineTradeAgreement>
<ram:SpecifiedLineTradeDelivery>
<ram:BilledQuantity unitCode="C62">1.0</ram:BilledQuantity>
</ram:SpecifiedLineTradeDelivery>
<ram:SpecifiedLineTradeSettlement>
<ram:SpecifiedTradeSettlementLineMonetarySummation>
<ram:LineTotalAmount>0.00</ram:LineTotalAmount>
</ram:SpecifiedTradeSettlementLineMonetarySummation>
</ram:SpecifiedLineTradeSettlement>
</ram:IncludedSupplyChainTradeLineItem>
<ram:ApplicableHeaderTradeAgreement>
<ram:SellerTradeParty>
<ram:Name>partner_1</ram:Name>
<ram:DefinedTradeContact>
<ram:PersonName>partner_1</ram:PersonName>
<ram:TelephoneUniversalCommunication>
<ram:CompleteNumber>+1 (650) 555-0111</ram:CompleteNumber>
</ram:TelephoneUniversalCommunication>
<ram:EmailURIUniversalCommunication>
<ram:URIID schemeID="SMTP">partner1@yourcompany.com</ram:URIID>
</ram:EmailURIUniversalCommunication>
</ram:DefinedTradeContact>
<ram:PostalTradeAddress>
<ram:PostcodeCode>75000</ram:PostcodeCode>
<ram:LineOne>Rue Jean Jaur&#232;s, 42</ram:LineOne>
<ram:CityName>Paris</ram:CityName>
<ram:CountryID>FR</ram:CountryID>
</ram:PostalTradeAddress>
<ram:SpecifiedTaxRegistration>
<ram:ID schemeID="VA">FR05677404089</ram:ID>
</ram:SpecifiedTaxRegistration>
</ram:SellerTradeParty>
<ram:BuyerTradeParty>
<ram:Name>Buyer</ram:Name>
<ram:DefinedTradeContact>
<ram:PersonName>Buyer</ram:PersonName>
<ram:TelephoneUniversalCommunication>
<ram:CompleteNumber>1111</ram:CompleteNumber>
</ram:TelephoneUniversalCommunication>
<ram:EmailURIUniversalCommunication>
<ram:URIID schemeID="SMTP">buyer@yahoo.com</ram:URIID>
</ram:EmailURIUniversalCommunication>
</ram:DefinedTradeContact>
<ram:SpecifiedTaxRegistration>
<ram:ID schemeID="VA">FR89215010646</ram:ID>
</ram:SpecifiedTaxRegistration>
</ram:BuyerTradeParty>
<ram:BuyerOrderReferencedDocument>
<ram:IssuerAssignedID>INV/2017/01/0001</ram:IssuerAssignedID>
</ram:BuyerOrderReferencedDocument>
</ram:ApplicableHeaderTradeAgreement>
<ram:ApplicableHeaderTradeDelivery>
<ram:ShipToTradeParty>
<ram:Name>Buyer</ram:Name>
<ram:DefinedTradeContact>
<ram:PersonName>Buyer</ram:PersonName>
<ram:TelephoneUniversalCommunication>
<ram:CompleteNumber>1111</ram:CompleteNumber>
</ram:TelephoneUniversalCommunication>
<ram:EmailURIUniversalCommunication>
<ram:URIID schemeID="SMTP">buyer@yahoo.com</ram:URIID>
</ram:EmailURIUniversalCommunication>
</ram:DefinedTradeContact>
</ram:ShipToTradeParty>
<ram:ActualDeliverySupplyChainEvent>
<ram:OccurrenceDateTime>
<udt:DateTimeString format="102">20170101</udt:DateTimeString>
</ram:OccurrenceDateTime>
</ram:ActualDeliverySupplyChainEvent>
</ram:ApplicableHeaderTradeDelivery>
<ram:ApplicableHeaderTradeSettlement>
<ram:PaymentReference>INV/2017/01/0001</ram:PaymentReference>
<ram:InvoiceCurrencyCode>USD</ram:InvoiceCurrencyCode>
<ram:SpecifiedTradeSettlementPaymentMeans>
<ram:TypeCode>42</ram:TypeCode>
<ram:PayeePartyCreditorFinancialAccount>
<ram:ProprietaryID>FR15001559627230</ram:ProprietaryID>
</ram:PayeePartyCreditorFinancialAccount>
</ram:SpecifiedTradeSettlementPaymentMeans>
<ram:SpecifiedTradePaymentTerms>
<ram:Description>30% Advance End of Following Month</ram:Description>
</ram:SpecifiedTradePaymentTerms>
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
<ram:LineTotalAmount>0.00</ram:LineTotalAmount>
<ram:TaxBasisTotalAmount>0.00</ram:TaxBasisTotalAmount>
<ram:TaxTotalAmount currencyID="USD">0.00</ram:TaxTotalAmount>
<ram:GrandTotalAmount>0.00</ram:GrandTotalAmount>
<ram:TotalPrepaidAmount>0.00</ram:TotalPrepaidAmount>
<ram:DuePayableAmount>0.00</ram:DuePayableAmount>
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
</ram:ApplicableHeaderTradeSettlement>
</rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>
@@ -0,0 +1,102 @@
<?xml version='1.0' encoding='UTF-8'?>
<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
<cbc:UBLVersionID>2.1</cbc:UBLVersionID>
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0
</cbc:CustomizationID>
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
<cbc:ID>INV/2017/01/0002</cbc:ID>
<cbc:IssueDate>2017-01-01</cbc:IssueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:Note>test narration</cbc:Note>
<cbc:DocumentCurrencyCode>USD</cbc:DocumentCurrencyCode>
<cbc:BuyerReference>Buyer</cbc:BuyerReference>
<cac:OrderReference>
<cbc:ID>test invoice origin</cbc:ID>
</cac:OrderReference>
<cac:AccountingSupplierParty>
<cac:Party>
<cbc:EndpointID schemeID="9925">BE0202239951</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>partner_1</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Chauss&#233;e de Namur 40</cbc:StreetName>
<cbc:CityName>Ramillies</cbc:CityName>
<cbc:PostalZone>1367</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>BE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyTaxScheme>
<cbc:CompanyID>BE0202239951</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>partner_1</cbc:RegistrationName>
<cbc:CompanyID>BE0202239951</cbc:CompanyID>
</cac:PartyLegalEntity>
<cac:Contact>
<cbc:Name>partner_1</cbc:Name>
</cac:Contact>
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cbc:EndpointID>BE980737405</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>Buyer</cbc:Name>
</cac:PartyName>
<cac:PartyTaxScheme>
<cbc:CompanyID>BE980737405</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Buyer</cbc:RegistrationName>
<cbc:CompanyID>BE980737405</cbc:CompanyID>
</cac:PartyLegalEntity>
<cac:Contact>
<cbc:Name>Buyer</cbc:Name>
<cbc:Telephone>1111</cbc:Telephone>
<cbc:ElectronicMail>buyer@yahoo.com</cbc:ElectronicMail>
</cac:Contact>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:PaymentMeans>
<cbc:PaymentMeansCode name="credit transfer">30</cbc:PaymentMeansCode>
<cbc:PaymentID>+++000/0000/08282+++</cbc:PaymentID>
<cac:PayeeFinancialAccount>
<cbc:ID>BE15001559627230</cbc:ID>
</cac:PayeeFinancialAccount>
</cac:PaymentMeans>
<cac:PaymentTerms>
<cbc:Note>30% Advance End of Following Month</cbc:Note>
</cac:PaymentTerms>
<cac:TaxTotal>
<cbc:TaxAmount currencyID="USD">-0.00</cbc:TaxAmount>
</cac:TaxTotal>
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="USD">0.00</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="USD">0.00</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="USD">0.00</cbc:TaxInclusiveAmount>
<cbc:PrepaidAmount currencyID="USD">0.00</cbc:PrepaidAmount>
<cbc:PayableAmount currencyID="USD">0.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
<cac:InvoiceLine>
<cbc:ID>259</cbc:ID>
<cbc:InvoicedQuantity unitCode="C62">1.0</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="USD">0.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Description>product_a</cbc:Description>
<cbc:Name>product_a</cbc:Name>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="USD">0.00</cbc:PriceAmount>
</cac:Price>
</cac:InvoiceLine>
</Invoice>
@@ -304,6 +304,34 @@ class TestCIIFR(TestUBLCommon):
)
self._test_import_partner(invoice.ubl_cii_xml_id, self.partner_1, self.partner_2)
def test_import_and_create_partner_facturx(self):
""" Tests whether the partner is created at import if no match is found when decoding the EDI attachment
"""
partner_vals = {
'name': "Buyer",
'mail': "buyer@yahoo.com",
'phone': "1111",
'vat': "FR89215010646",
}
# assert there is no matching partner
partner_match = self.env['res.partner']._retrieve_partner(**partner_vals)
self.assertFalse(partner_match)
# Import attachment as an invoice
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'journal_id': self.company_data['default_journal_sale'].id,
})
self._update_invoice_from_file(
module_name='l10n_account_edi_ubl_cii_tests',
subfolder='tests/test_files/from_odoo',
filename='facturx_test_import_partner.xml',
invoice=invoice)
# assert a new partner has been created
partner_vals['email'] = partner_vals.pop('mail')
self.assertRecordValues(invoice.partner_id, [partner_vals])
def test_import_tax_included(self):
"""
Tests whether the tax included / tax excluded are correctly decoded when
@@ -219,6 +219,34 @@ class TestUBLBE(TestUBLCommon):
)
self._test_import_partner(invoice.ubl_cii_xml_id, self.partner_1, self.partner_2)
def test_import_and_create_partner_ubl(self):
""" Tests whether the partner is created at import if no match is found when decoding the EDI attachment
"""
partner_vals = {
'name': "Buyer",
'mail': "buyer@yahoo.com",
'phone': "1111",
'vat': "BE980737405",
}
# assert there is no matching partner
partner_match = self.env['res.partner']._retrieve_partner(**partner_vals)
self.assertFalse(partner_match)
# Import attachment as an invoice
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'journal_id': self.company_data['default_journal_sale'].id,
})
self._update_invoice_from_file(
module_name='l10n_account_edi_ubl_cii_tests',
subfolder='tests/test_files/from_odoo',
filename='ubl_test_import_partner.xml',
invoice=invoice)
# assert a new partner has been created
partner_vals['email'] = partner_vals.pop('mail')
self.assertRecordValues(invoice.partner_id, [partner_vals])
def test_import_export_invoice_xml(self):
"""
Test whether the elements only specific to ubl_be are correctly exported