diff --git a/templates/etichetta_spedizione_ddt/body.php b/templates/etichetta_spedizione_ddt/body.php
new file mode 100644
index 000000000..75e3df8d4
--- /dev/null
+++ b/templates/etichetta_spedizione_ddt/body.php
@@ -0,0 +1,86 @@
+
+ .shipping-label {
+ width: 100%;
+ padding: 3mm 4mm;
+ box-sizing: border-box;
+ font-family: sans-serif;
+ color: #222;
+ }
+ .shipping-label .section-title {
+ margin: 0 0 2mm;
+ font-size: 8pt;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: .4px;
+ }
+ .shipping-label .separator {
+ border-top: .5mm solid #000;
+ margin: 4mm 0;
+ }
+ .shipping-label .sender {
+ min-height: 24mm;
+ font-size: 10pt;
+ line-height: 1.35;
+ }
+ .shipping-label .recipient {
+ min-height: 52mm;
+ padding: 3mm 0;
+ font-size: 16pt;
+ line-height: 1.4;
+ }
+ .shipping-label .recipient strong {
+ font-size: 18pt;
+ }
+ .shipping-label .reference {
+ padding-top: 1mm;
+ font-size: 9pt;
+ }
+ .shipping-label .reference-table {
+ width: 100%;
+ border-collapse: collapse;
+ }
+ .shipping-label .reference-table td {
+ padding: 1mm 2mm 0 0;
+ vertical-align: top;
+ }
+ .shipping-label .notes {
+ margin-top: 3mm;
+ font-size: 8.5pt;
+ line-height: 1.35;
+ }
+
+
+
+
+
'.tr('Mittente').'
+ $mittente$
+
+
+
+
+
+
'.tr('Destinatario').'
+ $destinatario$
+
+
+
+
+
+
+
+ | $tipo_doc$ |
+ '.tr('N.').' $numero$ |
+
+
+ | '.tr('Data').': $data$ |
+ '.tr('Colli').': $n_colli$ |
+
+
+ $notes_block$
+
+
';
diff --git a/templates/etichetta_spedizione_ddt/footer.php b/templates/etichetta_spedizione_ddt/footer.php
new file mode 100644
index 000000000..36a615a78
--- /dev/null
+++ b/templates/etichetta_spedizione_ddt/footer.php
@@ -0,0 +1,3 @@
+fetchOne(
+ 'SELECT an_anagrafiche.ragione_sociale, an_sedi.nomesede, an_sedi.indirizzo, an_sedi.indirizzo2, an_sedi.cap, an_sedi.citta, an_sedi.provincia, an_sedi.id_nazione, an_sedi.telefono, an_sedi.cellulare
+ FROM an_sedi
+ INNER JOIN an_anagrafiche ON an_anagrafiche.idanagrafica = an_sedi.idanagrafica
+ WHERE an_sedi.idanagrafica = '.prepare($idanagrafica).' AND an_sedi.id = '.prepare($idsede)
+ );
+ } else {
+ $result = $dbo->fetchOne(
+ 'SELECT ragione_sociale, NULL AS nomesede, indirizzo, indirizzo2, cap, citta, provincia, id_nazione, telefono, cellulare
+ FROM an_anagrafiche
+ WHERE idanagrafica = '.prepare($idanagrafica)
+ );
+ }
+
+ return $result ?: [];
+}
+
+/**
+ * Compone un indirizzo HTML compatto adatto all'etichetta.
+ */
+function formatShippingAddress($data)
+{
+ $lines = [];
+
+ if (!empty($data['ragione_sociale'])) {
+ $lines[] = ''.htmlspecialchars((string) $data['ragione_sociale']).'';
+ }
+ if (!empty($data['nomesede'])) {
+ $lines[] = htmlspecialchars((string) $data['nomesede']);
+ }
+ if (!empty($data['indirizzo'])) {
+ $lines[] = htmlspecialchars((string) $data['indirizzo']);
+ }
+ if (!empty($data['indirizzo2'])) {
+ $lines[] = htmlspecialchars((string) $data['indirizzo2']);
+ }
+
+ $city = trim((string) ($data['cap'] ?? '').' '.(string) ($data['citta'] ?? ''));
+ if (!empty($data['provincia'])) {
+ $city .= ' ('.htmlspecialchars((string) $data['provincia']).')';
+ }
+ if ($city !== '') {
+ $lines[] = htmlspecialchars($city);
+ }
+
+ if (!empty($data['id_nazione'])) {
+ $nazione = Nazione::find($data['id_nazione']);
+ if ($nazione && $nazione['iso2'] !== 'IT') {
+ $lines[] = htmlspecialchars((string) $nazione->getTranslation('title'));
+ }
+ }
+
+ $contacts = array_filter([
+ !empty($data['telefono']) ? tr('Tel').': '.htmlspecialchars((string) $data['telefono']) : '',
+ !empty($data['cellulare']) ? tr('Cell').': '.htmlspecialchars((string) $data['cellulare']) : '',
+ ]);
+ if (!empty($contacts)) {
+ $lines[] = implode(' - ', $contacts);
+ }
+
+ return implode('
', $lines);
+}
+
+$tipo_doc = $documento->tipo ? $documento->tipo->getTranslation('title') : tr('DDT');
+$tipo_doc_normalized = mb_strtolower(trim((string) $tipo_doc));
+$is_outgoing = $tipo_doc_normalized === mb_strtolower('Ddt in uscita');
+
+// Logica coerente con la stampa DDT standard OSM:
+// - DDT in uscita: partenza azienda, destinazione cliente
+// - altri DDT: partenza cliente, destinazione azienda
+if ($is_outgoing) {
+ $sender_data = getShippingAddress($dbo, $id_azienda, $documento['idsede_partenza']);
+ $recipient_data = getShippingAddress($dbo, $id_cliente, $documento['idsede_destinazione']);
+} else {
+ $sender_data = getShippingAddress($dbo, $id_cliente, $documento['idsede_partenza']);
+ $recipient_data = getShippingAddress($dbo, $id_azienda, $documento['idsede_destinazione']);
+}
+
+$numero = !empty($documento['numero_esterno']) ? $documento['numero_esterno'] : $documento['numero'];
+
+$notes = [];
+if (!empty($documento['note'])) {
+ $notes[] = nl2br(htmlspecialchars((string) $documento['note']));
+}
+if (!empty($documento['note_aggiuntive'])) {
+ $notes[] = ''.tr('Note aggiuntive').':
'.nl2br(htmlspecialchars((string) $documento['note_aggiuntive']));
+}
+$notes_block = '';
+if (!empty($notes)) {
+ $notes_block = ''.tr('Note').'
'.implode('
', $notes).'
';
+}
+
+$custom = [
+ 'mittente' => formatShippingAddress($sender_data),
+ 'destinatario' => formatShippingAddress($recipient_data),
+ 'tipo_doc' => $tipo_doc,
+ 'numero' => $numero,
+ 'data' => Translator::dateToLocale($documento['data']),
+ 'n_colli' => !empty($documento['n_colli']) ? $documento['n_colli'] : '',
+ 'notes_block' => $notes_block,
+];
+
+// Accesso coerente con la stampa DDT standard.
+if ((auth_osm()->getUser()['gruppo'] === 'Clienti' && $id_cliente != auth_osm()->getUser()['idanagrafica'] && !AuthOSM::admin()) || Modules::getPermission($documento->module) === '-') {
+ exit(tr('Non hai i permessi per questa stampa!'));
+}
diff --git a/update/2_12_1.sql b/update/2_12_1.sql
new file mode 100644
index 000000000..2e33c4ee4
--- /dev/null
+++ b/update/2_12_1.sql
@@ -0,0 +1,65 @@
+-- Stampa Etichetta spedizione per il modulo DDT.
+
+SET @id_module_ddt := (
+ SELECT `id`
+ FROM `zz_modules`
+ WHERE `directory` = 'ddt'
+ LIMIT 1
+);
+
+INSERT INTO `zz_prints`
+ (`id_module`, `is_record`, `name`, `directory`, `previous`, `options`, `icon`, `version`, `compatibility`, `order`, `predefined`, `enabled`, `available_options`)
+SELECT
+ @id_module_ddt,
+ 1,
+ 'Etichetta spedizione',
+ 'etichetta_spedizione_ddt',
+ 'idddt',
+ '{"format":"A6","orientation":"P","margin-top":5,"margin-bottom":5,"margin-left":5,"margin-right":5,"hide-header":true,"last-page-footer":false}',
+ 'fa fa-print',
+ '1.0',
+ '2.10.0',
+ 20,
+ 0,
+ 1,
+ '[]'
+WHERE @id_module_ddt IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `zz_prints`
+ WHERE `name` = 'Etichetta spedizione'
+ AND `id_module` = @id_module_ddt
+ );
+
+SET @id_print_etichetta := (
+ SELECT `id`
+ FROM `zz_prints`
+ WHERE `name` = 'Etichetta spedizione'
+ AND `id_module` = @id_module_ddt
+ LIMIT 1
+);
+
+INSERT INTO `zz_prints_lang` (`id_lang`, `id_record`, `title`, `filename`)
+SELECT 1, @id_print_etichetta, 'Etichetta spedizione', 'Etichetta spedizione DDT'
+WHERE @id_print_etichetta IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `zz_prints_lang`
+ WHERE `id_record` = @id_print_etichetta
+ AND `id_lang` = 1
+ );
+
+INSERT INTO `zz_prints_lang` (`id_lang`, `id_record`, `title`, `filename`)
+SELECT 2, @id_print_etichetta, 'Shipping label', 'DDT shipping label'
+WHERE @id_print_etichetta IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `zz_prints_lang`
+ WHERE `id_record` = @id_print_etichetta
+ AND `id_lang` = 2
+ );
+
+UPDATE `zz_prints`
+SET `icon` = 'fa fa-print'
+WHERE `name` = 'Etichetta spedizione'
+ AND `id_module` = @id_module_ddt;