pasquale991 commited on
Commit
c99bfb4
·
verified ·
1 Parent(s): b968882

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +282 -168
src/streamlit_app.py CHANGED
@@ -4,6 +4,96 @@ import os
4
  import streamlit as st
5
  import numpy as np # Importa numpy per np.nan
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
8
  """
9
  Genera un file .dat per Poste Italiane unendo i dati da due DataFrame.
@@ -23,45 +113,38 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
23
  df_shipment_order = df_shipment_order_input.copy()
24
 
25
  # Assicurati che i nomi delle colonne siano in minuscolo per una maggiore robustezza
 
26
  df_shipping_mark.columns = df_shipping_mark.columns.str.lower()
27
  df_shipment_order.columns = df_shipment_order.columns.str.lower()
28
 
29
  # Verifica la presenza delle colonne necessarie nel file shipping mark
30
- # Ora include totalweight e totalvolume
31
  required_shipping_mark_cols = ['shippingmark', 'orderref', 'totalweight', 'totalvolume']
32
  if not all(col in df_shipping_mark.columns for col in required_shipping_mark_cols):
33
- st.error(f"Il file delle shipping mark deve contenere le colonne: {required_shipping_mark_cols}")
34
  return None, None
35
 
36
- # Mapping dei nomi delle colonne dal file ShipmentOrder_ ai nomi interni/standardizzati.
37
- # **IMPORTANTE**: Le chiavi di questo dizionario devono corrispondere esattamente
38
- # ai nomi delle colonne (in minuscolo) presenti nel tuo file ShipmentOrder_.xlsx.
39
- # Se le tue colonne sono "consegnee.partner.address.address", DEVONO ESSERE ESATTAMENTE COSÌ.
40
- # Se sono flat come "consignee_address", devi modificare queste chiavi.
41
- column_mapping = {
42
- # Campi destinatario e data che si aspettano dal file ShipmentOrder_.
43
  'orderdate': 'order_date', # Per Campo 7
44
  'consegnee.partner.businessname': 'ragione_sociale_destinatario', # Campo 20
45
- 'consegnee.partner.contact.name': 'consignee_contact', # Campo 21
46
- 'consegnee.partner.address.streettype': 'consignee_street_type', # Campo 22
47
- 'consegnee.partner.address.address': 'consignee_address', # Campo 23
48
- 'consegnee.partner.address.addressnumber': 'consignee_house_number', # Campo 24
49
- 'consegnee.partner.phone.number': 'consignee_phone', # Campo 25
50
- 'consegnee.partner.address.zipcode': 'consignee_zip_code', # Campo 26
51
- 'consegnee.partner.address.city': 'consignee_city', # Campo 27
52
- 'consegnee.partner.address.province': 'consignee_province', # Campo 28 (assumendo che esista questa colonna per la provincia)
53
- # Nota: 'consegnee.partner.address.country' non è nel tracciato .dat fornito.
54
  }
55
-
56
- # Applica il mapping delle colonne a df_shipment_order.
57
- # La funzione lambda `x` viene applicata ai nomi delle colonne attuali (già in minuscolo)
58
- # e poi cerca nel `column_mapping`.
59
- df_shipment_order = df_shipment_order.rename(columns=lambda x: column_mapping.get(x, x))
60
 
61
  # Assicurati che 'orderref' e 'order_date' siano presenti in df_shipment_order dopo il rename
62
  required_shipment_order_cols = ['orderref', 'order_date']
63
  if not all(col in df_shipment_order.columns for col in required_shipment_order_cols):
64
- st.error(f"Il file degli ordini deve contenere le colonne: {required_shipment_order_cols} (anche dopo il mapping).")
65
  return None, None
66
 
67
  # Pulisci le colonne di join per una corrispondenza robusta (rimuovi spazi e assicurati che siano stringhe)
@@ -78,82 +161,6 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
78
  st.warning("Nessun record corrispondente trovato dopo la join. Assicurati che 'orderRef' sia presente e corrisponda in entrambi i file.")
79
  return None, None
80
 
81
- # Dizionario per i valori di default/fissi per i campi obbligatori o predeterminati
82
- # Basato sul NUOVO tracciato fornito.
83
- fixed_and_default_values = {
84
- # Campi fissi Mittente (Campi 1-18) - Valori dalla tua ultima tabella
85
- 'codice_identificativo_cliente_fixed': 'CLI000000009', # Campo 1
86
- 'id_ldv_fixed': 'FIL01', # Campo 2
87
- 'tipo_spedizione_fixed': 'N', # Campo 3
88
- 'attivazione_ritiro_fixed': 'N', # Campo 4
89
- 'stampa_ldv_fixed': 'N', # Campo 5
90
- # Campo 6 (orderref) dal file
91
- # Campo 7 (data_spedizione_derived) è derivato da order_date dal file
92
- 'ritiro_mp_fixed': 'P', # Campo 8
93
- 'codice_indirizzo_mittente_fixed': 'IND001', # Campo 9
94
- 'ragione_sociale_mittente_fixed': 'Poste Logistics', # Campo 10
95
- 'contatto_mittente_fixed': 'postelogistics@poste.it', # Campo 11
96
- 'tipo_via_mittente_fixed': 'Via', # Campo 12
97
- 'via_mittente_fixed': 'Via da Inserire', # Campo 13
98
- 'numero_civico_mittente_fixed': '54', # Campo 14
99
- 'numero_telefono_mittente_fixed': '60898001', # Campo 15
100
- 'cap_mittente_fixed': '70019', # Campo 16
101
- 'localita_mittente_fixed': 'Vidigulfo', # Campo 17
102
- 'provincia_mittente_fixed': 'PV', # Campo 18
103
-
104
- # Altri campi fissi o con default obbligatorio dal NUOVO tracciato
105
- 'service_code_fixed': 'STD', # Campo 34
106
- 'branch_code_fixed': 'FIL05', # Campo 43
107
- 'client_type_fixed': 'MGCS', # Campo 49
108
- }
109
-
110
- # Definizione dettagliata del layout del file .dat.
111
- # INCLUDE SOLO I CAMPI DALL'ULTIMO TRACCIATO FORNITO DALL'UTENTE.
112
- # 'source_type': 'fixed' (valore dal dizionario fixed_and_default_values),
113
- # 'file' (valore dal DataFrame unito df_merged),
114
- # 'derived' (calcolato, es. data_spedizione_derived)
115
- dat_format_spec = [
116
- # BLOCCO MITTENTE FISSO (Campi 1-18 dal NUOVO tracciato)
117
- {'name': 'codice_identificativo_cliente_fixed', 'length': 12, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 1
118
- {'name': 'id_ldv_fixed', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 2
119
- {'name': 'tipo_spedizione_fixed', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 3
120
- {'name': 'attivazione_ritiro_fixed', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 4
121
- {'name': 'stampa_ldv_fixed', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 5
122
- {'name': 'orderref', 'length': 25, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 6 (Numero Riferimento Spedizione - dal file shipping mark)
123
- {'name': 'order_date', 'length': 8, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 7 (Data della spedizione - da orderDate dal file shipmentOrder)
124
- {'name': 'ritiro_mp_fixed', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 8
125
- {'name': 'codice_indirizzo_mittente_fixed', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 9
126
- {'name': 'ragione_sociale_mittente_fixed', 'length': 40, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 10
127
- {'name': 'contatto_mittente_fixed', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 11
128
- {'name': 'tipo_via_mittente_fixed', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 12
129
- {'name': 'via_mittente_fixed', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 13
130
- {'name': 'numero_civico_mittente_fixed','length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 14
131
- {'name': 'numero_telefono_mittente_fixed', 'length': 15, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 15
132
- {'name': 'cap_mittente_fixed', 'length': 9, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 16
133
- {'name': 'localita_mittente_fixed', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 17
134
- {'name': 'provincia_mittente_fixed', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 18
135
- {'name': 'filler_19_gap', 'length': 60, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False}, # Gap tra 18 e 20
136
-
137
- # Campi 20-30 (Destinatario e Colli)
138
- {'name': 'ragione_sociale_destinatario','length': 40, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 20 (da file shipmentOrder)
139
- {'name': 'consignee_contact', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 21 (da file shipmentOrder)
140
- {'name': 'consignee_street_type', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 22 (da file shipmentOrder)
141
- {'name': 'consignee_address', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 23 (da file shipmentOrder)
142
- {'name': 'consignee_house_number', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 24 (da file shipmentOrder)
143
- {'name': 'consignee_phone', 'length': 15, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 25 (da file shipmentOrder)
144
- {'name': 'consignee_zip_code', 'length': 9, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 26 (da file shipmentOrder)
145
- {'name': 'consignee_city', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 27 (da file shipmentOrder)
146
- {'name': 'consignee_province', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 28 (da file shipmentOrder)
147
- {'name': 'totalvolume', 'length': 3, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': True}, # 29 (Numero colli - da totalVolume da shippingMark)
148
- {'name': 'totalweight', 'length': 7, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': True}, # 30 (Peso colli - da totalWeight da shippingMark)
149
-
150
- # Campi 34, 42, 43, 49 (Servizio, Identificativo, Filiale, Tipo Cliente)
151
- {'name': 'service_code_fixed', 'length': 3, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 34 (STD)
152
- {'name': 'shippingmark', 'length': 25, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 42 (Identificativo collo - shippingMark)
153
- {'name': 'branch_code_fixed', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 43 (FIL05)
154
- {'name': 'client_type_fixed', 'length': 4, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # 49 (MGCS)
155
- ]
156
-
157
  # Genera il contenuto del file .dat
158
  dat_content = []
159
  for index, row in df_merged.iterrows():
@@ -162,62 +169,63 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
162
  for field_spec in dat_format_spec:
163
  field_name = field_spec['name']
164
  length = field_spec['length']
165
- is_numeric_zero_padded = field_spec['numeric_pad_zero']
166
- dec_places = field_spec['decimals']
167
- is_mandatory = field_spec['mandatory']
 
 
168
 
169
  value_to_format = ''
170
 
171
- if field_name.endswith('_fixed'): # Campi fissi
172
  value_to_format = fixed_and_default_values.get(field_name, '')
173
- elif field_name.endswith('_derived'): # Campi derivati (es. data)
174
- if field_name == 'order_date': # Correzione: order_date è da 'file'
175
- value_to_format = row.get(field_name, '')
 
 
 
 
 
 
 
176
  if pd.notna(value_to_format):
177
- # Tenta di convertire la data nel formato AAAAMMGG
178
  try:
179
- # Se è già una stringa e ha la lunghezza giusta, usala
180
- if isinstance(value_to_format, str) and len(value_to_format) == 8 and value_to_format.isdigit():
 
 
181
  pass
182
- else: # Altrimenti, prova a convertirla da un formato data pandas
183
  value_to_format = pd.to_datetime(value_to_format).strftime("%Y%m%d")
184
  except Exception:
185
  value_to_format = '' # Fallback se la conversione fallisce
186
  else:
187
  value_to_format = '' # Se NaN, usa vuoto
188
-
189
- elif field_name == 'data_spedizione_derived': # Data corrente per il campo 7, se non da file
190
- value_to_format = datetime.now().strftime("%Y%m%d")
191
- else:
192
- value_to_format = '' # Fallback per altri derivati
193
- elif field_name == 'filler_19_gap':
194
- value_to_format = ' ' * length
195
- else: # Campi da prendere direttamente dal DataFrame unito (file)
196
- value_to_format = row.get(field_name)
197
- # Gestisci NaN dopo la merge, se il campo dal file non è presente per quel record
198
- if pd.isna(value_to_format):
199
- value_to_format = ''
200
 
201
  # Conversione in stringa e pulizia iniziale
202
  value_str = str(value_to_format).strip()
203
 
204
- # Se il campo è obbligatorio e il valore è ancora vuoto dopo aver cercato, usa il default
205
- if is_mandatory and not value_str:
206
- if is_numeric_zero_padded:
 
 
 
207
  value_str = '0' * length
208
  else:
209
- value_str = ' ' * length # Riempie con spazi se stringa obbligatoria vuota
210
 
211
  # Formattazione finale (allineamento, padding)
212
- if is_numeric_zero_padded:
213
  try:
214
  clean_value = value_str.replace(',', '.') # Sostituisce virgole con punti
215
-
216
- if dec_places > 0:
217
- num_value = float(clean_value) * (10**dec_places)
218
- formatted_value = str(int(round(num_value))).rjust(length, '0')
219
- else:
220
- formatted_value = str(int(float(clean_value))).rjust(length, '0')
221
  except ValueError:
222
  formatted_value = '0'.rjust(length, '0') # Fallback se la conversione fallisce
223
  else:
@@ -228,11 +236,12 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
228
 
229
  dat_content.append("".join(record_parts))
230
 
231
- # Genera il nome del file con timestamp
232
  now = datetime.now()
233
  filename_timestamp = now.strftime("%Y%m%d%H%M%S%f")[:-4] # AAAAMMGGhhmmsscc
234
- dat_filename = f"SDA{filename_timestamp}.dat"
235
- dis_filename = f"SDA{filename_timestamp}.dis"
 
236
 
237
  # Usa la directory temporanea /tmp che è scrivibile su Hugging Face Spaces
238
  temp_dir = "/tmp"
@@ -244,12 +253,10 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
244
  with open(dat_filepath, 'w', encoding='utf-8') as f: # Specifica encoding utf-8
245
  for line in dat_content:
246
  f.write(line + '\n')
247
- st.success(f"File '{dat_filename}' generato con successo.")
248
-
249
  with open(dis_filepath, 'w') as f:
250
  pass # Crea un file vuoto
251
- st.success(f"File '{dis_filename}' generato con successo.")
252
-
253
  return dat_filepath, dis_filepath
254
 
255
  except pd.errors.EmptyDataError:
@@ -263,6 +270,110 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
263
  st.warning("Verifica che i nomi delle colonne nei tuoi file Excel corrispondano alle aspettative del programma, specialmente 'orderRef' e i campi relativi al destinatario. Ricorda che l'intestazione è alla seconda riga.")
264
  return None, None
265
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  # --- Sezione App Streamlit ---
267
  def app():
268
  st.set_page_config(layout="centered", page_title="Generatore File Poste .DAT")
@@ -289,55 +400,58 @@ def app():
289
  df_shipping_mark = pd.read_excel(shipping_mark_file, header=1)
290
  df_shipment_order = pd.read_excel(shipment_order_file, header=1)
291
 
292
- st.write("Colonne lette da ShippingMark_:", df_shipping_mark.columns.tolist())
293
- st.write("Colonne lette da ShipmentOrder_:", df_shipment_order.columns.tolist())
294
-
295
- # DEBUGGING: Mostra il DataFrame ShipmentOrder_ dopo la lettura e il mapping
296
- st.subheader("Anteprima del DataFrame ShipmentOrder_ (dopo la lettura e il mapping delle colonne):")
297
- st.dataframe(df_shipment_order)
298
-
299
  # Esegui la funzione di generazione
300
  dat_filepath, dis_filepath = generate_poste_dat_file(df_shipping_mark, df_shipment_order)
301
 
302
- # DEBUGGING: Per mostrare df_merged, la variabile deve essere accessibile qui.
303
- # Questo richiede una piccola modifica nella funzione generate_poste_dat_file
304
- # per restituire anche df_merged, oppure duplicare la logica di merge qui solo per il debug.
305
- # Per semplicità, la lascio commentata qui a meno che non ci sia una richiesta specifica.
306
- # Oppure puoi ispezionare df_shipment_order e le colonne mappate per vedere i dati.
307
-
308
-
309
  except Exception as e:
310
- st.error(f"Errore nella lettura dei file Excel: {e}")
311
  st.warning("Assicurati che i file siano in formato .xlsx, non siano corrotti e che l'intestazione sia effettivamente alla seconda riga.")
312
 
313
  if dat_filepath and dis_filepath:
314
- st.subheader("Download dei file generati:")
 
315
 
 
 
 
316
  # Per consentire il download in Streamlit, dobbiamo leggere il contenuto del file
317
  # in modalità binaria ('rb') e passarlo al download_button.
318
  with open(dat_filepath, "rb") as f:
319
  st.download_button(
320
- label=f"Scarica {os.path.basename(dat_filepath)}",
321
  data=f.read(),
322
- file_name=os.path.basename(dat_filepath),
323
- mime="text/plain" # Tipo MIME per file di testo generici
 
324
  )
325
 
326
  with open(dis_filepath, "rb") as f:
327
  st.download_button(
328
- label=f"Scarica {os.path.basename(dis_filepath)}",
329
  data=f.read(),
330
- file_name=os.path.basename(dis_filepath),
331
- mime="text/plain"
 
332
  )
333
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  st.markdown("---")
335
  st.info("""
