[14.0]intrastat - improved brexit support

This commit is contained in:
Luc De Meyer
2022-05-02 15:35:56 +02:00
parent a8cdd7459a
commit 2874b76ab8
9 changed files with 240 additions and 21 deletions

View File

@@ -3,3 +3,4 @@ from . import res_company
from . import account_fiscal_position
from . import account_fiscal_position_template
from . import account_move
from . import res_partner

View File

@@ -0,0 +1,67 @@
# Copyright 2022 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, models
from odoo.exceptions import UserError
XI_COUNTY_NAMES = [
"antrim",
"armagh",
"down",
"fermanagh",
"londonderry",
"tyrone",
"northern ireland",
]
XI_COUNTIES = [
"base.state_uk18", # County Antrim
"base.state_uk19", # County Armagh
"base.state_uk20", # County Down
"base.state_uk22", # County Fermanagh
"base.state_uk23", # County Londonderry
"base.state_uk24", # County Tyrone
"base.state_ie_27", # Antrim
"base.state_ie_28", # Armagh
"base.state_ie_29", # Down
"base.state_ie_30", # Fermanagh
"base.state_ie_31", # Londonderry
"base.state_ie_32", # Tyrone
]
class ResPartner(models.Model):
_inherit = "res.partner"
@api.model
def _get_xi_counties(self):
return [self.env.ref(x) for x in XI_COUNTIES]
@api.model
def _get_xu_counties(self):
uk_counties = self.env.ref("base.uk").state_ids
xu_counties = uk_counties.filtered(lambda r: r not in self._get_xi_counties())
return xu_counties
def _get_intrastat_country_code(self, country=None, state=None):
if self:
self.ensure_one()
country = self.country_id
state = self.state_id
else:
state = state or self.env["res.country.state"]
country = country or state.country_id
if not country:
raise UserError(
_("Programming Error when calling '_get_intrastat_country_code()")
)
cc = country.code
if cc == "GB":
cc = "XU"
if state and cc in ["XU", "IE"]:
if (
state in self._get_xi_counties()
or state.name.lower().strip() in XI_COUNTY_NAMES
):
cc = "XI"
return cc

View File

