Skip to content

Commit 95a9cd2

Browse files
committed
[FIX] l10n_ro_edi: country code prefix on VAT / company_registry
This commit makes the CIUS-RO e-invoice emitted by a Romanian company without VAT number compliant with the CIUS-RO requirements. The validator for CIUS-RO (which extends the validations from the BIS3 Schematron) asserts a rule `[BR-CO-09]` where: if the PartyTaxScheme/TaxScheme/ID == 'VAT', CompanyID must start with a country code prefix. In Romania however, there are multiple types of "Tax IDs", and it is perfectly valid in Romania to have a Tax ID without RO (country code prefix) in front of them. They are not a subject to paying VAT, and it should still be possible to generate CIUS-RO XML with their tax identifications. We have to handle their cases by changing the TaxScheme/ID to 'something other than VAT', preventing the trigger of the rule and allow Romanian companies without prefixed VAT to use CIUS-RO. ```xml <rule context="//cac:PartyTaxScheme[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']"> <assert id="BR-CO-09" flag="fatal" test="( contains( ' 1A AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG EH EL ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HM HN HR HT HU ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN KP KR KW KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ML MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW PY QA RE RO RS RU RW SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV SX SY SZ TC TD TF TG TH TJ TK TL TM TN TO TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI VN VU WF WS XI YE YT ZA ZM ZW ',substring(cbc:CompanyID,1,2) ) )">[BR-CO-09]-The Seller VAT identifier (BT-31), the Seller tax representative VAT identifier (BT-63) and the Buyer VAT identifier (BT-48) shall have a prefix in accordance with ISO code ISO 3166-1 alpha-2 by which the country of issue may be identified. Nevertheless, Greece may use the prefix ‘EL’.</assert> </rule> ``` This commit also fixes and clean some of the irrelevant constraints and tests previously written in `l10n_ro_edi`. closes odoo#155252 Task-id: 3649426 Signed-off-by: Brice Bartoletti (bib) <bib@odoo.com>
1 parent d111e66 commit 95a9cd2

File tree

3 files changed

+202
-27
lines changed

3 files changed

+202
-27
lines changed

addons/l10n_ro_edi/models/account_edi_xml_ubl_ciusro.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,17 @@ def _get_partner_party_tax_scheme_vals_list(self, partner, role):
4242
vals_list = super()._get_partner_party_tax_scheme_vals_list(partner, role)
4343

4444
if not partner.vat and partner.company_registry:
45-
return [{
46-
'company_id': partner.company_registry,
47-
'tax_scheme_vals': {
48-
'id': 'VAT',
49-
},
50-
}]
45+
# Use company_registry (Company ID) as the VAT replacement
46+
vals_list = [{'company_id': partner.company_registry, 'tax_scheme_vals': {'id': 'VAT'}}]
47+
48+
# The validator for CIUS-RO (which extends the validations from the BIS3 Schematron) asserts a rule where:
49+
# [BR-CO-09] if the PartyTaxScheme/TaxScheme/ID == 'VAT', CompanyID must start with a country code prefix.
50+
# In Romania however, the CompanyID can be with or without country code prefix and still be perfectly valid.
51+
# We have to handle their cases by changing the TaxScheme/ID to 'something other than VAT',
52+
# preventing the trigger of the rule and allow Romanian companies without prefixed VAT to use CIUS-RO.
53+
for vals in vals_list:
54+
if partner.country_code == 'RO' and not vals['company_id'].upper().startswith('RO'):
55+
vals['tax_scheme_vals']['id'] = 'NO_VAT'
5156

5257
return vals_list
5358

@@ -98,12 +103,6 @@ def _export_invoice_constraints(self, invoice, vals):
98103
"At least one of them is required. ",
99104
partner.name)
100105