336
- **Nota sulle colonne:**
337
- Assicurati che i tuoi file Excel contengano le colonne corrette.
338
- - Il file `ShippingMark_` deve contenere le colonne 'shippingMark', 'orderRef', 'totalWeight' e 'totalVolume'.
339
- - Il file `ShipmentOrder_` deve contenere 'orderRef', 'orderDate' e i campi specifici del destinatario (es. 'consegnee.partner.businessname', 'consegnee.partner.address.address', ecc.).
340
- I nomi delle colonne nel tuo file `ShipmentOrder_` devono corrispondere **esattamente** (ignorando maiuscole/minuscole) ai nomi completi con la notazione a punti (es. 'consegnee.partner.address.address') per un corretto mapping.
341
  """)
342
 
343
  if __name__ == "__main__":
 
4
  import streamlit as st
5
  import numpy as np # Importa numpy per np.nan
6
 
7
+ # --- Dati di Configurazione del Tracciato ---
8
+ # Dizionario per i valori di default/fissi per i campi specifici del .dat
9
+ # Basato sull'ultimo tracciato fornito nell'immagine.
10
+ fixed_and_default_values = {
11
+ # Campi fissi Mittente (Campi 1-18) - Valori dalla tabella utente
12
+ 'codice_identificativo_cliente': 'CLI 000000009', # Campo 1
13
+ 'id_ldv': 'FIL01', # Campo 2
14
+ 'tipo_spedizione': 'N', # Campo 3
15
+ 'attivazione_ritiro': 'N', # Campo 4
16
+ 'stampa_ldv': 'N', # Campo 5
17
+ # Campo 6 (Numero Riferimento Spedizione) dal file shippingMark
18
+ # Campo 7 (Data Spedizione) dal file shipmentOrder.orderDate
19
+ 'ritiro_mp': 'P', # Campo 8
20
+ 'codice_indirizzo_mittente': 'IND001', # Campo 9
21
+ 'ragione_sociale_mittente': 'Poste Logistics + Technolt', # Campo 10
22
+ 'contatto_mittente': 'postelogistics@poste.it', # Campo 11
23
+ 'tipo_via_mittente': 'Viale', # Campo 12
24
+ 'via_mittente': 'delle Industrie', # Campo 13
25
+ 'numero_civico_mittente': '9-11-13', # Campo 14
26
+ 'numero_telefono_mittente': '60898001', # Campo 15
27
+ 'cap_mittente': '27018', # Campo 16
28
+ 'localita_mittente': 'Vidigulfo', # Campo 17
29
+ 'provincia_mittente': 'PV', # Campo 18
30
+
31
+ # Altri campi fissi o con default obbligatorio
32
+ 'codice_servizio': 'EXT', # Campo 34
33
+ 'filiale': 'MD', # Campo 43
34
+ 'tipo_cliente': 'MGCS', # Campo 49
35
+ }
36
+
37
+ # Definizione dettagliata del layout del file .dat.
38
+ # INCLUDE SOLO I CAMPI DALL'ULTIMO TRACCIATO FORNITO DALL'UTENTE.
39
+ # 'name': Nome interno per il campo nel codice.
40
+ # 'length': Lunghezza fissa del campo.
41
+ # 'numeric_pad_zero': True se il campo è numerico e va riempito con '0' a sinistra.
42
+ # 'decimals': Numero di decimali (per campi numerici di valuta, non usati nel nuovo tracciato).
43
+ # 'mandatory': True se il campo è obbligatorio (*) o (**) nel tracciato.
44
+ # 'source': Indica la fonte del valore: 'fixed' (da fixed_and_default_values),
45
+ # 'shipping_mark_file' (dal DataFrame shippingMark),
46
+ # 'shipment_order_file' (dal DataFrame shipmentOrder),
47
+ # 'derived' (calcolato, es. data corrente o campi concatenati),
48
+ # 'filler' (gap nel tracciato).
49
+ # 'excel_col': Il nome esatto della colonna nel file Excel di origine (se 'source' è 'shipping_mark_file' o 'shipment_order_file').
50
+ # 'description': Descrizione del campo (per il file di specifiche).
51
+ # 'start_pos': Posizione iniziale del campo (per il file di specifiche).
52
+ dat_format_spec = [
53
+ # Id | Nome | Obbl | Tipo | Lungh | Inizio | Descrizione | Default | Campi
54
+ {'id': '1', 'name': 'codice_identificativo_cliente', 'length': 12, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Valore costante che identifica il cliente, viene comunicato al cliente al momento dell'attivazione del servizio, un esempio di codice è CLI 000000009", 'start_pos': 1},
55
+ {'id': '2', 'name': 'id_ldv', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Fillare con la numerazione della ldv", 'start_pos': 13},
56
+ {'id': '3', 'name': 'tipo_spedizione', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Indica il tipo di spedizione N Porto Franco P porto assegnato T triangolazione", 'start_pos': 43},
57
+ {'id': '4', 'name': 'attivazione_ritiro', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Indica se deve essere attivato il ritiro. In assenza di accordi con Poste impostare il valore costante N", 'start_pos': 44},
58
+ {'id': '5', 'name': 'stampa_ldv', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Mettere N", 'start_pos': 45},
59
+ {'id': '6', 'name': 'numero_riferimento_spedizione', 'length': 25, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipping_mark_file', 'excel_col': 'orderref', 'description': "Numero Rif. Ord. Cliente, è il riferimento della spedizione per il cliente", 'start_pos': 46},
60
+ {'id': '7', 'name': 'data_spedizione', 'length': 8, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipment_order_file', 'excel_col': 'orderdate', 'description': "Data della spedizione in formato AAAAMMGG (Mese, Giorno)", 'start_pos': 71},
61
+ {'id': '8', 'name': 'ritiro_mp', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Indica il tipo di ritiro. In assenza di accordi con Poste impostare il valore costante P", 'start_pos': 79},
62
+ {'id': '9', 'name': 'codice_indirizzo_mittente', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Identifica la codifica dell'indirizzo di ritiro del mittente", 'start_pos': 80},
63
+ {'id': '10', 'name': 'ragione_sociale_mittente', 'length': 40, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Ignorato se è valorizzato il campo 9", 'start_pos': 100},
64
+ {'id': '11', 'name': 'contatto_mittente', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Ignorato se è valorizzato il campo 9", 'start_pos': 140},
65
+ {'id': '12', 'name': 'tipo_via_mittente', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Ignorato se è valorizzato il campo 9", 'start_pos': 160},
66
+ {'id': '13', 'name': 'via_mittente', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Ignorato se è valorizzato il campo 9", 'start_pos': 165},
67
+ {'id': '14', 'name': 'numero_civico_mittente', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Ignorato se è valorizzato il campo 9", 'start_pos': 195},
68
+ {'id': '15', 'name': 'numero_telefono_mittente', 'length': 15, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Ignorato se è valorizzato il campo 9", 'start_pos': 200},
69
+ {'id': '16', 'name': 'cap_mittente', 'length': 9, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Ignorato se è valorizzato il campo 9", 'start_pos': 215},
70
+ {'id': '17', 'name': 'localita_mittente', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Ignorato se è valorizzato il campo 9", 'start_pos': 224},
71
+ {'id': '18', 'name': 'provincia_mittente', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Ignorato se è valorizzato il campo 9", 'start_pos': 254},
72
+
73
+ # Filler per il gap tra Campo 18 (fine a 255) e Campo 20 (inizio a 315)
74
+ {'id': '', 'name': 'filler_gap_18_20', 'length': 60, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source': 'filler', 'excel_col': None, 'description': "Gap tra i campi 18 e 20", 'start_pos': 256}, # 315 - 255 = 60
75
+
76
+ # Campi Destinatario e Colli
77
+ {'id': '20', 'name': 'ragione_sociale_destinatario', 'length': 40, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipment_order_file', 'excel_col': 'consegnee.partner.businessname', 'description': "Ragione sociale del destinatario", 'start_pos': 315},
78
+ {'id': '21', 'name': 'contatto_destinatario', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipment_order_file', 'excel_col': 'consegnee.partner.name', 'description': "Nome Contatto presso il Destinatario", 'start_pos': 355},
79
+ {'id': '22', 'name': 'tipo_via_destinatario', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipment_order_file', 'excel_col': 'consegnee.partner.address.streettype', 'description': "Descrizione tipo via", 'start_pos': 375},
80
+ {'id': '23', 'name': 'via_destinatario', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipment_order_file', 'excel_col': 'consegnee.partner.address.address', 'description': "Indirizzo del destinatario", 'start_pos': 380},
81
+ {'id': '24', 'name': 'numero_civico_destinatario', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipment_order_file', 'excel_col': 'consegnee.partner.address.addressnumber', 'description': "Numero Civico Destinatario allineato a sinistra", 'start_pos': 410},
82
+ {'id': '25', 'name': 'numero_telefono_destinatario','length': 15, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipment_order_file', 'excel_col': 'consegnee.partner.phone.number', 'description': "Numero di telefono Destinatario", 'start_pos': 415},
83
+ {'id': '26', 'name': 'cap_destinatario', 'length': 9, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipment_order_file', 'excel_col': 'consegnee.partner.address.zipcode', 'description': "CAP del destinatario allineato a sinistra", 'start_pos': 430},
84
+ {'id': '27', 'name': 'localita_destinatario', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipment_order_file', 'excel_col': 'consegnee.partner.address.city', 'description': "Località del destinatario", 'start_pos': 439},
85
+ {'id': '28', 'name': 'provincia_destinatario', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipment_order_file', 'excel_col': 'consegnee.partner.address.country', 'description': "Provincia del destinatario", 'start_pos': 469}, # Assumo 'country' è provincia 2 char
86
+ {'id': '29', 'name': 'numero_colli', 'length': 3, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': True, 'source': 'shipping_mark_file', 'excel_col': 'totalvolume', 'description': "Numero dei colli totali che compongono la spedizione", 'start_pos': 471},
87
+ {'id': '30', 'name': 'peso_colli', 'length': 7, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': True, 'source': 'shipping_mark_file', 'excel_col': 'totalweight', 'description': "Peso espresso in grammi del singolo collo", 'start_pos': 474},
88
+
89
+ # Altri Campi Obbligatori dal tracciato
90
+ {'id': '34', 'name': 'codice_servizio', 'length': 3, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Vedi codifica", 'start_pos': 481},
91
+ {'id': '42', 'name': 'identificativo_collo', 'length': 25, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipping_mark_file', 'excel_col': 'shippingmark', 'description': "Identificativo del singolo collo (barcode)", 'start_pos': 484},
92
+ {'id': '43', 'name': 'filiale', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Codice Filiale per il ritiro (Comunicato da Poste)", 'start_pos': 509},
93
+ {'id': '49', 'name': 'tipo_cliente', 'length': 4, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Valore costante: MGCS", 'start_pos': 511},
94
+ ]
95
+
96
+ # --- Funzione per generare il file DAT e DIS ---
97
  def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
98
  """
