From 54a010ee12298077ce8219a4174957212177ac26 Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Sat, 23 Oct 2021 18:46:52 +0200 Subject: [PATCH 01/12] [14.0][IMP] intrastat_*: Improve tests and code --- intrastat_base/models/intrastat_common.py | 86 ++++++++++++++++++- intrastat_base/tests/__init__.py | 1 + intrastat_base/tests/common.py | 64 ++++++++++++++ intrastat_base/tests/data/mail_template.xml | 59 +++++++++++++ intrastat_base/tests/models.py | 9 ++ intrastat_base/tests/test_all.py | 69 +++++++++++++-- .../models/intrastat_product_declaration.py | 82 ------------------ test-requirements.txt | 1 + 8 files changed, 282 insertions(+), 89 deletions(-) create mode 100644 intrastat_base/tests/common.py create mode 100644 intrastat_base/tests/data/mail_template.xml create mode 100644 intrastat_base/tests/models.py create mode 100644 test-requirements.txt diff --git a/intrastat_base/models/intrastat_common.py b/intrastat_base/models/intrastat_common.py index 94dc5a0..4b37057 100644 --- a/intrastat_base/models/intrastat_common.py +++ b/intrastat_base/models/intrastat_common.py @@ -10,7 +10,7 @@ from traceback import format_exception from lxml import etree from odoo import _, api, fields, models, tools -from odoo.exceptions import UserError +from odoo.exceptions import UserError, ValidationError logger = logging.getLogger(__name__) @@ -20,6 +20,90 @@ class IntrastatCommon(models.AbstractModel): _description = "Common functions for intrastat reports for products " "and services" + company_id = fields.Many2one( + comodel_name="res.company", + string="Company", + required=True, + states={"done": [("readonly", True)]}, + default=lambda self: self._default_company_id(), + ) + company_country_code = fields.Char( + compute="_compute_company_country_code", + string="Company Country Code", + readonly=True, + store=True, + help="Used in views and methods of localization modules.", + ) + state = fields.Selection( + selection=[("draft", "Draft"), ("done", "Done")], + string="State", + readonly=True, + tracking=True, + copy=False, + default="draft", + help="State of the declaration. When the state is set to 'Done', " + "the parameters become read-only.", + ) + note = fields.Text( + string="Notes", help="You can add some comments here if you want." + ) + + year = fields.Char( + string="Year", required=True, states={"done": [("readonly", True)]} + ) + month = fields.Selection( + selection=[ + ("01", "01"), + ("02", "02"), + ("03", "03"), + ("04", "04"), + ("05", "05"), + ("06", "06"), + ("07", "07"), + ("08", "08"), + ("09", "09"), + ("10", "10"), + ("11", "11"), + ("12", "12"), + ], + string="Month", + required=True, + states={"done": [("readonly", True)]}, + ) + year_month = fields.Char( + compute="_compute_year_month", + string="Period", + readonly=True, + tracking=True, + store=True, + help="Year and month of the declaration.", + ) + + @api.model + def _default_company_id(self): + return self.env.company + + @api.depends("company_id") + def _compute_company_country_code(self): + for this in self: + if this.company_id: + if not this.company_id.country_id: + raise ValidationError(_("You must set company's country !")) + this.company_country_code = this.company_id.country_id.code.lower() + + @api.depends("year", "month") + def _compute_year_month(self): + for this in self: + if this.year and this.month: + this.year_month = "-".join([this.year, this.month]) + + @api.model + @api.constrains("year") + def _check_year(self): + for this in self: + if len(this.year) != 4 or this.year[0] != "2": + raise ValidationError(_("Invalid Year !")) + # The method _compute_numbers has been removed # because it was using a loop on lines, which is slow -> we should # use read_group() instead, but then the code depends on diff --git a/intrastat_base/tests/__init__.py b/intrastat_base/tests/__init__.py index 7836283..a2afb4b 100644 --- a/intrastat_base/tests/__init__.py +++ b/intrastat_base/tests/__init__.py @@ -1 +1,2 @@ +from . import common from . import test_all diff --git a/intrastat_base/tests/common.py b/intrastat_base/tests/common.py new file mode 100644 index 0000000..640c62a --- /dev/null +++ b/intrastat_base/tests/common.py @@ -0,0 +1,64 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo_test_helper import FakeModelLoader + +from odoo.modules.module import get_resource_path +from odoo.tools import convert_file + + +class IntrastatCommon(object): + @classmethod + def _load_xml(cls, module, filepath): + convert_file( + cls.env.cr, + module, + get_resource_path(module, filepath), + {}, + mode="init", + noupdate=False, + kind="test", + ) + + @classmethod + def _load_test_declaration(cls): + cls.loader = FakeModelLoader(cls.env, cls.__module__) + cls.loader.backup_registry() + + # The fake class is imported here !! After the backup_registry + from .models import IntrastatDeclarationTest + + cls.loader.update_registry((IntrastatDeclarationTest,)) + + @classmethod + def _create_declaration(cls, vals=None): + values = { + "company_id": cls.declaration_test_obj._default_company_id().id, + "year": "2021", + "month": "03", + } + if vals is not None: + values.update(vals) + cls.declaration = cls.declaration_test_obj.create(values) + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.chart_template_obj = cls.env["account.chart.template"] + cls.mail_obj = cls.env["mail.mail"] + + cls.demo_user = cls.env.ref("base.user_demo") + cls.demo_company = cls.env.ref("base.main_company") + + cls.shipping_cost = cls.env.ref("intrastat_base.shipping_costs_exclude") + cls._load_test_declaration() + cls.declaration_test_obj = cls.env["intrastat.declaration.test"] + cls._load_xml("intrastat_base", "tests/data/mail_template.xml") + cls.mail_template_id = ( + "intrastat_base.base_intrastat_product_reminder_email_template" + ) + + @classmethod + def tearDownClass(cls): + cls.loader.restore_registry() + super().tearDownClass() diff --git a/intrastat_base/tests/data/mail_template.xml b/intrastat_base/tests/data/mail_template.xml new file mode 100644 index 0000000..b3313f2 --- /dev/null +++ b/intrastat_base/tests/data/mail_template.xml @@ -0,0 +1,59 @@ + + + + + Intrastat Product Reminder + + + ${object.company_id.email or 'odoo@example.com'} + ${object.company_id.intrastat_email_list} + ${object.type} DEB ${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}.

+ +

As there were no ${object.type} DEB for that month in Odoo, a draft DEB 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:

+ +

