From 7db50cfea120f302d7712db8ade24e5cacdd07d9 Mon Sep 17 00:00:00 2001
From: Alexis de Lattre
Date: Fri, 4 Nov 2022 17:54:40 +0100
Subject: [PATCH 1/7] l10n_fr_intrastat_* : update, improve and simplify
l10n_fr_intrastat_product:
- adapt to the very big changes I made in intrastat_product
- DEB -> EMEBI
- remove intrastat_fiscal_representative_id on res.partner
- add a default intrastat transaction for B2C
- filter intrastat_transaction_id on invoice form depending on move_type
l10n_fr_intrastat_service:
- auto-generate XML export upon confirmation to harmonize ergonomy with intrastat product
- auto-delete XML upon back2draft
- add confirmation pop-up when going back to draft
- fix bug on total amount and number of lines when removing all lines
- adapt for the fact that EU B2C fiscal positions now have intrastat=True
---
l10n_fr_intrastat_product/__manifest__.py | 11 +-
.../data/intrastat_product_reminder.xml | 27 +-
.../data/intrastat_transaction.xml | 13 +-
.../demo/intrastat_demo.xml | 17 +-
l10n_fr_intrastat_product/models/__init__.py | 1 -
.../models/intrastat_product_declaration.py | 297 ++++--------------
.../models/intrastat_transaction.py | 10 +-
.../models/intrastat_unit.py | 2 +-
.../models/res_company.py | 17 +-
.../models/res_partner.py | 54 ----
l10n_fr_intrastat_product/models/stock.py | 11 +-
l10n_fr_intrastat_product/post_install.py | 44 +--
.../readme/DESCRIPTION.rst | 4 +-
l10n_fr_intrastat_product/readme/USAGE.rst | 2 +-
.../security/intrastat_product_security.xml | 20 --
.../security/ir.model.access.csv | 4 -
.../views/account_fiscal_position.xml | 20 ++
.../views/account_move.xml | 21 ++
.../views/intrastat_product_declaration.xml | 111 ++-----
.../views/res_partner.xml | 22 --
.../models/intrastat_service.py | 27 +-
.../readme/DESCRIPTION.rst | 2 +-
.../views/intrastat_service_view.xml | 15 +-
23 files changed, 227 insertions(+), 525 deletions(-)
delete mode 100644 l10n_fr_intrastat_product/models/res_partner.py
delete mode 100644 l10n_fr_intrastat_product/security/intrastat_product_security.xml
delete mode 100644 l10n_fr_intrastat_product/security/ir.model.access.csv
create mode 100644 l10n_fr_intrastat_product/views/account_fiscal_position.xml
create mode 100644 l10n_fr_intrastat_product/views/account_move.xml
delete mode 100644 l10n_fr_intrastat_product/views/res_partner.xml
diff --git a/l10n_fr_intrastat_product/__manifest__.py b/l10n_fr_intrastat_product/__manifest__.py
index ea54a74b5..4c0a42334 100644
--- a/l10n_fr_intrastat_product/__manifest__.py
+++ b/l10n_fr_intrastat_product/__manifest__.py
@@ -1,13 +1,13 @@
-# Copyright 2010-2020 Akretion France (http://www.akretion.com)
+# Copyright 2010-2022 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
- "name": "DEB",
+ "name": "EMEBI",
"version": "14.0.2.0.0",
"category": "Localisation/Report Intrastat",
"license": "AGPL-3",
- "summary": "DEB (Déclaration d'Échange de Biens) for France",
+ "summary": "EMEBI (ex-DEB) for France",
"author": "Akretion,Odoo Community Association (OCA)",
"maintainers": ["alexis-via"],
"website": "https://github.com/OCA/l10n-france",
@@ -18,16 +18,15 @@
],
"data": [
"data/account_fiscal_position_template.xml",
- "security/intrastat_product_security.xml",
- "security/ir.model.access.csv",
"views/intrastat_product_declaration.xml",
+ "views/account_fiscal_position.xml",
"data/intrastat_transaction.xml",
"views/intrastat_transaction.xml",
"views/intrastat_unit.xml",
"data/intrastat_product_reminder.xml",
"views/res_config_settings.xml",
- "views/res_partner.xml",
"views/product_template.xml",
+ "views/account_move.xml",
],
"post_init_hook": "set_fr_company_intrastat",
"demo": ["demo/intrastat_demo.xml"],
diff --git a/l10n_fr_intrastat_product/data/intrastat_product_reminder.xml b/l10n_fr_intrastat_product/data/intrastat_product_reminder.xml
index 434e8be71..8c4e98986 100644
--- a/l10n_fr_intrastat_product/data/intrastat_product_reminder.xml
+++ b/l10n_fr_intrastat_product/data/intrastat_product_reminder.xml
@@ -7,13 +7,16 @@
- DEB Reminder
+ EMEBI Reminder1months-1
-
+ codemodel._scheduler_reminder()
@@ -23,10 +26,10 @@
id="l10n_fr_intrastat_product_reminder_email_template"
model="mail.template"
>
- DEB Reminder
+ EMEBI Reminder${object.company_id.intrastat_email_list}${object.declaration_type} DEB ${object.year_month} for ${object.company_id.name}
+ >${object.declaration_type} EMEBI ${object.year_month} for ${object.company_id.name}
-
I would like to remind you that we are approaching the deadline for the DEB for month ${object.year_month}.
+
I would like to remind you that we are approaching the deadline for the EMEBI for month ${object.year_month}.
-
As there were no ${object.declaration_type} DEB for that month in Odoo, a draft ${object.declaration_type} DEB has been generated automatically by Odoo.
+
As there were no ${object.declaration_type} EMEBI for that month in Odoo, a draft ${object.declaration_type} EMEBI has been generated automatically by Odoo.
% if ctx.get('exception'):
-
When trying to generate the lines of the ${object.declaration_type} DEB, the following error was encountered:
+
When trying to generate the lines of the ${object.declaration_type} EMEBI, the following error was encountered:
${ctx.get('error_msg')}
-
You should solve this error, then go to the menu "Invoicing > Reporting > Intrastat > DEB", open the ${object.declaration_type} declaration for month ${object.year_month} and click on the button "Generate lines from invoices".
+
You should solve this error, then go to the menu "Invoicing > Reporting > Intrastat > EMEBI", open the ${object.declaration_type} declaration for month ${object.year_month} and click on the button "Generate lines from invoices".
% else:
% if object.num_lines and object.num_lines > 0:
-
This draft ${object.declaration_type} DEB contains ${object.num_decl_lines} ${object.num_decl_lines == 1 and 'line' or 'lines'}.
+
This draft ${object.declaration_type} EMEBI contains ${object.num_decl_lines} ${object.num_decl_lines == 1 and 'line' or 'lines'}.
% else:
-
This draft ${object.declaration_type} DEB generated automatically by Odoo doesn't contain any line.
+
This draft ${object.declaration_type} EMEBI generated automatically by Odoo doesn't contain any line.
% endif
-
Go and check this declaration in Odoo in the menu "Invoicing > Reporting > Intrastat > DEB".
+
Go and check this declaration in Odoo in the menu "Invoicing > Reporting > Intrastat > EMEBI".
% endif
diff --git a/l10n_fr_intrastat_product/data/intrastat_transaction.xml b/l10n_fr_intrastat_product/data/intrastat_transaction.xml
index 36dec1203..a7e81c15c 100644
--- a/l10n_fr_intrastat_product/data/intrastat_transaction.xml
+++ b/l10n_fr_intrastat_product/data/intrastat_transaction.xml
@@ -1,6 +1,6 @@
@@ -19,7 +19,7 @@
Vente Client (Livraisons intracomm. exo. en France et taxables dans l'Etat d'arrivée)
+ >Vente Client B2B (Livraisons intracomm. exo. en France et taxables dans l'Etat d'arrivée)21out_invoice11
@@ -27,6 +27,15 @@
1dispatches
+
+ Vente Client B2C (soumises à TVA)
+ 29
+ out_invoice
+ 12
+
+ 0
+ dispatches
+ A12B
-
-
- 1
@@ -29,10 +20,4 @@
PCE
-
-
diff --git a/l10n_fr_intrastat_product/models/__init__.py b/l10n_fr_intrastat_product/models/__init__.py
index 316820b60..9ba2e4e6b 100644
--- a/l10n_fr_intrastat_product/models/__init__.py
+++ b/l10n_fr_intrastat_product/models/__init__.py
@@ -1,5 +1,4 @@
from . import intrastat_transaction
-from . import res_partner
from . import intrastat_unit
from . import stock
from . import res_company
diff --git a/l10n_fr_intrastat_product/models/intrastat_product_declaration.py b/l10n_fr_intrastat_product/models/intrastat_product_declaration.py
index 8712e81c5..0ea5f5deb 100644
--- a/l10n_fr_intrastat_product/models/intrastat_product_declaration.py
+++ b/l10n_fr_intrastat_product/models/intrastat_product_declaration.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2020 Akretion France (http://www.akretion.com)
+# Copyright 2009-2022 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
@@ -9,7 +9,7 @@
from lxml import etree
from odoo import _, api, fields, models
-from odoo.exceptions import UserError
+from odoo.exceptions import UserError, ValidationError
logger = logging.getLogger(__name__)
@@ -37,53 +37,36 @@ def _compute_fr_numbers(self):
decl.num_decl_lines = num_lines
decl.total_amount = total_amount
-
-class L10nFrIntrastatProductDeclaration(models.Model):
- _name = "l10n.fr.intrastat.product.declaration"
- _description = "Intrastat Product for France (DEB)"
- _inherit = [
- "intrastat.product.declaration",
- "mail.thread",
- "mail.activity.mixin",
- "report.intrastat_product.product_declaration_xls",
- ]
-
- computation_line_ids = fields.One2many(
- "l10n.fr.intrastat.product.computation.line",
- "parent_id",
- string="Intrastat Product Computation Lines",
- states={"done": [("readonly", True)]},
- )
- declaration_line_ids = fields.One2many(
- "l10n.fr.intrastat.product.declaration.line",
- "parent_id",
- string="Intrastat Product Declaration Lines",
- states={"done": [("readonly", True)]},
- )
+ @api.constrains("reporting_level", "declaration_type")
+ def _check_fr_declaration(self):
+ for decl in self:
+ if (
+ decl.declaration_type == "arrivals"
+ and decl.reporting_level == "standard"
+ and decl.company_id.country_id.code == "FR"
+ ):
+ raise ValidationError(
+ _(
+ "In France, an arrival EMEBI cannot have a 'standard' reporting level."
+ )
+ )
def _prepare_invoice_domain(self):
domain = super()._prepare_invoice_domain()
- for index, entry in enumerate(domain):
- if entry[0] == "move_type":
- domain.pop(index)
if self.declaration_type == "arrivals":
+ for index, entry in enumerate(domain):
+ if entry[0] == "move_type":
+ domain.pop(index)
domain.append(("move_type", "=", "in_invoice"))
- elif self.declaration_type == "dispatches":
- domain.append(("move_type", "in", ("out_invoice", "out_refund")))
return domain
- def _get_product_origin_country(self, inv_line, notedict):
- """Inherit to add warning when origin_country_id is missing"""
- if (
- self.reporting_level == "extended"
- and not inv_line.product_id.origin_country_id
- ):
- line_notes = [
- _("Missing country of origin on product '%s'.")
- % inv_line.product_id.display_name
- ]
- self._format_line_note(inv_line, notedict, line_notes)
- return super()._get_product_origin_country(inv_line, notedict)
+ def _get_region_code(self, inv_line, notedict):
+ if self.company_id.country_id.code != "FR":
+ return super()._get_region_code(inv_line, notedict)
+ else:
+ dpt = self._get_fr_department(inv_line, notedict)
+ region_code = dpt and dpt.code or False
+ return region_code
def _get_fr_department(self, inv_line, notedict):
dpt = False
@@ -95,100 +78,29 @@ def _get_fr_department(self, inv_line, notedict):
if po_line:
wh = po_line.order_id.picking_type_id.warehouse_id
if wh:
- dpt = wh.get_fr_department()
+ dpt = wh._get_fr_department()
elif po_line.move_ids:
location = po_line.move_ids[0].location_dest_id
- dpt = location.get_fr_department()
+ dpt = location._get_fr_department()
elif move_type in ("out_invoice", "out_refund"):
so_line = self.env["sale.order.line"].search(
[("invoice_lines", "in", inv_line.id)], limit=1
)
if so_line:
so = so_line.order_id
- dpt = so.warehouse_id.get_fr_department()
+ dpt = so.warehouse_id._get_fr_department()
if not dpt:
dpt = self.company_id.partner_id.department_id
- return dpt
-
- def _update_computation_line_vals(self, inv_line, line_vals, notedict):
- super()._update_computation_line_vals(inv_line, line_vals, notedict)
- if not line_vals.get("vat"):
- inv = inv_line.move_id
- commercial_partner = inv.commercial_partner_id
- eu_countries = self.env.ref("base.europe").country_ids
- if (
- commercial_partner.country_id not in eu_countries
- and not commercial_partner.intrastat_fiscal_representative_id
- ):
+ if not dpt:
line_notes = [
_(
- "Missing fiscal representative on partner '%s'."
- % commercial_partner.display_name
+ "Missing department on partner '%s'. "
+ "To set it, set the country and the zip code on this partner."
)
+ % self.company_id.partner_id.display_name
]
self._format_line_note(inv_line, notedict, line_notes)
- else:
- fiscal_rep = commercial_partner.intrastat_fiscal_representative_id
- if not fiscal_rep.vat:
- line_notes = [
- _(
- "Missing VAT number on partner '%s' which is the "
- "fiscal representative of partner '%s'."
- % (fiscal_rep.display_name, commercial_partner.display_name)
- )
- ]
- self._format_line_note(inv_line, notedict, line_notes)
- else:
- line_vals["vat"] = fiscal_rep.vat
- dpt = self._get_fr_department(inv_line, notedict)
- line_vals["fr_department_id"] = dpt and dpt.id or False
-
- @api.model
- def _group_line_hashcode_fields(self, computation_line):
- res = super()._group_line_hashcode_fields(computation_line)
- res["fr_department_id"] = computation_line.fr_department_id.id or False
- return res
-
- @api.model
- def _prepare_grouped_fields(self, computation_line, fields_to_sum):
- vals = super()._prepare_grouped_fields(computation_line, fields_to_sum)
- vals["fr_department_id"] = computation_line.fr_department_id.id
- return vals
-
- def _get_region(self, inv_line, notedict):
- # TODO : modify only for country == FR
- return False
-
- @api.model
- def _xls_template(self):
- res = super()._xls_template()
- res.update(
- {
- "fr_department": {
- "header": {
- "type": "string",
- "value": self._("Department"),
- },
- "line": {
- "value": self._render("line.fr_department_id.display_name"),
- },
- "width": 18,
- }
- }
- )
- return res
-
- @api.model
- def _xls_computation_line_fields(self):
- field_list = super()._xls_computation_line_fields()
- field_list += ["fr_department"]
- return field_list
-
- @api.model
- def _xls_declaration_line_fields(self):
- field_list = super()._xls_declaration_line_fields()
- field_list += ["fr_department"]
- return field_list
+ return dpt
def _generate_xml(self):
"""Generate the INSTAT XML file export."""
@@ -249,7 +161,7 @@ def _generate_xml(self):
declaration_type_code = etree.SubElement(declaration, "declarationTypeCode")
level2letter = {
"standard": "4",
- "extended": "5", # DEB 2022: stat + fisc, 2 in 1 combo
+ "extended": "5", # EMEBI 2022: stat + fisc, 2 in 1 combo
}
assert self.reporting_level in level2letter
declaration_type_code.text = level2letter[self.reporting_level]
@@ -288,7 +200,7 @@ def _generate_xml(self):
@api.model
def _scheduler_reminder(self):
- logger.info("Start DEB reminder")
+ logger.info("Start EMEBI reminder")
previous_month = datetime.strftime(
datetime.today() + relativedelta(day=1, months=-1), "%Y-%m"
)
@@ -350,7 +262,7 @@ def _scheduler_reminder(self):
}
)
logger.info(
- "An %s DEB for month %s has been created by Odoo for "
+ "An %s EMEBI for month %s has been created by Odoo for "
"company %s",
declaration_type,
previous_month,
@@ -358,7 +270,7 @@ def _scheduler_reminder(self):
)
intrastat.message_post(
body=_(
- "This DEB has been auto-generated by the DEB reminder "
+ "This EMEBI has been auto-generated by the EMEBI reminder "
"scheduled action."
)
)
@@ -372,7 +284,7 @@ def _scheduler_reminder(self):
if company.intrastat_remind_user_ids:
mail_template.send_mail(intrastat.id)
logger.info(
- "DEB Reminder email has been sent to %s",
+ "EMEBI Reminder email has been sent to %s",
company.intrastat_email_list,
)
else:
@@ -381,83 +293,15 @@ def _scheduler_reminder(self):
"is empty on company %s",
company.display_name,
)
- logger.info("End of the DEB reminder")
+ logger.info("End of the EMEBI reminder")
return
-class L10nFrIntrastatProductComputationLine(models.Model):
- _name = "l10n.fr.intrastat.product.computation.line"
- _description = "DEB computation lines"
- _inherit = "intrastat.product.computation.line"
-
- parent_id = fields.Many2one(
- "l10n.fr.intrastat.product.declaration",
- string="Intrastat Product Declaration",
- ondelete="cascade",
- readonly=True,
- )
- declaration_line_id = fields.Many2one(
- "l10n.fr.intrastat.product.declaration.line",
- string="Declaration Line",
- readonly=True,
- )
- fr_department_id = fields.Many2one(
- "res.country.department", string="Department", ondelete="restrict"
- )
- # the 2 fields below are useful for reports
- amount_company_currency_sign = fields.Float(
- compute="_compute_amount_company_currency_sign", store=True
- )
- amount_accessory_cost_company_currency_sign = fields.Float(
- compute="_compute_amount_company_currency_sign", store=True
- )
-
- @api.depends(
- "amount_company_currency",
- "amount_accessory_cost_company_currency",
- "transaction_id.fr_fiscal_value_multiplier",
- )
- def _compute_amount_company_currency_sign(self):
- for line in self:
- sign = line.transaction_id.fr_fiscal_value_multiplier or 1
- line.amount_company_currency_sign = sign * line.amount_company_currency
- line.amount_accessory_cost_company_currency_sign = (
- sign * line.amount_accessory_cost_company_currency
- )
-
-
-class L10nFrIntrastatProductDeclarationLine(models.Model):
- _name = "l10n.fr.intrastat.product.declaration.line"
- _description = "DEB declaration lines"
+class IntrastatProductDeclarationLine(models.Model):
_inherit = "intrastat.product.declaration.line"
- parent_id = fields.Many2one(
- "l10n.fr.intrastat.product.declaration",
- string="Intrastat Product Declaration",
- ondelete="cascade",
- readonly=True,
- )
- computation_line_ids = fields.One2many(
- "l10n.fr.intrastat.product.computation.line",
- "declaration_line_id",
- string="Computation Lines",
- readonly=True,
- )
- fr_department_id = fields.Many2one(
- "res.country.department", string="Departement", ondelete="restrict"
- )
- # the field below is useful for reports
- amount_company_currency_sign = fields.Float(
- compute="_compute_amount_company_currency_sign", store=True
- )
-
- @api.depends("amount_company_currency", "transaction_id.fr_fiscal_value_multiplier")
- def _compute_amount_company_currency_sign(self):
- for line in self:
- sign = line.transaction_id.fr_fiscal_value_multiplier or 1
- line.amount_company_currency_sign = sign * line.amount_company_currency
-
# flake8: noqa: C901
+ # TODO update error message to avoid quoting declaration line number
def _generate_xml_line(self, parent_node, eu_countries, line_number):
self.ensure_one()
decl = self.parent_id
@@ -481,43 +325,21 @@ def _generate_xml_line(self, parent_node, eu_countries, line_number):
su_code.text = iunit_id.fr_xml_label or iunit_id.name
src_dest_country = etree.SubElement(item, "MSConsDestCode")
- if not self.src_dest_country_id:
- raise UserError(
- _("Missing Country of Origin/Destination on line %d.") % line_number
- )
- src_dest_country_code = self.src_dest_country_id.code
- if (
- self.src_dest_country_id not in eu_countries
- and src_dest_country_code != "GB"
- ):
+ if not self.src_dest_country_code:
raise UserError(
- _(
- "On line %d, the source/destination country is '%s', "
- "which is not part of the European Union."
- )
- % (line_number, self.src_dest_country_id.name)
+ _("Missing Country Code of Origin/Destination on line %d.")
+ % line_number
)
- if src_dest_country_code == "GB" and decl.year >= "2021":
- # all warnings are done during generation
- src_dest_country_code = "XI"
- src_dest_country.text = src_dest_country_code
+ src_dest_country.text = self.src_dest_country_code
- # DEB 2022 : origin country is now for arrival AND dispatches
+ # EMEBI 2022 : origin country is now for arrival AND dispatches
country_origin = etree.SubElement(item, "countryOfOriginCode")
- if not self.product_origin_country_id:
+ if not self.product_origin_country_code:
raise UserError(
- _("Missing product country of origin on line %d.") % line_number
+ _("Missing product country of origin code on line %d.")
+ % line_number
)
- country_origin_code = self.product_origin_country_id.code
- # BOD dated 5/1/2021 says:
- # Si, pour une marchandise produite au Royaume-Uni,
- # le déclarant ignore si le lieu de production de la
- # marchandise est situé en Irlande du Nord ou dans le
- # reste du Royaume-Uni, il utilise également le code XU.
- # => we always use XU
- if country_origin == "GB" and decl.year >= "2021":
- country_origin_code = "XU"
- country_origin.text = country_origin_code
+ country_origin.text = self.product_origin_country_code
weight = etree.SubElement(item, "netMass")
if not self.weight:
@@ -530,17 +352,18 @@ def _generate_xml_line(self, parent_node, eu_countries, line_number):
raise UserError(_("Missing quantity on line %d.") % line_number)
quantity_in_SU.text = str(self.suppl_unit_qty)
- # START of elements that are part of all DEBs
+ # 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)
- # DEB 2022 : Partner VAT now required for all dispatches
+ # 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:
+ if not self.vat and transaction.code != "29":
raise UserError(_("Missing VAT number on line %d.") % line_number)
- if self.vat.startswith("GB") and decl.year >= "2021":
+ if self.vat and self.vat.startswith("GB") and decl.year >= "2021":
raise UserError(
_(
"Bad VAT number '%s' on line %d. Brexit took place "
@@ -549,8 +372,8 @@ def _generate_xml_line(self, parent_node, eu_countries, line_number):
)
% (self.vat, line_number)
)
- partner_vat.text = self.vat.replace(" ", "")
- # Code régime is on all DEBs
+ partner_vat.text = self.vat and self.vat.replace(" ", "") or ""
+ # Code régime is on all EMEBIs
statistical_procedure_code = etree.SubElement(item, "statisticalProcedureCode")
statistical_procedure_code.text = transaction.code
@@ -576,6 +399,6 @@ def _generate_xml_line(self, parent_node, eu_countries, line_number):
)
mode_of_transport_code.text = str(self.transport_id.code)
region_code = etree.SubElement(item, "regionCode")
- if not self.fr_department_id:
- raise UserError(_("Department is not set on line %d.") % line_number)
- region_code.text = self.fr_department_id.code
+ if not self.region_code:
+ raise UserError(_("Region Code is not set on line %d.") % line_number)
+ region_code.text = self.region_code
diff --git a/l10n_fr_intrastat_product/models/intrastat_transaction.py b/l10n_fr_intrastat_product/models/intrastat_transaction.py
index b3af74c76..32cf5f5a8 100644
--- a/l10n_fr_intrastat_product/models/intrastat_transaction.py
+++ b/l10n_fr_intrastat_product/models/intrastat_transaction.py
@@ -1,7 +1,9 @@
-# Copyright 2010-2020 Akretion France (http://www.akretion.com/)
+# Copyright 2010-2022 Akretion France (http://www.akretion.com/)
# @author Alexis de Lattre
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+from textwrap import shorten
+
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
@@ -48,7 +50,7 @@ class IntrastatTransaction(models.Model):
("99", "99"),
],
string="Transaction code",
- help="For the 'DEB' declaration to France's customs "
+ help="For the 'EMEBI' declaration to France's customs "
"administration, you should enter the number 'nature de la "
"transaction' here.",
)
@@ -58,12 +60,12 @@ class IntrastatTransaction(models.Model):
)
fr_fiscal_value_multiplier = fields.Integer(
string="Fiscal value multiplier",
+ default=1,
help="'0' for procedure codes 19 and 29, "
"'-1' for procedure code 25, '1' for all the others. "
"This multiplier is used to compute the total fiscal value of "
"the declaration.",
)
- # TODO : see with Luc if we can move it to intrastat_product
fr_intrastat_product_type = fields.Selection(
[
("arrivals", "Arrivals"),
@@ -121,6 +123,6 @@ def name_get(self):
name += "/%s" % trans.fr_transaction_code
if trans.description:
name += " " + trans.description
- name = len(name) > 55 and name[:55] + "..." or name
+ name = shorten(name, 55, placeholder="...")
res.append((trans.id, name))
return res
diff --git a/l10n_fr_intrastat_product/models/intrastat_unit.py b/l10n_fr_intrastat_product/models/intrastat_unit.py
index 5d20a5d99..44b261c1e 100644
--- a/l10n_fr_intrastat_product/models/intrastat_unit.py
+++ b/l10n_fr_intrastat_product/models/intrastat_unit.py
@@ -1,4 +1,4 @@
-# Copyright 2010-2020 Akretion France (http://www.akretion.com)
+# Copyright 2010-2022 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
diff --git a/l10n_fr_intrastat_product/models/res_company.py b/l10n_fr_intrastat_product/models/res_company.py
index 925f6eca5..713c2e158 100644
--- a/l10n_fr_intrastat_product/models/res_company.py
+++ b/l10n_fr_intrastat_product/models/res_company.py
@@ -1,4 +1,4 @@
-# Copyright 2010-2020 Akretion France (http://www.akretion.com)
+# Copyright 2010-2022 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -16,14 +16,17 @@ class ResCompany(models.Model):
fr_intrastat_accreditation = fields.Char(
string="Customs accreditation identifier",
size=4,
- help="Company identifier for Intrastat file export. " "Size : 4 characters.",
+ help="Company identifier for Intrastat file export. Size: 4 characters.",
)
@api.constrains("intrastat_arrivals", "country_id")
def check_fr_intrastat(self):
for company in self:
- if company.country_id and company.country_id.code == "FR":
- if company.intrastat_arrivals == "standard":
- raise ValidationError(
- _("In France, Arrival DEB can only be Exempt " "or Extended.")
- )
+ if (
+ company.country_id
+ and company.country_id.code == "FR"
+ and company.intrastat_arrivals == "standard"
+ ):
+ raise ValidationError(
+ _("In France, Arrival EMEBI can only be Exempt or Extended.")
+ )
diff --git a/l10n_fr_intrastat_product/models/res_partner.py b/l10n_fr_intrastat_product/models/res_partner.py
deleted file mode 100644
index 2f3ef6771..000000000
--- a/l10n_fr_intrastat_product/models/res_partner.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright 2011-2020 Akretion France (http://www.akretion.com)
-# @author Alexis de Lattre
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-
-from odoo import _, api, fields, models
-from odoo.exceptions import ValidationError
-
-
-class ResPartner(models.Model):
- _inherit = "res.partner"
-
- intrastat_fiscal_representative_id = fields.Many2one(
- "res.partner",
- string="EU fiscal representative",
- domain=[("parent_id", "=", False)],
- help="If this partner is located outside the EU but you "
- "deliver the goods inside the UE, the partner needs to "
- "have a fiscal representative with a VAT number inside the EU. "
- "In this scenario, the VAT number of the fiscal representative "
- "will be used for the Intrastat Product report (DEB).",
- )
-
- @api.constrains("intrastat_fiscal_representative_id")
- def _check_fiscal_representative(self):
- """The Fiscal rep. must be based in the same country as our
- company or in an intrastat country"""
- eu_countries = self.env.ref("base.europe").country_ids
- for partner in self:
- rep = partner.intrastat_fiscal_representative_id
- if rep:
- if not rep.country_id:
- raise ValidationError(
- _(
- "The fiscal representative '%s' of partner '%s' "
- "must have a country."
- )
- % (rep.display_name, partner.display_name)
- )
- if rep.country_id not in eu_countries:
- raise ValidationError(
- _(
- "The fiscal representative '%s' of partner '%s' "
- "must be based in an EU country."
- )
- % (rep.display_name, partner.display_name)
- )
- if not rep.vat:
- raise ValidationError(
- _(
- "The fiscal representative '%s' of partner '%s' "
- "must have a VAT number."
- )
- % (rep.display_name, partner.display_name)
- )
diff --git a/l10n_fr_intrastat_product/models/stock.py b/l10n_fr_intrastat_product/models/stock.py
index 7013cfe7c..ada275dfd 100644
--- a/l10n_fr_intrastat_product/models/stock.py
+++ b/l10n_fr_intrastat_product/models/stock.py
@@ -1,4 +1,4 @@
-# Copyright 2010-2020 Akretion France (http://www.akretion.com)
+# Copyright 2010-2022 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -9,7 +9,7 @@
class StockWarehouse(models.Model):
_inherit = "stock.warehouse"
- def get_fr_department(self):
+ def _get_fr_department(self):
self.ensure_one()
if not self.partner_id:
raise UserError(_("Missing partner on warehouse '%s'.") % self.display_name)
@@ -19,14 +19,11 @@ def get_fr_department(self):
class StockLocation(models.Model):
_inherit = "stock.location"
- def get_fr_department(self):
- """I don't think it's a good idea to use the get_intrastat_region()
- of intrastat_product because it doesn't return the same object.
- That's why there is a small code duplication in this method"""
+ def _get_fr_department(self):
self.ensure_one()
warehouse = self.env["stock.warehouse"].search(
[("lot_stock_id", "parent_of", self.ids)], limit=1
)
if warehouse:
- return warehouse.get_fr_department()
+ return warehouse._get_fr_department()
return None
diff --git a/l10n_fr_intrastat_product/post_install.py b/l10n_fr_intrastat_product/post_install.py
index 08c3b17c0..249aa3c48 100644
--- a/l10n_fr_intrastat_product/post_install.py
+++ b/l10n_fr_intrastat_product/post_install.py
@@ -16,40 +16,48 @@ def set_fr_company_intrastat(cr, registry):
afpo = env["account.fiscal.position"]
fr_id = env.ref("base.fr").id
companies = env["res.company"].search([("partner_id.country_id", "=", fr_id)])
- out_inv_trans_id = env.ref(
+ out_inv_b2b_trans_id = env.ref(
"l10n_fr_intrastat_product.intrastat_transaction_21_11"
).id
+ out_inv_b2c_trans_id = env.ref(
+ "l10n_fr_intrastat_product.intrastat_transaction_29_12"
+ ).id
out_ref_trans_id = env.ref(
"l10n_fr_intrastat_product.intrastat_transaction_25"
).id
in_inv_trans_id = env.ref(
"l10n_fr_intrastat_product.intrastat_transaction_11_11"
).id
+ fpdict = {
+ "intraeub2b": out_inv_b2b_trans_id,
+ "intraeub2c": out_inv_b2c_trans_id,
+ }
for company in companies:
- company.write(
- {
- "intrastat_transaction_out_invoice": out_inv_trans_id,
- "intrastat_transaction_out_refund": out_ref_trans_id,
- "intrastat_transaction_in_invoice": in_inv_trans_id,
- "intrastat_accessory_costs": True,
- }
- )
+ company.write({"intrastat_accessory_costs": True})
fps = afpo.search([("company_id", "=", company.id)])
for fp in fps:
xmlid_rec = imdo.search(
[
("model", "=", "account.fiscal.position"),
- ("module", "=", "l10n_fr"),
+ ("module", "=like", "l10n_fr%"),
("res_id", "=", fp.id),
- ("name", "=like", "%_fiscal_position_template_intraeub2b"),
],
limit=1,
)
if xmlid_rec:
- logger.debug(
- "set_fr_company_intrastat writing intrastat=True "
- "on fiscal position ID %d",
- fp.id,
- )
- fp.write({"intrastat": True})
- return
+ for fp_type, out_inv_trans_id in fpdict.items():
+ if xmlid_rec.name.endswith(fp_type):
+ logger.debug(
+ "set_fr_company_intrastat writing intrastat=True "
+ "on fiscal position ID %d",
+ fp.id,
+ )
+ fp.write(
+ {
+ "intrastat": True,
+ "intrastat_out_invoice_transaction_id": out_inv_trans_id,
+ "intrastat_out_refund_transaction_id": out_ref_trans_id,
+ "intrastat_in_invoice_transaction_id": in_inv_trans_id,
+ }
+ )
+ break
diff --git a/l10n_fr_intrastat_product/readme/DESCRIPTION.rst b/l10n_fr_intrastat_product/readme/DESCRIPTION.rst
index 587f21600..2bff2e1d3 100644
--- a/l10n_fr_intrastat_product/readme/DESCRIPTION.rst
+++ b/l10n_fr_intrastat_product/readme/DESCRIPTION.rst
@@ -1,3 +1,3 @@
-This module adds support for the *Déclaration d'Échange de Biens* (DEB) for France.
+This module adds support for the *Enquête mensuelle statistique sur les échanges de biens intra-UE* (EMEBI), for France. Before 2022, this declaration was called Déclaration d'Échange de Biens (DEB).
-More information about the DEB is available on this `official web page `_.
+More information about the EMEBI is available on this `official web page `_.
diff --git a/l10n_fr_intrastat_product/readme/USAGE.rst b/l10n_fr_intrastat_product/readme/USAGE.rst
index 1b0a0084d..79ed357c5 100644
--- a/l10n_fr_intrastat_product/readme/USAGE.rst
+++ b/l10n_fr_intrastat_product/readme/USAGE.rst
@@ -1 +1 @@
-To use this module, you need to go to the menu Invoicing > Reports > Intrastat > DEB and create a new DEB. Depending on your obligation levels, you may have to create 2 DEBs: one for export (Expéditions) and one for import (Introductions). Then, click on the button *Generate lines from invoices* to automatically generate the lines of DEB. After checking the lines that have been automatically generated, click on the button *Attach XML file* to create the XML file corresponding to the DEB. Eventually, connect to your account on `pro.douane `_ and upload the DEB XML file.
+To use this module, you need to go to the menu Invoicing > Reports > Intrastat > EMEBI and create a new EMEBI. Depending on your obligation levels, you may have to create 2 EMEBIs: one for departures (Expéditions) and one for arrivals (Introductions). Then, click on the button *Generate lines from invoices* to automatically generate the computation lines of EMEBI. After checking the lines that have been automatically generated, click on the button *Confirm* to generate the declaration lines, create the XML file and set the declaration readonly. Eventually, connect to your account on `douane.gouv.fr `_ and upload the EMEBI XML file.
diff --git a/l10n_fr_intrastat_product/security/intrastat_product_security.xml b/l10n_fr_intrastat_product/security/intrastat_product_security.xml
deleted file mode 100644
index eaf4c31c3..000000000
--- a/l10n_fr_intrastat_product/security/intrastat_product_security.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
- DEB multi-company
-
- ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]
-
-
diff --git a/l10n_fr_intrastat_product/security/ir.model.access.csv b/l10n_fr_intrastat_product/security/ir.model.access.csv
deleted file mode 100644
index ae692613a..000000000
--- a/l10n_fr_intrastat_product/security/ir.model.access.csv
+++ /dev/null
@@ -1,4 +0,0 @@
-id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_fr_intrastat_product,Full access to l10n.fr.intrastat.product.declaration to accountant,model_l10n_fr_intrastat_product_declaration,account.group_account_user,1,1,1,1
-access_fr_intrastat_product_computation_line,Full access to l10n.fr.intrastat.product.computation.line to accountant,model_l10n_fr_intrastat_product_computation_line,account.group_account_user,1,1,1,1
-access_fr_intrastat_product_declaration_line,Full access to l10n.fr.intrastat.product.declaration.line to accountant,model_l10n_fr_intrastat_product_declaration_line,account.group_account_user,1,1,1,1
diff --git a/l10n_fr_intrastat_product/views/account_fiscal_position.xml b/l10n_fr_intrastat_product/views/account_fiscal_position.xml
new file mode 100644
index 000000000..ad880c98a
--- /dev/null
+++ b/l10n_fr_intrastat_product/views/account_fiscal_position.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ intrastat_product.account.fiscal.position.form
+ account.fiscal.position
+
+
+
+ {'invisible': [('company_country_code', '=', 'FR')]}
+
+
+
+
diff --git a/l10n_fr_intrastat_product/views/account_move.xml b/l10n_fr_intrastat_product/views/account_move.xml
new file mode 100644
index 000000000..ac9eb2c15
--- /dev/null
+++ b/l10n_fr_intrastat_product/views/account_move.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+ account.move
+
+
+
+ [('fr_object_type', '=', move_type)]
+
+
+
+
+
diff --git a/l10n_fr_intrastat_product/views/intrastat_product_declaration.xml b/l10n_fr_intrastat_product/views/intrastat_product_declaration.xml
index c63599508..7eea6df4b 100644
--- a/l10n_fr_intrastat_product/views/intrastat_product_declaration.xml
+++ b/l10n_fr_intrastat_product/views/intrastat_product_declaration.xml
@@ -5,122 +5,71 @@
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
-
- l10n.fr.intrastat.product.declaration.form
- l10n.fr.intrastat.product.declaration
- primary
-
-
-
-
-
-
- l10n.fr.intrastat.product.declaration.tree
- l10n.fr.intrastat.product.declaration
- primary
-
-
-
- DEB
-
-
-
-
- l10n.fr.intrastat.product.computation.line.form
- l10n.fr.intrastat.product.computation.line
- primary
+
+ intrastat.product.computation.line
-
- 0
-
-
-
+
+ {'invisible': [('declaration_type', '=', 'arrivals'), ('company_country_code', '=', 'FR')]}
-
- l10n.fr.intrastat.product.computation.line.tree
- l10n.fr.intrastat.product.computation.line
- primary
+
+ intrastat.product.computation.line
-
- 0
-
-
-
+
+ {'column_invisible': [('parent.declaration_type', '=', 'arrivals'), ('parent.company_country_code', '=', 'FR')]}
-
-
- l10n.fr.intrastat.product.declaration.line.form
- l10n.fr.intrastat.product.declaration.line
- primary
+
+
+ intrastat.product.declaration.line
-
- 0
-
-
-
+
+ {'invisible': [('declaration_type', '=', 'arrivals'), ('company_country_code', '=', 'FR')]}
-
- l10n.fr.intrastat.product.declaration.line.tree
- l10n.fr.intrastat.product.declaration.line
- primary
+
+
+
+ intrastat.product.declaration.line
-
- 0
-
-
-
+
+ {'column_invisible': [('parent.declaration_type', '=', 'arrivals'), ('parent.company_country_code', '=', 'FR')]}
-
- l10n.fr.intrastat.product.declaration.search
- l10n.fr.intrastat.product.declaration
- primary
-
-
-
- Search DEB
-
-
-
+
- DEB
- l10n.fr.intrastat.product.declaration
+ EMEBI
+ intrastat.product.declarationtree,form,graph,pivot
-
-
As there were no DES for that month in Odoo, a draft declaration has been generated automatically.
-
-% if ctx.get('exception'):
-
When trying to generate the DES lines, the following error was encountered:
-
-
${ctx.get('error_msg')}
-
-
You should solve this error, then go to the menu Invoicing > Reporting > Intrastat > DES, open the declaration of month ${object.year_month} and click on the button Re-generate lines.
-
-% else:
-% if object.num_decl_lines > 0:
-
This draft DES contains ${object.num_decl_lines} ${object.num_decl_lines == 1 and 'line' or 'lines'} and the total amount is ${object.total_amount} ${object.currency_id.symbol}.
-% else:
-
This draft DES generated automatically by Odoo doesn't contain any line.
-% endif
-
-
Go and check this declaration in the menu Invoicing > Reporting > Intrastat > DES.
I would like to remind you that we are approaching the deadline for the DES declaration for month ${object.year_month}.
+
+As there were no DES for that month in Odoo, a draft declaration has been generated automatically.
+
+% if ctx.get('exception'):
+When trying to generate the DES lines, the following error was encountered:
+
+${ctx.get('error_msg')}
+
+You should solve this error, then go to the menu Invoicing > Reporting > Intrastat > DES, open the declaration of month ${object.year_month} and click on the button Re-generate lines.
+
+% else:
+% if object.num_decl_lines != 0:
+This draft DES contains ${object.num_decl_lines} ${object.num_decl_lines == 1 and 'line' or 'lines'} and the total amount is ${object.total_amount} ${object.currency_id.symbol}.
+% else:
+This draft DES generated automatically by Odoo doesn't contain any line.
+% endif
+
+Go and check this declaration in the menu Invoicing > Reporting > Intrastat > DES.
+
+% endif
+
+
+
+--
+Automatic e-mail sent by Odoo.
+
+
+
+
+
From 1f651b1f60ddaedc44e21e896f15c709f8abf096 Mon Sep 17 00:00:00 2001
From: Alexis de Lattre
Date: Tue, 24 Jan 2023 16:30:03 +0100
Subject: [PATCH 3/7] emebi: adapt to new warn msg code
auto-reformatting
---
.../models/intrastat_product_declaration.py | 14 ++++----
.../data/mail_template.xml | 34 +++++++++++++------
2 files changed, 29 insertions(+), 19 deletions(-)
diff --git a/l10n_fr_intrastat_product/models/intrastat_product_declaration.py b/l10n_fr_intrastat_product/models/intrastat_product_declaration.py
index 0ea5f5deb..1d4934891 100644
--- a/l10n_fr_intrastat_product/models/intrastat_product_declaration.py
+++ b/l10n_fr_intrastat_product/models/intrastat_product_declaration.py
@@ -92,14 +92,12 @@ def _get_fr_department(self, inv_line, notedict):
if not dpt:
dpt = self.company_id.partner_id.department_id
if not dpt:
- line_notes = [
- _(
- "Missing department on partner '%s'. "
- "To set it, set the country and the zip code on this partner."
- )
- % self.company_id.partner_id.display_name
- ]
- self._format_line_note(inv_line, notedict, line_notes)
+ msg = _(
+ "Missing department. "
+ "To set it, set the country and the zip code on this partner."
+ )
+ partner_name = self.company_id.partner_id.display_name
+ notedict["partner"][partner_name][msg].add(notedict["inv_origin"])
return dpt
def _generate_xml(self):
diff --git a/l10n_fr_intrastat_service/data/mail_template.xml b/l10n_fr_intrastat_service/data/mail_template.xml
index 53a2a7ae7..f964f21e5 100644
--- a/l10n_fr_intrastat_service/data/mail_template.xml
+++ b/l10n_fr_intrastat_service/data/mail_template.xml
@@ -15,34 +15,46 @@
${object.company_id.email}${object.company_id.intrastat_email_list}
- DES ${object.year_month} for ${object.company_id.name}
+ DES ${object.year_month} for ${object.company_id.name}
-
I would like to remind you that we are approaching the deadline for the DES declaration for month ${object.year_month}.
+
I would like to remind you that we are approaching the deadline for the DES declaration for month ${object.year_month}.
-As there were no DES for that month in Odoo, a draft declaration has been generated automatically.
+As there were no DES for that month in Odoo, a draft declaration has been generated automatically.
% if ctx.get('exception'):
-When trying to generate the DES lines, the following error was encountered:
+When trying to generate the DES lines, the following error was encountered:
-${ctx.get('error_msg')}
+${ctx.get('error_msg')}
-You should solve this error, then go to the menu Invoicing > Reporting > Intrastat > DES, open the declaration of month ${object.year_month} and click on the button Re-generate lines.
+You should solve this error, then go to the menu Invoicing > Reporting > Intrastat > DES, open the declaration of month ${object.year_month} and click on the button Re-generate lines.
% else:
% if object.num_decl_lines != 0:
-This draft DES contains ${object.num_decl_lines} ${object.num_decl_lines == 1 and 'line' or 'lines'} and the total amount is ${object.total_amount} ${object.currency_id.symbol}.
+This draft DES contains ${object.num_decl_lines} ${object.num_decl_lines == 1 and 'line' or 'lines'} and the total amount is ${object.total_amount} ${object.currency_id.symbol}.
% else:
-This draft DES generated automatically by Odoo doesn't contain any line.
+This draft DES generated automatically by Odoo doesn't contain any line.
% endif
-Go and check this declaration in the menu Invoicing > Reporting > Intrastat > DES.
+Go and check this declaration in the menu Invoicing > Reporting > Intrastat > DES.