99
  Genera un file .dat per Poste Italiane unendo i dati da due DataFrame.
 
113
  df_shipment_order = df_shipment_order_input.copy()
114
 
115
  # Assicurati che i nomi delle colonne siano in minuscolo per una maggiore robustezza
116
+ # Nota: per nomi con punti come 'consegnee.partner.address.address', .lower() li mantiene così.
117
  df_shipping_mark.columns = df_shipping_mark.columns.str.lower()
118
  df_shipment_order.columns = df_shipment_order.columns.str.lower()
119
 
120
  # Verifica la presenza delle colonne necessarie nel file shipping mark
 
121
  required_shipping_mark_cols = ['shippingmark', 'orderref', 'totalweight', 'totalvolume']
122
  if not all(col in df_shipping_mark.columns for col in required_shipping_mark_cols):
123
+ st.error(f"Errore: Il file delle shipping mark deve contenere le colonne: {required_shipping_mark_cols}")
124
  return None, None
125
 
126
+ # Applica il mapping delle colonne a df_shipment_order.
127
+ # Le chiavi del mapping DEVONO corrispondere esattamente ai nomi delle colonne (in minuscolo)
128
+ # nel tuo file Excel.
129
+ # Esempio: se la colonna in Excel è "consegnee.partner.businessName", la chiave qui deve essere "consegnee.partner.businessname".
130
+ excel_col_mapping = {
 
 
131
  'orderdate': 'order_date', # Per Campo 7
132
  'consegnee.partner.businessname': 'ragione_sociale_destinatario', # Campo 20
133
+ 'consegnee.partner.name': 'contatto_destinatario', # Campo 21
134
+ 'consegnee.partner.address.streettype': 'tipo_via_destinatario', # Campo 22
135
+ 'consegnee.partner.address.address': 'via_destinatario', # Campo 23
136
+ 'consegnee.partner.address.addressnumber': 'numero_civico_destinatario', # Campo 24
137
+ 'consegnee.partner.phone.number': 'numero_telefono_destinatario', # Campo 25
138
+ 'consegnee.partner.address.zipcode': 'cap_destinatario', # Campo 26
139
+ 'consegnee.partner.address.city': 'localita_destinatario', # Campo 27
140
+ 'consegnee.partner.address.country': 'provincia_destinatario', # Campo 28 (assunto essere codice provincia 2 char)
 
141
  }
