Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[16.0] l10n_fr_intrastat_product: update for 3 steps declaration + lxml/objectify to generate XML #494

Merged
merged 4 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions l10n_fr_intrastat_product/migrations/14.0.1.0.0/pre-migration.py

This file was deleted.

191 changes: 95 additions & 96 deletions l10n_fr_intrastat_product/models/intrastat_product_declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from datetime import datetime

from dateutil.relativedelta import relativedelta
from lxml import etree
from lxml import etree, objectify

from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
Expand Down Expand Up @@ -127,7 +127,13 @@
if invoice.intrastat_fiscal_position == "b2b":
regime_code = 21
elif invoice.intrastat_fiscal_position == "b2c":
regime_code = 29
# 29 is only for EMEBI (extended),
# not for the fiscal declaration (standard)
if self.reporting_level == "standard":
line_vals.clear()
return

Check warning on line 134 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L133-L134

Added lines #L133 - L134 were not covered by tests
else:
regime_code = 29

Check warning on line 136 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L136

Added line #L136 was not covered by tests
if regime_code:
regime = self.env.ref(
"l10n_fr_intrastat_product.fr_regime_%d" % regime_code
Expand Down Expand Up @@ -157,71 +163,58 @@
my_company_currency = self.company_id.currency_id.name
eu_countries = self.env.ref("base.europe").country_ids

root = etree.Element("INSTAT")
envelope = etree.SubElement(root, "Envelope")
envelope_id = etree.SubElement(envelope, "envelopeId")
root = objectify.Element("INSTAT")
envelope = objectify.SubElement(root, "Envelope")

Check warning on line 167 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L166-L167

Added lines #L166 - L167 were not covered by tests
if not self.company_id.fr_intrastat_accreditation:
self.message_post(
body=_(
"No XML file generated because the <b>Customs Accreditation "
"Identifier</b> is not set on the accounting configuration "
"page of the company '%s'."
msg = (

Check warning on line 169 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L169

Added line #L169 was not covered by tests
_(
"The Customs Accreditation "
"Identifier is not set for the company '%s'."
)
% self.company_id.display_name
)
return
envelope_id.text = self.company_id.fr_intrastat_accreditation
create_date_time = etree.SubElement(envelope, "DateTime")
create_date = etree.SubElement(create_date_time, "date")
self._account_config_warning(msg)
envelope.envelopeId = self.company_id.fr_intrastat_accreditation
create_date_time = objectify.SubElement(envelope, "DateTime")

Check warning on line 178 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L176-L178

Added lines #L176 - L178 were not covered by tests
now_user_tz = fields.Datetime.context_timestamp(self, datetime.now())
create_date.text = datetime.strftime(now_user_tz, "%Y-%m-%d")
create_time = etree.SubElement(create_date_time, "time")
create_time.text = datetime.strftime(now_user_tz, "%H:%M:%S")
party = etree.SubElement(envelope, "Party", partyType="PSI", partyRole="PSI")
party_id = etree.SubElement(party, "partyId")
party_id.text = my_company_identifier
party_name = etree.SubElement(party, "partyName")
party_name.text = self.company_id.name
software_used = etree.SubElement(envelope, "softwareUsed")
software_used.text = "Odoo"
declaration = etree.SubElement(envelope, "Declaration")
declaration_id = etree.SubElement(declaration, "declarationId")
declaration_id.text = self.year_month.replace("-", "")
reference_period = etree.SubElement(declaration, "referencePeriod")
reference_period.text = self.year_month
psi_id = etree.SubElement(declaration, "PSIId")
psi_id.text = my_company_identifier
function = etree.SubElement(declaration, "Function")
function_code = etree.SubElement(function, "functionCode")
function_code.text = "O"
declaration_type_code = etree.SubElement(declaration, "declarationTypeCode")
create_date_time.date = datetime.strftime(now_user_tz, "%Y-%m-%d")
create_date_time.time = datetime.strftime(now_user_tz, "%H:%M:%S")
party = objectify.SubElement(

Check warning on line 182 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L180-L182

Added lines #L180 - L182 were not covered by tests
envelope, "Party", partyType="PSI", partyRole="PSI"
)
party.partyId = my_company_identifier
party.partyName = self.company_id.name
envelope.softwareUsed = "Odoo"
declaration = objectify.SubElement(envelope, "Declaration")
declaration.declarationId = self.year_month.replace("-", "")
declaration.referencePeriod = self.year_month
declaration.PSIId = my_company_identifier
function = objectify.SubElement(declaration, "Function")
function.functionCode = "O" # O = Déclaration originelle

Check warning on line 193 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L185-L193

Added lines #L185 - L193 were not covered by tests
level2letter = {
"standard": "4",
"extended": "5", # EMEBI 2022: stat + fisc, 2 in 1 combo
}
assert self.reporting_level in level2letter
declaration_type_code.text = level2letter[self.reporting_level]
flow_code = etree.SubElement(declaration, "flowCode")
declaration.declarationTypeCode = level2letter[self.reporting_level]

Check warning on line 199 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L199

Added line #L199 was not covered by tests
type2letter = {
"arrivals": "A",
"dispatches": "D",
}
assert self.declaration_type in type2letter
flow_code.text = type2letter[self.declaration_type]
currency_code = etree.SubElement(declaration, "currencyCode")
declaration.flowCode = type2letter[self.declaration_type]

Check warning on line 205 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L205

Added line #L205 was not covered by tests
assert my_company_currency == "EUR", "Company currency must be 'EUR'"
currency_code.text = my_company_currency
declaration.currencyCode = my_company_currency

Check warning on line 207 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L207

Added line #L207 was not covered by tests

# THEN, the fields which vary from a line to the next
if not self.declaration_line_ids:
raise UserError(
_("No declaration lines. You probably forgot to generate " "them !")
)
line = 0
for pline in self.declaration_line_ids:
line += 1 # increment line number
pline._generate_xml_line(declaration, eu_countries, line)
pline._generate_xml_line(declaration, eu_countries)

Check warning on line 215 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L215

Added line #L215 was not covered by tests

objectify.deannotate(root, xsi_nil=True, cleanup_namespaces=True)

Check warning on line 217 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L217

Added line #L217 was not covered by tests
xml_bytes = etree.tostring(
root, pretty_print=True, encoding="UTF-8", xml_declaration=True
)
Expand Down Expand Up @@ -410,105 +403,111 @@
)

# flake8: noqa: C901
# TODO update error message to avoid quoting declaration line number
def _generate_xml_line(self, parent_node, eu_countries, line_number):
def _generate_xml_line(self, parent_node, eu_countries):
self.ensure_one()
decl = self.parent_id
assert self.fr_regime_id, "Missing Intrastat Type"
transaction = self.transaction_id
regime = self.fr_regime_id
item = etree.SubElement(parent_node, "Item")
item_number = etree.SubElement(item, "itemNumber")
item_number.text = str(line_number)
item = objectify.SubElement(parent_node, "Item")
item.itemNumber = str(self.line_number)

Check warning on line 413 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L412-L413

Added lines #L412 - L413 were not covered by tests
# START of elements which are only required in "detailed" level
if decl.reporting_level == "extended" and not regime.is_fiscal_only:
cn8 = etree.SubElement(item, "CN8")
cn8_code = etree.SubElement(cn8, "CN8Code")
cn8 = objectify.SubElement(item, "CN8")

Check warning on line 416 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L416

Added line #L416 was not covered by tests
if not self.hs_code_id:
raise UserError(_("Missing H.S. code on line %d.") % line_number)
raise UserError(

Check warning on line 418 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L418

Added line #L418 was not covered by tests
_("Missing H.S. code on declaration line %d.") % self.line_number
)
# local_code is required=True, so no need to check it
cn8_code.text = self.hs_code_id.local_code
cn8.CN8Code = self.hs_code_id.local_code

Check warning on line 422 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L422

Added line #L422 was not covered by tests
# We fill SUCode only if the H.S. code requires it
iunit_id = self.intrastat_unit_id
if iunit_id:
su_code = etree.SubElement(cn8, "SUCode")
su_code.text = iunit_id.fr_xml_label or iunit_id.name
cn8.SUCode = iunit_id.fr_xml_label or iunit_id.name

Check warning on line 426 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L426

Added line #L426 was not covered by tests

src_dest_country = etree.SubElement(item, "MSConsDestCode")
if not self.src_dest_country_code:
raise UserError(
_("Missing Country Code of Origin/Destination on line %d.")
% line_number
_(
"Missing country code of origin/destination on declaration line %d."
)
% self.line_number
)
src_dest_country.text = self.src_dest_country_code
item.MSConsDestCode = self.src_dest_country_code

Check warning on line 435 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L435

Added line #L435 was not covered by tests

# EMEBI 2022 : origin country is now for arrival AND dispatches
country_origin = etree.SubElement(item, "countryOfOriginCode")
if not self.product_origin_country_code:
raise UserError(
_("Missing product country of origin code on line %d.")
% line_number
_("Missing product country of origin code on declaration line %d.")
% self.line_number
)
country_origin.text = self.product_origin_country_code
item.countryOfOriginCode = self.product_origin_country_code

Check warning on line 443 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L443

Added line #L443 was not covered by tests

weight = etree.SubElement(item, "netMass")
# no need for float_is_zero() because weight is an integer on decl lines
if not self.weight:
raise UserError(_("Missing weight on line %d.") % line_number)
weight.text = str(self.weight)
raise UserError(

Check warning on line 447 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L447

Added line #L447 was not covered by tests
_("Missing weight on declaration line %d.") % self.line_number
)
item.netMass = str(self.weight)

Check warning on line 450 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L450

Added line #L450 was not covered by tests

if iunit_id:
quantity_in_SU = etree.SubElement(item, "quantityInSU")
# no need for float_is_zero() because suppl_unit_qty is an integer
# on declaration lines
if not self.suppl_unit_qty:
raise UserError(_("Missing quantity on line %d.") % line_number)
quantity_in_SU.text = str(self.suppl_unit_qty)
raise UserError(

Check warning on line 456 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L456

Added line #L456 was not covered by tests
_("Missing quantity on declaration line %d.") % self.line_number
)
item.quantityInSU = str(self.suppl_unit_qty)

Check warning on line 459 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L459

Added line #L459 was not covered by tests

# START of elements that are part of all EMEBIs
invoiced_amount = etree.SubElement(item, "invoicedAmount")
if not self.amount_company_currency:
raise UserError(_("Missing fiscal value on line %d.") % line_number)
invoiced_amount.text = str(self.amount_company_currency)
raise UserError(

Check warning on line 463 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L463

Added line #L463 was not covered by tests
_("Missing fiscal value on declaration line %d.") % self.line_number
)
item.invoicedAmount = str(self.amount_company_currency)

Check warning on line 466 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L466

Added line #L466 was not covered by tests
# EMEBI 2022 : Partner VAT now required for all dispatches with
# some exceptions for regime 29 in case of B2C
if decl.declaration_type == "dispatches":
partner_vat = etree.SubElement(item, "partnerId")
if not self.vat and regime.code != "29":
raise UserError(_("Missing VAT number on line %d.") % line_number)
raise UserError(

Check warning on line 471 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L471

Added line #L471 was not covered by tests
_("Missing VAT number on declaration line %d.") % self.line_number
)
if self.vat and self.vat.startswith("GB") and decl.year >= "2021":
raise UserError(
_(
"Bad VAT number '%(vat)s' on line %(line_number)d. "
"Bad VAT number '%(vat)s' on declaration line %(line_number)d. "
"Brexit took place on January 1st 2021 and companies "
"in Northern Ireland have a new VAT number starting with 'XI'."
"in Northern Ireland have a new VAT number starting with 'XI'.",
vat=self.vat,
line_number=self.line_number,
)
% {"vat": self.vat, "line_number": line_number}
)
partner_vat.text = self.vat and self.vat.replace(" ", "") or ""
item.partnerId = self.vat or ""

Check warning on line 484 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L484

Added line #L484 was not covered by tests
# Code régime is on all EMEBIs
statistical_procedure_code = etree.SubElement(item, "statisticalProcedureCode")
statistical_procedure_code.text = regime.code
item.statisticalProcedureCode = regime.code

Check warning on line 486 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L486

Added line #L486 was not covered by tests

# START of elements which are only required in "detailed" level
if decl.reporting_level == "extended" and not regime.is_fiscal_only:
transaction_nature = etree.SubElement(item, "NatureOfTransaction")
transaction_nature_a = etree.SubElement(
transaction_nature, "natureOfTransactionACode"
)
transaction_nature_a.text = transaction.code[0]
transaction_nature_b = etree.SubElement(
transaction_nature, "natureOfTransactionBCode"
)
if len(transaction.code) != 2:
if not transaction:
raise UserError(

Check warning on line 491 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L491

Added line #L491 was not covered by tests
_("Missing intrastat transaction on declaration line %d.")
% self.line_number
)
if len(transaction.code) != 2 or not transaction.code.isdigit():
raise UserError(
_("Transaction code on line %d should have 2 digits.") % line_number
_("Transaction code on declaration line %d should have 2 digits.")
% self.line_number
)
transaction_nature_b.text = transaction.code[1]
mode_of_transport_code = etree.SubElement(item, "modeOfTransportCode")
transaction_nature = objectify.SubElement(item, "NatureOfTransaction")
transaction_nature.natureOfTransactionACode = transaction.code[0]
transaction_nature.natureOfTransactionBCode = transaction.code[1]

Check warning on line 502 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L500-L502

Added lines #L500 - L502 were not covered by tests
if not self.transport_id:
raise UserError(
_("Mode of transport is not set on line %d.") % line_number
_("Missing mode of transport on declaration line %d.")
% self.line_number
)
mode_of_transport_code.text = str(self.transport_id.code)
region_code = etree.SubElement(item, "regionCode")
item.modeOfTransportCode = str(self.transport_id.code)

Check warning on line 508 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L508

Added line #L508 was not covered by tests
if not self.region_code:
raise UserError(_("Region Code is not set on line %d.") % line_number)
region_code.text = self.region_code
raise UserError(

Check warning on line 510 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L510

Added line #L510 was not covered by tests
_("Missing region code on declaration line %d.") % self.line_number
)
item.regionCode = self.region_code

Check warning on line 513 in l10n_fr_intrastat_product/models/intrastat_product_declaration.py

View check run for this annotation

Codecov / codecov/patch

l10n_fr_intrastat_product/models/intrastat_product_declaration.py#L513

Added line #L513 was not covered by tests
Loading