mirror of
https://github.com/OCA/intrastat-extrastat.git
synced 2025-02-16 17:13:41 +02:00
@@ -9,12 +9,11 @@
|
|||||||
"category": "Intrastat",
|
"category": "Intrastat",
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
"summary": "Base module for Intrastat reporting",
|
"summary": "Base module for Intrastat reporting",
|
||||||
"author": "Akretion,Noviat,Odoo Community Association (OCA)",
|
"author": "ACSONE SA/NV, Akretion,Noviat,Odoo Community Association (OCA)",
|
||||||
"website": "https://github.com/OCA/intrastat-extrastat",
|
"website": "https://github.com/OCA/intrastat-extrastat",
|
||||||
"depends": ["base_vat", "account"],
|
"depends": ["base_vat", "account"],
|
||||||
"excludes": ["account_intrastat"],
|
"excludes": ["account_intrastat"],
|
||||||
"data": [
|
"data": [
|
||||||
"security/ir.model.access.csv",
|
|
||||||
"views/product_template.xml",
|
"views/product_template.xml",
|
||||||
"views/res_partner.xml",
|
"views/res_partner.xml",
|
||||||
"views/res_config_settings.xml",
|
"views/res_config_settings.xml",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from . import product_template
|
from . import product_template
|
||||||
from . import res_company
|
from . import res_company
|
||||||
from . import intrastat_common
|
|
||||||
from . import account_fiscal_position
|
from . import account_fiscal_position
|
||||||
|
from . import account_fiscal_position_template
|
||||||
from . import account_move
|
from . import account_move
|
||||||
|
|||||||
17
intrastat_base/models/account_chart_template.py
Normal file
17
intrastat_base/models/account_chart_template.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Copyright 2020 Akretion France (http://www.akretion.com/)
|
||||||
|
# @author: <alexis.delattre@akretion.com>
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
|
class AccountChartTemplate(models.Model):
|
||||||
|
_inherit = "account.chart.template"
|
||||||
|
|
||||||
|
def _get_fp_vals(self, company, position):
|
||||||
|
"""
|
||||||
|
Get fiscal position chart template instrastat value
|
||||||
|
to create fiscal position
|
||||||
|
"""
|
||||||
|
vals = super()._get_fp_vals(company, position)
|
||||||
|
vals["intrastat"] = position.intrastat
|
||||||
|
return vals
|
||||||
@@ -13,18 +13,3 @@ class AccountFiscalPosition(models.Model):
|
|||||||
help="Set to True if the invoices with this fiscal position should "
|
help="Set to True if the invoices with this fiscal position should "
|
||||||
"be taken into account for the generation of the intrastat reports.",
|
"be taken into account for the generation of the intrastat reports.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AccountFiscalPositionTemplate(models.Model):
|
|
||||||
_inherit = "account.fiscal.position.template"
|
|
||||||
|
|
||||||
intrastat = fields.Boolean(string="Intrastat")
|
|
||||||
|
|
||||||
|
|
||||||
class AccountChartTemplate(models.Model):
|
|
||||||
_inherit = "account.chart.template"
|
|
||||||
|
|
||||||
def _get_fp_vals(self, company, position):
|
|
||||||
vals = super()._get_fp_vals(company, position)
|
|
||||||
vals["intrastat"] = position.intrastat
|
|
||||||
return vals
|
|
||||||
|
|||||||
14
intrastat_base/models/account_fiscal_position_template.py
Normal file
14
intrastat_base/models/account_fiscal_position_template.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Copyright 2020 Akretion France (http://www.akretion.com/)
|
||||||
|
# @author: <alexis.delattre@akretion.com>
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class AccountFiscalPositionTemplate(models.Model):
|
||||||
|
_inherit = "account.fiscal.position.template"
|
||||||
|
|
||||||
|
intrastat = fields.Boolean(
|
||||||
|
string="Intrastat",
|
||||||
|
help="Check this if you want to generate intrastat declarations with"
|
||||||
|
"the created fiscal position",
|
||||||
|
)
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
# Copyright 2010-2020 Akretion (<alexis.delattre@akretion.com>)
|
|
||||||
# Copyright 2009-2020 Noviat (http://www.noviat.com)
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from io import BytesIO
|
|
||||||
from sys import exc_info
|
|
||||||
from traceback import format_exception
|
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from odoo import _, api, fields, models, tools
|
|
||||||
from odoo.exceptions import UserError
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class IntrastatCommon(models.AbstractModel):
|
|
||||||
_name = "intrastat.common"
|
|
||||||
_description = "Common functions for intrastat reports for products "
|
|
||||||
"and services"
|
|
||||||
|
|
||||||
# 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
|
|
||||||
# the line object, so it can't be factorized here
|
|
||||||
|
|
||||||
def _check_generate_xml(self):
|
|
||||||
for this in self:
|
|
||||||
if not this.company_id.partner_id.vat:
|
|
||||||
raise UserError(
|
|
||||||
_("The VAT number is not set for the partner '%s'.")
|
|
||||||
% this.company_id.partner_id.name
|
|
||||||
)
|
|
||||||
|
|
||||||
@api.model
|
|
||||||
def _check_xml_schema(self, xml_bytes, xsd_file):
|
|
||||||
"""Validate the XML file against the XSD"""
|
|
||||||
xsd_etree_obj = etree.parse(tools.file_open(xsd_file, mode="rb"))
|
|
||||||
official_schema = etree.XMLSchema(xsd_etree_obj)
|
|
||||||
try:
|
|
||||||
t = etree.parse(BytesIO(xml_bytes))
|
|
||||||
official_schema.assertValid(t)
|
|
||||||
except (etree.XMLSchemaParseError, etree.DocumentInvalid) as e:
|
|
||||||
logger.warning("The XML file is invalid against the XML Schema Definition")
|
|
||||||
logger.warning(xml_bytes)
|
|
||||||
logger.warning(e)
|
|
||||||
usererror = "{}\n\n{}".format(e.__class__.__name__, str(e))
|
|
||||||
raise UserError(usererror)
|
|
||||||
except Exception:
|
|
||||||
error = _("Unknown Error")
|
|
||||||
tb = "".join(format_exception(*exc_info()))
|
|
||||||
error += "\n%s" % tb
|
|
||||||
logger.warning(error)
|
|
||||||
raise UserError(error)
|
|
||||||
|
|
||||||
def _attach_xml_file(self, xml_bytes, declaration_name):
|
|
||||||
"""Attach the XML file to the report_intrastat_product/service
|
|
||||||
object"""
|
|
||||||
self.ensure_one()
|
|
||||||
filename = "{}_{}.xml".format(self.year_month, declaration_name)
|
|
||||||
attach = self.env["ir.attachment"].create(
|
|
||||||
{
|
|
||||||
"name": filename,
|
|
||||||
"res_id": self.id,
|
|
||||||
"res_model": self._name,
|
|
||||||
"raw": xml_bytes,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return attach.id
|
|
||||||
|
|
||||||
def _unlink_attachments(self):
|
|
||||||
atts = self.env["ir.attachment"].search(
|
|
||||||
[("res_model", "=", self._name), ("res_id", "=", self.id)]
|
|
||||||
)
|
|
||||||
atts.unlink()
|
|
||||||
|
|
||||||
# Method _open_attach_view() removed
|
|
||||||
# Let's handle attachments like in l10n_fr_intrastat_service v14
|
|
||||||
# with the field attachment_id on the declaration and the download
|
|
||||||
# link directly on the form view of the declaration.
|
|
||||||
|
|
||||||
def _generate_xml(self):
|
|
||||||
"""
|
|
||||||
Inherit this method in the localization module
|
|
||||||
to generate the INTRASTAT Declaration XML file
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
string with XML data
|
|
||||||
|
|
||||||
Call the _check_xml_schema() method
|
|
||||||
before returning the XML string.
|
|
||||||
"""
|
|
||||||
return False
|
|
||||||
|
|
||||||
def send_reminder_email(self, mail_template_xmlid):
|
|
||||||
mail_template = self.env.ref(mail_template_xmlid)
|
|
||||||
for this in self:
|
|
||||||
if this.company_id.intrastat_remind_user_ids:
|
|
||||||
mail_template.send_mail(this.id)
|
|
||||||
logger.info(
|
|
||||||
"Intrastat Reminder email has been sent (XMLID: %s)."
|
|
||||||
% mail_template_xmlid
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
"The list of users receiving the Intrastat Reminder is "
|
|
||||||
"empty on company %s" % this.company_id.name
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def unlink(self):
|
|
||||||
for intrastat in self:
|
|
||||||
if intrastat.state == "done":
|
|
||||||
raise UserError(
|
|
||||||
_("Cannot delete the declaration %s " "because it is in Done state")
|
|
||||||
% self.year_month
|
|
||||||
)
|
|
||||||
return super().unlink()
|
|
||||||
|
|
||||||
|
|
||||||
class IntrastatResultView(models.TransientModel):
|
|
||||||
"""
|
|
||||||
Transient Model to display Intrastat Report results
|
|
||||||
"""
|
|
||||||
|
|
||||||
_name = "intrastat.result.view"
|
|
||||||
_description = "Pop-up to show errors on intrastat report generation"
|
|
||||||
|
|
||||||
note = fields.Text(
|
|
||||||
string="Notes", readonly=True, default=lambda self: self._context.get("note")
|
|
||||||
)
|
|
||||||
@@ -2,8 +2,17 @@
|
|||||||
# @author: <alexis.delattre@akretion.com>
|
# @author: <alexis.delattre@akretion.com>
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from odoo import _, api, fields, models
|
import logging
|
||||||
from odoo.exceptions import ValidationError
|
from io import BytesIO
|
||||||
|
from sys import exc_info
|
||||||
|
from traceback import format_exception
|
||||||
|
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
from odoo import _, api, fields, models, tools
|
||||||
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ResCompany(models.Model):
|
class ResCompany(models.Model):
|
||||||
@@ -40,3 +49,24 @@ class ResCompany(models.Model):
|
|||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("Missing e-mail address on user '%s'.") % (user.name)
|
_("Missing e-mail address on user '%s'.") % (user.name)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _intrastat_check_xml_schema(self, xml_bytes, xsd_file):
|
||||||
|
"""Validate the XML file against the XSD"""
|
||||||
|
xsd_etree_obj = etree.parse(tools.file_open(xsd_file, mode="rb"))
|
||||||
|
official_schema = etree.XMLSchema(xsd_etree_obj)
|
||||||
|
try:
|
||||||
|
t = etree.parse(BytesIO(xml_bytes))
|
||||||
|
official_schema.assertValid(t)
|
||||||
|
except (etree.XMLSchemaParseError, etree.DocumentInvalid) as e:
|
||||||
|
logger.warning("The XML file is invalid against the XML Schema Definition")
|
||||||
|
logger.warning(xml_bytes)
|
||||||
|
logger.warning(e)
|
||||||
|
usererror = "{}\n\n{}".format(e.__class__.__name__, str(e))
|
||||||
|
raise UserError(usererror)
|
||||||
|
except Exception:
|
||||||
|
error = _("Unknown Error")
|
||||||
|
tb = "".join(format_exception(*exc_info()))
|
||||||
|
error += "\n%s" % tb
|
||||||
|
logger.warning(error)
|
||||||
|
raise UserError(error)
|
||||||
|
|||||||
@@ -2,3 +2,4 @@
|
|||||||
* Luc De Meyer, Noviat <info@noviat.com>
|
* Luc De Meyer, Noviat <info@noviat.com>
|
||||||
* Kumar Aberer, brain-tec AG <kumar.aberer@braintec-group.com>
|
* Kumar Aberer, brain-tec AG <kumar.aberer@braintec-group.com>
|
||||||
* Andrea Stirpe <a.stirpe@onestein.nl>
|
* Andrea Stirpe <a.stirpe@onestein.nl>
|
||||||
|
* Denis Roussel <denis.roussel@acsone.eu>
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
|
||||||
access_intrastat_result_view,Access on intrastat.result.view,model_intrastat_result_view,account.group_account_user,1,1,1,0
|
|
||||||
|
@@ -1 +1,2 @@
|
|||||||
|
from . import common
|
||||||
from . import test_all
|
from . import test_all
|
||||||
|
|||||||
16
intrastat_base/tests/common.py
Normal file
16
intrastat_base/tests/common.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Copyright 2021 ACSONE SA/NV
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
|
||||||
|
class IntrastatCommon(object):
|
||||||
|
@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")
|
||||||
10
intrastat_base/tests/models.py
Normal file
10
intrastat_base/tests/models.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# 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"
|
||||||
|
_description = "Intrastat Declaration Test"
|
||||||
@@ -1,13 +1,30 @@
|
|||||||
from odoo.tests.common import TransactionCase
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
from .common import IntrastatCommon
|
||||||
|
|
||||||
|
|
||||||
class TestIntrastatBase(TransactionCase):
|
class TestIntrastatBase(IntrastatCommon):
|
||||||
"""Tests for this module"""
|
"""Tests for this module"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
|
||||||
def test_company(self):
|
def test_company(self):
|
||||||
# add 'Demo user' to intrastat_remind_user_ids
|
# add 'Demo user' to intrastat_remind_user_ids
|
||||||
demo_user = self.env.ref("base.user_demo")
|
self.demo_company.write(
|
||||||
demo_company = self.env.ref("base.main_company")
|
{"intrastat_remind_user_ids": [(6, False, [self.demo_user.id])]}
|
||||||
demo_company.write({"intrastat_remind_user_ids": [(6, False, [demo_user.id])]})
|
)
|
||||||
# then check if intrastat_email_list contains the email of the user
|
# 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"
|
||||||
|
|||||||
@@ -18,18 +18,4 @@
|
|||||||
parent="account.menu_finance_configuration"
|
parent="account.menu_finance_configuration"
|
||||||
sequence="50"
|
sequence="50"
|
||||||
/>
|
/>
|
||||||
<record id="intrastat_result_view_form" model="ir.ui.view">
|
|
||||||
<field name="name">intrastat.result_view_form</field>
|
|
||||||
<field name="model">intrastat.result.view</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<form string="Intrastat Result View">
|
|
||||||
<group name="main">
|
|
||||||
<field name="note" nolabel="1" />
|
|
||||||
</group>
|
|
||||||
<footer>
|
|
||||||
<button string="Ok" class="btn-primary" special="cancel" />
|
|
||||||
</footer>
|
|
||||||
</form>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
from . import models
|
from . import models
|
||||||
from . import report
|
from . import report
|
||||||
|
from . import wizards
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"category": "Intrastat",
|
"category": "Intrastat",
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
"summary": "Base module for Intrastat Product",
|
"summary": "Base module for Intrastat Product",
|
||||||
"author": "brain-tec AG, Akretion, Noviat, Odoo Community Association (OCA)",
|
"author": "ACSONE SA/NV, brain-tec AG, Akretion, Noviat, Odoo Community Association (OCA)",
|
||||||
"website": "https://github.com/OCA/intrastat-extrastat",
|
"website": "https://github.com/OCA/intrastat-extrastat",
|
||||||
"depends": [
|
"depends": [
|
||||||
"intrastat_base",
|
"intrastat_base",
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
"views/account_move.xml",
|
"views/account_move.xml",
|
||||||
"views/sale_order.xml",
|
"views/sale_order.xml",
|
||||||
"views/stock_warehouse.xml",
|
"views/stock_warehouse.xml",
|
||||||
|
"wizards/intrastat_result_view.xml",
|
||||||
"data/intrastat_transport_mode.xml",
|
"data/intrastat_transport_mode.xml",
|
||||||
"data/intrastat_unit.xml",
|
"data/intrastat_unit.xml",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class IntrastatProductDeclaration(models.Model):
|
|||||||
_name = "intrastat.product.declaration"
|
_name = "intrastat.product.declaration"
|
||||||
_description = "Intrastat Product Report Base Object"
|
_description = "Intrastat Product Report Base Object"
|
||||||
_rec_name = "year_month"
|
_rec_name = "year_month"
|
||||||
_inherit = ["mail.thread", "mail.activity.mixin", "intrastat.common"]
|
_inherit = ["mail.thread", "mail.activity.mixin"]
|
||||||
_order = "year_month desc, declaration_type, revision"
|
_order = "year_month desc, declaration_type, revision"
|
||||||
_sql_constraints = [
|
_sql_constraints = [
|
||||||
(
|
(
|
||||||
@@ -46,14 +46,26 @@ class IntrastatProductDeclaration(models.Model):
|
|||||||
string="Company",
|
string="Company",
|
||||||
required=True,
|
required=True,
|
||||||
states={"done": [("readonly", True)]},
|
states={"done": [("readonly", True)]},
|
||||||
default=lambda self: self._default_company_id(),
|
default=lambda self: self.env.company,
|
||||||
)
|
)
|
||||||
company_country_code = fields.Char(
|
company_country_code = fields.Char(
|
||||||
compute="_compute_company_country_code",
|
compute="_compute_company_country_code",
|
||||||
string="Company Country Code",
|
string="Company Country Code",
|
||||||
readonly=True,
|
readonly=True,
|
||||||
store=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(
|
year = fields.Char(
|
||||||
string="Year", required=True, states={"done": [("readonly", True)]}
|
string="Year", required=True, states={"done": [("readonly", True)]}
|
||||||
@@ -133,19 +145,6 @@ class IntrastatProductDeclaration(models.Model):
|
|||||||
currency_id = fields.Many2one(
|
currency_id = fields.Many2one(
|
||||||
"res.currency", related="company_id.currency_id", string="Currency"
|
"res.currency", related="company_id.currency_id", string="Currency"
|
||||||
)
|
)
|
||||||
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."
|
|
||||||
)
|
|
||||||
reporting_level = fields.Selection(
|
reporting_level = fields.Selection(
|
||||||
selection="_get_reporting_level",
|
selection="_get_reporting_level",
|
||||||
string="Reporting Level",
|
string="Reporting Level",
|
||||||
@@ -160,10 +159,6 @@ class IntrastatProductDeclaration(models.Model):
|
|||||||
related="xml_attachment_id.name", string="XML Filename"
|
related="xml_attachment_id.name", string="XML Filename"
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.model
|
|
||||||
def _default_company_id(self):
|
|
||||||
return self.env.company
|
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _get_declaration_type(self):
|
def _get_declaration_type(self):
|
||||||
res = []
|
res = []
|
||||||
@@ -208,12 +203,11 @@ class IntrastatProductDeclaration(models.Model):
|
|||||||
for this in self:
|
for this in self:
|
||||||
this.valid = True
|
this.valid = True
|
||||||
|
|
||||||
@api.model
|
|
||||||
@api.constrains("year")
|
@api.constrains("year")
|
||||||
def _check_year(self):
|
def _check_year(self):
|
||||||
for this in self:
|
for this in self:
|
||||||
if len(this.year) != 4 or this.year[0] != "2":
|
if len(this.year) != 4 or this.year[0] != "2":
|
||||||
raise ValidationError(_("Invalid Year !"))
|
raise ValidationError(_("Invalid Year!"))
|
||||||
|
|
||||||
@api.onchange("declaration_type")
|
@api.onchange("declaration_type")
|
||||||
def _onchange_declaration_type(self):
|
def _onchange_declaration_type(self):
|
||||||
@@ -242,6 +236,36 @@ class IntrastatProductDeclaration(models.Model):
|
|||||||
msg, action.id, _("Go to Accounting Configuration Settings screen")
|
msg, action.id, _("Go to Accounting Configuration Settings screen")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _attach_xml_file(self, xml_bytes, declaration_name):
|
||||||
|
"""Attach the XML file to the report_intrastat_product/service
|
||||||
|
object"""
|
||||||
|
self.ensure_one()
|
||||||
|
filename = "{}_{}.xml".format(self.year_month, declaration_name)
|
||||||
|
attach = self.env["ir.attachment"].create(
|
||||||
|
{
|
||||||
|
"name": filename,
|
||||||
|
"res_id": self.id,
|
||||||
|
"res_model": self._name,
|
||||||
|
"raw": xml_bytes,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return attach.id
|
||||||
|
|
||||||
|
def _unlink_attachments(self):
|
||||||
|
atts = self.env["ir.attachment"].search(
|
||||||
|
[("res_model", "=", self._name), ("res_id", "=", self.id)]
|
||||||
|
)
|
||||||
|
atts.unlink()
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
for this in self:
|
||||||
|
if this.state == "done":
|
||||||
|
raise UserError(
|
||||||
|
_("Cannot delete the declaration %s because it is in Done state.")
|
||||||
|
% this.display_name
|
||||||
|
)
|
||||||
|
return super().unlink()
|
||||||
|
|
||||||
def _get_partner_country(self, inv_line, notedict, eu_countries):
|
def _get_partner_country(self, inv_line, notedict, eu_countries):
|
||||||
inv = inv_line.move_id
|
inv = inv_line.move_id
|
||||||
country = inv.src_dest_country_id or inv.partner_id.country_id
|
country = inv.src_dest_country_id or inv.partner_id.country_id
|
||||||
@@ -794,7 +818,7 @@ class IntrastatProductDeclaration(models.Model):
|
|||||||
|
|
||||||
self.write(vals)
|
self.write(vals)
|
||||||
if vals["note"]:
|
if vals["note"]:
|
||||||
result_view = self.env.ref("intrastat_base.intrastat_result_view_form")
|
result_view = self.env.ref("intrastat_product.intrastat_result_view_form")
|
||||||
return {
|
return {
|
||||||
"name": _("Generate lines from invoices: results"),
|
"name": _("Generate lines from invoices: results"),
|
||||||
"view_type": "form",
|
"view_type": "form",
|
||||||
@@ -891,6 +915,14 @@ class IntrastatProductDeclaration(models.Model):
|
|||||||
cl.write({"declaration_line_id": declaration_line.id})
|
cl.write({"declaration_line_id": declaration_line.id})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _check_generate_xml(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if not self.company_id.partner_id.vat:
|
||||||
|
raise UserError(
|
||||||
|
_("The VAT number is not set for the partner '%s'.")
|
||||||
|
% self.company_id.partner_id.display_name
|
||||||
|
)
|
||||||
|
|
||||||
def generate_xml(self):
|
def generate_xml(self):
|
||||||
""" generate the INTRASTAT Declaration XML file """
|
""" generate the INTRASTAT Declaration XML file """
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
* Alexis de Lattre, Akretion <alexis.delattre@akretion.com>
|
* Alexis de Lattre, Akretion <alexis.delattre@akretion.com>
|
||||||
* Luc De Meyer, Noviat <info@noviat.com>
|
* Luc De Meyer, Noviat <info@noviat.com>
|
||||||
|
* Denis Roussel <denis.roussel@acsone.eu>
|
||||||
* Tecnativa <www.tecnativa.com>:
|
* Tecnativa <www.tecnativa.com>:
|
||||||
|
|
||||||
* João Marques
|
* João Marques
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ access_account_move_intrastat_line,Full access on Invoice Intrastat Lines,model_
|
|||||||
access_intrastat_product_declaration,Full access on Intrastat Product Declarations to Accountant,model_intrastat_product_declaration,account.group_account_user,1,1,1,1
|
access_intrastat_product_declaration,Full access on Intrastat Product Declarations to Accountant,model_intrastat_product_declaration,account.group_account_user,1,1,1,1
|
||||||
access_intrastat_product_computation_line,Full access on Intrastat Product Computation Lines to Accountant,model_intrastat_product_computation_line,account.group_account_user,1,1,1,1
|
access_intrastat_product_computation_line,Full access on Intrastat Product Computation Lines to Accountant,model_intrastat_product_computation_line,account.group_account_user,1,1,1,1
|
||||||
access_intrastat_product_declaration_line,Full access on Intrastat Product Declaration Lines to Accountant,model_intrastat_product_declaration_line,account.group_account_user,1,1,1,1
|
access_intrastat_product_declaration_line,Full access on Intrastat Product Declaration Lines to Accountant,model_intrastat_product_declaration_line,account.group_account_user,1,1,1,1
|
||||||
|
access_intrastat_result_view,Access on intrastat.result.view,model_intrastat_result_view,account.group_account_user,1,1,1,0
|
||||||
|
|||||||
|
7
intrastat_product/tests/__init__.py
Normal file
7
intrastat_product/tests/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from . import common
|
||||||
|
from . import common_purchase
|
||||||
|
from . import common_sale
|
||||||
|
from . import test_intrastat_product
|
||||||
|
from . import test_company
|
||||||
|
from . import test_purchase_order
|
||||||
|
from . import test_sale_order
|
||||||
114
intrastat_product/tests/common.py
Normal file
114
intrastat_product/tests/common.py
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# Copyright 2021 ACSONE SA/NV
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo.addons.intrastat_base.tests.common import IntrastatCommon
|
||||||
|
|
||||||
|
|
||||||
|
class IntrastatProductCommon(IntrastatCommon):
|
||||||
|
@classmethod
|
||||||
|
def _init_products(cls):
|
||||||
|
# Create category - don't init intrastat values, do it in tests
|
||||||
|
vals = {
|
||||||
|
"name": "Robots",
|
||||||
|
"parent_id": cls.category_saleable.id,
|
||||||
|
}
|
||||||
|
cls.categ_robots = cls.category_obj.create(vals)
|
||||||
|
|
||||||
|
vals = {
|
||||||
|
"name": "C3PO",
|
||||||
|
"categ_id": cls.categ_robots.id,
|
||||||
|
"origin_country_id": cls.env.ref("base.us").id,
|
||||||
|
"weight": 300,
|
||||||
|
# Computer - C3PO is one of them
|
||||||
|
"hs_code_id": cls.hs_code_computer.id,
|
||||||
|
}
|
||||||
|
cls.product_c3po = cls.product_template_obj.create(vals)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _init_company(cls):
|
||||||
|
# Default transport for company is Road
|
||||||
|
cls.demo_company.intrastat_transport_id = cls.transport_road
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _init_fiscal_position(cls):
|
||||||
|
vals = {
|
||||||
|
"name": "Intrastat Fiscal Position",
|
||||||
|
"intrastat": True,
|
||||||
|
}
|
||||||
|
cls.position = cls.position_obj.create(vals)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _init_regions(cls):
|
||||||
|
# Create Belgium Region
|
||||||
|
cls._create_region()
|
||||||
|
|
||||||
|
vals = {
|
||||||
|
"code": "DE",
|
||||||
|
"name": "Germany",
|
||||||
|
"country_id": cls.env.ref("base.de").id,
|
||||||
|
"description": "Germany",
|
||||||
|
}
|
||||||
|
cls._create_region(vals)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
cls.region_obj = cls.env["intrastat.region"]
|
||||||
|
cls.transaction_obj = cls.env["intrastat.transaction"]
|
||||||
|
cls.transport_mode_obj = cls.env["intrastat.transport_mode"]
|
||||||
|
cls.partner_obj = cls.env["res.partner"]
|
||||||
|
cls.category_saleable = cls.env.ref("product.product_category_1")
|
||||||
|
cls.category_obj = cls.env["product.category"]
|
||||||
|
cls.product_template_obj = cls.env["product.template"]
|
||||||
|
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.transport_rail = cls.env.ref("intrastat_product.intrastat_transport_2")
|
||||||
|
cls.transport_road = cls.env.ref("intrastat_product.intrastat_transport_3")
|
||||||
|
|
||||||
|
cls._init_regions()
|
||||||
|
cls._init_company()
|
||||||
|
cls._init_fiscal_position()
|
||||||
|
cls._init_products()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create_region(cls, vals=None):
|
||||||
|
values = {
|
||||||
|
"code": "BE_w",
|
||||||
|
"country_id": cls.env.ref("base.be").id,
|
||||||
|
"company_id": cls.env.company.id,
|
||||||
|
"description": "Belgium Walloon Region",
|
||||||
|
"name": "Walloon Region",
|
||||||
|
}
|
||||||
|
if vals is not None:
|
||||||
|
values.update(vals)
|
||||||
|
cls.region = cls.region_obj.create(values)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create_transaction(cls, vals=None):
|
||||||
|
values = {
|
||||||
|
"code": "11",
|
||||||
|
"company_id": cls.env.company.id,
|
||||||
|
"description": "Sale / Purchase",
|
||||||
|
}
|
||||||
|
if vals is not None:
|
||||||
|
values.update(vals)
|
||||||
|
cls.transaction = cls.transaction_obj.create(values)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create_transport_mode(cls, vals=None):
|
||||||
|
values = {}
|
||||||
|
if vals is not None:
|
||||||
|
values.update(vals)
|
||||||
|
cls.transport_mode = cls.transport_mode_obj.create(values)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create_declaration(cls, vals=None):
|
||||||
|
values = {
|
||||||
|
"company_id": cls.env.company.id,
|
||||||
|
"declaration_type": "dispatches",
|
||||||
|
}
|
||||||
|
if vals is not None:
|
||||||
|
values.update(vals)
|
||||||
|
cls.declaration = cls.declaration_obj.create(values)
|
||||||
81
intrastat_product/tests/common_purchase.py
Normal file
81
intrastat_product/tests/common_purchase.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# Copyright 2021 ACSONE SA/NV
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
from odoo.tests import Form
|
||||||
|
|
||||||
|
from .common import IntrastatProductCommon
|
||||||
|
|
||||||
|
|
||||||
|
class IntrastatPurchaseCommon(IntrastatProductCommon):
|
||||||
|
"""
|
||||||
|
We define common flow:
|
||||||
|
- Supplier in Germany
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _get_expected_vals(self, line):
|
||||||
|
return {
|
||||||
|
"declaration_type": "arrivals",
|
||||||
|
"suppl_unit_qty": line.qty_received,
|
||||||
|
"hs_code_id": line.product_id.hs_code_id,
|
||||||
|
"product_origin_country_id": line.product_id.origin_country_id,
|
||||||
|
"amount_company_currency": line.price_subtotal,
|
||||||
|
"src_dest_country_id": line.partner_id.country_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _check_line_values(self, final=False, declaration=None, purchase=None):
|
||||||
|
"""
|
||||||
|
This method allows to test computation lines and declaration
|
||||||
|
lines values from original sale order line
|
||||||
|
"""
|
||||||
|
if declaration is None:
|
||||||
|
declaration = self.declaration
|
||||||
|
if purchase is None:
|
||||||
|
purchase = self.purchase
|
||||||
|
for line in purchase.order_line:
|
||||||
|
expected_vals = self._get_expected_vals(line)
|
||||||
|
comp_line = declaration.computation_line_ids.filtered(
|
||||||
|
lambda cline: cline.product_id == line.product_id
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
all(comp_line[key] == val for key, val in expected_vals.items())
|
||||||
|
)
|
||||||
|
if final:
|
||||||
|
decl_line = declaration.declaration_line_ids.filtered(
|
||||||
|
lambda dline: comp_line in dline.computation_line_ids
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
all(decl_line[key] == val for key, val in expected_vals.items())
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _init_supplier(cls, vals=None):
|
||||||
|
values = {
|
||||||
|
"name": "DE Supplier",
|
||||||
|
"country_id": cls.env.ref("base.de").id,
|
||||||
|
"property_account_position_id": cls.position.id,
|
||||||
|
}
|
||||||
|
if vals is not None:
|
||||||
|
values.update(vals)
|
||||||
|
cls.supplier = cls.partner_obj.create(values)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
cls.purchase_obj = cls.env["purchase.order"]
|
||||||
|
cls.move_obj = cls.env["account.move"]
|
||||||
|
cls._init_supplier()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create_purchase_order(cls, vals=None):
|
||||||
|
vals = {
|
||||||
|
"partner_id": cls.supplier.id,
|
||||||
|
}
|
||||||
|
purchase_new = cls.purchase_obj.new(vals)
|
||||||
|
purchase_new.onchange_partner_id()
|
||||||
|
purchase_vals = purchase_new._convert_to_write(purchase_new._cache)
|
||||||
|
cls.purchase = cls.purchase_obj.create(purchase_vals)
|
||||||
|
with Form(cls.purchase) as purchase_form:
|
||||||
|
with purchase_form.order_line.new() as line:
|
||||||
|
line.product_id = cls.product_c3po.product_variant_ids[0]
|
||||||
|
line.product_qty = 3.0
|
||||||
|
# Price should not be void - if no purchase pricelist
|
||||||
|
line.price_unit = 150.0
|
||||||
76
intrastat_product/tests/common_sale.py
Normal file
76
intrastat_product/tests/common_sale.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Copyright 2021 ACSONE SA/NV
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
from odoo.tests import Form
|
||||||
|
|
||||||
|
from .common import IntrastatProductCommon
|
||||||
|
|
||||||
|
|
||||||
|
class IntrastatSaleCommon(IntrastatProductCommon):
|
||||||
|
"""
|
||||||
|
We define common flow:
|
||||||
|
- Customer in Netherlands
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _get_expected_vals(self, line):
|
||||||
|
return {
|
||||||
|
"declaration_type": "dispatches",
|
||||||
|
"suppl_unit_qty": line.qty_delivered,
|
||||||
|
"hs_code_id": line.product_id.hs_code_id,
|
||||||
|
"product_origin_country_id": line.product_id.origin_country_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _check_line_values(self, final=False, declaration=None, sale=None):
|
||||||
|
"""
|
||||||
|
This method allows to test computation lines and declaration
|
||||||
|
lines values from original sale order line
|
||||||
|
"""
|
||||||
|
if declaration is None:
|
||||||
|
declaration = self.declaration
|
||||||
|
if sale is None:
|
||||||
|
sale = self.sale
|
||||||
|
for line in sale.order_line:
|
||||||
|
expected_vals = self._get_expected_vals(line)
|
||||||
|
comp_line = declaration.computation_line_ids.filtered(
|
||||||
|
lambda cline: cline.product_id == line.product_id
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
all(comp_line[key] == val for key, val in expected_vals.items())
|
||||||
|
)
|
||||||
|
if final:
|
||||||
|
decl_line = declaration.declaration_line_ids.filtered(
|
||||||
|
lambda dline: comp_line in dline.computation_line_ids
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
all(decl_line[key] == val for key, val in expected_vals.items())
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _init_customer(cls, vals=None):
|
||||||
|
values = {
|
||||||
|
"name": "NL Customer",
|
||||||
|
"country_id": cls.env.ref("base.nl").id,
|
||||||
|
"property_account_position_id": cls.position.id,
|
||||||
|
}
|
||||||
|
if vals is not None:
|
||||||
|
values.update(vals)
|
||||||
|
cls.customer = cls.partner_obj.create(values)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
cls.sale_obj = cls.env["sale.order"]
|
||||||
|
cls._init_customer()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create_sale_order(cls, vals=None):
|
||||||
|
vals = {
|
||||||
|
"partner_id": cls.customer.id,
|
||||||
|
}
|
||||||
|
sale_new = cls.sale_obj.new(vals)
|
||||||
|
sale_new.onchange_partner_id()
|
||||||
|
sale_vals = sale_new._convert_to_write(sale_new._cache)
|
||||||
|
cls.sale = cls.sale_obj.create(sale_vals)
|
||||||
|
with Form(cls.sale) as sale_form:
|
||||||
|
with sale_form.order_line.new() as line:
|
||||||
|
line.product_id = cls.product_c3po.product_variant_ids[0]
|
||||||
|
line.product_uom_qty = 3.0
|
||||||
40
intrastat_product/tests/test_company.py
Normal file
40
intrastat_product/tests/test_company.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Copyright 2021 ACSONE SA/NV
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
from odoo.tests.common import SavepointCase
|
||||||
|
|
||||||
|
from .common import IntrastatProductCommon
|
||||||
|
|
||||||
|
|
||||||
|
class TestIntrastatCompany(IntrastatProductCommon):
|
||||||
|
"""Tests for this module"""
|
||||||
|
|
||||||
|
def test_company_values(self):
|
||||||
|
# Exempt for arrivals and dispatches => exempt
|
||||||
|
self.demo_company.update(
|
||||||
|
{
|
||||||
|
"intrastat_arrivals": "exempt",
|
||||||
|
"intrastat_dispatches": "exempt",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual("exempt", self.demo_company.intrastat)
|
||||||
|
|
||||||
|
# Extended for arrivals or dispatches => extended
|
||||||
|
self.demo_company.update(
|
||||||
|
{
|
||||||
|
"intrastat_arrivals": "extended",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual("extended", self.demo_company.intrastat)
|
||||||
|
|
||||||
|
# standard for arrivals or dispatches => standard
|
||||||
|
self.demo_company.update(
|
||||||
|
{
|
||||||
|
"intrastat_arrivals": "exempt",
|
||||||
|
"intrastat_dispatches": "standard",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual("standard", self.demo_company.intrastat)
|
||||||
|
|
||||||
|
|
||||||
|
class TestIntrastatProductCase(TestIntrastatCompany, SavepointCase):
|
||||||
|
""" Test Intrastat Product """
|
||||||
67
intrastat_product/tests/test_intrastat_product.py
Normal file
67
intrastat_product/tests/test_intrastat_product.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Copyright 2021 ACSONE SA/NV
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
from psycopg2 import IntegrityError
|
||||||
|
|
||||||
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
from odoo.tests.common import SavepointCase
|
||||||
|
from odoo.tools import mute_logger
|
||||||
|
|
||||||
|
from .common import IntrastatProductCommon
|
||||||
|
|
||||||
|
|
||||||
|
class TestIntrastatProduct(IntrastatProductCommon):
|
||||||
|
"""Tests for this module"""
|
||||||
|
|
||||||
|
# Test duplicates
|
||||||
|
@mute_logger("odoo.sql_db")
|
||||||
|
def test_region(self):
|
||||||
|
with self.assertRaises(IntegrityError):
|
||||||
|
self._create_region()
|
||||||
|
|
||||||
|
@mute_logger("odoo.sql_db")
|
||||||
|
def test_transaction(self):
|
||||||
|
self._create_transaction()
|
||||||
|
with self.assertRaises(IntegrityError):
|
||||||
|
self._create_transaction()
|
||||||
|
|
||||||
|
@mute_logger("odoo.sql_db")
|
||||||
|
def test_transport_mode(self):
|
||||||
|
vals = {"code": 1, "name": "Sea"}
|
||||||
|
with self.assertRaises(IntegrityError):
|
||||||
|
self._create_transport_mode(vals)
|
||||||
|
|
||||||
|
def test_copy(self):
|
||||||
|
"""
|
||||||
|
When copying declaration, the new one has an incremented revision
|
||||||
|
value.
|
||||||
|
"""
|
||||||
|
vals = {"declaration_type": "dispatches"}
|
||||||
|
self._create_declaration(vals)
|
||||||
|
decl_copy = self.declaration.copy()
|
||||||
|
self.assertEqual(self.declaration.revision + 1, decl_copy.revision)
|
||||||
|
|
||||||
|
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_state(self):
|
||||||
|
self._create_declaration()
|
||||||
|
self.declaration.unlink()
|
||||||
|
|
||||||
|
self._create_declaration()
|
||||||
|
self.declaration.state = "done"
|
||||||
|
with self.assertRaises(UserError):
|
||||||
|
self.declaration.unlink()
|
||||||
|
|
||||||
|
|
||||||
|
class TestIntrastatProductCase(TestIntrastatProduct, SavepointCase):
|
||||||
|
""" Test Intrastat Product """
|
||||||
53
intrastat_product/tests/test_purchase_order.py
Normal file
53
intrastat_product/tests/test_purchase_order.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Copyright 2021 ACSONE SA/NV
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
from freezegun import freeze_time
|
||||||
|
|
||||||
|
from odoo import fields
|
||||||
|
from odoo.tests.common import SavepointCase
|
||||||
|
|
||||||
|
from .common_purchase import IntrastatPurchaseCommon
|
||||||
|
|
||||||
|
|
||||||
|
class TestIntrastatProductPurchase(IntrastatPurchaseCommon):
|
||||||
|
"""Tests for this module"""
|
||||||
|
|
||||||
|
def test_purchase_to_invoice_default(self):
|
||||||
|
date_order = "2021-09-01"
|
||||||
|
declaration_date = "2021-10-01"
|
||||||
|
with freeze_time(date_order):
|
||||||
|
self._create_purchase_order()
|
||||||
|
self.purchase.button_confirm()
|
||||||
|
self.purchase.picking_ids.action_assign()
|
||||||
|
for line in self.purchase.picking_ids.move_line_ids:
|
||||||
|
line.qty_done = line.product_uom_qty
|
||||||
|
self.purchase.picking_ids._action_done()
|
||||||
|
self.assertEqual("done", self.purchase.picking_ids.state)
|
||||||
|
|
||||||
|
with freeze_time(date_order):
|
||||||
|
action = self.purchase.action_create_invoice()
|
||||||
|
invoice_id = action["res_id"]
|
||||||
|
invoice = self.move_obj.browse(invoice_id)
|
||||||
|
|
||||||
|
invoice.invoice_date = fields.Date.from_string(date_order)
|
||||||
|
invoice.action_post()
|
||||||
|
|
||||||
|
# Check if transport mode has been transmitted to invoice
|
||||||
|
# It should be None as not defined on sale order
|
||||||
|
self.assertFalse(
|
||||||
|
invoice.intrastat_transport_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
vals = {
|
||||||
|
"declaration_type": "arrivals",
|
||||||
|
}
|
||||||
|
with freeze_time(declaration_date):
|
||||||
|
self._create_declaration(vals)
|
||||||
|
self.declaration.action_gather()
|
||||||
|
|
||||||
|
self._check_line_values()
|
||||||
|
self.declaration.generate_declaration()
|
||||||
|
self._check_line_values(final=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TestIntrastatProductPurchaseCase(TestIntrastatProductPurchase, SavepointCase):
|
||||||
|
""" Test Intrastat Purchase """
|
||||||
88
intrastat_product/tests/test_sale_order.py
Normal file
88
intrastat_product/tests/test_sale_order.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Copyright 2021 ACSONE SA/NV
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
from freezegun import freeze_time
|
||||||
|
|
||||||
|
from odoo.tests.common import SavepointCase
|
||||||
|
|
||||||
|
from .common_sale import IntrastatSaleCommon
|
||||||
|
|
||||||
|
|
||||||
|
class TestIntrastatProductSale(IntrastatSaleCommon):
|
||||||
|
"""Tests for this module"""
|
||||||
|
|
||||||
|
def test_sale_to_invoice_default(self):
|
||||||
|
self._create_sale_order()
|
||||||
|
self.sale.action_confirm()
|
||||||
|
self.sale.picking_ids.action_assign()
|
||||||
|
for line in self.sale.picking_ids.move_line_ids:
|
||||||
|
line.qty_done = line.product_uom_qty
|
||||||
|
self.sale.picking_ids._action_done()
|
||||||
|
self.assertEqual("done", self.sale.picking_ids.state)
|
||||||
|
|
||||||
|
invoice = self.sale._create_invoices()
|
||||||
|
invoice.action_post()
|
||||||
|
|
||||||
|
# Check if transport mode has been transmitted to invoice
|
||||||
|
# It should be None as not defined on sale order
|
||||||
|
self.assertFalse(
|
||||||
|
invoice.intrastat_transport_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test specific transport set on sale to invoice
|
||||||
|
def test_sale_to_invoice(self):
|
||||||
|
self._create_sale_order()
|
||||||
|
# Set intrastat transport mode to rail
|
||||||
|
self.sale.intrastat_transport_id = self.transport_rail
|
||||||
|
self.sale.action_confirm()
|
||||||
|
self.sale.picking_ids.action_assign()
|
||||||
|
for line in self.sale.picking_ids.move_line_ids:
|
||||||
|
line.qty_done = line.product_uom_qty
|
||||||
|
self.sale.picking_ids._action_done()
|
||||||
|
self.assertEqual("done", self.sale.picking_ids.state)
|
||||||
|
|
||||||
|
invoice = self.sale._create_invoices()
|
||||||
|
invoice.action_post()
|
||||||
|
|
||||||
|
# Check if transport mode has been transmitted to invoice
|
||||||
|
self.assertEqual(
|
||||||
|
self.transport_rail,
|
||||||
|
invoice.intrastat_transport_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sale_declaration(self):
|
||||||
|
date_order = "2021-09-01"
|
||||||
|
declaration_date = "2021-10-01"
|
||||||
|
with freeze_time(date_order):
|
||||||
|
self._create_sale_order()
|
||||||
|
# Set intrastat transport mode to rail
|
||||||
|
self.sale.intrastat_transport_id = self.transport_rail
|
||||||
|
self.sale.action_confirm()
|
||||||
|
self.sale.picking_ids.action_assign()
|
||||||
|
for line in self.sale.picking_ids.move_line_ids:
|
||||||
|
line.qty_done = line.product_uom_qty
|
||||||
|
self.sale.picking_ids._action_done()
|
||||||
|
self.assertEqual("done", self.sale.picking_ids.state)
|
||||||
|
|
||||||
|
with freeze_time(date_order):
|
||||||
|
invoice = self.sale._create_invoices()
|
||||||
|
invoice.action_post()
|
||||||
|
|
||||||
|
# Check if transport mode has been transmitted to invoice
|
||||||
|
self.assertEqual(
|
||||||
|
self.transport_rail,
|
||||||
|
invoice.intrastat_transport_id,
|
||||||
|
)
|
||||||
|
vals = {
|
||||||
|
"declaration_type": "dispatches",
|
||||||
|
}
|
||||||
|
with freeze_time(declaration_date):
|
||||||
|
self._create_declaration(vals)
|
||||||
|
self.declaration.action_gather()
|
||||||
|
|
||||||
|
self._check_line_values()
|
||||||
|
self.declaration.generate_declaration()
|
||||||
|
self._check_line_values(final=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TestIntrastatProductSaleCase(TestIntrastatProductSale, SavepointCase):
|
||||||
|
""" Test Intrastat Sale """
|
||||||
1
intrastat_product/wizards/__init__.py
Normal file
1
intrastat_product/wizards/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import intrastat_result_view
|
||||||
14
intrastat_product/wizards/intrastat_result_view.py
Normal file
14
intrastat_product/wizards/intrastat_result_view.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Copyright 2010-2021 Akretion (<alexis.delattre@akretion.com>)
|
||||||
|
# Copyright 2009-2021 Noviat (http://www.noviat.com)
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class IntrastatResultView(models.TransientModel):
|
||||||
|
_name = "intrastat.result.view"
|
||||||
|
_description = "Pop-up to show errors on intrastat report generation"
|
||||||
|
|
||||||
|
note = fields.Text(
|
||||||
|
string="Notes", readonly=True, default=lambda self: self._context.get("note")
|
||||||
|
)
|
||||||
22
intrastat_product/wizards/intrastat_result_view.xml
Normal file
22
intrastat_product/wizards/intrastat_result_view.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<!--
|
||||||
|
Copyright 2011-2021 Akretion France (http://www.akretion.com/)
|
||||||
|
Copyright 2015-2021 Noviat (http://www.noviat.com/)
|
||||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
-->
|
||||||
|
<odoo>
|
||||||
|
<record id="intrastat_result_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">intrastat.result_view_form</field>
|
||||||
|
<field name="model">intrastat.result.view</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Intrastat Result View">
|
||||||
|
<group name="main">
|
||||||
|
<field name="note" nolabel="1" />
|
||||||
|
</group>
|
||||||
|
<footer>
|
||||||
|
<button string="Ok" class="btn-primary" special="cancel" />
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
2
test-requirements.txt
Normal file
2
test-requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
odoo-test-helper
|
||||||
|
freezegun
|
||||||
Reference in New Issue
Block a user