@@ -1,5 +1,5 @@
# Copyright 2011-2020 Akretion France (http://www.akretion.com)
# Copyright 2009-2021 Noviat (http://www.noviat.com)
# Copyright 2009-2022 Noviat (http://www.noviat.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# @author Luc de Meyer <info@noviat.com>
@@ -492,21 +492,20 @@ class IntrastatProductDeclaration(models.Model):
def _get_product_origin_country_code(
self, inv_line, product_origin_country, notedict
):
product_origin_country_code = "QU"
cc = "QU"
if product_origin_country.code:
product_origin_country_code = product_origin_country.code
cc = 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
product_origin_state = getattr(
inv_line.product_id,
"origin_state_id",
self.env["res.country.state"],
)
cc = self.env["res.partner"]._get_intrastat_country_code(
product_origin_country, product_origin_state
)
return cc
def _get_vat(self, inv_line, notedict):
vat = False
@@ -683,6 +682,9 @@ class IntrastatProductDeclaration(models.Model):
partner_country = self._get_partner_country(
inv_line, notedict, eu_countries
)
partner_country_code = (
invoice.commercial_partner_id._get_intrastat_country_code()
)
if inv_intrastat_line:
hs_code = inv_intrastat_line.hs_code_id
@@ -741,6 +743,7 @@ class IntrastatProductDeclaration(models.Model):
"parent_id": self.id,
"invoice_line_id": inv_line.id,
"src_dest_country_id": partner_country.id,
"src_dest_country_code": partner_country_code,
"product_id": inv_line.product_id.id,
"hs_code_id": hs_code.id,
"weight": weight,
@@ -847,7 +850,7 @@ class IntrastatProductDeclaration(models.Model):
@api.model
def _group_line_hashcode_fields(self, computation_line):
return {
"country": computation_line.src_dest_country_id.id or False,
"country": computation_line.src_dest_country_code,
"hs_code_id": computation_line.hs_code_id.id or False,
"intrastat_unit": computation_line.intrastat_unit_id.id or False,
"transaction": computation_line.transaction_id.id or False,
@@ -866,6 +869,7 @@ class IntrastatProductDeclaration(models.Model):
def _prepare_grouped_fields(self, computation_line, fields_to_sum):
vals = {
"src_dest_country_id": computation_line.src_dest_country_id.id,
"src_dest_country_code": computation_line.src_dest_country_code,
"intrastat_unit_id": computation_line.intrastat_unit_id.id,
"hs_code_id": computation_line.hs_code_id.id,
"transaction_id": computation_line.transaction_id.id,
@@ -1070,6 +1074,15 @@ class IntrastatProductComputationLine(models.Model):
string="Country",
help="Country of Origin/Destination",
)
src_dest_country_code = fields.Char(
string="Country Code",
compute="_compute_src_dest_country_code",
store=True,
required=True,
readonly=False,
help="2 digit code of country of origin/destination.\n"
"Specify 'XI' for UK Northern Ireland and 'XU' for rest of the UK.",
)
product_id = fields.Many2one(
"product.product", related="invoice_line_id.product_id"
)
@@ -1129,6 +1142,14 @@ class IntrastatProductComputationLine(models.Model):
incoterm_id = fields.Many2one("account.incoterms", string="Incoterm")
transport_id = fields.Many2one("intrastat.transport_mode", string="Transport Mode")
@api.onchange("src_dest_country_id")
def _onchange_src_dest_country_id(self):
self.src_dest_country_code = self.src_dest_country_id.code
if self.parent_id.year >= "2021":
self.src_dest_country_code = self.env[
"res.partner"
]._get_intrastat_country_code(country=self.src_dest_country_id)
@api.depends("transport_id")
def _compute_check_validity(self):
"""TO DO: logic based upon fields"""
@@ -1184,6 +1205,12 @@ class IntrastatProductDeclarationLine(models.Model):
string="Country",
help="Country of Origin/Destination",
)
src_dest_country_code = fields.Char(
string="Country Code",
required=True,
help="2 digit code of country of origin/destination.\n"
"Specify 'XI' for UK Northern Ireland and 'XU' for rest of the UK.",
)
hs_code_id = fields.Many2one("hs.code", string="Intrastat Code")
intrastat_unit_id = fields.Many2one(
"intrastat.unit",
@@ -1226,6 +1253,14 @@ class IntrastatProductDeclarationLine(models.Model):
incoterm_id = fields.Many2one("account.incoterms", string="Incoterm")
transport_id = fields.Many2one("intrastat.transport_mode", string="Transport Mode")
@api.onchange("src_dest_country_id")
def _onchange_src_dest_country_id(self):
self.src_dest_country_code = self.src_dest_country_id.code
if self.parent_id.year >= "2021":
self.src_dest_country_code = self.env[
"res.partner"
]._get_intrastat_country_code(country=self.src_dest_country_id)
@api.constrains("vat")
def _check_vat(self):
for this in self:

View File

@@ -1,4 +1,4 @@
# Copyright 2009-2020 Noviat
# Copyright 2009-2022 Noviat
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
@@ -60,7 +60,7 @@ class IntrastatProductDeclarationXlsx(models.AbstractModel):
},
"line": {
"type": "string",
"value": self._render("line.src_dest_country_id.name"),
"value": self._render("line.src_dest_country_code"),
},
"width": 28,
},

View File

@@ -2,6 +2,7 @@ from . import common
from . import common_purchase
from . import common_sale
from . import test_intrastat_product
from . import test_brexit
from . import test_company
from . import test_purchase_order
from . import test_sale_order

View File

