Skip to content

Commit

Permalink
[ADD] l10n_it_fatturapa_out: edit invoice sent SDI
Browse files Browse the repository at this point in the history
Co-authored-by: guidgaro75 <guido.garofalo75@gmail.com>
  • Loading branch information
Borruso and guidgaro75 committed Jul 26, 2024
1 parent 2473e77 commit 76d4f94
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 8 deletions.
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

0 comments on commit 76d4f94

Please sign in to comment.