pasquale991 commited on
Commit
0ff9e41
·
verified ·
1 Parent(s): a162414

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +69 -48
src/streamlit_app.py CHANGED
@@ -38,43 +38,43 @@ fixed_and_default_values = {
38
  dat_format_spec = [
39
  # Id | Nome | Obbl | Tipo | Lungh | Inizio | Descrizione | Default | Campi
40
  {'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},
41
- {'id': '2', 'name': 'id_ldv', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipping_mark_file', 'excel_col': 'shippingMark', 'description': "Fillare con la numerazione della ldv (uguale a shippingMark)", 'start_pos': 11},
42
- {'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': 41},
43
- {'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': 42},
44
- {'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': 43},
45
- {'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': 44},
46
- {'id': '7', 'name': 'data_spedizione', 'length': 8, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipping_mark_file', 'excel_col': 'requestedShipment', 'description': "Data della spedizione in formato AAAAMMGG (Mese, Giorno)", 'start_pos': 69},
47
- {'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': 77},
48
- {'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': 78},
49
- {'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': 98},
50
- {'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': 138},
51
- {'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': 158},
52
- {'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': 163},
53
- {'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': 193},
54
- {'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': 198},
55
- {'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': 213},
56
- {'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': 222},
57
- {'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': 252},
58
  # Filler per il gap tra Campo 18 (fine a 254) e Campo 20 (inizio a 275)
59
- {'id': '', 'name': 'filler_gap_18_20', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source': 'filler', 'excel_col': None, 'description': "Gap tra i campi 18 e 20", 'start_pos': 254},
60
  # Campi Destinatario e Colli
61
  # Nota: Questi campi si riferiscono a colonne nel file ShipmentOrder_, i nomi sono mantenuti come nel tracciato
62
- {'id': '20', 'name': 'ragione_sociale_destinatario', 'length': 40, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipment_order_file', 'excel_col': 'consegnee.partner.name', 'description': "Ragione sociale del destinatario", 'start_pos': 274},
63
- {'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': 314},
64
- {'id': '22', 'name': 'tipo_via_destinatario', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Descrizione tipo via", 'start_pos': 334},
65
- {'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': 339},
66
- {'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': 369},
67
- {'id': '25', 'name': 'numero_telefono_destinatario','length': 15, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipment_order_file', 'excel_col': 'consegnee.partner.phoneNumber', 'description': "Numero di telefono Destinatario", 'start_pos': 374},
68
- {'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': 389},
69
- {'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': 398},
70
- {'id': '28', 'name': 'provincia_destinatario', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Provincia del destinatario", 'start_pos': 428},
71
- {'id': '29', 'name': 'numero_colli', 'length': 3, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': True, 'source': 'shipping_mark_file', 'excel_col': 'totalLdv', 'description': "Numero dei colli totali che compongono la spedizione", 'start_pos': 430},
72
- {'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': 433},
73
  # Altri Campi Obbligatori dal tracciato
74
- {'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': 455},
75
- {'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': 552},
76
- {'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': 577},
77
- {'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': 626},
78
  ]
79
 
80
  # --- Funzione per generare il file DAT e DIS ---
@@ -100,7 +100,7 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
100
  # Verifica la presenza delle colonne necessarie nel file shipping mark
101
  required_shipping_mark_cols = ['shippingMark', 'orderRef', 'totalWeight', 'totalLdv', 'requestedShipment']
102
  if not all(col in df_shipping_mark.columns for col in required_shipping_mark_cols):
103
- st.error(f"Errore: Il file delle shipping mark deve contenere le colonne: {required_shipping_mark_cols}. Verifica il naming esatto.")
104
  return None, None
105
 
106
  # Verifica le colonne necessarie nel file shipment order
@@ -116,7 +116,7 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
116
  ]
117
  if not all(col in df_shipment_order.columns for col in required_shipment_order_cols):
118
  missing_cols = [col for col in required_shipment_order_cols if col not in df_shipment_order.columns]
119
- st.error(f"Errore: Il file degli ordini (ShipmentOrder_) deve contenere le seguenti colonne: {missing_cols}. Verifica attentamente la corrispondenza dei nomi (con punti o underscore). Ricorda che l'intestazione è alla seconda riga.")
120
  return None, None
121
 
122
  # Pulisci le colonne di join per una corrispondenza robusta (rimuovi spazi e assicurati che siano stringhe)
@@ -124,10 +124,7 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
124
  df_shipment_order['orderRef'] = df_shipment_order['orderRef'].astype(str).str.strip()
125
 
126
  # Esegue la join: cruciale 'how=inner' per una join "secca"
127
- # Quando 'on' è specificato, la colonna di merge (orderRef) non viene suffissa.
128
- # Le altre colonne, se non hanno nomi duplicati tra i due DF, non vengono suffisse.
129
- # Poiché i nomi delle colonne di ShipmentOrder sono nidificati (.name, .address.address),
130
- # non ci dovrebbero essere duplicati a parte 'orderRef', quindi non vengono suffisse.
131
  df_merged = pd.merge(df_shipping_mark, df_shipment_order, on='orderRef', how='inner')
132
 
133
  if df_merged.empty:
@@ -138,6 +135,9 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
138
  dat_content = []
139
  for index, row in df_merged.iterrows():
140
  record_parts = []
 
 
 
141
  for field_spec in dat_format_spec:
142
  field_name = field_spec['name']
143
  length = field_spec['length']
@@ -146,17 +146,25 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
146
  mandatory = field_spec['mandatory']
147
  source_type = field_spec['source']
148
  excel_col_name = field_spec['excel_col'] # Nome della colonna come specificato nel dat_format_spec
 
 
 
 
 
 
 
 
149
 
150
  value_to_format = ''
151
  if source_type == 'fixed':
152
  value_to_format = fixed_and_default_values.get(field_name, '')
153
  elif source_type == 'shipping_mark_file':
154
- # Accede alle colonne di shipping_mark direttamente con il loro nome originale
155
  col_key = excel_col_name
156
- if col_key in row: # Verifica se la colonna esiste nel row (DataFrame fuso)
157
  value_to_format = row.get(col_key)
158
  else:
159
- st.warning(f"Colonna '{excel_col_name}' dal file ShippingMark_ non trovata nel DataFrame fuso (cercato come '{col_key}'). Verificare il nome originale della colonna.")
 
160
 
161
  if pd.isna(value_to_format):
162
  value_to_format = ''
@@ -168,7 +176,9 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
168
  if isinstance(value_to_format, datetime):
169
  value_to_format = value_to_format.strftime("%Y%m%d")
170
  elif isinstance(value_to_format, (int, float)): # Le date di Excel possono essere numeri
171
- # L'epoch di Excel è 1899-12-30 per le date
 
 
172
  value_to_format = pd.to_datetime(value_to_format, unit='D', origin='1899-12-30', errors='coerce')
173
  if pd.notna(value_to_format):
174
  value_to_format = value_to_format.strftime("%Y%m%d")
@@ -184,20 +194,30 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
184
  else: # Fallback per tipi inaspettati
185
  value_to_format = ''
186
  except Exception as e:
187
- st.warning(f"Errore durante la conversione della data per campo '{field_name}': {value_to_format} - {e}")
188
  value_to_format = ''
189
  else: # Se NaN o vuoto, usa stringa vuota
190
  value_to_format = ''
191
  elif source_type == 'shipment_order_file':
192
- # Accede alle colonne di shipment_order direttamente con il loro nome originale
193
  col_key = excel_col_name
194
- if col_key in row: # Verifica se la colonna esiste nel row (DataFrame fuso)
195
  value_to_format = row.get(col_key)
196
  else:
197
- st.warning(f"Colonna '{excel_col_name}' dal file ShipmentOrder_ non trovata nel DataFrame fuso (cercato come '{col_key}'). Verificare il nome originale della colonna.")
198
 
199
  if pd.isna(value_to_format):
200
  value_to_format = ''
 
 
 
 
 
 
 
 
 
 
 
201
  elif source_type == 'filler':
202
  value_to_format = ' ' * length # Riempie il filler con spazi
203
 
@@ -230,6 +250,7 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
230
  # Trunca se la stringa è più lunga della lunghezza specificata
231
  final_field_value = formatted_value[:length]
232
  record_parts.append(final_field_value)
 
233
 
234
  dat_content.append("".join(record_parts))
235
 
@@ -496,4 +517,4 @@ def app():
496
 
497
  # Punto di ingresso dell'applicazione Streamlit
498
  if __name__ == "__main__":
499
- app()
 
38
  dat_format_spec = [
39
  # Id | Nome | Obbl | Tipo | Lungh | Inizio | Descrizione | Default | Campi
40
  {'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},
41
+ {'id': '2', 'name': 'id_ldv', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipping_mark_file', 'excel_col': 'shippingMark', 'description': "Fillare con la numerazione della ldv (uguale a shippingMark)", 'start_pos': 12},
42
+ {'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': 42},
43
+ {'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': 43},
44
+ {'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': 44},
45
+ {'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': 45},
46
+ {'id': '7', 'name': 'data_spedizione', 'length': 8, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipping_mark_file', 'excel_col': 'requestedShipment', 'description': "Data della spedizione in formato AAAAMMGG (Mese, Giorno)", 'start_pos': 70},
47
+ {'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': 78},
48
+ {'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': 79},
49
+ {'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': 99},
50
+ {'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': 139},
51
+ {'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': 159},
52
+ {'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': 164},
53
+ {'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': 194},
54
+ {'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': 199},
55
+ {'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': 214},
56
+ {'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': 223},
57
+ {'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': 253},
58
  # Filler per il gap tra Campo 18 (fine a 254) e Campo 20 (inizio a 275)
59
+ {'id': '', 'name': 'filler_gap_18_20', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source': 'filler', 'excel_col': None, 'description': "Gap tra i campi 18 e 20", 'start_pos': 255},
60
  # Campi Destinatario e Colli
61
  # Nota: Questi campi si riferiscono a colonne nel file ShipmentOrder_, i nomi sono mantenuti come nel tracciato
62
+ {'id': '20', 'name': 'ragione_sociale_destinatario', 'length': 40, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipment_order_file', 'excel_col': 'consegnee.partner.name', 'description': "Ragione sociale del destinatario", 'start_pos': 275},
63
+ {'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': 315},
64
+ {'id': '22', 'name': 'tipo_via_destinatario', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Descrizione tipo via", 'start_pos': 335},
65
+ {'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': 340},
66
+ {'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': 370},
67
+ {'id': '25', 'name': 'numero_telefono_destinatario','length': 15, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'shipment_order_file', 'excel_col': 'consegnee.partner.phoneNumber', 'description': "Numero di telefono Destinatario", 'start_pos': 375},
68
+ {'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': 390},
69
+ {'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': 399},
70
+ {'id': '28', 'name': 'provincia_destinatario', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source': 'fixed', 'excel_col': None, 'description': "Provincia del destinatario", 'start_pos': 429},
71
+ {'id': '29', 'name': 'numero_colli', 'length': 3, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': True, 'source': 'shipping_mark_file', 'excel_col': 'totalLdv', 'description': "Numero dei colli totali che compongono la spedizione", 'start_pos': 431},
72
+ {'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': 434},
73
  # Altri Campi Obbligatori dal tracciato
74
+ {'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': 456},
75
+ {'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': 553},
76
+ {'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': 578},
77
+ {'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': 627},
78
  ]
79
 
80
  # --- Funzione per generare il file DAT e DIS ---
 
100
  # Verifica la presenza delle colonne necessarie nel file shipping mark
101
  required_shipping_mark_cols = ['shippingMark', 'orderRef', 'totalWeight', 'totalLdv', 'requestedShipment']
102
  if not all(col in df_shipping_mark.columns for col in required_shipping_mark_cols):
103
+ st.error(f"Errore: Il file delle shipping mark deve contenere le colonne: {required_shipping_mark_cols}. Verifica il naming esatto e la capitalizzazione.")
104
  return None, None
105
 
106
  # Verifica le colonne necessarie nel file shipment order
 
116
  ]
117
  if not all(col in df_shipment_order.columns for col in required_shipment_order_cols):
118
  missing_cols = [col for col in required_shipment_order_cols if col not in df_shipment_order.columns]
119
+ st.error(f"Errore: Il file degli ordini (ShipmentOrder_) deve contenere le seguenti colonne: {missing_cols}. Verifica attentamente la corrispondenza dei nomi (con punti o underscore) e la capitalizzazione. Ricorda che l'intestazione è alla seconda riga.")
120
  return None, None
121
 
122
  # Pulisci le colonne di join per una corrispondenza robusta (rimuovi spazi e assicurati che siano stringhe)
 
124
  df_shipment_order['orderRef'] = df_shipment_order['orderRef'].astype(str).str.strip()
125
 
126
  # Esegue la join: cruciale 'how=inner' per una join "secca"
127
+ # Le colonne manterranno i loro nomi originali nel DataFrame fuso, a meno che non ci siano duplicati (non il caso qui).
 
 
 
128
  df_merged = pd.merge(df_shipping_mark, df_shipment_order, on='orderRef', how='inner')
129
 
130
  if df_merged.empty:
 
135
  dat_content = []
136
  for index, row in df_merged.iterrows():
137
  record_parts = []
138
+ # Mantieni traccia della lunghezza corrente della riga per gestire i posizionamenti precisi
139
+ current_line_length = 0
140
+
141
  for field_spec in dat_format_spec:
142
  field_name = field_spec['name']
143
  length = field_spec['length']
 
146
  mandatory = field_spec['mandatory']
147
  source_type = field_spec['source']
148
  excel_col_name = field_spec['excel_col'] # Nome della colonna come specificato nel dat_format_spec
149
+ expected_start_pos = field_spec['start_pos'] # Posizione di inizio 1-indexed dal tracciato
150
+
151
+ # Calcola il padding necessario per raggiungere la posizione di inizio esatta
152
+ # Se la lunghezza attuale è inferiore alla posizione di inizio desiderata (0-indexed)
153
+ padding_needed = (expected_start_pos - 1) - current_line_length
154
+ if padding_needed > 0:
155
+ record_parts.append(' ' * padding_needed)
156
+ current_line_length += padding_needed
157
 
158
  value_to_format = ''
159
  if source_type == 'fixed':
160
  value_to_format = fixed_and_default_values.get(field_name, '')
161
  elif source_type == 'shipping_mark_file':
 
162
  col_key = excel_col_name
163
+ if col_key in row:
164
  value_to_format = row.get(col_key)
165
  else:
166
+ # Non dovrebbe succedere se il controllo iniziale delle colonne è robusto
167
+ st.warning(f"Colonna '{excel_col_name}' dal file ShippingMark_ non trovata nel DataFrame fuso. Controllare il nome originale della colonna.")
168
 
169
  if pd.isna(value_to_format):
170
  value_to_format = ''
 
176
  if isinstance(value_to_format, datetime):
177
  value_to_format = value_to_format.strftime("%Y%m%d")
178
  elif isinstance(value_to_format, (int, float)): # Le date di Excel possono essere numeri
179
+ # L'epoch di Excel è 1899-12-30 per le date, ma è meglio usare il proprio parser di Excel se possibile
180
+ # O provare a convertire da numero seriale di Excel
181
+ # Per ora, proviamo con il formato stringa diretto se numerico
182
  value_to_format = pd.to_datetime(value_to_format, unit='D', origin='1899-12-30', errors='coerce')
183
  if pd.notna(value_to_format):
184
  value_to_format = value_to_format.strftime("%Y%m%d")
 
194
  else: # Fallback per tipi inaspettati
195
  value_to_format = ''
196
  except Exception as e:
197
+ st.warning(f"Errore durante la conversione della data per campo '{field_name}' (valore: {value_to_format}). Assicurati che sia un formato data valido: {e}")
198
  value_to_format = ''
199
  else: # Se NaN o vuoto, usa stringa vuota
200
  value_to_format = ''
201
  elif source_type == 'shipment_order_file':
 
202
  col_key = excel_col_name
203
+ if col_key in row:
204
  value_to_format = row.get(col_key)
205
  else:
206
+ st.warning(f"Colonna '{excel_col_name}' dal file ShipmentOrder_ non trovata nel DataFrame fuso. Controllare il nome originale della colonna.")
207
 
208
  if pd.isna(value_to_format):
209
  value_to_format = ''
210
+
211
+ # Gestione specifica per CAP Destinatario per evitare .0
212
+ if field_name == 'cap_destinatario':
213
+ if pd.notna(value_to_format) and value_to_format != '':
214
+ try:
215
+ value_to_format = str(int(float(value_to_format))) # Converte in float, poi int, poi stringa
216
+ except ValueError:
217
+ st.warning(f"Impossibile convertire '{value_to_format}' in un CAP numerico intero. Verrà trattato come stringa.")
218
+ value_to_format = str(value_to_format) # Fallback a stringa
219
+ else:
220
+ value_to_format = '' # Se vuoto o NaN, lascia vuoto
221
  elif source_type == 'filler':
222
  value_to_format = ' ' * length # Riempie il filler con spazi
223
 
 
250
  # Trunca se la stringa è più lunga della lunghezza specificata
251
  final_field_value = formatted_value[:length]
252
  record_parts.append(final_field_value)
253
+ current_line_length += len(final_field_value)
254
 
255
  dat_content.append("".join(record_parts))
256
 
 
517
 
518
  # Punto di ingresso dell'applicazione Streamlit
519
  if __name__ == "__main__":
520
+ app()