Skip to content

Commit f8377ae

Browse files
author
matteo.tognini
committed
[IMP]l10n_it_edi_delivery_note: gestione td24
1 parent ed120b9 commit f8377ae

7 files changed

Lines changed: 223 additions & 1 deletion

File tree

l10n_it_edi_delivery_note/README.rst

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ ITA - Fatturazione Elettronica - Documento di Trasporto
77
!! This file is generated by oca-gen-addon-readme !!
88
!! changes will be overwritten. !!
99
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10-
!! source digest: sha256:cdaa614305bb9753b3e6c8d45e774a8b93906c3b1dc35d273f7a76211656030f
10+
!! source digest: sha256:33a6a6421087debc578f5893942948d27d41d03a3eca27f7bfafca1d630434d9
1111
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1212
1313
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
@@ -39,6 +39,14 @@ includendo:
3939
- Numero del DDT
4040
- Data del DDT
4141

42+
Il modulo gestisce anche la distinzione tra fatture immediate e
43+
differite:
44+
45+
- **TD01 (Fattura immediata)**: quando la fattura ha la stessa data dei
46+
DDT collegati
47+
- **TD24 (Fattura differita)**: quando la fattura è emessa in un giorno
48+
diverso rispetto ai DDT
49+
4250
**Nota importante**: Una volta installato questo modulo, è necessario
4351
disinstallare ``l10n_it_stock_ddt`` in quanto entrambi popolano la
4452
fattura elettronica con i DatiDdt.
@@ -56,6 +64,14 @@ including:
5664
- DDT Number
5765
- DDT Date
5866

67+
The module also handles the distinction between immediate and deferred
68+
invoices:
69+
70+
- **TD01 (Immediate invoice)**: when the invoice has the same date as
71+
the linked DDTs
72+
- **TD24 (Deferred invoice)**: when the invoice is issued on a
73+
different day than the DDTs
74+
5975
**Important note**: Once this module is installed, you must uninstall
6076
``l10n_it_stock_ddt`` as both populate the electronic invoice with
6177
DatiDdt data.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
# Copyright 2026 Nextev Srl
22
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
4+
from . import models
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import account_invoice
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
from odoo import models
4+
5+
6+
class AccountMove(models.Model):
7+
_inherit = "account.move"
8+
9+
def _l10n_it_edi_document_type_mapping(self):
10+
"""Deferred invoices (not direct) require TD24 FatturaPA Document Type."""
11+
res = super()._l10n_it_edi_document_type_mapping()
12+
for document_type, infos in res.items():
13+
if document_type == "TD07":
14+
continue
15+
infos["direct_invoice"] = True
16+
res["TD24"] = {
17+
"move_types": ["out_invoice"],
18+
"import_type": "in_invoice",
19+
"direct_invoice": False,
20+
}
21+
return res
22+
23+
def _l10n_it_edi_invoice_is_direct(self):
24+
"""An invoice is direct if ddt are all done the same day as the invoice."""
25+
for ddt in self.delivery_note_ids:
26+
if not ddt.date or ddt.date != self.invoice_date:
27+
return False
28+
return True
29+
30+
def _l10n_it_edi_features_for_document_type_selection(self):
31+
res = super()._l10n_it_edi_features_for_document_type_selection()
32+
res["direct_invoice"] = self._l10n_it_edi_invoice_is_direct()
33+
return res

l10n_it_edi_delivery_note/readme/DESCRIPTION.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ Aggiunge automaticamente i riferimenti ai DDT nell'XML esportato, includendo:
77
- Numero del DDT
88
- Data del DDT
99

10+
Il modulo gestisce anche la distinzione tra fatture immediate e differite:
11+
- **TD01 (Fattura immediata)**: quando la fattura ha la stessa data dei DDT collegati
12+
- **TD24 (Fattura differita)**: quando la fattura è emessa in un giorno diverso rispetto ai DDT
13+
1014
**Nota importante**: Una volta installato questo modulo, è necessario disinstallare `l10n_it_stock_ddt`
1115
in quanto entrambi popolano la fattura elettronica con i DatiDdt.
1216

@@ -21,5 +25,9 @@ It automatically adds references to Delivery Notes in the exported XML, includin
2125
- DDT Number
2226
- DDT Date
2327