101-
if (not partner.vat and partner.company_registry
102-
and not partner.company_registry.startswith(partner.country_code)):
103-
constraints[f"ciusro_{partner_type}_country_code_company_registry_required"] = _(
104-
"The following partner's doesn't have a country code prefix in their Company ID: %s.",
105-
partner.name)
106-
107106
if (partner.country_code == 'RO'
108107
and partner.state_id
109108
and partner.state_id.code == 'B'
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
3+
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:efactura.mfinante.ro:CIUS-RO:1.0.1</cbc:CustomizationID>
4+
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
5+
<cbc:ID>INV/2017/00001</cbc:ID>
6+
<cbc:IssueDate>2017-01-01</cbc:IssueDate>
7+
<cbc:DueDate>2017-02-28</cbc:DueDate>
8+
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
9+
<cbc:Note>test narration</cbc:Note>
10+
<cbc:DocumentCurrencyCode>RON</cbc:DocumentCurrencyCode>
11+
<cbc:TaxCurrencyCode>RON</cbc:TaxCurrencyCode>
12+
<cbc:BuyerReference>ref_partner_a</cbc:BuyerReference>
13+
<cac:OrderReference>
14+
<cbc:ID>ref_move</cbc:ID>
15+
</cac:OrderReference>
16+
<cac:AdditionalDocumentReference>
17+
<cbc:ID>INV_2017_00001.pdf</cbc:ID>
18+
<cac:Attachment>
19+
<cbc:EmbeddedDocumentBinaryObject mimeCode="application/pdf" filename="INV_2017_00001.pdf">___ignore___</cbc:EmbeddedDocumentBinaryObject>
20+
</cac:Attachment>
21+
</cac:AdditionalDocumentReference>
22+
<cac:AccountingSupplierParty>
23+
<cac:Party>
24+
<cbc:EndpointID schemeID="9947">___ignore___</cbc:EndpointID>
25+
<cac:PartyName>
26+
<cbc:Name>Hudson Construction</cbc:Name>
27+
</cac:PartyName>
28+
<cac:PostalAddress>
29+
<cbc:StreetName>Strada Kunst, 3</cbc:StreetName>
30+
<cbc:CityName>SECTOR1</cbc:CityName>
31+
<cbc:PostalZone>010101</cbc:PostalZone>
32+
<cbc:CountrySubentity>RO-B</cbc:CountrySubentity>
33+
<cac:Country>
34+
<cbc:IdentificationCode>RO</cbc:IdentificationCode>
35+
</cac:Country>
36+
</cac:PostalAddress>
37+
<cac:PartyTaxScheme>
38+
<cbc:CompanyID>1234567897</cbc:CompanyID>
39+
<cac:TaxScheme>
40+
<cbc:ID>NO_VAT</cbc:ID>
41+
</cac:TaxScheme>
42+
</cac:PartyTaxScheme>
43+
<cac:PartyLegalEntity>
44+
<cbc:RegistrationName>Hudson Construction</cbc:RegistrationName>
45+
<cbc:CompanyID>1234567897</cbc:CompanyID>
46+
</cac:PartyLegalEntity>
47+
<cac:Contact>
48+
<cbc:Name>Hudson Construction</cbc:Name>
49+
<cbc:Telephone>+40 123 456 789</cbc:Telephone>
50+
</cac:Contact>
51+
</cac:Party>
52+
</cac:AccountingSupplierParty>
53+
<cac:AccountingCustomerParty>
54+
<cac:Party>
55+
<cbc:EndpointID schemeID="9947">___ignore___</cbc:EndpointID>
56+
<cac:PartyName>
57+
<cbc:Name>Roasted Romanian Roller</cbc:Name>
58+
</cac:PartyName>
59+
<cac:PostalAddress>
60+
<cbc:StreetName>Rolling Roast, 88</cbc:StreetName>
61+
<cbc:CityName>SECTOR3</cbc:CityName>
62+
<cbc:PostalZone>010101</cbc:PostalZone>
63+
<cbc:CountrySubentity>RO-B</cbc:CountrySubentity>
64+
<cac:Country>
65+
<cbc:IdentificationCode>RO</cbc:IdentificationCode>
66+
</cac:Country>
67+
</cac:PostalAddress>
68+
<cac:PartyTaxScheme>
69+
<cbc:CompanyID>1234567897</cbc:CompanyID>
70+
<cac:TaxScheme>
71+
<cbc:ID>NO_VAT</cbc:ID>
72+
</cac:TaxScheme>
73+
</cac:PartyTaxScheme>
74+
<cac:PartyLegalEntity>
75+
<cbc:RegistrationName>Roasted Romanian Roller</cbc:RegistrationName>
76+
<cbc:CompanyID>1234567897</cbc:CompanyID>
77+
</cac:PartyLegalEntity>
78+
<cac:Contact>
79+
<cbc:Name>Roasted Romanian Roller</cbc:Name>
80+
<cbc:Telephone>+40 123 456 780</cbc:Telephone>
81+
</cac:Contact>
82+
</cac:Party>
83+
</cac:AccountingCustomerParty>
84+
<cac:Delivery>
85+
<cac:DeliveryLocation>
86+
<cac:Address>
87+
<cbc:StreetName>Rolling Roast, 88</cbc:StreetName>
88+
<cbc:CityName>SECTOR3</cbc:CityName>
89+
<cbc:PostalZone>010101</cbc:PostalZone>
90+
<cbc:CountrySubentity>RO-B</cbc:CountrySubentity>
91+
<cac:Country>
92+
<cbc:IdentificationCode>RO</cbc:IdentificationCode>
93+
</cac:Country>
94+
</cac:Address>
95+
</cac:DeliveryLocation>
96+
</cac:Delivery>
97+
<cac:PaymentMeans>
98+
<cbc:PaymentMeansCode name="credit transfer">30</cbc:PaymentMeansCode>
99+
<cbc:PaymentID>INV/2017/00001</cbc:PaymentID>
100+
<cac:PayeeFinancialAccount>
101+
<cbc:ID>RO98RNCB1234567890123456</cbc:ID>
102+
</cac:PayeeFinancialAccount>
103+
</cac:PaymentMeans>
104+
<cac:PaymentTerms>
105+
<cbc:Note>Payment terms: 30% Advance End of Following Month</cbc:Note>
106+
</cac:PaymentTerms>
107+
<cac:TaxTotal>
108+
<cbc:TaxAmount currencyID="RON">285.00</cbc:TaxAmount>
109+
<cac:TaxSubtotal>
110+
<cbc:TaxableAmount currencyID="RON">1500.00</cbc:TaxableAmount>
111+
<cbc:TaxAmount currencyID="RON">285.00</cbc:TaxAmount>
112+
<cac:TaxCategory>
113+
<cbc:ID>S</cbc:ID>
114+
<cbc:Percent>19.0</cbc:Percent>
115+
<cac:TaxScheme>
116+
<cbc:ID>VAT</cbc:ID>
117+
</cac:TaxScheme>
118+
</cac:TaxCategory>
119+
</cac:TaxSubtotal>
120+
</cac:TaxTotal>
121+
<cac:LegalMonetaryTotal>
122+
<cbc:LineExtensionAmount currencyID="RON">1500.00</cbc:LineExtensionAmount>
123+
<cbc:TaxExclusiveAmount currencyID="RON">1500.00</cbc:TaxExclusiveAmount>
124+
<cbc:TaxInclusiveAmount currencyID="RON">1785.00</cbc:TaxInclusiveAmount>
125+
<cbc:PrepaidAmount currencyID="RON">0.00</cbc:PrepaidAmount>
126+
<cbc:PayableAmount currencyID="RON">1785.00</cbc:PayableAmount>
127+
</cac:LegalMonetaryTotal>
128+
<cac:InvoiceLine>
129+
<cbc:ID>___ignore___</cbc:ID>
130+
<cbc:InvoicedQuantity unitCode="C62">1.0</cbc:InvoicedQuantity>
131+
<cbc:LineExtensionAmount currencyID="RON">500.00</cbc:LineExtensionAmount>
132+
<cac:Item>
133+
<cbc:Description>product_a</cbc:Description>
134+
<cbc:Name>product_a</cbc:Name>
135+
<cac:ClassifiedTaxCategory>
136+
<cbc:ID>S</cbc:ID>
137+
<cbc:Percent>19.0</cbc:Percent>
138+
<cac:TaxScheme>
139+
<cbc:ID>VAT</cbc:ID>
140+
</cac:TaxScheme>
141+
</cac:ClassifiedTaxCategory>
142+
</cac:Item>
143+
<cac:Price>
144+
<cbc:PriceAmount currencyID="RON">500.0</cbc:PriceAmount>
145+
</cac:Price>
146+
</cac:InvoiceLine>
147+
<cac:InvoiceLine>
148+
<cbc:ID>___ignore___</cbc:ID>
149+
<cbc:InvoicedQuantity unitCode="DZN">1.0</cbc:InvoicedQuantity>
150+
<cbc:LineExtensionAmount currencyID="RON">1000.00</cbc:LineExtensionAmount>
151+
<cac:Item>
152+
<cbc:Description>product_b</cbc:Description>
153+
<cbc:Name>product_b</cbc:Name>
154+
<cac:ClassifiedTaxCategory>
155+
<cbc:ID>S</cbc:ID>
156+
<cbc:Percent>19.0</cbc:Percent>
157+
<cac:TaxScheme>
158+
<cbc:ID>VAT</cbc:ID>
159+
</cac:TaxScheme>
160+
</cac:ClassifiedTaxCategory>
161+
</cac:Item>
162+
<cac:Price>
163+
<cbc:PriceAmount currencyID="RON">1000.0</cbc:PriceAmount>
164+
</cac:Price>
165+
</cac:InvoiceLine>
166+
</Invoice>

addons/l10n_ro_edi/tests/test_xml_ubl_ro.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -97,29 +97,39 @@ def test_export_invoice_different_currency(self):
9797
self._assert_invoice_attachment(attachment, xpaths=None, expected_file_path='from_odoo/ciusro_out_invoice_different_currency.xml')
9898
self.currency_data['currency'] = self.env.ref('base.RON')
9999

100-
def test_export_no_vat_but_have_company_id_without_prefix(self):
101-
self.company_data['company'].write({
102-
'vat': None,
103-
'company_registry': '1234567897',
104-
})
105-
invoice = self.create_move("out_invoice", send=False)
106-
with self.assertRaisesRegex(UserError, "doesn't have a country code prefix in their Company ID"):
107-
invoice._generate_pdf_and_send_invoice(self.move_template, allow_fallback_pdf=False)
100+
def test_export_invoice_without_country_code_prefix_in_vat(self):
101+
self.company_data['company'].write({'vat': '1234567897'})
102+
self.partner_a.write({'vat': '1234567897'})
103+
invoice = self.create_move("out_invoice")
104+
attachment = self.get_attachment(invoice)
105+
self._assert_invoice_attachment(attachment, xpaths=None, expected_file_path='from_odoo/ciusro_out_invoice_no_prefix_vat.xml')
108106

109-
def test_export_no_vat_but_have_company_id_with_prefix(self):
110-
self.company_data['company'].write({
111-
'vat': None,
112-
'company_registry': 'RO1234567897',
113-
})
107+
def test_export_no_vat_but_have_company_registry(self):
108+
self.company_data['company'].write({'vat': False, 'company_registry': 'RO1234567897'})
109+
self.partner_a.write({'vat': False, 'company_registry': 'RO1234567897'})
114110
invoice = self.create_move("out_invoice")
115111
attachment = self.get_attachment(invoice)
116112
self._assert_invoice_attachment(attachment, xpaths=None, expected_file_path='from_odoo/ciusro_out_invoice.xml')
117113

114+
def test_export_no_vat_but_have_company_registry_without_prefix(self):
115+
self.company_data['company'].write({'vat': False, 'company_registry': '1234567897'})
116+
self.partner_a.write({'vat': False, 'company_registry': '1234567897'})
117+
invoice = self.create_move("out_invoice")
118+
attachment = self.get_attachment(invoice)
119+
self._assert_invoice_attachment(attachment, xpaths=None, expected_file_path='from_odoo/ciusro_out_invoice_no_prefix_vat.xml')
120+
121+
def test_export_no_vat_and_no_company_registry_raises_error(self):
122+
self.company_data['company'].write({'vat': False, 'company_registry': False})
123+
self.partner_a.write({'vat': False, 'company_registry': False})
124+
invoice = self.create_move("out_invoice", send=False)
125+
with self.assertRaisesRegex(UserError, "doesn't have a VAT nor Company ID"):
126+
invoice._generate_pdf_and_send_invoice(self.move_template, allow_fallback_pdf=False)
127+
118128
def test_export_constraints(self):
119-
self.company_data['company'].company_registry = None
129+
self.company_data['company'].company_registry = False
120130
for required_field in ('city', 'street', 'state_id', 'vat'):
121131
prev_val = self.company_data["company"][required_field]
122-
self.company_data["company"][required_field] = None
132+
self.company_data["company"][required_field] = False
123133
invoice = self.create_move("out_invoice", send=False)
124134
with self.assertRaisesRegex(UserError, "required"):
125135
invoice._generate_pdf_and_send_invoice(self.move_template, allow_fallback_pdf=False)

0 commit comments

Comments
 (0)