@@ -39,7 +39,7 @@ class IntrastatProductCommon(IntrastatCommon):
@classmethod
def _init_regions(cls):
# Create Belgium Region
# Create Region
cls._create_region()
vals = {
@@ -75,7 +75,7 @@ class IntrastatProductCommon(IntrastatCommon):
@classmethod
def _create_region(cls, vals=None):
values = {
"code": "BE_w",
"code": "2",
"country_id": cls.env.ref("base.be").id,
"company_id": cls.env.company.id,
"description": "Belgium Walloon Region",

View File

@@ -0,0 +1,111 @@
# Copyright 2022 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests import Form, SavepointCase
from .common import IntrastatProductCommon
class TestIntrastatBrexit(IntrastatProductCommon, SavepointCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.inv_obj = cls.env["account.move"]
cls.hs_code_whiskey = cls.env["hs.code"].create(
{
"description": "Whiskey",
"local_code": "22083000",
}
)
cls.product_xi = cls.env["product.product"].create(
{
"name": "Bushmills Original",
"weight": 1.4,
"list_price": 30.0,
"standard_price": 15.0,
"origin_country_id": cls.env.ref("base.uk").id,
"origin_state_id": cls.env.ref("base.state_uk18").id,
"hs_code_id": cls.hs_code_whiskey.id,
}
)
cls.product_xu = cls.env["product.product"].create(
{
"name": "Glenfiddich",
"weight": 1.4,
"list_price": 50.0,
"standard_price": 25.0,
"origin_country_id": cls.env.ref("base.uk").id,
"origin_state_id": cls.env.ref("base.state_uk6").id,
"hs_code_id": cls.hs_code_whiskey.id,
}
)
cls.partner_xi = cls.env["res.partner"].create(
{
"name": "Bushmills Distillery",
"country_id": cls.env.ref("base.uk").id,
"state_id": cls.env.ref("base.state_uk18").id,
"vat": "XI123456782",
"property_account_position_id": cls.position.id,
}
)
def test_brexit_sale(self):
inv_out_xi = self.inv_obj.with_context(default_move_type="out_invoice").create(
{
"partner_id": self.partner_xi.id,
"fiscal_position_id": self.position.id,
}
)
with Form(inv_out_xi) as inv_form:
with inv_form.invoice_line_ids.new() as ail:
ail.product_id = self.product_c3po.product_variant_ids[0]
inv_out_xi.action_post()
self._create_declaration(
{
"declaration_type": "dispatches",
"year": str(inv_out_xi.date.year),
"month": str(inv_out_xi.date.month).zfill(2),
}
)
self.declaration.action_gather()
self.declaration.generate_declaration()
cline = self.declaration.computation_line_ids
dline = self.declaration.declaration_line_ids
self.assertEqual(cline.src_dest_country_code, "XI")
self.assertEqual(dline.src_dest_country_code, "XI")
def test_brexit_purchase(self):
inv_in_xi = self.inv_obj.with_context(default_move_type="in_invoice").create(
{
"partner_id": self.partner_xi.id,
"fiscal_position_id": self.position.id,
}
)
with Form(inv_in_xi) as inv_form:
with inv_form.invoice_line_ids.new() as ail:
ail.product_id = self.product_xi
with inv_form.invoice_line_ids.new() as ail:
ail.product_id = self.product_xu
inv_in_xi.invoice_date = inv_in_xi.date
inv_in_xi.action_post()
self._create_declaration(
{
"declaration_type": "arrivals",
"year": str(inv_in_xi.date.year),
"month": str(inv_in_xi.date.month).zfill(2),
}
)
self.declaration.action_gather()
self.declaration.generate_declaration()
clines = self.declaration.computation_line_ids
cl_xi = clines.filtered(lambda r: r.product_id == self.product_xi)
cl_xu = clines.filtered(lambda r: r.product_id == self.product_xu)
dlines = self.declaration.declaration_line_ids
dl_xi = dlines.filtered(lambda r: r.computation_line_ids == cl_xi)
dl_xu = dlines.filtered(lambda r: r.computation_line_ids == cl_xu)
self.assertEqual(cl_xi.product_origin_country_code, "XI")
self.assertEqual(cl_xu.product_origin_country_code, "XU")
self.assertEqual(dl_xi.product_origin_country_code, "XI")
self.assertEqual(dl_xu.product_origin_country_code, "XU")

View File

@@ -224,6 +224,7 @@
<field name="product_id" />
<field name="hs_code_id" />
<field name="src_dest_country_id" />
<field name="src_dest_country_code" />
<field
name="amount_company_currency"
widget="monetary"
@@ -278,6 +279,7 @@
<field name="product_id" />
<field name="hs_code_id" />
<field name="src_dest_country_id" />
<field name="src_dest_country_code" />
<field name="amount_company_currency" />
<field name="amount_accessory_cost_company_currency" />
<field name="transaction_id" />
@@ -322,6 +324,7 @@
/>
<field name="hs_code_id" />
<field name="src_dest_country_id" />
<field name="src_dest_country_code" />
<field
name="amount_company_currency"
widget="monetary"
@@ -367,7 +370,8 @@
invisible="not context.get('intrastat_product_declaration_line_main_view')"
/>
<field name="hs_code_id" />
<field name="src_dest_country_id" />
<field name="src_dest_country_id" invisible="1" />
<field name="src_dest_country_code" />
<field name="amount_company_currency" />
<field name="transaction_id" />
<field name="weight" />

View File

@@ -33,9 +33,9 @@ class ProductTemplate(models.Model):
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' "
"selecting one of the Northern Ireland counties will set the code 'XI' "
"for products from the United Kingdom whereas code 'XU' "
"will be used for the other UK states.",
"will be used for the other UK counties.",
)