Skip to content

Commit 1666b9b

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

5 files changed

Lines changed: 198 additions & 0 deletions

File tree

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
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)