142
+ df_shipment_order = df_shipment_order.rename(columns=lambda x: excel_col_mapping.get(x, x))
 
 
 
 
143
 
144
  # Assicurati che 'orderref' e 'order_date' siano presenti in df_shipment_order dopo il rename
145
  required_shipment_order_cols = ['orderref', 'order_date']
146
  if not all(col in df_shipment_order.columns for col in required_shipment_order_cols):
147
+ st.error(f"Errore: Il file degli ordini deve contenere le colonne: {required_shipment_order_cols} (anche dopo il mapping).")
148
  return None, None
149
 
150
  # Pulisci le colonne di join per una corrispondenza robusta (rimuovi spazi e assicurati che siano stringhe)
 
161
  st.warning("Nessun record corrispondente trovato dopo la join. Assicurati che 'orderRef' sia presente e corrisponda in entrambi i file.")
162
  return None, None
163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  # Genera il contenuto del file .dat
165
  dat_content = []
166
  for index, row in df_merged.iterrows():
 
169
  for field_spec in dat_format_spec:
170
  field_name = field_spec['name']
171
  length = field_spec['length']
172
+ is_numeric_pad_zero = field_spec['numeric_pad_zero']
173
+ decimals = field_spec['decimals']
174
+ mandatory = field_spec['mandatory']
175
+ source_type = field_spec['source']
176
+ excel_col_name = field_spec['excel_col'] # Nome della colonna in Excel (mappata)
177
 
