Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[16.0] [IMP] l10n_it_fatturapa_out: edit invoice sent SDI #3568

Open
wants to merge 1 commit into
base: 16.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions l10n_it_fatturapa_out/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"views/partner_view.xml",
"views/company_view.xml",
"data/l10n_it_fatturapa_out_data.xml",
"security/groups.xml",
"security/ir.model.access.csv",
"security/rules.xml",
],
Expand Down
229 changes: 229 additions & 0 deletions l10n_it_fatturapa_out/models/account.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# Copyright 2014 Davide Corio
# Copyright 2016 Lorenzo Battistini - Agile Business Group

import base64

from lxml import etree

from odoo import api, fields, models
from odoo.exceptions import UserError
from odoo.http import request
from odoo.tools.translate import _

fatturapa_attachment_state_mapping = {
Expand Down Expand Up @@ -99,11 +104,235 @@ def preventive_checks(self):
)
return

@api.model
def check_tag(self, new_xml, original_xml, tags, precision=None):
"""
This function check if tag in new xml generated after function write()
is the same of original xml

:param new_xml: new xml generated after function write()
:param original_xml: original xml linked to invoice
:param tags: tags of xml to check
:param precision: precision to apply on text tag for check
:return: True if tags is the same else
"""
for tag in tags:
new_tag_text = new_xml.find(tag) is not None and new_xml.find(tag).text
original_tag_text = (
original_xml.find(tag) is not None and original_xml.find(tag).text
)
if precision:
new_tag_text = "{text:.{precision}f}".format(
text=float(new_tag_text), precision=precision
)
original_tag_text = "{text:.{precision}f}".format(
text=float(original_tag_text), precision=precision
)
if (new_tag_text or "").strip() != (original_tag_text or "").strip():
raise UserError(
_("%(tag)s isn't equal to tag in file e-invoice already created!")
% {"tag": tag[2:]}
)

def check_CessionarioCommittente(self, new_xml, original_xml):
list_tag = [
".//CessionarioCommittente/DatiAnagrafici/IdFiscaleIVA/IdPaese",
".//CessionarioCommittente/DatiAnagrafici/IdFiscaleIVA/IdCodice",
]
self.check_tag(new_xml, original_xml, list_tag)

def check_DatiGeneraliDocumento(self, new_xml, original_xml):
price_precision = self.env["decimal.precision"].precision_get(
"Product Price for XML e-invoices"
)

list_tag = [
".//DatiGeneraliDocumento/Data",
".//DatiGeneraliDocumento/TipoDocumento",
".//DatiGeneraliDocumento/Divisa",
".//DatiGeneraliDocumento/Numero",
]
self.check_tag(new_xml, original_xml, list_tag)
list_tag = [
".//DatiGeneraliDocumento/ImportoTotaleDocumento",
]
self.check_tag(new_xml, original_xml, list_tag, price_precision)

if len(new_xml.findall(".//DatiGeneraliDocumento/DatiRitenuta")) != len(
original_xml.findall(".//DatiGeneraliDocumento/DatiRitenuta")
):
raise UserError(
_(
"DatiGeneraliDocumento/DatiRitenuta "
"isn't equal to tag in file e-invoice already created!"
)
)
for lr, new_line_ritenuta in enumerate(
new_xml.findall(".//DatiGeneraliDocumento/DatiRitenuta")
):
original_line_ritenuta = original_xml.findall(
".//DatiGeneraliDocumento/DatiRitenuta"
)[lr]
list_tag_DatiRitenuta = [
".//TipoRitenuta",
".//CausalePagamento",
]
self.check_tag(
new_line_ritenuta, original_line_ritenuta, list_tag_DatiRitenuta
)
list_tag_DatiRitenuta = [
".//ImportoRitenuta",
".//AliquotaRitenuta",
]
self.check_tag(
new_line_ritenuta,
original_line_ritenuta,
list_tag_DatiRitenuta,
price_precision,
)

def check_DatiBeniServizi(self, new_xml, original_xml):
price_precision = self.env["decimal.precision"].precision_get(
"Product Price for XML e-invoices"
)
uom_precision = self.env["decimal.precision"].precision_get(
"Product Unit of Measure"
)

if len(new_xml.findall(".//DatiBeniServizi/DettaglioLinee")) != len(
original_xml.findall(".//DatiBeniServizi/DettaglioLinee")
):
raise UserError(
_(
"DatiBeniServizi/DettaglioLinee "
"isn't equal to tag in file e-invoice already created!"
)
)
for ld, new_line_details in enumerate(
new_xml.findall(".//DatiBeniServizi/DettaglioLinee")
):
original_line_details = original_xml.findall(
".//DatiBeniServizi/DettaglioLinee"
)[ld]
list_tag_DettaglioLinee = [
".//NumeroLinea",
".//CodiceTipo",
".//CodiceValore",
".//Descrizione",
".//Natura",
".//Ritenuta",
]
self.check_tag(
new_line_details, original_line_details, list_tag_DettaglioLinee
)
list_tag_DettaglioLinee = [
".//Quantita",
]
self.check_tag(
new_line_details,
original_line_details,
list_tag_DettaglioLinee,
uom_precision,
)
list_tag_DettaglioLinee = [
".//PrezzoUnitario",
".//AliquotaIVA",
".//PrezzoTotale",
]
self.check_tag(
new_line_details,
original_line_details,
list_tag_DettaglioLinee,
price_precision,
)