${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".

+ +% else: +% if object.num_lines and object.num_lines > 0: +

This draft ${object.type} DEB contains ${object.num_decl_lines} ${object.num_decl_lines == 1 and 'line' or 'lines'}.

+% else: +

This draft ${object.type} DEB generated automatically by Odoo doesn't contain any line.

+% endif + +

Go and check this declaration in Odoo in the menu "Invoicing > Reporting > Intrastat > DEB".

+ +% endif + +

+-- +Automatic e-mail sent by Odoo. +

+ +]]> +
+
+
diff --git a/intrastat_base/tests/models.py b/intrastat_base/tests/models.py new file mode 100644 index 0000000..e543530 --- /dev/null +++ b/intrastat_base/tests/models.py @@ -0,0 +1,9 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class IntrastatDeclarationTest(models.Model): + _inherit = ["mail.thread", "mail.activity.mixin", "intrastat.common"] + _name = "intrastat.declaration.test" diff --git a/intrastat_base/tests/test_all.py b/intrastat_base/tests/test_all.py index f05fc49..f25e1dc 100644 --- a/intrastat_base/tests/test_all.py +++ b/intrastat_base/tests/test_all.py @@ -1,13 +1,70 @@ -from odoo.tests.common import TransactionCase +from odoo.exceptions import UserError, ValidationError +from odoo.tests.common import SavepointCase + +from .common import IntrastatCommon -class TestIntrastatBase(TransactionCase): +class TestIntrastatBase(IntrastatCommon): """Tests for this module""" def test_company(self): # add 'Demo user' to intrastat_remind_user_ids - demo_user = self.env.ref("base.user_demo") - demo_company = self.env.ref("base.main_company") - demo_company.write({"intrastat_remind_user_ids": [(6, False, [demo_user.id])]}) + self.demo_company.write( + {"intrastat_remind_user_ids": [(6, False, [self.demo_user.id])]} + ) # then check if intrastat_email_list contains the email of the user - self.assertEqual(demo_company.intrastat_email_list, demo_user.email) + self.assertEqual(self.demo_company.intrastat_email_list, self.demo_user.email) + + def test_no_email(self): + self.demo_user.email = False + with self.assertRaises(ValidationError): + self.demo_company.write( + {"intrastat_remind_user_ids": [(6, False, [self.demo_user.id])]} + ) + + def test_accessory(self): + with self.assertRaises(ValidationError): + self.shipping_cost.type = "consu" + + def test_declaration_no_country(self): + self.demo_company.country_id = False + with self.assertRaises(ValidationError): + self._create_declaration() + self.declaration.flush() + + def test_declaration_no_vat(self): + self.demo_company.partner_id.vat = False + with self.assertRaises(UserError): + self._create_declaration() + self.declaration.flush() + self.declaration._check_generate_xml() + + def test_declaration_send_mail(self): + self._create_declaration() + mail_before = self.mail_obj.search([]) + self.declaration.send_reminder_email(self.mail_template_id) + mail_after = self.mail_obj.search([]) - mail_before + self.assertEqual(0, len(mail_after)) + self.demo_company.write( + {"intrastat_remind_user_ids": [(6, False, [self.demo_user.id])]} + ) + self.declaration.send_reminder_email(self.mail_template_id) + mail_after = self.mail_obj.search([]) - mail_before + self.assertEqual(1, len(mail_after)) + self.assertIn( + mail_after.email_to, + self.demo_user.email, + ) + + def test_declaration_state(self): + self._create_declaration() + self.declaration.unlink() + + self._create_declaration() + self.declaration.state = "done" + with self.assertRaises(UserError): + self.declaration.unlink() + + +class TestIntrastat(TestIntrastatBase, SavepointCase): + """ Test Intrastat """ diff --git a/intrastat_product/models/intrastat_product_declaration.py b/intrastat_product/models/intrastat_product_declaration.py index a13a5c8..d7e3c42 100644 --- a/intrastat_product/models/intrastat_product_declaration.py +++ b/intrastat_product/models/intrastat_product_declaration.py @@ -41,50 +41,6 @@ class IntrastatProductDeclaration(models.Model): ) return res - company_id = fields.Many2one( - comodel_name="res.company", - string="Company", - required=True, - states={"done": [("readonly", True)]}, - default=lambda self: self._default_company_id(), - ) - company_country_code = fields.Char( - compute="_compute_company_country_code", - string="Company Country Code", - readonly=True, - store=True, - help="Used in views and methods of localization modules.", - ) - year = fields.Char( - string="Year", required=True, states={"done": [("readonly", True)]} - ) - month = fields.Selection( - selection=[ - ("01", "01"), - ("02", "02"), - ("03", "03"), - ("04", "04"), - ("05", "05"), - ("06", "06"), - ("07", "07"), - ("08", "08"), - ("09", "09"), - ("10", "10"), - ("11", "11"), - ("12", "12"), - ], - string="Month", - required=True, - states={"done": [("readonly", True)]}, - ) - year_month = fields.Char( - compute="_compute_year_month", - string="Period", - readonly=True, - tracking=True, - store=True, - help="Year and month of the declaration.", - ) declaration_type = fields.Selection( selection="_get_declaration_type", string="Type", @@ -133,19 +89,6 @@ class IntrastatProductDeclaration(models.Model): currency_id = fields.Many2one( "res.currency", related="company_id.currency_id", string="Currency" ) - state = fields.Selection( - selection=[("draft", "Draft"), ("done", "Done")], - string="State", - readonly=True, - tracking=True, - copy=False, - default="draft", - help="State of the declaration. When the state is set to 'Done', " - "the parameters become read-only.", - ) - note = fields.Text( - string="Notes", help="You can add some comments here if you want." - ) reporting_level = fields.Selection( selection="_get_reporting_level", string="Reporting Level", @@ -160,10 +103,6 @@ class IntrastatProductDeclaration(models.Model): related="xml_attachment_id.name", string="XML Filename" ) - @api.model - def _default_company_id(self): - return self.env.company - @api.model def _get_declaration_type(self): res = [] @@ -188,33 +127,12 @@ class IntrastatProductDeclaration(models.Model): ("nihil", _("Nihil")), ] - @api.depends("company_id") - def _compute_company_country_code(self): - for this in self: - if this.company_id: - if not this.company_id.country_id: - raise ValidationError(_("You must set company's country !")) - this.company_country_code = this.company_id.country_id.code.lower() - - @api.depends("year", "month") - def _compute_year_month(self): - for this in self: - if this.year and this.month: - this.year_month = "-".join([this.year, this.month]) - @api.depends("month") def _compute_check_validity(self): """ TO DO: logic based upon computation lines """ for this in self: this.valid = True - @api.model - @api.constrains("year") - def _check_year(self): - for this in self: - if len(this.year) != 4 or this.year[0] != "2": - raise ValidationError(_("Invalid Year !")) - @api.onchange("declaration_type") def _onchange_declaration_type(self): if self.declaration_type == "arrivals": diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..4ad8e0e --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1 @@ +odoo-test-helper From b85db31edbc111913c1ec44a22c8e358c5372356 Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Sat, 23 Oct 2021 20:23:45 +0200 Subject: [PATCH 02/12] [14.0][IMP] intrastat_base: Split models files --- intrastat_base/models/account_chart_template.py | 17 +++++++++++++++++ .../models/account_fiscal_position.py | 15 --------------- .../models/account_fiscal_position_template.py | 14 ++++++++++++++ 3 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 intrastat_base/models/account_chart_template.py create mode 100644 intrastat_base/models/account_fiscal_position_template.py diff --git a/intrastat_base/models/account_chart_template.py b/intrastat_base/models/account_chart_template.py new file mode 100644 index 0000000..939e9e2 --- /dev/null +++ b/intrastat_base/models/account_chart_template.py @@ -0,0 +1,17 @@ +# Copyright 2020 Akretion France (http://www.akretion.com/) +# @author: +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import models + + +class AccountChartTemplate(models.Model): + _inherit = "account.chart.template" + + def _get_fp_vals(self, company, position): + """ + Get fiscal position chart template instrastat value + to create fiscal position + """ + vals = super()._get_fp_vals(company, position) + vals["intrastat"] = position.intrastat + return vals diff --git a/intrastat_base/models/account_fiscal_position.py b/intrastat_base/models/account_fiscal_position.py index fe3ebfb..0c56e59 100644 --- a/intrastat_base/models/account_fiscal_position.py +++ b/intrastat_base/models/account_fiscal_position.py @@ -13,18 +13,3 @@ class AccountFiscalPosition(models.Model): help="Set to True if the invoices with this fiscal position should " "be taken into account for the generation of the intrastat reports.", ) - - -class AccountFiscalPositionTemplate(models.Model): - _inherit = "account.fiscal.position.template" - - intrastat = fields.Boolean(string="Intrastat") - - -class AccountChartTemplate(models.Model): - _inherit = "account.chart.template" - - def _get_fp_vals(self, company, position): - vals = super()._get_fp_vals(company, position) - vals["intrastat"] = position.intrastat - return vals diff --git a/intrastat_base/models/account_fiscal_position_template.py b/intrastat_base/models/account_fiscal_position_template.py new file mode 100644 index 0000000..9085f53 --- /dev/null +++ b/intrastat_base/models/account_fiscal_position_template.py @@ -0,0 +1,14 @@ +# Copyright 2020 Akretion France (http://www.akretion.com/) +# @author: +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class AccountFiscalPositionTemplate(models.Model): + _inherit = "account.fiscal.position.template" + + intrastat = fields.Boolean( + string="Intrastat", + help="Check this if you want to generate intrastat declarations with" + "the created fiscal position", + ) From f3573088e5befe527327a20d1cfb5c819edc962b Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Sat, 23 Oct 2021 20:24:19 +0200 Subject: [PATCH 03/12] [14.0][IMP] intrastat_*: CONTRIBUTORS.rst --- intrastat_base/__manifest__.py | 2 +- intrastat_base/readme/CONTRIBUTORS.rst | 1 + intrastat_product/__manifest__.py | 2 +- intrastat_product/readme/CONTRIBUTORS.rst | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/intrastat_base/__manifest__.py b/intrastat_base/__manifest__.py index 031abb3..e12e23d 100644 --- a/intrastat_base/__manifest__.py +++ b/intrastat_base/__manifest__.py @@ -9,7 +9,7 @@ "category": "Intrastat", "license": "AGPL-3", "summary": "Base module for Intrastat reporting", - "author": "Akretion,Noviat,Odoo Community Association (OCA)", + "author": "ACSONE SA/NV, Akretion,Noviat,Odoo Community Association (OCA)", "website": "https://github.com/OCA/intrastat-extrastat", "depends": ["base_vat", "account"], "excludes": ["account_intrastat"], diff --git a/intrastat_base/readme/CONTRIBUTORS.rst b/intrastat_base/readme/CONTRIBUTORS.rst index 6a9e1c1..af9ef52 100644 --- a/intrastat_base/readme/CONTRIBUTORS.rst +++ b/intrastat_base/readme/CONTRIBUTORS.rst @@ -2,3 +2,4 @@ * Luc De Meyer, Noviat * Kumar Aberer, brain-tec AG * Andrea Stirpe +* Denis Roussel diff --git a/intrastat_product/__manifest__.py b/intrastat_product/__manifest__.py index 7c6e2ef..10753dd 100644 --- a/intrastat_product/__manifest__.py +++ b/intrastat_product/__manifest__.py @@ -11,7 +11,7 @@ "category": "Intrastat", "license": "AGPL-3", "summary": "Base module for Intrastat Product", - "author": "brain-tec AG, Akretion, Noviat, Odoo Community Association (OCA)", + "author": "ACSONE SA/NV, brain-tec AG, Akretion, Noviat, Odoo Community Association (OCA)", "website": "https://github.com/OCA/intrastat-extrastat", "depends": [ "intrastat_base", diff --git a/intrastat_product/readme/CONTRIBUTORS.rst b/intrastat_product/readme/CONTRIBUTORS.rst index 08d6018..bd08e8d 100644 --- a/intrastat_product/readme/CONTRIBUTORS.rst +++ b/intrastat_product/readme/CONTRIBUTORS.rst @@ -1,5 +1,6 @@ * Alexis de Lattre, Akretion * Luc De Meyer, Noviat +* Denis Roussel * Tecnativa : * João Marques From e34cf22462bb2f8f896a876c9fc9a59ec99e696a Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Sun, 24 Oct 2021 12:24:14 +0200 Subject: [PATCH 04/12] [14.0][IMP] intrastat_*: Add tests for base and for sale flows --- intrastat_base/tests/common.py | 5 - intrastat_base/tests/test_all.py | 9 ++ intrastat_product/tests/__init__.py | 5 + intrastat_product/tests/common.py | 113 ++++++++++++++++++ intrastat_product/tests/common_sale.py | 43 +++++++ intrastat_product/tests/test_company.py | 40 +++++++ .../tests/test_intrastat_product.py | 34 ++++++ intrastat_product/tests/test_sale_order.py | 92 ++++++++++++++ test-requirements.txt | 1 + 9 files changed, 337 insertions(+), 5 deletions(-) create mode 100644 intrastat_product/tests/__init__.py create mode 100644 intrastat_product/tests/common.py create mode 100644 intrastat_product/tests/common_sale.py create mode 100644 intrastat_product/tests/test_company.py create mode 100644 intrastat_product/tests/test_intrastat_product.py create mode 100644 intrastat_product/tests/test_sale_order.py diff --git a/intrastat_base/tests/common.py b/intrastat_base/tests/common.py index 640c62a..58a2491 100644 --- a/intrastat_base/tests/common.py +++ b/intrastat_base/tests/common.py @@ -52,11 +52,6 @@ class IntrastatCommon(object): cls.shipping_cost = cls.env.ref("intrastat_base.shipping_costs_exclude") cls._load_test_declaration() - cls.declaration_test_obj = cls.env["intrastat.declaration.test"] - cls._load_xml("intrastat_base", "tests/data/mail_template.xml") - cls.mail_template_id = ( - "intrastat_base.base_intrastat_product_reminder_email_template" - ) @classmethod def tearDownClass(cls): diff --git a/intrastat_base/tests/test_all.py b/intrastat_base/tests/test_all.py index f25e1dc..cc7b203 100644 --- a/intrastat_base/tests/test_all.py +++ b/intrastat_base/tests/test_all.py @@ -7,6 +7,15 @@ from .common import IntrastatCommon class TestIntrastatBase(IntrastatCommon): """Tests for this module""" + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.declaration_test_obj = cls.env["intrastat.declaration.test"] + cls._load_xml("intrastat_base", "tests/data/mail_template.xml") + cls.mail_template_id = ( + "intrastat_base.base_intrastat_product_reminder_email_template" + ) + def test_company(self): # add 'Demo user' to intrastat_remind_user_ids self.demo_company.write( diff --git a/intrastat_product/tests/__init__.py b/intrastat_product/tests/__init__.py new file mode 100644 index 0000000..c8e439c --- /dev/null +++ b/intrastat_product/tests/__init__.py @@ -0,0 +1,5 @@ +from . import common +from . import common_sale +from . import test_intrastat_product +from . import test_company +from . import test_sale_order diff --git a/intrastat_product/tests/common.py b/intrastat_product/tests/common.py new file mode 100644 index 0000000..77e350d --- /dev/null +++ b/intrastat_product/tests/common.py @@ -0,0 +1,113 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.addons.intrastat_base.tests.common import IntrastatCommon + + +class IntrastatProductCommon(IntrastatCommon): + @classmethod + def _init_products(cls): + # Create category - don't init intrastat values, do it in tests + vals = { + "name": "Robots", + "parent_id": cls.category_saleable.id, + } + cls.categ_robots = cls.category_obj.create(vals) + + vals = { + "name": "C3PO", + "categ_id": cls.categ_robots.id, + "origin_country_id": cls.env.ref("base.us").id, + "weight": 300, + # Computer - C3PO is one of them + "hs_code_id": cls.hs_code_computer.id, + } + cls.product_c3po = cls.product_template_obj.create(vals) + + @classmethod + def _init_company(cls): + # Default transport for company is Road + cls.demo_company.intrastat_transport_id = cls.transport_road + + @classmethod + def _init_fiscal_position(cls): + vals = { + "name": "Intrastat Fiscal Position", + "intrastat": True, + } + cls.position = cls.position_obj.create(vals) + + @classmethod + def _init_regions(cls): + # Create Belgium + cls._create_region() + + vals = { + "code": "DE", + "name": "Germany", + "country_id": cls.env.ref("base.de").id, + "description": "Germany", + } + cls._create_region(vals) + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.region_obj = cls.env["intrastat.region"] + cls.transaction_obj = cls.env["intrastat.transaction"] + cls.transport_mode_obj = cls.env["intrastat.transport_mode"] + cls.partner_obj = cls.env["res.partner"] + cls.category_saleable = cls.env.ref("product.product_category_1") + cls.category_obj = cls.env["product.category"] + cls.product_template_obj = cls.env["product.template"] + cls.declaration_obj = cls.env["intrastat.product.declaration"] + cls.position_obj = cls.env["account.fiscal.position"] + cls.hs_code_computer = cls.env.ref("product_harmonized_system.84715000") + + cls.transport_rail = cls.env.ref("intrastat_product.intrastat_transport_2") + cls.transport_road = cls.env.ref("intrastat_product.intrastat_transport_3") + + cls._init_regions() + cls._init_company() + cls._init_fiscal_position() + cls._init_products() + + @classmethod + def _create_region(cls, vals=None): + values = { + "code": "BE", + "country_id": cls.env.ref("base.be").id, + "company_id": cls.env.company.id, + "description": "Belgium", + "name": "Belgium", + } + if vals is not None: + values.update(vals) + cls.region = cls.region_obj.create(values) + + @classmethod + def _create_transaction(cls, vals=None): + values = { + "code": "11", + "company_id": cls.env.company.id, + "description": "Sale / Purchase", + } + if vals is not None: + values.update(vals) + cls.transaction = cls.transaction_obj.create(values) + + @classmethod + def _create_transport_mode(cls, vals=None): + values = {} + if vals is not None: + values.update(vals) + cls.transport_mode = cls.transport_mode_obj.create(values) + + @classmethod + def _create_declaration(cls, vals=None): + values = { + "company_id": cls.env.company.id, + } + if vals is not None: + values.update(vals) + cls.declaration = cls.declaration_obj.create(values) diff --git a/intrastat_product/tests/common_sale.py b/intrastat_product/tests/common_sale.py new file mode 100644 index 0000000..9dc07ca --- /dev/null +++ b/intrastat_product/tests/common_sale.py @@ -0,0 +1,43 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo.tests import Form + +from .common import IntrastatProductCommon + + +class IntrastatSaleCommon(IntrastatProductCommon): + """ + We define common flow: + - Customer in Netherlands + """ + + @classmethod + def _init_customer(cls, vals=None): + values = { + "name": "NL Customer", + "country_id": cls.env.ref("base.nl").id, + "property_account_position_id": cls.position.id, + } + if vals is not None: + values.update(vals) + cls.customer = cls.partner_obj.create(values) + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.sale_obj = cls.env["sale.order"] + cls._init_customer() + + @classmethod + def _create_sale_order(cls, vals=None): + vals = { + "partner_id": cls.customer.id, + } + sale_new = cls.sale_obj.new(vals) + sale_new.onchange_partner_id() + sale_vals = sale_new._convert_to_write(sale_new._cache) + cls.sale = cls.sale_obj.create(sale_vals) + with Form(cls.sale) as sale_form: + with sale_form.order_line.new() as line: + line.product_id = cls.product_c3po.product_variant_ids[0] + line.product_uom_qty = 3.0 diff --git a/intrastat_product/tests/test_company.py b/intrastat_product/tests/test_company.py new file mode 100644 index 0000000..7fe99ac --- /dev/null +++ b/intrastat_product/tests/test_company.py @@ -0,0 +1,40 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo.tests.common import SavepointCase + +from .common import IntrastatProductCommon + + +class TestIntrastatCompany(IntrastatProductCommon): + """Tests for this module""" + + def test_company_values(self): + # Exempt for arrivals and dispatches => exempt + self.demo_company.update( + { + "intrastat_arrivals": "exempt", + "intrastat_dispatches": "exempt", + } + ) + self.assertEqual("exempt", self.demo_company.intrastat) + + # Extended for arrivals or dispatches => extended + self.demo_company.update( + { + "intrastat_arrivals": "extended", + } + ) + self.assertEqual("extended", self.demo_company.intrastat) + + # standard for arrivals or dispatches => standard + self.demo_company.update( + { + "intrastat_arrivals": "exempt", + "intrastat_dispatches": "standard", + } + ) + self.assertEqual("standard", self.demo_company.intrastat) + + +class TestIntrastatProductCase(TestIntrastatCompany, SavepointCase): + """ Test Intrastat Product """ diff --git a/intrastat_product/tests/test_intrastat_product.py b/intrastat_product/tests/test_intrastat_product.py new file mode 100644 index 0000000..0e30a7a --- /dev/null +++ b/intrastat_product/tests/test_intrastat_product.py @@ -0,0 +1,34 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from psycopg2 import IntegrityError + +from odoo.tests.common import SavepointCase +from odoo.tools import mute_logger + +from .common import IntrastatProductCommon + + +class TestIntrastatProduct(IntrastatProductCommon): + """Tests for this module""" + + # Test duplicates + @mute_logger("odoo.sql_db") + def test_region(self): + with self.assertRaises(IntegrityError): + self._create_region() + + @mute_logger("odoo.sql_db") + def test_transaction(self): + self._create_transaction() + with self.assertRaises(IntegrityError): + self._create_transaction() + + @mute_logger("odoo.sql_db") + def test_transport_mode(self): + vals = {"code": 1, "name": "Sea"} + with self.assertRaises(IntegrityError): + self._create_transport_mode(vals) + + +class TestIntrastatProductCase(TestIntrastatProduct, SavepointCase): + """ Test Intrastat Product """ diff --git a/intrastat_product/tests/test_sale_order.py b/intrastat_product/tests/test_sale_order.py new file mode 100644 index 0000000..e3e6819 --- /dev/null +++ b/intrastat_product/tests/test_sale_order.py @@ -0,0 +1,92 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from freezegun import freeze_time + +from odoo.tests.common import SavepointCase + +from .common_sale import IntrastatSaleCommon + + +class TestIntrastatProductSale(IntrastatSaleCommon): + """Tests for this module""" + + def test_sale_to_invoice_default(self): + self._create_sale_order() + self.sale.action_confirm() + self.sale.picking_ids.action_assign() + for line in self.sale.picking_ids.move_line_ids: + line.qty_done = line.product_uom_qty + self.sale.picking_ids._action_done() + self.assertEqual("done", self.sale.picking_ids.state) + + invoice = self.sale._create_invoices() + invoice.action_post() + + # Check if transport mode has been transmitted to invoice + # It should be None as not defined on sale order + self.assertFalse( + invoice.intrastat_transport_id, + ) + + # Test specific transport set on sale to invoice + def test_sale_to_invoice(self): + self._create_sale_order() + # Set intrastat transport mode to rail + self.sale.intrastat_transport_id = self.transport_rail + self.sale.action_confirm() + self.sale.picking_ids.action_assign() + for line in self.sale.picking_ids.move_line_ids: + line.qty_done = line.product_uom_qty + self.sale.picking_ids._action_done() + self.assertEqual("done", self.sale.picking_ids.state) + + invoice = self.sale._create_invoices() + invoice.action_post() + + # Check if transport mode has been transmitted to invoice + self.assertEqual( + self.transport_rail, + invoice.intrastat_transport_id, + ) + + def test_sale_declaration(self): + date_order = "2021-09-01" + declaration_date = "2021-10-01" + with freeze_time(date_order): + self._create_sale_order() + # Set intrastat transport mode to rail + self.sale.intrastat_transport_id = self.transport_rail + self.sale.action_confirm() + self.sale.picking_ids.action_assign() + for line in self.sale.picking_ids.move_line_ids: + line.qty_done = line.product_uom_qty + self.sale.picking_ids._action_done() + self.assertEqual("done", self.sale.picking_ids.state) + + with freeze_time(date_order): + invoice = self.sale._create_invoices() + invoice.action_post() + + # Check if transport mode has been transmitted to invoice + self.assertEqual( + self.transport_rail, + invoice.intrastat_transport_id, + ) + vals = { + "declaration_type": "dispatches", + } + with freeze_time(declaration_date): + self._create_declaration(vals) + self.declaration.action_gather() + + expected_vals = { + "declaration_type": "dispatches", + "suppl_unit_qty": 3.0, + "hs_code_id": self.hs_code_computer, + } + line = self.declaration.computation_line_ids + self.assertDictContainsSubset(expected_vals, line) + + +class TestIntrastatProductSaleCase(TestIntrastatProductSale, SavepointCase): + """ Test Intrastat Sale """ diff --git a/test-requirements.txt b/test-requirements.txt index 4ad8e0e..864b829 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,2 @@ odoo-test-helper +freezegun From 75c6978d5dbbc750ba19fd43af0f9ede9f560aa8 Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Sun, 24 Oct 2021 13:10:20 +0200 Subject: [PATCH 05/12] [14.0][IMP] intrastat_product: Add test for declaration line values --- intrastat_product/tests/common_sale.py | 30 ++++++++++++++++++++++ intrastat_product/tests/test_sale_order.py | 10 +++----- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/intrastat_product/tests/common_sale.py b/intrastat_product/tests/common_sale.py index 9dc07ca..28bc782 100644 --- a/intrastat_product/tests/common_sale.py +++ b/intrastat_product/tests/common_sale.py @@ -11,6 +11,36 @@ class IntrastatSaleCommon(IntrastatProductCommon): - Customer in Netherlands """ + def _check_line_values(self, final=False, declaration=None, sale=None): + """ + This method allows to test computation lines and declaration + lines values from original sale order line + """ + if declaration is None: + declaration = self.declaration + if sale is None: + sale = self.sale + for line in sale.order_line: + expected_vals = { + "declaration_type": "dispatches", + "suppl_unit_qty": line.qty_delivered, + "hs_code_id": line.product_id.hs_code_id, + "product_origin_country_id": line.product_id.origin_country_id, + } + comp_line = declaration.computation_line_ids.filtered( + lambda cline: cline.product_id == line.product_id + ) + self.assertTrue( + all(comp_line[key] == val for key, val in expected_vals.items()) + ) + if final: + decl_line = declaration.declaration_line_ids.filtered( + lambda dline: comp_line in dline.computation_line_ids + ) + self.assertTrue( + all(decl_line[key] == val for key, val in expected_vals.items()) + ) + @classmethod def _init_customer(cls, vals=None): values = { diff --git a/intrastat_product/tests/test_sale_order.py b/intrastat_product/tests/test_sale_order.py index e3e6819..7d5b411 100644 --- a/intrastat_product/tests/test_sale_order.py +++ b/intrastat_product/tests/test_sale_order.py @@ -79,13 +79,9 @@ class TestIntrastatProductSale(IntrastatSaleCommon): self._create_declaration(vals) self.declaration.action_gather() - expected_vals = { - "declaration_type": "dispatches", - "suppl_unit_qty": 3.0, - "hs_code_id": self.hs_code_computer, - } - line = self.declaration.computation_line_ids - self.assertDictContainsSubset(expected_vals, line) + self._check_line_values() + self.declaration.generate_declaration() + self._check_line_values(final=True) class TestIntrastatProductSaleCase(TestIntrastatProductSale, SavepointCase): From cc243da726fba0a92b69b43f8ef5c0c31c6c9b3e Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Sun, 24 Oct 2021 13:45:19 +0200 Subject: [PATCH 06/12] [14.0][IMP] intrastat_product: Add test for purchase lines --- intrastat_product/tests/__init__.py | 2 + intrastat_product/tests/common_purchase.py | 78 +++++++++++++++++++ .../tests/test_purchase_order.py | 53 +++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 intrastat_product/tests/common_purchase.py create mode 100644 intrastat_product/tests/test_purchase_order.py diff --git a/intrastat_product/tests/__init__.py b/intrastat_product/tests/__init__.py index c8e439c..804b5f7 100644 --- a/intrastat_product/tests/__init__.py +++ b/intrastat_product/tests/__init__.py @@ -1,5 +1,7 @@ from . import common +from . import common_purchase from . import common_sale from . import test_intrastat_product from . import test_company +from . import test_purchase_order from . import test_sale_order diff --git a/intrastat_product/tests/common_purchase.py b/intrastat_product/tests/common_purchase.py new file mode 100644 index 0000000..1abc011 --- /dev/null +++ b/intrastat_product/tests/common_purchase.py @@ -0,0 +1,78 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo.tests import Form + +from .common import IntrastatProductCommon + + +class IntrastatPurchaseCommon(IntrastatProductCommon): + """ + We define common flow: + - Supplier in Germany + """ + + def _check_line_values(self, final=False, declaration=None, purchase=None): + """ + This method allows to test computation lines and declaration + lines values from original sale order line + """ + if declaration is None: + declaration = self.declaration + if purchase is None: + purchase = self.purchase + for line in purchase.order_line: + expected_vals = { + "declaration_type": "arrivals", + "suppl_unit_qty": line.qty_received, + "hs_code_id": line.product_id.hs_code_id, + "product_origin_country_id": line.product_id.origin_country_id, + "amount_company_currency": line.price_subtotal, + "src_dest_country_id": line.partner_id.country_id, + } + comp_line = declaration.computation_line_ids.filtered( + lambda cline: cline.product_id == line.product_id + ) + self.assertTrue( + all(comp_line[key] == val for key, val in expected_vals.items()) + ) + if final: + decl_line = declaration.declaration_line_ids.filtered( + lambda dline: comp_line in dline.computation_line_ids + ) + self.assertTrue( + all(decl_line[key] == val for key, val in expected_vals.items()) + ) + + @classmethod + def _init_supplier(cls, vals=None): + values = { + "name": "DE Supplier", + "country_id": cls.env.ref("base.de").id, + "property_account_position_id": cls.position.id, + } + if vals is not None: + values.update(vals) + cls.supplier = cls.partner_obj.create(values) + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.purchase_obj = cls.env["purchase.order"] + cls.move_obj = cls.env["account.move"] + cls._init_supplier() + + @classmethod + def _create_purchase_order(cls, vals=None): + vals = { + "partner_id": cls.supplier.id, + } + purchase_new = cls.purchase_obj.new(vals) + purchase_new.onchange_partner_id() + purchase_vals = purchase_new._convert_to_write(purchase_new._cache) + cls.purchase = cls.purchase_obj.create(purchase_vals) + with Form(cls.purchase) as purchase_form: + with purchase_form.order_line.new() as line: + line.product_id = cls.product_c3po.product_variant_ids[0] + line.product_qty = 3.0 + # Price should not be void - if no purchase pricelist + line.price_unit = 150.0 diff --git a/intrastat_product/tests/test_purchase_order.py b/intrastat_product/tests/test_purchase_order.py new file mode 100644 index 0000000..f6538c1 --- /dev/null +++ b/intrastat_product/tests/test_purchase_order.py @@ -0,0 +1,53 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from freezegun import freeze_time + +from odoo import fields +from odoo.tests.common import SavepointCase + +from .common_purchase import IntrastatPurchaseCommon + + +class TestIntrastatProductPurchase(IntrastatPurchaseCommon): + """Tests for this module""" + + def test_purchase_to_invoice_default(self): + date_order = "2021-09-01" + declaration_date = "2021-10-01" + with freeze_time(date_order): + self._create_purchase_order() + self.purchase.button_confirm() + self.purchase.picking_ids.action_assign() + for line in self.purchase.picking_ids.move_line_ids: + line.qty_done = line.product_uom_qty + self.purchase.picking_ids._action_done() + self.assertEqual("done", self.purchase.picking_ids.state) + + with freeze_time(date_order): + action = self.purchase.action_create_invoice() + invoice_id = action["res_id"] + invoice = self.move_obj.browse(invoice_id) + + invoice.invoice_date = fields.Date.from_string(date_order) + invoice.action_post() + + # Check if transport mode has been transmitted to invoice + # It should be None as not defined on sale order + self.assertFalse( + invoice.intrastat_transport_id, + ) + + vals = { + "declaration_type": "arrivals", + } + with freeze_time(declaration_date): + self._create_declaration(vals) + self.declaration.action_gather() + + self._check_line_values() + self.declaration.generate_declaration() + self._check_line_values(final=True) + + +class TestIntrastatProductPurchaseCase(TestIntrastatProductPurchase, SavepointCase): + """ Test Intrastat Purchase """ From 15582cc2e2e3c353927dd608251db0f206d8bb3b Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Sun, 24 Oct 2021 13:58:47 +0200 Subject: [PATCH 07/12] [14.0][IMP] intrastat_product: Add description for fake model --- intrastat_base/tests/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/intrastat_base/tests/models.py b/intrastat_base/tests/models.py index e543530..302914c 100644 --- a/intrastat_base/tests/models.py +++ b/intrastat_base/tests/models.py @@ -7,3 +7,4 @@ from odoo import models class IntrastatDeclarationTest(models.Model): _inherit = ["mail.thread", "mail.activity.mixin", "intrastat.common"] _name = "intrastat.declaration.test" + _description = "Intrastat Declaration Test" From 69c79ae07e599b861f634959476b54ed4129e5db Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Sun, 24 Oct 2021 13:59:08 +0200 Subject: [PATCH 08/12] [14.0][IMP] intrastat_product: Test declaration copy --- intrastat_product/tests/test_intrastat_product.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/intrastat_product/tests/test_intrastat_product.py b/intrastat_product/tests/test_intrastat_product.py index 0e30a7a..8a6b138 100644 --- a/intrastat_product/tests/test_intrastat_product.py +++ b/intrastat_product/tests/test_intrastat_product.py @@ -29,6 +29,16 @@ class TestIntrastatProduct(IntrastatProductCommon): with self.assertRaises(IntegrityError): self._create_transport_mode(vals) + def test_copy(self): + """ + When copying declaration, the new one has an incremented revision + value. + """ + vals = {"declaration_type": "dispatches"} + self._create_declaration(vals) + decl_copy = self.declaration.copy() + self.assertEqual(self.declaration.revision + 1, decl_copy.revision) + class TestIntrastatProductCase(TestIntrastatProduct, SavepointCase): """ Test Intrastat Product """ From f1d53760cf27b7b10a4017437dab538a725ccb10 Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Sun, 24 Oct 2021 15:54:16 +0200 Subject: [PATCH 09/12] [14.0][IMP] intrastat_product: Add hook for test values --- intrastat_product/tests/common_purchase.py | 19 +++++++++++-------- intrastat_product/tests/common_sale.py | 15 +++++++++------ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/intrastat_product/tests/common_purchase.py b/intrastat_product/tests/common_purchase.py index 1abc011..7574457 100644 --- a/intrastat_product/tests/common_purchase.py +++ b/intrastat_product/tests/common_purchase.py @@ -11,6 +11,16 @@ class IntrastatPurchaseCommon(IntrastatProductCommon): - Supplier in Germany """ + def _get_expected_vals(self, line): + return { + "declaration_type": "arrivals", + "suppl_unit_qty": line.qty_received, + "hs_code_id": line.product_id.hs_code_id, + "product_origin_country_id": line.product_id.origin_country_id, + "amount_company_currency": line.price_subtotal, + "src_dest_country_id": line.partner_id.country_id, + } + def _check_line_values(self, final=False, declaration=None, purchase=None): """ This method allows to test computation lines and declaration @@ -21,14 +31,7 @@ class IntrastatPurchaseCommon(IntrastatProductCommon): if purchase is None: purchase = self.purchase for line in purchase.order_line: - expected_vals = { - "declaration_type": "arrivals", - "suppl_unit_qty": line.qty_received, - "hs_code_id": line.product_id.hs_code_id, - "product_origin_country_id": line.product_id.origin_country_id, - "amount_company_currency": line.price_subtotal, - "src_dest_country_id": line.partner_id.country_id, - } + expected_vals = self._get_expected_vals(line) comp_line = declaration.computation_line_ids.filtered( lambda cline: cline.product_id == line.product_id ) diff --git a/intrastat_product/tests/common_sale.py b/intrastat_product/tests/common_sale.py index 28bc782..4a3a457 100644 --- a/intrastat_product/tests/common_sale.py +++ b/intrastat_product/tests/common_sale.py @@ -11,6 +11,14 @@ class IntrastatSaleCommon(IntrastatProductCommon): - Customer in Netherlands """ + def _get_expected_vals(self, line): + return { + "declaration_type": "dispatches", + "suppl_unit_qty": line.qty_delivered, + "hs_code_id": line.product_id.hs_code_id, + "product_origin_country_id": line.product_id.origin_country_id, + } + def _check_line_values(self, final=False, declaration=None, sale=None): """ This method allows to test computation lines and declaration @@ -21,12 +29,7 @@ class IntrastatSaleCommon(IntrastatProductCommon): if sale is None: sale = self.sale for line in sale.order_line: - expected_vals = { - "declaration_type": "dispatches", - "suppl_unit_qty": line.qty_delivered, - "hs_code_id": line.product_id.hs_code_id, - "product_origin_country_id": line.product_id.origin_country_id, - } + expected_vals = self._get_expected_vals(line) comp_line = declaration.computation_line_ids.filtered( lambda cline: cline.product_id == line.product_id ) From 7d224e4a2e2151e4c6c957137c5025a9a406534e Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Thu, 11 Nov 2021 13:20:27 +0100 Subject: [PATCH 10/12] [IMP] intrastat_product: Better tests data --- intrastat_product/tests/common.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/intrastat_product/tests/common.py b/intrastat_product/tests/common.py index 77e350d..fbe94d1 100644 --- a/intrastat_product/tests/common.py +++ b/intrastat_product/tests/common.py @@ -39,7 +39,7 @@ class IntrastatProductCommon(IntrastatCommon): @classmethod def _init_regions(cls): - # Create Belgium + # Create Belgium Region cls._create_region() vals = { @@ -75,11 +75,11 @@ class IntrastatProductCommon(IntrastatCommon): @classmethod def _create_region(cls, vals=None): values = { - "code": "BE", + "code": "BE_w", "country_id": cls.env.ref("base.be").id, "company_id": cls.env.company.id, - "description": "Belgium", - "name": "Belgium", + "description": "Belgium Walloon Region", + "name": "Walloon Region", } if vals is not None: values.update(vals) From 190acd9765ea509768540725c93c3fb61acbadce Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Wed, 15 Dec 2021 12:49:02 +0100 Subject: [PATCH 11/12] intrastat_base: Remove intrastat.common class --- intrastat_base/__manifest__.py | 1 - intrastat_base/models/__init__.py | 2 +- intrastat_base/models/intrastat_common.py | 216 ------------------ intrastat_base/models/res_company.py | 34 ++- intrastat_base/security/ir.model.access.csv | 2 - intrastat_base/tests/common.py | 43 ---- intrastat_base/tests/data/mail_template.xml | 59 ----- intrastat_base/tests/test_all.py | 51 +---- intrastat_base/views/intrastat.xml | 14 -- intrastat_product/__init__.py | 1 + intrastat_product/__manifest__.py | 1 + .../models/intrastat_product_declaration.py | 118 +++++++++- .../security/ir.model.access.csv | 1 + intrastat_product/tests/common.py | 1 + .../tests/test_intrastat_product.py | 24 +- intrastat_product/wizards/__init__.py | 1 + .../wizards/intrastat_result_view.py | 14 ++ .../wizards/intrastat_result_view.xml | 22 ++ 18 files changed, 214 insertions(+), 391 deletions(-) delete mode 100644 intrastat_base/models/intrastat_common.py delete mode 100644 intrastat_base/security/ir.model.access.csv delete mode 100644 intrastat_base/tests/data/mail_template.xml create mode 100644 intrastat_product/wizards/__init__.py create mode 100644 intrastat_product/wizards/intrastat_result_view.py create mode 100644 intrastat_product/wizards/intrastat_result_view.xml diff --git a/intrastat_base/__manifest__.py b/intrastat_base/__manifest__.py index e12e23d..e414249 100644 --- a/intrastat_base/__manifest__.py +++ b/intrastat_base/__manifest__.py @@ -14,7 +14,6 @@ "depends": ["base_vat", "account"], "excludes": ["account_intrastat"], "data": [ - "security/ir.model.access.csv", "views/product_template.xml", "views/res_partner.xml", "views/res_config_settings.xml", diff --git a/intrastat_base/models/__init__.py b/intrastat_base/models/__init__.py index eff3d68..cea19b9 100644 --- a/intrastat_base/models/__init__.py +++ b/intrastat_base/models/__init__.py @@ -1,5 +1,5 @@ from . import product_template from . import res_company -from . import intrastat_common from . import account_fiscal_position +from . import account_fiscal_position_template from . import account_move diff --git a/intrastat_base/models/intrastat_common.py b/intrastat_base/models/intrastat_common.py deleted file mode 100644 index 4b37057..0000000 --- a/intrastat_base/models/intrastat_common.py +++ /dev/null @@ -1,216 +0,0 @@ -# Copyright 2010-2020 Akretion () -# Copyright 2009-2020 Noviat (http://www.noviat.com) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -import logging -from io import BytesIO -from sys import exc_info -from traceback import format_exception - -from lxml import etree - -from odoo import _, api, fields, models, tools -from odoo.exceptions import UserError, ValidationError - -logger = logging.getLogger(__name__) - - -class IntrastatCommon(models.AbstractModel): - _name = "intrastat.common" - _description = "Common functions for intrastat reports for products " - "and services" - - company_id = fields.Many2one( - comodel_name="res.company", - string="Company", - required=True, - states={"done": [("readonly", True)]}, - default=lambda self: self._default_company_id(), - ) - company_country_code = fields.Char( - compute="_compute_company_country_code", - string="Company Country Code", - readonly=True, - store=True, - help="Used in views and methods of localization modules.", - ) - state = fields.Selection( - selection=[("draft", "Draft"), ("done", "Done")], - string="State", - readonly=True, - tracking=True, - copy=False, - default="draft", - help="State of the declaration. When the state is set to 'Done', " - "the parameters become read-only.", - ) - note = fields.Text( - string="Notes", help="You can add some comments here if you want." - ) - - year = fields.Char( - string="Year", required=True, states={"done": [("readonly", True)]} - ) - month = fields.Selection( - selection=[ - ("01", "01"), - ("02", "02"), - ("03", "03"), - ("04", "04"), - ("05", "05"), - ("06", "06"), - ("07", "07"), - ("08", "08"), - ("09", "09"), - ("10", "10"), - ("11", "11"), - ("12", "12"), - ], - string="Month", - required=True, - states={"done": [("readonly", True)]}, - ) - year_month = fields.Char( - compute="_compute_year_month", - string="Period", - readonly=True, - tracking=True, - store=True, - help="Year and month of the declaration.", - ) - - @api.model - def _default_company_id(self): - return self.env.company - - @api.depends("company_id") - def _compute_company_country_code(self): - for this in self: - if this.company_id: - if not this.company_id.country_id: - raise ValidationError(_("You must set company's country !")) - this.company_country_code = this.company_id.country_id.code.lower() - - @api.depends("year", "month") - def _compute_year_month(self): - for this in self: - if this.year and this.month: - this.year_month = "-".join([this.year, this.month]) - - @api.model - @api.constrains("year") - def _check_year(self): - for this in self: - if len(this.year) != 4 or this.year[0] != "2": - raise ValidationError(_("Invalid Year !")) - - # The method _compute_numbers has been removed - # because it was using a loop on lines, which is slow -> we should - # use read_group() instead, but then the code depends on - # the line object, so it can't be factorized here - - def _check_generate_xml(self): - for this in self: - if not this.company_id.partner_id.vat: - raise UserError( - _("The VAT number is not set for the partner '%s'.") - % this.company_id.partner_id.name - ) - - @api.model - def _check_xml_schema(self, xml_bytes, xsd_file): - """Validate the XML file against the XSD""" - xsd_etree_obj = etree.parse(tools.file_open(xsd_file, mode="rb")) - official_schema = etree.XMLSchema(xsd_etree_obj) - try: - t = etree.parse(BytesIO(xml_bytes)) - official_schema.assertValid(t) - except (etree.XMLSchemaParseError, etree.DocumentInvalid) as e: - logger.warning("The XML file is invalid against the XML Schema Definition") - logger.warning(xml_bytes) - logger.warning(e) - usererror = "{}\n\n{}".format(e.__class__.__name__, str(e)) - raise UserError(usererror) - except Exception: - error = _("Unknown Error") - tb = "".join(format_exception(*exc_info())) - error += "\n%s" % tb - logger.warning(error) - raise UserError(error) - - def _attach_xml_file(self, xml_bytes, declaration_name): - """Attach the XML file to the report_intrastat_product/service - object""" - self.ensure_one() - filename = "{}_{}.xml".format(self.year_month, declaration_name) - attach = self.env["ir.attachment"].create( - { - "name": filename, - "res_id": self.id, - "res_model": self._name, - "raw": xml_bytes, - } - ) - return attach.id - - def _unlink_attachments(self): - atts = self.env["ir.attachment"].search( - [("res_model", "=", self._name), ("res_id", "=", self.id)] - ) - atts.unlink() - - # Method _open_attach_view() removed - # Let's handle attachments like in l10n_fr_intrastat_service v14 - # with the field attachment_id on the declaration and the download - # link directly on the form view of the declaration. - - def _generate_xml(self): - """ - Inherit this method in the localization module - to generate the INTRASTAT Declaration XML file - - Returns: - string with XML data - - Call the _check_xml_schema() method - before returning the XML string. - """ - return False - - def send_reminder_email(self, mail_template_xmlid): - mail_template = self.env.ref(mail_template_xmlid) - for this in self: - if this.company_id.intrastat_remind_user_ids: - mail_template.send_mail(this.id) - logger.info( - "Intrastat Reminder email has been sent (XMLID: %s)." - % mail_template_xmlid - ) - else: - logger.warning( - "The list of users receiving the Intrastat Reminder is " - "empty on company %s" % this.company_id.name - ) - return True - - def unlink(self): - for intrastat in self: - if intrastat.state == "done": - raise UserError( - _("Cannot delete the declaration %s " "because it is in Done state") - % self.year_month - ) - return super().unlink() - - -class IntrastatResultView(models.TransientModel): - """ - Transient Model to display Intrastat Report results - """ - - _name = "intrastat.result.view" - _description = "Pop-up to show errors on intrastat report generation" - - note = fields.Text( - string="Notes", readonly=True, default=lambda self: self._context.get("note") - ) diff --git a/intrastat_base/models/res_company.py b/intrastat_base/models/res_company.py index eefce32..7a2dbc9 100644 --- a/intrastat_base/models/res_company.py +++ b/intrastat_base/models/res_company.py @@ -2,8 +2,17 @@ # @author: # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import _, api, fields, models -from odoo.exceptions import ValidationError +import logging +from io import BytesIO +from sys import exc_info +from traceback import format_exception + +from lxml import etree + +from odoo import _, api, fields, models, tools +from odoo.exceptions import UserError, ValidationError + +logger = logging.getLogger(__name__) class ResCompany(models.Model): @@ -40,3 +49,24 @@ class ResCompany(models.Model): raise ValidationError( _("Missing e-mail address on user '%s'.") % (user.name) ) + + @api.model + def _intrastat_check_xml_schema(self, xml_bytes, xsd_file): + """Validate the XML file against the XSD""" + xsd_etree_obj = etree.parse(tools.file_open(xsd_file, mode="rb")) + official_schema = etree.XMLSchema(xsd_etree_obj) + try: + t = etree.parse(BytesIO(xml_bytes)) + official_schema.assertValid(t) + except (etree.XMLSchemaParseError, etree.DocumentInvalid) as e: + logger.warning("The XML file is invalid against the XML Schema Definition") + logger.warning(xml_bytes) + logger.warning(e) + usererror = "{}\n\n{}".format(e.__class__.__name__, str(e)) + raise UserError(usererror) + except Exception: + error = _("Unknown Error") + tb = "".join(format_exception(*exc_info())) + error += "\n%s" % tb + logger.warning(error) + raise UserError(error) diff --git a/intrastat_base/security/ir.model.access.csv b/intrastat_base/security/ir.model.access.csv deleted file mode 100644 index fbe710c..0000000 --- a/intrastat_base/security/ir.model.access.csv +++ /dev/null @@ -1,2 +0,0 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_intrastat_result_view,Access on intrastat.result.view,model_intrastat_result_view,account.group_account_user,1,1,1,0 diff --git a/intrastat_base/tests/common.py b/intrastat_base/tests/common.py index 58a2491..ac1fbf3 100644 --- a/intrastat_base/tests/common.py +++ b/intrastat_base/tests/common.py @@ -1,45 +1,8 @@ # Copyright 2021 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo_test_helper import FakeModelLoader - -from odoo.modules.module import get_resource_path -from odoo.tools import convert_file class IntrastatCommon(object): - @classmethod - def _load_xml(cls, module, filepath): - convert_file( - cls.env.cr, - module, - get_resource_path(module, filepath), - {}, - mode="init", - noupdate=False, - kind="test", - ) - - @classmethod - def _load_test_declaration(cls): - cls.loader = FakeModelLoader(cls.env, cls.__module__) - cls.loader.backup_registry() - - # The fake class is imported here !! After the backup_registry - from .models import IntrastatDeclarationTest - - cls.loader.update_registry((IntrastatDeclarationTest,)) - - @classmethod - def _create_declaration(cls, vals=None): - values = { - "company_id": cls.declaration_test_obj._default_company_id().id, - "year": "2021", - "month": "03", - } - if vals is not None: - values.update(vals) - cls.declaration = cls.declaration_test_obj.create(values) - @classmethod def setUpClass(cls): super().setUpClass() @@ -51,9 +14,3 @@ class IntrastatCommon(object): cls.demo_company = cls.env.ref("base.main_company") cls.shipping_cost = cls.env.ref("intrastat_base.shipping_costs_exclude") - cls._load_test_declaration() - - @classmethod - def tearDownClass(cls): - cls.loader.restore_registry() - super().tearDownClass() diff --git a/intrastat_base/tests/data/mail_template.xml b/intrastat_base/tests/data/mail_template.xml deleted file mode 100644 index b3313f2..0000000 --- a/intrastat_base/tests/data/mail_template.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - Intrastat Product Reminder - - - ${object.company_id.email or 'odoo@example.com'} - ${object.company_id.intrastat_email_list} - ${object.type} DEB ${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}.

