Skip to content

Commit

Permalink
[MIG] l10n_fr_intrastat_service: migrate to v18
Browse files Browse the repository at this point in the history
Add ACL for account viewer group
No more currency conversion in the code: use price_subtotal AND balance
Improve checks on company for which DES is generated: VAT number, EUR currency, etc...
Add prepare method for attachment generation, to allow easy customization of filename.
Improve some strings
  • Loading branch information
alexis-via committed Jan 4, 2025
1 parent c5765ce commit dd51857
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 78 deletions.
5 changes: 2 additions & 3 deletions l10n_fr_intrastat_service/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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,
}
3 changes: 0 additions & 3 deletions l10n_fr_intrastat_service/data/ir_cron.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">months</field>
<field name="numbercall">-1</field>
<!-- don't limit the number of calls -->
<field name="doall" eval="False" />
<field name="model_id" ref="model_l10n_fr_intrastat_service_declaration" />
<field name="state">code</field>
<field name="code">model._scheduler_reminder()</field>
Expand Down
130 changes: 88 additions & 42 deletions l10n_fr_intrastat_service/models/intrastat_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -153,20 +154,23 @@ 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:
return False

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
lines_to_remove = line_obj.search(
[("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:
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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):
Expand All @@ -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 :
Expand Down Expand Up @@ -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:
Expand All @@ -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):
Expand Down Expand Up @@ -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)],
)
Expand All @@ -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):
Expand Down
2 changes: 2 additions & 0 deletions l10n_fr_intrastat_service/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -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
21 changes: 5 additions & 16 deletions l10n_fr_intrastat_service/tests/test_fr_intrastat_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
Expand All @@ -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,
}
)

Expand All @@ -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"]
Expand Down
Loading

0 comments on commit dd51857

Please sign in to comment.