178
  value_to_format = ''
179
 
180
+ if source_type == 'fixed':
181
  value_to_format = fixed_and_default_values.get(field_name, '')
182
+ elif source_type == 'shipping_mark_file':
183
+ value_to_format = row.get(excel_col_name)
184
+ if pd.isna(value_to_format):
185
+ value_to_format = ''
186
+ elif source_type == 'shipment_order_file':
187
+ value_to_format = row.get(excel_col_name)
188
+ if pd.isna(value_to_format):
189
+ value_to_format = ''
190
+ # Gestione specifica per 'order_date' (Campo 7) per formattazione AAAAMMGG
191
+ if field_name == 'data_spedizione':
192
  if pd.notna(value_to_format):
 
193
  try:
194
+ if isinstance(value_to_format, datetime): # Se è già un oggetto datetime
195
+ value_to_format = value_to_format.strftime("%Y%m%d")
196
+ elif isinstance(value_to_format, str) and len(value_to_format) == 8 and value_to_format.isdigit():
197
+ # Se è già una stringa nel formato giusto, usala così com'è
198
  pass
199
+ else: # Prova a convertirla
200
  value_to_format = pd.to_datetime(value_to_format).strftime("%Y%m%d")
201
  except Exception:
202
  value_to_format = '' # Fallback se la conversione fallisce
