From 47cbc65d0ff1a10655193f4115f81901b100204d Mon Sep 17 00:00:00 2001 From: Luc De Meyer Date: Sun, 26 Dec 2021 13:51:33 +0100 Subject: [PATCH] [14.0]Intrastat Brexit support --- intrastat_product/models/account_move.py | 44 ++++++++++- .../models/intrastat_product_declaration.py | 73 ++++++++++++++----- intrastat_product/views/account_move.xml | 5 +- .../views/intrastat_product_declaration.xml | 22 ++++++ .../models/product_template.py | 12 ++- .../views/product_template.xml | 4 + 6 files changed, 135 insertions(+), 25 deletions(-) diff --git a/intrastat_product/models/account_move.py b/intrastat_product/models/account_move.py index f916aa4..991a371 100644 --- a/intrastat_product/models/account_move.py +++ b/intrastat_product/models/account_move.py @@ -1,9 +1,10 @@ # Copyright 2011-2020 Akretion France (http://www.akretion.com) -# Copyright 2009-2020 Noviat (http://www.noviat.com) +# Copyright 2009-2021 Noviat (http://www.noviat.com) # @author Alexis de Lattre # @author Luc de Meyer -from odoo import api, fields, models +from odoo import _, api, fields, models +from odoo.exceptions import UserError class AccountMove(models.Model): @@ -93,13 +94,17 @@ class AccountMove(models.Model): if not hs_code: return vals weight, qty = decl_model._get_weight_and_supplunits(line, hs_code, notedict) + product_origin_country = line.product_id.origin_country_id + product_origin_country_code = decl_model._get_product_origin_country_code( + line, product_origin_country + ) vals.update( { "invoice_line_id": line.id, "hs_code_id": hs_code.id, "transaction_weight": int(weight), "transaction_suppl_unit_qty": qty, - "product_origin_country_id": line.product_id.origin_country_id.id, + "product_origin_country_code": product_origin_country_code, } ) return vals @@ -160,11 +165,22 @@ class AccountMoveIntrastatLine(models.Model): transaction_weight = fields.Integer( help="Transaction weight in Kg: Quantity x Product Weight" ) + # product_origin_country_id is replaced by product_origin_country_code + # this field should be dropped once the localisation modules have been + # adapted accordingly product_origin_country_id = fields.Many2one( comodel_name="res.country", string="Country of Origin", help="Country of origin of the product i.e. product " "'made in ____'.", ) + product_origin_country_code = fields.Char( + string="Country of Origin of the Product", + required=True, + default="QU", + help="2 digit code of country of origin of the product except for the UK.\n" + "Specify 'XI' for UK Northern Ireland and 'XU' for rest of the UK.\n" + "Specify 'QU' when the country is unknown.\n", + ) @api.onchange("invoice_line_id") def _onchange_move_id(self): @@ -176,3 +192,25 @@ class AccountMoveIntrastatLine(models.Model): ("id", "not in", moves.mapped("intrastat_line_ids.invoice_line_id").ids), ] return {"domain": {"invoice_line_id": dom}} + + @api.model + def create(self, vals): + self._format_vals(vals) + return super().create(vals) + + def write(self, vals): + self._format_vals(vals) + return super().write(vals) + + def _format_vals(self, vals): + if "product_origin_country_code" in vals: + vals["product_origin_country_code"] = ( + vals["product_origin_country_code"].upper().strip() + ) + if len(vals["product_origin_country_code"]) != 2: + raise UserError( + _( + "Intrastat transaction details error:\n" + "Product Origin Country Code must be 2 characters." + ) + ) diff --git a/intrastat_product/models/intrastat_product_declaration.py b/intrastat_product/models/intrastat_product_declaration.py index 91cc15d..b65f9f4 100644 --- a/intrastat_product/models/intrastat_product_declaration.py +++ b/intrastat_product/models/intrastat_product_declaration.py @@ -1,5 +1,5 @@ # Copyright 2011-2020 Akretion France (http://www.akretion.com) -# Copyright 2009-2020 Noviat (http://www.noviat.com) +# Copyright 2009-2021 Noviat (http://www.noviat.com) # @author Alexis de Lattre # @author Luc de Meyer @@ -489,8 +489,24 @@ class IntrastatProductDeclaration(models.Model): self._account_config_warning(msg) return incoterm - def _get_product_origin_country(self, inv_line, notedict): - return inv_line.product_id.origin_country_id + def _get_product_origin_country_code( + self, inv_line, product_origin_country, notedict + ): + product_origin_country_code = "QU" + if product_origin_country.code: + product_origin_country_code = product_origin_country.code + year = self.year or str(inv_line.move_id.date.year) + if year >= "2021": + if ( + hasattr(inv_line.product_id, "origin_state_id") + and inv_line.product_id.origin_state_id + and inv_line.product_id.origin_state_id.name.lower() + == "northern ireland" + ): + product_origin_country_code = "XI" + elif inv_line.product_id.origin_country_id.code == "GB": + product_origin_country_code = "XU" + return product_origin_country_code def _get_vat(self, inv_line, notedict): vat = False @@ -705,12 +721,13 @@ class IntrastatProductDeclaration(models.Model): total_inv_product_cc += amount_company_currency if inv_intrastat_line: - product_origin_country = ( - inv_intrastat_line.product_origin_country_id + product_origin_country_code = ( + inv_intrastat_line.product_origin_country_code ) else: - product_origin_country = self._get_product_origin_country( - inv_line, notedict + product_origin_country = inv_line.product_id.origin_country_id + product_origin_country_code = self._get_product_origin_country_code( + inv_line, product_origin_country, notedict ) region = self._get_region(inv_line, notedict) @@ -728,7 +745,7 @@ class IntrastatProductDeclaration(models.Model): "amount_company_currency": amount_company_currency, "amount_accessory_cost_company_currency": 0.0, "transaction_id": intrastat_transaction.id, - "product_origin_country_id": product_origin_country.id or False, + "product_origin_country_code": product_origin_country_code, "region_id": region and region.id or False, "vat": vat, } @@ -832,9 +849,8 @@ class IntrastatProductDeclaration(models.Model): "transaction": computation_line.transaction_id.id or False, "transport": computation_line.transport_id.id or False, "region": computation_line.region_id.id or False, - "product_origin_country": computation_line.product_origin_country_id.id - or False, "vat": computation_line.vat or False, + "product_origin_country_code": computation_line.product_origin_country_code, } def group_line_hashcode(self, computation_line): @@ -852,7 +868,7 @@ class IntrastatProductDeclaration(models.Model): "transport_id": computation_line.transport_id.id, "region_id": computation_line.region_id.id, "parent_id": computation_line.parent_id.id, - "product_origin_country_id": computation_line.product_origin_country_id.id, + "product_origin_country_code": computation_line.product_origin_country_code, "amount_company_currency": 0.0, "vat": computation_line.vat, } @@ -1086,16 +1102,28 @@ class IntrastatProductComputationLine(models.Model): "intrastat.transaction", string="Intrastat Transaction" ) region_id = fields.Many2one("intrastat.region", string="Intrastat Region") - # extended declaration - incoterm_id = fields.Many2one("account.incoterms", string="Incoterm") - transport_id = fields.Many2one("intrastat.transport_mode", string="Transport Mode") + # product_origin_country_id is replaced by product_origin_country_code + # this field should be dropped once the localisation modules have been + # adapted accordingly product_origin_country_id = fields.Many2one( "res.country", string="Country of Origin of the Product", help="Country of origin of the product i.e. product 'made in ____'", ) + product_origin_country_code = fields.Char( + string="Country of Origin of the Product", + required=True, + default="QU", + help="2 digit code of country of origin of the product except for the UK.\n" + "Specify 'XI' for UK Northern Ireland and 'XU' for rest of the UK.\n" + "Specify 'QU' when the country is unknown.\n", + ) vat = fields.Char(string="VAT Number") + # extended declaration + incoterm_id = fields.Many2one("account.incoterms", string="Incoterm") + transport_id = fields.Many2one("intrastat.transport_mode", string="Transport Mode") + @api.depends("transport_id") def _compute_check_validity(self): """TO DO: logic based upon fields""" @@ -1172,15 +1200,26 @@ class IntrastatProductDeclarationLine(models.Model): "intrastat.transaction", string="Intrastat Transaction" ) region_id = fields.Many2one("intrastat.region", string="Intrastat Region") - # extended declaration - incoterm_id = fields.Many2one("account.incoterms", string="Incoterm") - transport_id = fields.Many2one("intrastat.transport_mode", string="Transport Mode") + # product_origin_country_id is replaced by product_origin_country_code + # this field should be dropped once the localisation modules have been + # adapted accordingly product_origin_country_id = fields.Many2one( "res.country", string="Country of Origin of the Product", help="Country of origin of the product i.e. product 'made in ____'", ) + product_origin_country_code = fields.Char( + string="Country of Origin of the Product", + required=True, + default="QU", + help="2 digit code of country of origin of the product except for the UK.\n" + "Specify 'XI' for UK Northern Ireland and 'XU' for rest of the UK.\n" + "Specify 'QU' when the country is unknown.\n", + ) vat = fields.Char(string="VAT Number") + # extended declaration + incoterm_id = fields.Many2one("account.incoterms", string="Incoterm") + transport_id = fields.Many2one("intrastat.transport_mode", string="Transport Mode") @api.constrains("vat") def _check_vat(self): diff --git a/intrastat_product/views/account_move.xml b/intrastat_product/views/account_move.xml index 6d0ad07..654faf8 100644 --- a/intrastat_product/views/account_move.xml +++ b/intrastat_product/views/account_move.xml @@ -47,10 +47,7 @@ - + diff --git a/intrastat_product/views/intrastat_product_declaration.xml b/intrastat_product/views/intrastat_product_declaration.xml index ce21b8d..5c80885 100644 --- a/intrastat_product/views/intrastat_product_declaration.xml +++ b/intrastat_product/views/intrastat_product_declaration.xml @@ -250,7 +250,12 @@ /> + + @@ -287,11 +292,17 @@ attrs="{'required': [('reporting_level', '=', 'extended')], 'invisible': [('reporting_level', '!=', 'extended')]}" /> + + @@ -332,7 +343,12 @@ /> + + @@ -365,11 +381,17 @@ /> + + diff --git a/product_harmonized_system/models/product_template.py b/product_harmonized_system/models/product_template.py index 4670cfc..d524206 100644 --- a/product_harmonized_system/models/product_template.py +++ b/product_harmonized_system/models/product_template.py @@ -23,10 +23,20 @@ class ProductTemplate(models.Model): "and configure the H.S. code on the product category.", ) origin_country_id = fields.Many2one( - "res.country", + comodel_name="res.country", string="Country of Origin", help="Country of origin of the product i.e. product " "'made in ____'.", ) + origin_state_id = fields.Many2one( + comodel_name="res.country.state", + string="Country State of Origin", + domain="[('country_id', '=?', origin_country_id)]", + help="Country State of origin of the product.\n" + "This field is used for the Intrastat declaration, " + "selecting 'Northern Ireland' will set the code 'XI' " + "for products from the United Kingdom whereas code 'XU' " + "will be used for the other UK states.", + ) class ProductProduct(models.Model): diff --git a/product_harmonized_system/views/product_template.xml b/product_harmonized_system/views/product_template.xml index 59fa83e..79d2b27 100644 --- a/product_harmonized_system/views/product_template.xml +++ b/product_harmonized_system/views/product_template.xml @@ -20,6 +20,10 @@ name="origin_country_id" attrs="{'invisible': [('type', '=', 'service')]}" /> +