if len(new_xml.findall(".//DatiBeniServizi/DatiRiepilogo")) != len(
original_xml.findall(".//DatiBeniServizi/DatiRiepilogo")
):
raise UserError(
_(
"DatiBeniServizi/DatiRiepilogo "
"isn't equal to tag in file e-invoice already created!"
)
)
for lr, new_line_riepilogo in enumerate(
new_xml.findall(".//DatiBeniServizi/DatiRiepilogo")
):
original_line_riepilogo = original_xml.findall(
".//DatiBeniServizi/DatiRiepilogo"
)[lr]
list_tag_DatiRiepilogo = [
".//AliquotaIVA",
".//ImponibileImporto",
".//Imposta",
]
self.check_tag(
new_line_riepilogo,
original_line_riepilogo,
list_tag_DatiRiepilogo,
price_precision,
)

def elements_equal(self, new_xml, original_xml):
self.check_CessionarioCommittente(new_xml, original_xml)
self.check_DatiGeneraliDocumento(new_xml, original_xml)
self.check_DatiBeniServizi(new_xml, original_xml)

def check_move_confirmable(self):
self.ensure_one()

if not self.state == "posted" and not (
request
and request.params.get("method", False)
and request.params["method"] == "action_post"
):
return True
return False

def write(self, vals):
is_draft = {}
for move in self:
is_draft[move.id] = True if move.state == "draft" else False
res = super().write(vals)
for move in self:
if (
move.is_sale_document()
and move.fatturapa_attachment_out_id
and is_draft[move.id]
and not move.state == "cancel"
and not move.env.context.get("skip_check_xml", False)
and not (
request
and request.params.get("method", False)
and request.params["method"] == "button_draft"
)
):
context_partner = self.env.context.copy()
context_partner.update({"lang": move.partner_id.lang})
context_partner.update(skip_check_xml=True)
fatturapa, progressivo_invio = self.env[
"wizard.export.fatturapa"
].exportInvoiceXML(move.partner_id, [move.id], context=context_partner)
new_xml_content = fatturapa.to_xml(self.env)
original_xml_content = base64.decodebytes(
move.fatturapa_attachment_out_id.datas
)
parser = etree.XMLParser(remove_blank_text=True)
new_xml = etree.fromstring(new_xml_content, parser)
original_xml = etree.fromstring(original_xml_content, parser)
move.elements_equal(new_xml, original_xml)
if move.check_move_confirmable():
move.with_context(skip_check_xml=True).action_post()
return res

def button_draft(self):
for invoice in self:
if (
invoice.fatturapa_state != "error"
and invoice.fatturapa_attachment_out_id
and not self.env.user.has_group(
"l10n_it_fatturapa_out.group_edit_invoice_sent_sdi"
)
):
raise UserError(
_(
Expand Down
12 changes: 12 additions & 0 deletions l10n_it_fatturapa_out/security/groups.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>

<record id="group_edit_invoice_sent_sdi" model="res.groups">
<field name="name">Edit Invoice Sent SDI</field>
<field
name="comment"
>Can reset to draft and then edit invoice sent to SDI</field>
<field name="category_id" ref="base.module_category_hidden" />
</record>

</odoo>
11 changes: 4 additions & 7 deletions l10n_it_fatturapa_out/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@

/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.

Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.

See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
Expand Down Expand Up @@ -275,7 +274,7 @@
margin-left: 2em ;
margin-right: 2em }

pre.code .ln { color: gray; } /* line numbers */
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
Expand All @@ -301,7 +300,7 @@
span.pre {
white-space: pre }

span.problematic, pre.problematic {
span.problematic {
color: red }

span.section-subtitle {
Expand Down Expand Up @@ -486,9 +485,7 @@ <h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
Expand Down
30 changes: 30 additions & 0 deletions l10n_it_fatturapa_out/tests/test_fatturapa_xml_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1118,3 +1118,33 @@ def test_18_xml_export(self):
# XML doc to be validated
xml_content = base64.decodebytes(attachment.datas)
self.check_content(xml_content, "IT06363391001_00018.xml")

def test_edit_invoice_sent_sdi(self):
"""
Check e-invoice tags after edit invoice.
"""
invoice = self._create_invoice()
invoice.action_post()
self.run_wizard(invoice.id)
self.assertEqual(invoice.state, "posted")
self.assertTrue(invoice.fatturapa_attachment_out_id.exists())

with self.assertRaises(UserError), self.cr.savepoint():
invoice.with_user(self.account_manager.id).button_draft()

self.account_manager.groups_id += self.env.ref(
"l10n_it_fatturapa_out.group_edit_invoice_sent_sdi"
)
invoice.with_user(self.account_manager.id).button_draft()
self.assertEqual(invoice.state, "draft")

with self.assertRaises(UserError), self.cr.savepoint():
move_form = Form(invoice)
with move_form.invoice_line_ids.edit(0) as line_form:
line_form.price_unit = 800
move_form.save()

move_form = Form(invoice)
move_form.invoice_payment_term_id = self.account_payment_term
move_form.save()
self.assertEqual(invoice.state, "posted")
6 changes: 5 additions & 1 deletion l10n_it_fatturapa_out/wizard/wizard_export_fatturapa.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,11 @@ def exportInvoiceXML(self, partner, invoice_ids, attach=False, context=None):

# generate attachments (PDF version of invoice)
for inv in invoice_ids:
if not attach and inv.fatturapa_attachment_out_id:
if (
not attach
and inv.fatturapa_attachment_out_id
and not context.get("skip_check_xml", False)
):
raise UserError(
_("E-invoice export file still present for invoice %s.")
% (inv.name or "")
Expand Down
Loading