203
  else:
204
  value_to_format = '' # Se NaN, usa vuoto
205
+ elif source_type == 'filler':
206
+ value_to_format = ' ' * length # Riempie il filler con spazi
 
 
 
 
 
 
 
 
 
 
207
 
208
  # Conversione in stringa e pulizia iniziale
209
  value_str = str(value_to_format).strip()
210
 
211
+ # Gestione specifica per Campo 25 (Numero di telefono Destinatario) "se vuoto mettere tanti zeri"
212
+ if field_name == 'numero_telefono_destinatario' and mandatory and not value_str:
213
+ value_str = '0' * length
214
+ # Se il campo è obbligatorio e il valore è ancora vuoto dopo aver cercato/gestito casi speciali, usa il default (spazi o zeri)
215
+ elif mandatory and not value_str:
216
+ if is_numeric_pad_zero:
217
  value_str = '0' * length
218
  else:
219
+ value_str = ' ' * length
220
 
221
  # Formattazione finale (allineamento, padding)
222
+ if is_numeric_pad_zero:
223
  try:
224
  clean_value = value_str.replace(',', '.') # Sostituisce virgole con punti
225
+ # Converti in int dopo aver gestito i decimali (se presenti, altrimenti 0)
226
+ # Nota: nel nuovo tracciato non ci sono campi con 'decimals' > 0, ma la logica è robusta.
227
+ num_val = float(clean_value) * (10**decimals)
228
+ formatted_value = str(int(round(num_val))).rjust(length, '0')
 
 
229
  except ValueError:
230
  formatted_value = '0'.rjust(length, '0') # Fallback se la conversione fallisce
231
  else:
 
236
 
237
  dat_content.append("".join(record_parts))
238
 
239
+ # Genera il nome del file con timestamp (uguale per .dat e .dis)
240
  now = datetime.now()
241
  filename_timestamp = now.strftime("%Y%m%d%H%M%S%f")[:-4] # AAAAMMGGhhmmsscc
242
+ base_filename = f"SDA{filename_timestamp}"
243
+ dat_filename = f"{base_filename}.dat"
244
+ dis_filename = f"{base_filename}.dis"
245
 
246
  # Usa la directory temporanea /tmp che è scrivibile su Hugging Face Spaces
247
  temp_dir = "/tmp"
 
253
  with open(dat_filepath, 'w', encoding='utf-8') as f: # Specifica encoding utf-8
254
  for line in dat_content:
255
  f.write(line + '\n')
256
+
 
257
  with open(dis_filepath, 'w') as f:
258
  pass # Crea un file vuoto
259
+
 
260
  return dat_filepath, dis_filepath
261
 
262
  except pd.errors.EmptyDataError:
 
270
  st.warning("Verifica che i nomi delle colonne nei tuoi file Excel corrispondano alle aspettative del programma, specialmente 'orderRef' e i campi relativi al destinatario. Ricorda che l'intestazione è alla seconda riga.")
271
  return None, None
272
 
273
+ # --- Funzione per generare il testo delle specifiche del tracciato DAT ---
274
+ def generate_dat_format_spec_text():
275
+ spec_text = "Specifiche del Tracciato File .DAT\n\n"
276
+ spec_text += "Id\tNome Campo\tObbligatorio\tTipo\tLunghezza\tInizio\tDescrizione\tDefault / Fonte Dati\n"
277
+ spec_text += "--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n"
278
+
279
+ for field_spec in dat_format_spec:
280
+ field_id = field_spec.get('id', '')
281
+ display_name = field_spec['name'].replace('_', ' ').replace('fixed', '').replace('derived', '').strip().title()
282
+
283
+ # Gestione speciale per nomi lunghi per migliorare la leggibilità
284
+ if display_name == 'Numero Riferimento Spedizione':
285
+ display_name = 'Num. Rif. Spedizione'
286
+ elif display_name == 'Numero Civico Destinatario':
287
+ display_name = 'Num. Civico Dest.'
288
+ elif display_name == 'Numero Telefono Destinatario':
289
+ display_name = 'Tel. Dest.'
290
+ elif display_name == 'Numero Telefono Mittente':
291
+ display_name = 'Tel. Mittente'
292
+ elif display_name == 'Codice Identificativo Cliente':
293
+ display_name = 'Cod. Ident. Cliente'
294
+ elif display_name == 'Codice Indirizzo Mittente':
295
+ display_name = 'Cod. Ind. Mittente'
296
+ elif display_name == 'Ragione Sociale Mittente':
297
+ display_name = 'Rag. Soc. Mittente'
298
+ elif display_name == 'Contatto Mittente':
299
+ display_name = 'Contatto Mittente'
300
+ elif display_name == 'Tipo Via Mittente':
301
+ display_name = 'Tipo Via Mittente'
302
+ elif display_name == 'Via Mittente':
303
+ display_name = 'Via Mittente'
304
+ elif display_name == 'Numero Civico Mittente':
305
+ display_name = 'Num. Civico Mitt.'
306
+ elif display_name == 'Cap Mittente':
307
+ display_name = 'CAP Mittente'
308
+ elif display_name == 'Localita Mittente':
309
+ display_name = 'Località Mittente'
310
+ elif display_name == 'Provincia Mittente':
311
+ display_name = 'Prov. Mittente'
312
+ elif display_name == 'Ragione Sociale Destinatario':
313
+ display_name = 'Rag. Soc. Destinatario'
314
+ elif display_name == 'Contatto Destinatario':
315
+ display_name = 'Contatto Destinatario'
316
+ elif display_name == 'Tipo Via Destinatario':
317
+ display_name = 'Tipo Via Destinatario'
318
+ elif display_name == 'Via Destinatario':
319
+ display_name = 'Via Destinatario'
320
+ elif display_name == 'Cap Destinatario':
321
+ display_name = 'CAP Destinatario'
322
+ elif display_name == 'Localita Destinatario':
323
+ display_name = 'Località Destinatario'
324
+ elif display_name == 'Provincia Destinatario':
325
+ display_name = 'Prov. Destinatario'
326
+ elif display_name == 'Numero Colli':
327
+ display_name = 'Num. Colli'
328
+ elif display_name == 'Peso Colli':
329
+ display_name = 'Peso Colli'
330
+ elif display_name == 'Codice Servizio':
331
+ display_name = 'Cod. Servizio'
332
+ elif display_name == 'Identificativo Collo':
333
+ display_name = 'ID Collo'
334
+ elif display_name == 'Filiale':
335
+ display_name = 'Filiale'
336
+ elif display_name == 'Tipo Cliente':
337
+ display_name = 'Tipo Cliente'
338
+ elif display_name == 'Id Ldv':
339
+ display_name = 'ID LdV'
340
+ elif display_name == 'Data Spedizione':
341
+ display_name = 'Data Spedizione'
342
+ elif display_name == 'Ritiro M/P':
343
+ display_name = 'Ritiro M/P'
344
+ elif display_name == 'Filler Gap 18 20':
345
+ display_name = 'Gap'
346
+
347
+
348
+ obligatory = "*" if field_spec['mandatory'] else ""
349
+ field_type = "char" # Tutte sono char
350
+ length = field_spec['length']
351
+ start_pos = field_spec['start_pos']
352
+ description = field_spec['description']
353
+
354
+ source_info = ""
355
+ source_type = field_spec['source']
356
+ if source_type == 'fixed':
357
+ source_info = fixed_and_default_values.get(field_spec['name'], 'N/A')
358
+ elif source_type == 'shipping_mark_file':
359
+ source_info = f"Da ShippingMark_.xlsx: '{field_spec['excel_col']}'"
360
+ elif source_type == 'shipment_order_file':
361
+ source_info = f"Da ShipmentOrder_.xlsx: '{field_spec['excel_col']}'"
362
+ if field_spec['name'] == 'data_spedizione':
363
+ source_info += " (formato AAAAMMGG)"
364
+ elif field_spec['name'] == 'numero_telefono_destinatario':
365
+ source_info += " (riempi con '0' se vuoto)"
366
+ elif source_type == 'filler':
367
+ source_info = "Spazi vuoti (Gap)"
368
+
369
+ # Aggiusta la colonna Descrizione per non essere troppo larga nella tabella di testo
370
+ if len(description) > 50:
371
+ description = description[:47] + "..."
372
+
373
+ spec_text += f"{field_id}\t{display_name:<25}\t{obligatory:<12}\t{field_type:<4}\t{length:<9}\t{start_pos:<7}\t{description:<50}\t{source_info}\n"
374
+
375
+ return spec_text
376
+
377
  # --- Sezione App Streamlit ---