28+
The module also handles the distinction between immediate and deferred invoices:
29+
- **TD01 (Immediate invoice)**: when the invoice has the same date as the linked DDTs
30+
- **TD24 (Deferred invoice)**: when the invoice is issued on a different day than the DDTs
31+
2432
**Important note**: Once this module is installed, you must uninstall `l10n_it_stock_ddt`
2533
as both populate the electronic invoice with DatiDdt data.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import test_edi_delivery_note
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Copyright 2026 Nextev Srl
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
4+
from lxml import etree
5+
6+
from odoo.tests import tagged
7+
8+
from odoo.addons.l10n_it_edi.tests.common import TestItEdi
9+
10+
11+
@tagged("post_install", "-at_install")
12+
class TestEdiDeliveryNote(TestItEdi):
13+
@classmethod
14+
def setUpClass(cls):
15+
super().setUpClass()
16+
17+
# Product
18+
cls.product = (
19+
cls.env["product.product"]
20+
.with_company(cls.company)
21+
.create(
22+
{
23+
"name": "Test Product",
24+
"type": "consu",
25+
"list_price": 100.0,
26+
"invoice_policy": "delivery",
27+
}
28+
)
29+
)
30+
31+
def test_deferred_invoice_with_delivery_note(self):
32+
"""
33+
Test that a deferred invoice (TD24) with delivery notes
34+
includes DatiDDT in the FatturaPA XML.
35+
"""
36+
from datetime import timedelta
37+
38+
# Create sale order
39+
sale_order = (
40+
self.env["sale.order"]
41+
.with_company(self.company)
42+
.create(
43+
{
44+
"partner_id": self.italian_partner_a.id,
45+
"order_line": [
46+
(
47+
0,
48+
0,
49+
{
50+
"product_id": self.product.id,
51+
"product_uom_qty": 5,
52+
"price_unit": 100.0,
53+
"tax_id": [(6, 0, self.default_tax.ids)],
54+
},
55+
)
56+
],
57+
}
58+
)
59+
)
60+
sale_order.action_confirm()
61+
62+
# Deliver products
63+
picking = sale_order.picking_ids
64+
picking.move_ids.quantity = 5
65+
picking.button_validate()
66+
67+
# Create delivery note from picking
68+
wizard = (
69+
self.env["stock.delivery.note.create.wizard"]
70+
.with_context(
71+
active_model="stock.picking",
72+
active_ids=picking.ids,
73+
)
74+
.create(
75+
{
76+
"partner_shipping_id": self.italian_partner_a.id,
77+
}
78+
)
79+
)
80+
wizard.confirm()
81+
82+
delivery_note = self.env["stock.delivery.note"].search(
83+
[("picking_ids", "in", picking.ids)]
84+
)
85+
self.assertTrue(delivery_note, "Delivery note should be created")
86+
87+
# Validate delivery note
88+
delivery_note.action_confirm()
89+
90+
# Create invoice from delivery note (deferred)
91+
invoice_wizard = (
92+
self.env["stock.delivery.note.invoice.wizard"]
93+
.with_context(
94+
active_model="stock.delivery.note",
95+
active_ids=delivery_note.ids,
96+
)
97+
.create({})
98+
)
99+
invoice_wizard.create_invoices()
100+
101+
# Get the created invoice
102+
invoice = self.env["account.move"].search(
103+
[("partner_id", "=", self.italian_partner_a.id)],
104+
order="id desc",
105+
limit=1,
106+
)
107+
self.assertTrue(invoice, "Invoice should be created")
108+
109+
# Check that delivery_note_ids is populated
110+
self.assertEqual(
111+
len(invoice.delivery_note_ids),
112+
1,
113+
"Invoice should be linked to 1 delivery note",
114+
)
115+
116+
# Set invoice date to a different day from delivery to make it deferred (TD24)
117+
# This simulates the real scenario: delivery today, invoice later
118+
invoice.invoice_date = picking.date_done.date() + timedelta(days=1)
119+
120+
# Post invoice
121+
invoice.action_post()
122+
123+
# Generate XML
124+
xml_content = invoice._l10n_it_edi_render_xml()
125+
self.assertTrue(xml_content, "XML should be generated")
126+
127+
# Parse XML using local-name() to avoid namespace issues
128+
root = etree.fromstring(xml_content)
129+
130+
# Check that DatiDDT exists
131+
dati_ddt = root.xpath("//*[local-name()='DatiDDT']")
132+
self.assertTrue(dati_ddt, "DatiDDT should be present in XML")
133+
134+
# Check NumeroDDT
135+
numero_ddt = root.xpath(
136+
"//*[local-name()='DatiDDT']/*[local-name()='NumeroDDT']"
137+
)
138+
self.assertTrue(
139+
len(numero_ddt) >= 1, "Should have at least one NumeroDDT element"
140+
)
141+
# Verify one of them matches our delivery note
142+
numero_ddt_texts = [n.text for n in numero_ddt]
143+
self.assertIn(
144+
delivery_note.name,
145+
numero_ddt_texts,
146+
f"NumeroDDT should contain delivery note name {delivery_note.name}",
147+
)
148+
149+
# Check DataDDT
150+
data_ddt = root.xpath("//*[local-name()='DatiDDT']/*[local-name()='DataDDT']")
151+
self.assertTrue(len(data_ddt) >= 1, "Should have at least one DataDDT element")
152+
153+
# Check document type is TD24 for deferred invoice (different day delivery)
154+
tipo_documento = root.xpath(
155+
"//*[local-name()='DatiGeneraliDocumento']/*[local-name()='TipoDocumento']"
156+
)
157+
self.assertEqual(
158+
tipo_documento[0].text,
159+
"TD24",
160+
"Should be TD24 (deferred) invoice with DDT on different date",
161+
)

0 commit comments

Comments
 (0)