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 """