diff --git a/intrastat_product/models/intrastat_product_declaration.py b/intrastat_product/models/intrastat_product_declaration.py index 3fef185..130c274 100644 --- a/intrastat_product/models/intrastat_product_declaration.py +++ b/intrastat_product/models/intrastat_product_declaration.py @@ -1021,13 +1021,17 @@ class IntrastatProductDeclaration(models.Model): """ return [ "hs_code", + "product_origin_country_code", "product_origin_country", + "src_dest_country_code", "src_dest_country", "amount_company_currency", + "transaction_code", "transaction", "weight", "suppl_unit_qty", "suppl_unit", + "transport_code", "transport", "vat", ] diff --git a/intrastat_product/report/intrastat_product_report_xls.py b/intrastat_product/report/intrastat_product_report_xls.py index 54968fb..a666d25 100644 --- a/intrastat_product/report/intrastat_product_report_xls.py +++ b/intrastat_product/report/intrastat_product_report_xls.py @@ -28,6 +28,10 @@ class IntrastatProductDeclarationXlsx(models.AbstractModel): return val def _get_template(self, declaration): + """ + Return a dictionary that contains columns specifications + see: report_xlsx_helper / _write_line() method + """ template = { "product": { @@ -37,6 +41,14 @@ class IntrastatProductDeclarationXlsx(models.AbstractModel): }, "width": 36, }, + "product_origin_country_code": { + "header": {"type": "string", "value": self._("Product C/O Code")}, + "line": { + "type": "string", + "value": self._render("line.product_origin_country_id.code or ''"), + }, + "width": 28, + }, "product_origin_country": { "header": {"type": "string", "value": self._("Product C/O")}, "line": { @@ -53,6 +65,17 @@ class IntrastatProductDeclarationXlsx(models.AbstractModel): }, "width": 14, }, + "src_dest_country_code": { + "header": { + "type": "string", + "value": self._("Country of Origin/Destination"), + }, + "line": { + "type": "string", + "value": self._render("line.src_dest_country_id.code"), + }, + "width": 28, + }, "src_dest_country": { "header": { "type": "string", @@ -92,6 +115,14 @@ class IntrastatProductDeclarationXlsx(models.AbstractModel): }, "width": 18, }, + "transaction_code": { + "header": { + "type": "string", + "value": self._("Intrastat Transaction code"), + }, + "line": {"value": self._render("line.transaction_id.code")}, + "width": 36, + }, "transaction": { "header": {"type": "string", "value": self._("Intrastat Transaction")}, "line": {"value": self._render("line.transaction_id.display_name")}, @@ -135,6 +166,11 @@ class IntrastatProductDeclarationXlsx(models.AbstractModel): "line": {"value": self._render("line.incoterm_id.name or ''")}, "width": 14, }, + "transport_code": { + "header": {"type": "string", "value": self._("Transport Mode Code")}, + "line": {"value": self._render("line.transport_id.code or ''")}, + "width": 14, + }, "transport": { "header": {"type": "string", "value": self._("Transport Mode")}, "line": {"value": self._render("line.transport_id.name or ''")}, diff --git a/intrastat_product/tests/common.py b/intrastat_product/tests/common.py index ae84988..17518d5 100644 --- a/intrastat_product/tests/common.py +++ b/intrastat_product/tests/common.py @@ -1,5 +1,7 @@ # Copyright 2021 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import xlrd +from werkzeug.urls import url_encode from odoo.addons.intrastat_base.tests.common import IntrastatCommon @@ -63,6 +65,10 @@ class IntrastatProductCommon(IntrastatCommon): 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.report_obj = cls.env["ir.actions.report"] + cls.xls_declaration = cls.env[ + "report.intrastat_product.product_declaration_xls" + ] cls.transport_rail = cls.env.ref("intrastat_product.intrastat_transport_2") cls.transport_road = cls.env.ref("intrastat_product.intrastat_transport_3") @@ -72,6 +78,78 @@ class IntrastatProductCommon(IntrastatCommon): cls._init_fiscal_position() cls._init_products() + @classmethod + def _create_xls(cls, declaration=False): + """ + Prepare the Excel report to be tested + + :return: The Excel file + :rtype: bytes + """ + report = cls.declaration.with_context( + active_ids=cls.declaration.ids + ).create_xls() + report_name = report.get("report_name") + cls.report = cls.report_obj._get_report_from_name(report_name) + datas = { + "context": { + "active_ids": [cls.declaration.id], + } + } + data = {} + encoded_data = "report/report_xlsx/" + report_name + "?" + url_encode(data) + datas["data"] = encoded_data + context = { + "active_model": cls.declaration._name, + } + if not declaration: + context.update({"computation_lines": True}) + else: + context.update({"declaration_lines": True}) + file_data = cls.xls_declaration.with_context(context).create_xlsx_report( + None, datas + ) + return file_data + + def check_xls(self, xls, declaration=False): + """ + Check that the xls content correspond to computation/declaration + lines values + + :param xls: the Excel file content + :type xls: bytes + :param declaration: By default, check computation lines, either declaration ones + :type declaration: bool, optional + """ + book = xlrd.open_workbook(file_contents=xls) + sheet = book.sheet_by_index(0) + # Get the template used to build the Excel file lines + template = self.xls_declaration._get_template(self.declaration) + # Get the declaration lines or the computation ones + if not declaration: + declaration_lines = self.declaration.computation_line_ids + line_fields = self.declaration._xls_computation_line_fields() + else: + declaration_lines = self.declaration.declaration_line_ids + line_fields = self.declaration._xls_declaration_line_fields() + i = 0 + # Iterate on each row beginning on third one (two headers) + for rx in range(3, sheet.nrows): + line = declaration_lines[i] + row = sheet.row(rx) + j = 0 + dict_compare = dict() + for line_field in line_fields: + column_spec = template.get(line_field) + dict_compare.update( + {row[j].value: column_spec.get("line").get("value")} + ) + j += 1 + for key, value in dict_compare.items(): + value_eval = self.xls_declaration._eval(value, {"line": line}) + self.assertEqual(key, value_eval) + i += 1 + @classmethod def _create_region(cls, vals=None): values = { diff --git a/intrastat_product/tests/test_purchase_order.py b/intrastat_product/tests/test_purchase_order.py index 236ffe7..88528e2 100644 --- a/intrastat_product/tests/test_purchase_order.py +++ b/intrastat_product/tests/test_purchase_order.py @@ -48,6 +48,14 @@ class TestIntrastatProductPurchase(IntrastatPurchaseCommon): self.declaration.generate_declaration() self._check_line_values(final=True) + # Check the Excel computation file + file_data = self._create_xls() + self.check_xls(file_data[0]) + + # Check the Excel declaration file + file_data = self._create_xls(True) + self.check_xls(file_data[0], True) + class TestIntrastatProductPurchaseCase(TestIntrastatProductPurchase, SavepointCase): """Test Intrastat Purchase""" diff --git a/intrastat_product/tests/test_sale_order.py b/intrastat_product/tests/test_sale_order.py index 7c01f1b..aedfe7f 100644 --- a/intrastat_product/tests/test_sale_order.py +++ b/intrastat_product/tests/test_sale_order.py @@ -83,6 +83,14 @@ class TestIntrastatProductSale(IntrastatSaleCommon): self.declaration.generate_declaration() self._check_line_values(final=True) + # Check the Excel computation file + file_data = self._create_xls() + self.check_xls(file_data[0]) + + # Check the Excel declaration file + file_data = self._create_xls(True) + self.check_xls(file_data[0], True) + class TestIntrastatProductSaleCase(TestIntrastatProductSale, SavepointCase): """Test Intrastat Sale"""