diff --git a/l10n_fr_intrastat_service/__manifest__.py b/l10n_fr_intrastat_service/__manifest__.py
index 1c1ad1cfd..505dfb349 100644
--- a/l10n_fr_intrastat_service/__manifest__.py
+++ b/l10n_fr_intrastat_service/__manifest__.py
@@ -4,7 +4,7 @@
{
"name": "DES",
- "version": "17.0.1.0.0",
+ "version": "18.0.1.0.0",
"category": "Localisation/Report Intrastat",
"license": "AGPL-3",
"summary": "Module for Intrastat service reporting (DES) for France",
@@ -16,11 +16,10 @@
"data": [
"security/ir.model.access.csv",
"report/report.xml",
- "views/intrastat_service_view.xml",
+ "views/intrastat_service.xml",
"data/ir_cron.xml",
"data/mail_template.xml",
"security/intrastat_service_security.xml",
],
"installable": True,
- "application": True,
}
diff --git a/l10n_fr_intrastat_service/data/ir_cron.xml b/l10n_fr_intrastat_service/data/ir_cron.xml
index 8194272b7..7a490f91e 100644
--- a/l10n_fr_intrastat_service/data/ir_cron.xml
+++ b/l10n_fr_intrastat_service/data/ir_cron.xml
@@ -12,9 +12,6 @@
1
months
- -1
-
-
code
model._scheduler_reminder()
diff --git a/l10n_fr_intrastat_service/models/intrastat_service.py b/l10n_fr_intrastat_service/models/intrastat_service.py
index 7d9828ed1..de65d9147 100644
--- a/l10n_fr_intrastat_service/models/intrastat_service.py
+++ b/l10n_fr_intrastat_service/models/intrastat_service.py
@@ -27,6 +27,7 @@ class L10nFrIntrastatServiceDeclaration(models.Model):
string="Company",
required=True,
default=lambda self: self.env.company,
+ ondelete="cascade",
)
start_date = fields.Date(
required=True,
@@ -153,6 +154,7 @@ def _prepare_domain(self):
return domain
def _is_service(self, invoice_line):
+ # What are we supposed to do for the new 'combo' type ???
if invoice_line.product_id.type == "service":
return True
else:
@@ -160,6 +162,7 @@ def _is_service(self, invoice_line):
def generate_service_lines(self):
self.ensure_one()
+ self.company_id._check_company()
line_obj = self.env["l10n.fr.intrastat.service.declaration.line"]
amo = self.env["account.move"]
# delete all DES lines generated from invoices
@@ -167,6 +170,7 @@ def generate_service_lines(self):
[("move_id", "!=", False), ("parent_id", "=", self.id)]
)
lines_to_remove.unlink()
+ lines_to_create = []
company_currency = self.company_id.currency_id
invoices = amo.search(self._prepare_domain(), order="invoice_date")
for invoice in invoices:
@@ -184,8 +188,11 @@ def generate_service_lines(self):
amount_invoice_cur_to_write = 0.0
amount_company_cur_to_write = 0.0
amount_invoice_cur_regular_service = 0.0
+ amount_company_cur_regular_service = 0.0
amount_invoice_cur_accessory_cost = 0.0
+ amount_company_cur_accessory_cost = 0.0
regular_product_in_invoice = False
+ subtotal_sign = invoice.move_type == "out_refund" and -1 or 1
for line in invoice.invoice_line_ids.filtered(
lambda x: x.display_type == "product" and x.product_id
@@ -204,39 +211,37 @@ def generate_service_lines(self):
# - some HW products with value = 0
# - some accessory costs
# => we want to have the accessory costs in DEB, not in DES
- if line.currency_id.is_zero(line.price_subtotal):
+ if company_currency.is_zero(line.balance):
continue
if line.product_id.is_accessory_cost:
- amount_invoice_cur_accessory_cost += line.price_subtotal
+ amount_invoice_cur_accessory_cost += (
+ line.price_subtotal * subtotal_sign
+ )
+ amount_company_cur_accessory_cost += line.balance * -1
else:
- amount_invoice_cur_regular_service += line.price_subtotal
+ amount_invoice_cur_regular_service += (
+ line.price_subtotal * subtotal_sign
+ )
+ amount_company_cur_regular_service += line.balance * -1
# END of the loop on invoice lines
if regular_product_in_invoice:
amount_invoice_cur_to_write = amount_invoice_cur_regular_service
+ amount_company_cur_to_write = amount_company_cur_regular_service
else:
amount_invoice_cur_to_write = (
amount_invoice_cur_regular_service
+ amount_invoice_cur_accessory_cost
)
-
- amount_company_cur_to_write = int(
- round(
- invoice.currency_id._convert(
- amount_invoice_cur_to_write,
- company_currency,
- self.company_id,
- invoice.invoice_date,
- )
+ amount_company_cur_to_write = (
+ amount_company_cur_regular_service
+ + amount_company_cur_accessory_cost
)
- )
- if amount_company_cur_to_write:
- if invoice.move_type == "out_refund":
- amount_invoice_cur_to_write *= -1
- amount_company_cur_to_write *= -1
+ amount_company_cur_to_write = int(round(amount_company_cur_to_write))
+ if amount_company_cur_to_write:
# Why do I check that the Partner has a VAT number
# only here and not earlier ? Because, if I sell
# to a physical person in the EU with VAT, then
@@ -255,7 +260,7 @@ def generate_service_lines(self):
else:
partner_vat_to_write = invoice.commercial_partner_id.vat
- line_obj.create(
+ lines_to_create.append(
{
"parent_id": self.id,
"move_id": invoice.id,
@@ -266,12 +271,14 @@ def generate_service_lines(self):
"amount_company_currency": amount_company_cur_to_write,
}
)
+ line_obj.create(lines_to_create)
self.message_post(body=_("Re-generating lines from invoices"))
def done(self):
for decl in self:
assert decl.state == "draft"
- decl.generate_xml()
+ decl._check_company()
+ decl._generate_xml()
self.write({"state": "done"})
def back2draft(self):
@@ -281,12 +288,43 @@ def back2draft(self):
decl.attachment_id.unlink()
self.write({"state": "draft"})
- def _generate_des_xml_root(self):
+ def _check_company(self):
self.ensure_one()
- if not self.company_id.partner_id.vat:
+ company = self.company_id
+ company_vat = company.partner_id.vat
+ if not company_vat:
+ raise UserError(
+ _("Missing VAT number on company '%s'.") % company.display_name
+ )
+ if not company_vat.startswith("FR"):
+ raise UserError(
+ _(
+ "DES is only for French companies, so the VAT number should "
+ "start with 'FR'. VAT number of company '%(company)s' is %(vat)s.",
+ company=company.display_name,
+ vat=company_vat,
+ )
+ )
+ if not is_valid(company_vat):
raise UserError(
- _("Missing VAT number on company '%s'.") % self.company_id.display_name
+ _(
+ "The VAT number of company '%(company)s' is %(vat)s. "
+ "This VAT number is not valid.",
+ company=company.display_name,
+ vat=company_vat,
+ )
+ )
+ if company.currency_id.name != "EUR":
+ raise UserError(
+ _(
+ "The currency of company %(company)s is %(currency)s and not EUR.",
+ company=company.display_name,
+ currency=company.currency_id.name,
+ )
)
+
+ def _generate_des_xml_root(self):
+ self.ensure_one()
my_company_vat = self.company_id.partner_id.vat
# Tech spec of XML export are available here :
@@ -322,7 +360,7 @@ def _generate_des_xml_root(self):
ligne_des.partner_des = vat
return root
- def generate_xml(self):
+ def _generate_xml(self):
self.ensure_one()
assert not self.attachment_id
if not self.declaration_line_ids:
@@ -338,21 +376,20 @@ def generate_xml(self):
xml_bytes, "l10n_fr_intrastat_service/data/des.xsd"
)
# Attach the XML file
- attach_id = self._attach_xml_file(xml_bytes)
- self.write({"attachment_id": attach_id})
+ attach_vals = self._prepare_attachment(xml_bytes)
+ attach = self.env["ir.attachment"].create(attach_vals)
+ self.write({"attachment_id": attach.id})
- def _attach_xml_file(self, xml_bytes):
+ def _prepare_attachment(self, xml_bytes):
self.ensure_one()
filename = f"{self.year_month}_des.xml"
- attach = self.env["ir.attachment"].create(
- {
- "name": filename,
- "res_id": self.id,
- "res_model": self._name,
- "raw": xml_bytes,
- }
- )
- return attach.id
+ attach_vals = {
+ "name": filename,
+ "res_id": self.id,
+ "res_model": self._name,
+ "raw": xml_bytes,
+ }
+ return attach_vals
@api.model
def _scheduler_reminder(self):
@@ -452,10 +489,17 @@ class L10nFrIntrastatServiceDeclarationLine(models.Model):
invoice_date = fields.Date(
related="move_id.invoice_date", string="Invoice Date", store=True
)
- partner_vat = fields.Char(string="Customer VAT", required=True)
+ partner_vat = fields.Char(
+ string="Customer VAT",
+ required=True,
+ compute="_compute_partner_vat",
+ store=True,
+ readonly=False,
+ precompute=True,
+ )
partner_id = fields.Many2one(
"res.partner",
- string="Partner Name",
+ string="Customer",
ondelete="restrict",
domain=[("parent_id", "=", False)],
)
@@ -468,18 +512,20 @@ class L10nFrIntrastatServiceDeclarationLine(models.Model):
"date and rounded at 0 digits",
)
amount_invoice_currency = fields.Monetary(
- string="Amount in Invoice Currency",
+ string="Invoiced Amount",
readonly=True,
currency_field="invoice_currency_id",
+ help="Invoiced amount in the invoice currency",
)
invoice_currency_id = fields.Many2one(
"res.currency", "Invoice Currency", readonly=True
)
- @api.onchange("partner_id")
- def partner_on_change(self):
- if self.partner_id and self.partner_id.vat:
- self.partner_vat = self.partner_id.vat
+ @api.depends("partner_id")
+ def _compute_partner_vat(self):
+ for line in self:
+ if line.partner_id and line.partner_id.vat:
+ line.partner_vat = line.partner_id.vat
@api.constrains("partner_vat")
def _check_partner_vat(self):
diff --git a/l10n_fr_intrastat_service/security/ir.model.access.csv b/l10n_fr_intrastat_service/security/ir.model.access.csv
index b9a00c503..da3e13457 100644
--- a/l10n_fr_intrastat_service/security/ir.model.access.csv
+++ b/l10n_fr_intrastat_service/security/ir.model.access.csv
@@ -1,3 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_l10n_fr_intrastat_service_declaration,Full access to l10n.fr.intrastat.service.declaration to accountant,model_l10n_fr_intrastat_service_declaration,account.group_account_user,1,1,1,1
access_l10n_fr_intrastat_service_declaration_line,Full access to l10n.fr.intrastat.service.declaration.line to accoutant,model_l10n_fr_intrastat_service_declaration_line,account.group_account_user,1,1,1,1
+access_l10n_fr_intrastat_service_declaration_read,Read-only access to l10n.fr.intrastat.service.declaration to account viewer,model_l10n_fr_intrastat_service_declaration,account.group_account_readonly,1,0,0,0
+access_l10n_fr_intrastat_service_declaration_line_read,Read-only access to l10n.fr.intrastat.service.declaration.line to accout viewer,model_l10n_fr_intrastat_service_declaration_line,account.group_account_readonly,1,0,0,0
diff --git a/l10n_fr_intrastat_service/tests/test_fr_intrastat_service.py b/l10n_fr_intrastat_service/tests/test_fr_intrastat_service.py
index 5588a90e0..d058f8ad4 100644
--- a/l10n_fr_intrastat_service/tests/test_fr_intrastat_service.py
+++ b/l10n_fr_intrastat_service/tests/test_fr_intrastat_service.py
@@ -8,7 +8,6 @@
from dateutil.relativedelta import relativedelta
from lxml import etree
-from odoo import Command
from odoo.exceptions import UserError
from odoo.tests import tagged
from odoo.tools import float_compare
@@ -19,22 +18,15 @@
@tagged("post_install", "-at_install")
class TestFrIntrastatService(AccountTestInvoicingCommon):
@classmethod
- def setUpClass(cls, chart_template_ref=None):
- super().setUpClass(chart_template_ref=chart_template_ref)
+ @AccountTestInvoicingCommon.setup_country("fr")
+ def setUpClass(cls):
+ super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
- cls.fr_test_company = cls.setup_company_data(
- "Akretion France",
- chart_template=chart_template_ref,
- country_id=cls.env.ref("base.fr").id,
+ cls.fr_test_company = cls.setup_other_company(
+ name="Akretion France TEST DES",
vat="FR86792377731",
)
cls.company = cls.fr_test_company["company"]
- cls.user.write(
- {
- "company_ids": [Command.link(cls.company.id)],
- "company_id": cls.company.id,
- }
- )
cls.fp_eu_b2b = cls.env["account.fiscal.position"].create(
{
"name": "EU B2B",
@@ -49,14 +41,12 @@ def setUpClass(cls, chart_template_ref=None):
{
"name": "Engineering services",
"type": "service",
- "company_id": cls.company.id,
}
)
cls.hw_product = cls.env["product.product"].create(
{
"name": "Hardware product",
"type": "consu",
- "company_id": cls.company.id,
}
)
@@ -66,7 +56,6 @@ def setUpClass(cls, chart_template_ref=None):
"is_company": True,
"vat": "BE0477472701",
"country_id": cls.env.ref("base.be").id,
- "company_id": False,
}
)
cls.account_revenue = cls.fr_test_company["default_account_revenue"]
diff --git a/l10n_fr_intrastat_service/views/intrastat_service_view.xml b/l10n_fr_intrastat_service/views/intrastat_service.xml
similarity index 93%
rename from l10n_fr_intrastat_service/views/intrastat_service_view.xml
rename to l10n_fr_intrastat_service/views/intrastat_service.xml
index a046aa29f..3eb39e49a 100644
--- a/l10n_fr_intrastat_service/views/intrastat_service_view.xml
+++ b/l10n_fr_intrastat_service/views/intrastat_service.xml
@@ -75,20 +75,16 @@
/>
-
-
-
-
-
+
- fr.intrastat.service.declaration.tree
+ fr.intrastat.service.declaration.list
l10n.fr.intrastat.service.declaration
-
+
@@ -100,7 +96,7 @@
decoration-info="state == 'draft'"
widget="badge"
/>
-
+
@@ -176,7 +172,8 @@
fr.intrastat.service.declaration.line.tree
l10n.fr.intrastat.service.declaration.line
-
+
+
@@ -200,7 +199,7 @@
>
DES
l10n.fr.intrastat.service.declaration
- tree,form,pivot,graph
+ list,form,pivot,graph