- -

As there were no ${object.type} DEB for that month in Odoo, a draft DEB 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:

- -

${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".

- -% else: -% if object.num_lines and object.num_lines > 0: -

This draft ${object.type} DEB contains ${object.num_decl_lines} ${object.num_decl_lines == 1 and 'line' or 'lines'}.

-% else: -

This draft ${object.type} DEB generated automatically by Odoo doesn't contain any line.

-% endif - -

Go and check this declaration in Odoo in the menu "Invoicing > Reporting > Intrastat > DEB".

- -% endif - -

--- -Automatic e-mail sent by Odoo. -

- -]]> -
-
-
diff --git a/intrastat_base/tests/test_all.py b/intrastat_base/tests/test_all.py index cc7b203..40b414f 100644 --- a/intrastat_base/tests/test_all.py +++ b/intrastat_base/tests/test_all.py @@ -1,5 +1,4 @@ -from odoo.exceptions import UserError, ValidationError -from odoo.tests.common import SavepointCase +from odoo.exceptions import ValidationError from .common import IntrastatCommon @@ -10,11 +9,6 @@ class TestIntrastatBase(IntrastatCommon): @classmethod def setUpClass(cls): super().setUpClass() - cls.declaration_test_obj = cls.env["intrastat.declaration.test"] - cls._load_xml("intrastat_base", "tests/data/mail_template.xml") - cls.mail_template_id = ( - "intrastat_base.base_intrastat_product_reminder_email_template" - ) def test_company(self): # add 'Demo user' to intrastat_remind_user_ids @@ -34,46 +28,3 @@ class TestIntrastatBase(IntrastatCommon): def test_accessory(self): with self.assertRaises(ValidationError): self.shipping_cost.type = "consu" - - def test_declaration_no_country(self): - self.demo_company.country_id = False - with self.assertRaises(ValidationError): - self._create_declaration() - self.declaration.flush() - - def test_declaration_no_vat(self): - self.demo_company.partner_id.vat = False - with self.assertRaises(UserError): - self._create_declaration() - self.declaration.flush() - self.declaration._check_generate_xml() - - def test_declaration_send_mail(self): - self._create_declaration() - mail_before = self.mail_obj.search([]) - self.declaration.send_reminder_email(self.mail_template_id) - mail_after = self.mail_obj.search([]) - mail_before - self.assertEqual(0, len(mail_after)) - self.demo_company.write( - {"intrastat_remind_user_ids": [(6, False, [self.demo_user.id])]} - ) - self.declaration.send_reminder_email(self.mail_template_id) - mail_after = self.mail_obj.search([]) - mail_before - self.assertEqual(1, len(mail_after)) - self.assertIn( - mail_after.email_to, - self.demo_user.email, - ) - - def test_declaration_state(self): - self._create_declaration() - self.declaration.unlink() - - self._create_declaration() - self.declaration.state = "done" - with self.assertRaises(UserError): - self.declaration.unlink() - - -class TestIntrastat(TestIntrastatBase, SavepointCase): - """ Test Intrastat """ diff --git a/intrastat_base/views/intrastat.xml b/intrastat_base/views/intrastat.xml index ca14bae..4784270 100644 --- a/intrastat_base/views/intrastat.xml +++ b/intrastat_base/views/intrastat.xml @@ -18,18 +18,4 @@ parent="account.menu_finance_configuration" sequence="50" /> - - intrastat.result_view_form - intrastat.result.view - -
- - - -
-
-
-
-
diff --git a/intrastat_product/__init__.py b/intrastat_product/__init__.py index bf588bc..cf6083c 100644 --- a/intrastat_product/__init__.py +++ b/intrastat_product/__init__.py @@ -1,2 +1,3 @@ from . import models from . import report +from . import wizards diff --git a/intrastat_product/__manifest__.py b/intrastat_product/__manifest__.py index 10753dd..cc9f6c4 100644 --- a/intrastat_product/__manifest__.py +++ b/intrastat_product/__manifest__.py @@ -35,6 +35,7 @@ "views/account_move.xml", "views/sale_order.xml", "views/stock_warehouse.xml", + "wizards/intrastat_result_view.xml", "data/intrastat_transport_mode.xml", "data/intrastat_unit.xml", ], diff --git a/intrastat_product/models/intrastat_product_declaration.py b/intrastat_product/models/intrastat_product_declaration.py index d7e3c42..4b404b4 100644 --- a/intrastat_product/models/intrastat_product_declaration.py +++ b/intrastat_product/models/intrastat_product_declaration.py @@ -20,7 +20,7 @@ class IntrastatProductDeclaration(models.Model): _name = "intrastat.product.declaration" _description = "Intrastat Product Report Base Object" _rec_name = "year_month" - _inherit = ["mail.thread", "mail.activity.mixin", "intrastat.common"] + _inherit = ["mail.thread", "mail.activity.mixin"] _order = "year_month desc, declaration_type, revision" _sql_constraints = [ ( @@ -41,6 +41,62 @@ class IntrastatProductDeclaration(models.Model): ) return res + company_id = fields.Many2one( + comodel_name="res.company", + string="Company", + required=True, + states={"done": [("readonly", True)]}, + default=lambda self: self.env.company, + ) + company_country_code = fields.Char( + compute="_compute_company_country_code", + string="Company Country Code", + readonly=True, + store=True, + ) + state = fields.Selection( + selection=[("draft", "Draft"), ("done", "Done")], + string="State", + readonly=True, + tracking=True, + copy=False, + default="draft", + help="State of the declaration. When the state is set to 'Done', " + "the parameters become read-only.", + ) + note = fields.Text( + string="Notes", help="You can add some comments here if you want." + ) + year = fields.Char( + string="Year", required=True, states={"done": [("readonly", True)]} + ) + month = fields.Selection( + selection=[ + ("01", "01"), + ("02", "02"), + ("03", "03"), + ("04", "04"), + ("05", "05"), + ("06", "06"), + ("07", "07"), + ("08", "08"), + ("09", "09"), + ("10", "10"), + ("11", "11"), + ("12", "12"), + ], + string="Month", + required=True, + states={"done": [("readonly", True)]}, + ) + year_month = fields.Char( + compute="_compute_year_month", + string="Period", + readonly=True, + tracking=True, + store=True, + help="Year and month of the declaration.", + ) declaration_type = fields.Selection( selection="_get_declaration_type", string="Type", @@ -127,12 +183,32 @@ class IntrastatProductDeclaration(models.Model): ("nihil", _("Nihil")), ] + @api.depends("company_id") + def _compute_company_country_code(self): + for this in self: + if this.company_id: + if not this.company_id.country_id: + raise ValidationError(_("You must set company's country !")) + this.company_country_code = this.company_id.country_id.code.lower() + + @api.depends("year", "month") + def _compute_year_month(self): + for this in self: + if this.year and this.month: + this.year_month = "-".join([this.year, this.month]) + @api.depends("month") def _compute_check_validity(self): """ TO DO: logic based upon computation lines """ for this in self: this.valid = True + @api.constrains("year") + def _check_year(self): + for this in self: + if len(this.year) != 4 or this.year[0] != "2": + raise ValidationError(_("Invalid Year!")) + @api.onchange("declaration_type") def _onchange_declaration_type(self): if self.declaration_type == "arrivals": @@ -160,6 +236,36 @@ class IntrastatProductDeclaration(models.Model): msg, action.id, _("Go to Accounting Configuration Settings screen") ) + def _attach_xml_file(self, xml_bytes, declaration_name): + """Attach the XML file to the report_intrastat_product/service + object""" + self.ensure_one() + filename = "{}_{}.xml".format(self.year_month, declaration_name) + attach = self.env["ir.attachment"].create( + { + "name": filename, + "res_id": self.id, + "res_model": self._name, + "raw": xml_bytes, + } + ) + return attach.id + + def _unlink_attachments(self): + atts = self.env["ir.attachment"].search( + [("res_model", "=", self._name), ("res_id", "=", self.id)] + ) + atts.unlink() + + def unlink(self): + for this in self: + if this.state == "done": + raise UserError( + _("Cannot delete the declaration %s because it is in Done state.") + % this.display_name + ) + return super().unlink() + def _get_partner_country(self, inv_line, notedict, eu_countries): inv = inv_line.move_id country = inv.src_dest_country_id or inv.partner_id.country_id @@ -712,7 +818,7 @@ class IntrastatProductDeclaration(models.Model): self.write(vals) if vals["note"]: - result_view = self.env.ref("intrastat_base.intrastat_result_view_form") + result_view = self.env.ref("intrastat_product.intrastat_result_view_form") return { "name": _("Generate lines from invoices: results"), "view_type": "form", @@ -809,6 +915,14 @@ class IntrastatProductDeclaration(models.Model): cl.write({"declaration_line_id": declaration_line.id}) return True + def _check_generate_xml(self): + self.ensure_one() + if not self.company_id.partner_id.vat: + raise UserError( + _("The VAT number is not set for the partner '%s'.") + % self.company_id.partner_id.display_name + ) + def generate_xml(self): """ generate the INTRASTAT Declaration XML file """ self.ensure_one() diff --git a/intrastat_product/security/ir.model.access.csv b/intrastat_product/security/ir.model.access.csv index ac93450..65141ca 100644 --- a/intrastat_product/security/ir.model.access.csv +++ b/intrastat_product/security/ir.model.access.csv @@ -12,3 +12,4 @@ access_account_move_intrastat_line,Full access on Invoice Intrastat Lines,model_ access_intrastat_product_declaration,Full access on Intrastat Product Declarations to Accountant,model_intrastat_product_declaration,account.group_account_user,1,1,1,1 access_intrastat_product_computation_line,Full access on Intrastat Product Computation Lines to Accountant,model_intrastat_product_computation_line,account.group_account_user,1,1,1,1 access_intrastat_product_declaration_line,Full access on Intrastat Product Declaration Lines to Accountant,model_intrastat_product_declaration_line,account.group_account_user,1,1,1,1 +access_intrastat_result_view,Access on intrastat.result.view,model_intrastat_result_view,account.group_account_user,1,1,1,0 diff --git a/intrastat_product/tests/common.py b/intrastat_product/tests/common.py index fbe94d1..6efb68b 100644 --- a/intrastat_product/tests/common.py +++ b/intrastat_product/tests/common.py @@ -107,6 +107,7 @@ class IntrastatProductCommon(IntrastatCommon): def _create_declaration(cls, vals=None): values = { "company_id": cls.env.company.id, + "declaration_type": "dispatches", } if vals is not None: values.update(vals) diff --git a/intrastat_product/tests/test_intrastat_product.py b/intrastat_product/tests/test_intrastat_product.py index 8a6b138..dd1f095 100644 --- a/intrastat_product/tests/test_intrastat_product.py +++ b/intrastat_product/tests/test_intrastat_product.py @@ -4,7 +4,7 @@ from psycopg2 import IntegrityError from odoo.tests.common import SavepointCase from odoo.tools import mute_logger - +from odoo.exceptions import UserError, ValidationError from .common import IntrastatProductCommon @@ -39,6 +39,28 @@ class TestIntrastatProduct(IntrastatProductCommon): decl_copy = self.declaration.copy() self.assertEqual(self.declaration.revision + 1, decl_copy.revision) + def test_declaration_no_country(self): + self.demo_company.country_id = False + with self.assertRaises(ValidationError): + self._create_declaration() + self.declaration.flush() + + def test_declaration_no_vat(self): + self.demo_company.partner_id.vat = False + with self.assertRaises(UserError): + self._create_declaration() + self.declaration.flush() + self.declaration._check_generate_xml() + + def test_declaration_state(self): + self._create_declaration() + self.declaration.unlink() + + self._create_declaration() + self.declaration.state = "done" + with self.assertRaises(UserError): + self.declaration.unlink() + class TestIntrastatProductCase(TestIntrastatProduct, SavepointCase): """ Test Intrastat Product """ diff --git a/intrastat_product/wizards/__init__.py b/intrastat_product/wizards/__init__.py new file mode 100644 index 0000000..3277b28 --- /dev/null +++ b/intrastat_product/wizards/__init__.py @@ -0,0 +1 @@ +from . import intrastat_result_view diff --git a/intrastat_product/wizards/intrastat_result_view.py b/intrastat_product/wizards/intrastat_result_view.py new file mode 100644 index 0000000..efd743f --- /dev/null +++ b/intrastat_product/wizards/intrastat_result_view.py @@ -0,0 +1,14 @@ +# Copyright 2010-2021 Akretion () +# Copyright 2009-2021 Noviat (http://www.noviat.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class IntrastatResultView(models.TransientModel): + _name = "intrastat.result.view" + _description = "Pop-up to show errors on intrastat report generation" + + note = fields.Text( + string="Notes", readonly=True, default=lambda self: self._context.get("note") + ) diff --git a/intrastat_product/wizards/intrastat_result_view.xml b/intrastat_product/wizards/intrastat_result_view.xml new file mode 100644 index 0000000..16b9303 --- /dev/null +++ b/intrastat_product/wizards/intrastat_result_view.xml @@ -0,0 +1,22 @@ + + + + + intrastat.result_view_form + intrastat.result.view + +
+ + + +
+
+
+
+
+
From 19d17f7e34766cadfdb7e719731066f8560a0e69 Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Wed, 22 Dec 2021 13:40:02 +0100 Subject: [PATCH 12/12] [IMP] intrastat_product: Fix pre-commit --- intrastat_product/tests/test_intrastat_product.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/intrastat_product/tests/test_intrastat_product.py b/intrastat_product/tests/test_intrastat_product.py index dd1f095..af0b265 100644 --- a/intrastat_product/tests/test_intrastat_product.py +++ b/intrastat_product/tests/test_intrastat_product.py @@ -2,9 +2,10 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from psycopg2 import IntegrityError +from odoo.exceptions import UserError, ValidationError from odoo.tests.common import SavepointCase from odoo.tools import mute_logger -from odoo.exceptions import UserError, ValidationError + from .common import IntrastatProductCommon