378
  def app():
379
  st.set_page_config(layout="centered", page_title="Generatore File Poste .DAT")
 
400
  df_shipping_mark = pd.read_excel(shipping_mark_file, header=1)
401
  df_shipment_order = pd.read_excel(shipment_order_file, header=1)
402
 
 
 
 
 
 
 
 
403
  # Esegui la funzione di generazione
404
  dat_filepath, dis_filepath = generate_poste_dat_file(df_shipping_mark, df_shipment_order)
405
 
 
 
 
 
 
 
 
406
  except Exception as e:
407
+ st.error(f"Errore: {e}")
408
  st.warning("Assicurati che i file siano in formato .xlsx, non siano corrotti e che l'intestazione sia effettivamente alla seconda riga.")
409
 
410
  if dat_filepath and dis_filepath:
411
+ st.subheader("File generati:")
412
+ st.success("I file .DAT e .DIS sono stati generati con successo e sono pronti per il download.")
413
 
414
+ # Ottieni il nome base del file (es. SDA20240101123456789)
415
+ base_filename = os.path.basename(dat_filepath).replace('.dat', '')
416
+
417
  # Per consentire il download in Streamlit, dobbiamo leggere il contenuto del file
418
  # in modalità binaria ('rb') e passarlo al download_button.
419
  with open(dat_filepath, "rb") as f:
420
  st.download_button(
421
+ label=f"Scarica {base_filename}.dat",
422
  data=f.read(),
423
+ file_name=f"{base_filename}.dat",
424
+ mime="text/plain", # Tipo MIME per file di testo generici
425
+ key="download_dat_btn"
426
  )
427
 
428
  with open(dis_filepath, "rb") as f:
429
  st.download_button(
430
+ label=f"Scarica {base_filename}.dis",
431
  data=f.read(),
432
+ file_name=f"{base_filename}.dis",
433
+ mime="text/plain",
434
+ key="download_dis_btn"
435
  )
436
 
437
+ st.markdown("---")
438
+ st.subheader("Specifiche del Tracciato .DAT")
439
+ st.markdown("Qui puoi scaricare un file di testo con la descrizione dettagliata del formato del file .DAT generato:")
440
+
441
+ spec_text_content = generate_dat_format_spec_text()
442
+
443
+ st.download_button(
444
+ label="Scarica Specifiche Tracciato DAT",
445
+ data=spec_text_content.encode('utf-8'),
446
+ file_name="DAT_Format_Specification.txt",
447
+ mime="text/plain",
448
+ key="download_spec_btn"
449
+ )
450
+
451
  st.markdown("---")
452
  st.info("""
453
+ **Nota importante sui nomi delle colonne nei tuoi file Excel:**
454
+ Assicurati che le colonne nel tuo file `ShipmentOrder_` corrispondano **esattamente** ai nomi con la notazione a punti (es. 'consegnee.partner.businessname', 'consegnee.partner.address.address', 'orderdate') una volta letti da Excel con l'intestazione alla seconda riga.
 
 
 
455
  """)
456
 
457
  if __name__ == "__main__":