mirror of
https://github.com/OCA/intrastat-extrastat.git
synced 2025-02-16 17:13:41 +02:00
intrastat_base: Remove intrastat.common class
This commit is contained in:
@@ -14,7 +14,6 @@
|
|||||||
"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
|
||||||
|
|||||||
@@ -1,216 +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, ValidationError
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class IntrastatCommon(models.AbstractModel):
|
|
||||||
_name = "intrastat.common"
|
|
||||||
_description = "Common functions for intrastat reports for products "
|
|
||||||
"and services"
|
|
||||||
|
|
||||||
company_id = fields.Many2one(
|
|
||||||
comodel_name="res.company",
|
|
||||||
string="Company",
|
|
||||||
required=True,
|
|
||||||
states={"done": [("readonly", True)]},
|
|
||||||
default=lambda self: self._default_company_id(),
|
|
||||||
)
|
|
||||||
company_country_code = fields.Char(
|
|
||||||
compute="_compute_company_country_code",
|
|
||||||
string="Company Country Code",
|
|
||||||
readonly=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(
|
|
||||||
string="Year", required=True, states={"done": [("readonly", True)]}
|
|
||||||
)
|
|
||||||
month = fields.Selection(
|
|
||||||
selection=[
|
|
||||||
("01", "01"),
|
|
||||||
("02", "02"),
|
|
||||||
("03", "03"),
|
|
||||||
("04", "04"),
|
|
||||||
("05", "05"),
|
|
||||||
("06", "06"),
|
|
||||||
("07", "07"),
|
|
||||||
("08", "08"),
|
|
||||||
("09", "09"),
|
|
||||||
("10", "10"),
|
|
||||||
("11", "11"),
|
|
||||||
("12", "12"),
|
|
||||||
],
|
|
||||||
string="Month",
|
|
||||||
required=True,
|
|
||||||
states={"done": [("readonly", True)]},
|
|
||||||
)
|
|
||||||
year_month = fields.Char(
|
|
||||||
compute="_compute_year_month",
|
|
||||||
string="Period",
|
|
||||||
readonly=True,
|
|
||||||
tracking=True,
|
|
||||||
store=True,
|
|
||||||
help="Year and month of the declaration.",
|
|
||||||
)
|
|
||||||
|
|
||||||
@api.model
|
|
||||||
def _default_company_id(self):
|
|
||||||
return self.env.company
|
|
||||||
|
|
||||||
@api.depends("company_id")
|
|
||||||
def _compute_company_country_code(self):
|
|
||||||
for this in self:
|
|
||||||
if this.company_id:
|
|
||||||
if not this.company_id.country_id:
|
|
||||||
raise ValidationError(_("You must set company's country !"))
|
|
||||||
this.company_country_code = this.company_id.country_id.code.lower()
|
|
||||||
|
|
||||||
@api.depends("year", "month")
|
|
||||||
def _compute_year_month(self):
|
|
||||||
for this in self:
|
|
||||||
if this.year and this.month:
|
|
||||||
this.year_month = "-".join([this.year, this.month])
|
|
||||||
|
|
||||||
@api.model
|
|
||||||
@api.constrains("year")
|
|
||||||
def _check_year(self):
|
|
||||||
for this in self:
|
|
||||||
if len(this.year) != 4 or this.year[0] != "2":
|
|
||||||
raise ValidationError(_("Invalid Year !"))
|
|
||||||
|
|
||||||
# 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)
|
||||||
|
|||||||
@@ -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,45 +1,8 @@
|
|||||||
# Copyright 2021 ACSONE SA/NV
|
# Copyright 2021 ACSONE SA/NV
|
||||||
# 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_test_helper import FakeModelLoader
|
|
||||||
|
|
||||||
from odoo.modules.module import get_resource_path
|
|
||||||
from odoo.tools import convert_file
|
|
||||||
|
|
||||||
|
|
||||||
class IntrastatCommon(object):
|
class IntrastatCommon(object):
|
||||||
@classmethod
|
|
||||||
def _load_xml(cls, module, filepath):
|
|
||||||
convert_file(
|
|
||||||
cls.env.cr,
|
|
||||||
module,
|
|
||||||
get_resource_path(module, filepath),
|
|
||||||
{},
|
|
||||||
mode="init",
|
|
||||||
noupdate=False,
|
|
||||||
kind="test",
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _load_test_declaration(cls):
|
|
||||||
cls.loader = FakeModelLoader(cls.env, cls.__module__)
|
|
||||||
cls.loader.backup_registry()
|
|
||||||
|
|
||||||
# The fake class is imported here !! After the backup_registry
|
|
||||||
from .models import IntrastatDeclarationTest
|
|
||||||
|
|
||||||
cls.loader.update_registry((IntrastatDeclarationTest,))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _create_declaration(cls, vals=None):
|
|
||||||
values = {
|
|
||||||
"company_id": cls.declaration_test_obj._default_company_id().id,
|
|
||||||
"year": "2021",
|
|
||||||
"month": "03",
|
|
||||||
}
|
|
||||||
if vals is not None:
|
|
||||||
values.update(vals)
|
|
||||||
cls.declaration = cls.declaration_test_obj.create(values)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
@@ -51,9 +14,3 @@ class IntrastatCommon(object):
|
|||||||
cls.demo_company = cls.env.ref("base.main_company")
|
cls.demo_company = cls.env.ref("base.main_company")
|
||||||
|
|
||||||
cls.shipping_cost = cls.env.ref("intrastat_base.shipping_costs_exclude")
|
cls.shipping_cost = cls.env.ref("intrastat_base.shipping_costs_exclude")
|
||||||
cls._load_test_declaration()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def tearDownClass(cls):
|
|
||||||
cls.loader.restore_registry()
|
|
||||||
super().tearDownClass()
|
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
|
||||||
<!--
|
|
||||||
Copyright 2013-2020 Akretion France (http://www.akretion.com/)
|
|
||||||
@author Alexis de Lattre <alexis.delattre@akretion.com>
|
|
||||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
-->
|
|
||||||
<odoo>
|
|
||||||
<record
|
|
||||||
id="base_intrastat_product_reminder_email_template"
|
|
||||||
model="mail.template"
|
|
||||||
>
|
|
||||||
<field name="name">Intrastat Product Reminder</field>
|
|
||||||
<field
|
|
||||||
name="model_id"
|
|
||||||
ref="intrastat_base.model_intrastat_declaration_test"
|
|
||||||
/>
|
|
||||||
<field name="auto_delete" eval="False" />
|
|
||||||
<field
|
|
||||||
name="email_from"
|
|
||||||
>${object.company_id.email or 'odoo@example.com'}</field>
|
|
||||||
<field name="email_to">${object.company_id.intrastat_email_list}</field>
|
|
||||||
<field
|
|
||||||
name="subject"
|
|
||||||
>${object.type} DEB ${object.year_month} for ${object.company_id.name}</field>
|
|
||||||
<field name="body_html">
|
|
||||||
<![CDATA[
|
|
||||||
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: #FFF; ">
|
|
||||||
|
|
||||||
<p>I would like to remind you that we are approaching the deadline for the DEB for month ${object.year_month}.</p>
|
|
||||||
|
|
||||||
<p>As there were no ${object.type} DEB for that month in Odoo, a draft DEB has been generated automatically by Odoo.</p>
|
|
||||||
|
|
||||||
% if ctx.get('exception'):
|
|
||||||
<p>When trying to generate the lines of the ${object.declaration_type} DEB, the following error was encountered:</p>
|
|
||||||
|
|
||||||
<p>${ctx.get('error_msg')}</p>
|
|
||||||
|
|
||||||
<p>You should solve this error, then go to the menu "Invoicing > Reporting > Intrastat > DEB", open the ${object.declaration_type} declaration for month ${object.year_month} and click on the button "Generate lines from invoices".</p>
|
|
||||||
|
|
||||||
% else:
|
|
||||||
% if object.num_lines and object.num_lines > 0:
|
|
||||||
<p>This draft ${object.type} DEB contains ${object.num_decl_lines} ${object.num_decl_lines == 1 and 'line' or 'lines'}.</p>
|
|
||||||
% else:
|
|
||||||
<p>This draft ${object.type} DEB generated automatically by Odoo doesn't contain any line.</p>
|
|
||||||
% endif
|
|
||||||
|
|
||||||
<p>Go and check this declaration in Odoo in the menu "Invoicing > Reporting > Intrastat > DEB".</p>
|
|
||||||
|
|
||||||
% endif
|
|
||||||
|
|
||||||
<p>
|
|
||||||
--
|
|
||||||
Automatic e-mail sent by Odoo.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
]]>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
</odoo>
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
from odoo.exceptions import UserError, ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
from odoo.tests.common import SavepointCase
|
|
||||||
|
|
||||||
from .common import IntrastatCommon
|
from .common import IntrastatCommon
|
||||||
|
|
||||||
@@ -10,11 +9,6 @@ class TestIntrastatBase(IntrastatCommon):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
cls.declaration_test_obj = cls.env["intrastat.declaration.test"]
|
|
||||||
cls._load_xml("intrastat_base", "tests/data/mail_template.xml")
|
|
||||||
cls.mail_template_id = (
|
|
||||||
"intrastat_base.base_intrastat_product_reminder_email_template"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_company(self):
|
def test_company(self):
|
||||||
# add 'Demo user' to intrastat_remind_user_ids
|
# add 'Demo user' to intrastat_remind_user_ids
|
||||||
@@ -34,46 +28,3 @@ class TestIntrastatBase(IntrastatCommon):
|
|||||||
def test_accessory(self):
|
def test_accessory(self):
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
self.shipping_cost.type = "consu"
|
self.shipping_cost.type = "consu"
|
||||||
|
|
||||||
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_send_mail(self):
|
|
||||||
self._create_declaration()
|
|
||||||
mail_before = self.mail_obj.search([])
|
|
||||||
self.declaration.send_reminder_email(self.mail_template_id)
|
|
||||||
mail_after = self.mail_obj.search([]) - mail_before
|
|
||||||
self.assertEqual(0, len(mail_after))
|
|
||||||
self.demo_company.write(
|
|
||||||
{"intrastat_remind_user_ids": [(6, False, [self.demo_user.id])]}
|
|
||||||
)
|
|
||||||
self.declaration.send_reminder_email(self.mail_template_id)
|
|
||||||
mail_after = self.mail_obj.search([]) - mail_before
|
|
||||||
self.assertEqual(1, len(mail_after))
|
|
||||||
self.assertIn(
|
|
||||||
mail_after.email_to,
|
|
||||||
self.demo_user.email,
|
|
||||||
)
|
|
||||||
|
|
||||||
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 TestIntrastat(TestIntrastatBase, SavepointCase):
|
|
||||||
""" Test Intrastat """
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user