Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[14.0]intrastat Brexit support #163

Merged
merged 10 commits into from
May 15, 2022
1 change: 1 addition & 0 deletions intrastat_base/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
from . import account_fiscal_position
from . import account_fiscal_position_template
from . import account_move
from . import res_partner
67 changes: 67 additions & 0 deletions intrastat_base/models/res_partner.py
Original file line number Diff line number Diff line change
@@ -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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is really strange with it's "if self / else" ! When self has a value, then the arguments country and state are ignored, which is surprising. For me, instead of this wired code, we should have 2 separate methods, the first one calling the second one.

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
47 changes: 45 additions & 2 deletions intrastat_product/models/account_move.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Copyright 2011-2020 Akretion France (http://www.akretion.com)
# Copyright 2009-2020 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>

from odoo import api, fields, models
from odoo import _, api, fields, models
from odoo.exceptions import UserError


class AccountMove(models.Model):
Expand Down Expand Up @@ -93,13 +94,22 @@ def _get_intrastat_line_vals(self, line):
if not hs_code:
return vals
weight, qty = decl_model._get_weight_and_supplunits(line, hs_code, notedict)
product_country = line.product_id.origin_country_id
product_state = line.product_id.origin_state_id
country = product_country or product_state.country_id
product_origin_country_code = "QU"
if country:
product_origin_country_code = self.env[
"res.partner"
]._get_intrastat_country_code(product_country, product_state)
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
Expand Down Expand Up @@ -160,11 +170,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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it a bit "too easy" to use QU by default. I think we should display a warning in the warning dialog box about the fact that the country of origin is missing on product X and say that we've set the country code to QU as a temporary solution.

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):
Expand All @@ -176,3 +197,25 @@ def _onchange_move_id(self):
("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:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having a check here on the length, I think it would be more easier and user-friendly to have a max-length directly on the field to be warned beforehand.

raise UserError(
_(
"Intrastat transaction details error:\n"
"Product Origin Country Code must be 2 characters."
)
)
112 changes: 100 additions & 12 deletions intrastat_product/models/intrastat_product_declaration.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Copyright 2011-2020 Akretion France (http://www.akretion.com)
# Copyright 2009-2020 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>

import logging
import warnings
from datetime import date

from dateutil.relativedelta import relativedelta
Expand Down Expand Up @@ -490,8 +491,31 @@ def _get_incoterm(self, inv_line, notedict):
return incoterm

def _get_product_origin_country(self, inv_line, notedict):
warnings.warn(
"Method '_get_product_origin_country' is deprecated, "
"please use '_get_product_origin_country_code'.",
DeprecationWarning,
)
return inv_line.product_id.origin_country_id

def _get_product_origin_country_code(
self, inv_line, product_origin_country, notedict
):
cc = "QU"
if product_origin_country.code:
cc = product_origin_country.code
year = self.year or str(inv_line.move_id.date.year)
if year >= "2021":
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
inv = inv_line.move_id
Expand Down Expand Up @@ -667,6 +691,9 @@ def _gather_invoices(self, notedict):
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
Expand Down Expand Up @@ -708,9 +735,13 @@ def _gather_invoices(self, notedict):
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)
Expand All @@ -721,6 +752,7 @@ def _gather_invoices(self, notedict):
"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,
Expand All @@ -729,6 +761,7 @@ def _gather_invoices(self, notedict):
"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,
}
Expand Down Expand Up @@ -826,14 +859,13 @@ def action_gather(self):
@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,
"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,
"product_origin_country": computation_line.product_origin_country_code,
"vat": computation_line.vat or False,
}

Expand All @@ -846,13 +878,15 @@ def group_line_hashcode(self, computation_line):
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,
"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,
}
Expand Down Expand Up @@ -1049,6 +1083,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"
)
Expand Down Expand Up @@ -1086,16 +1129,36 @@ 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.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"""
Expand Down Expand Up @@ -1151,6 +1214,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",
Expand All @@ -1172,15 +1241,34 @@ 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.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):
Expand Down
4 changes: 2 additions & 2 deletions intrastat_product/report/intrastat_product_report_xls.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -60,7 +60,7 @@ def _get_template(self, declaration):
},
"line": {
"type": "string",
"value": self._render("line.src_dest_country_id.name"),
"value": self._render("line.src_dest_country_code"),
},
"width": 28,
},
Expand Down
Loading