pasquale991 commited on
Commit
bbe1a0c
·
verified ·
1 Parent(s): c399438

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +141 -163
src/streamlit_app.py CHANGED
@@ -36,8 +36,9 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
36
  # **IMPORTANTE**: Adatta queste chiavi ai nomi effettivi delle colonne
37
  # nel tuo file ShipmentOrder_.xlsx, ignorando maiuscole/minuscole per la chiave.
38
  column_mapping = {
 
39
  'contatto destinatario': 'consignee_contact',
40
- 'nome contatto presso il destinatario': 'consignee_contact_name',
41
  'tipo via destinatario': 'consignee_street_type',
42
  'via destinatario': 'consignee_address',
43
  'numero civico destinatario': 'consignee_house_number',
@@ -53,19 +54,16 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
53
  'codice servizio': 'service_code',
54
  'codice rif. prodotto': 'product_ref_code',
55
  'codice rif. destinatario': 'consignee_ref_code',
56
- 'valuta': 'currency',
57
  'importo contrassegno': 'cash_on_delivery_amount',
58
  'tipo pagamento': 'payment_type',
59
  'note': 'notes',
60
  'importo assicurata': 'insured_amount',
61
- 'identificativo collo': 'package_id', # Questo sarà sovrascritto dalla 'shippingmark' specifica per riga
62
  'filiale': 'branch_code',
63
  'contenuto': 'content_description',
64
  'tipo contenuto': 'content_type',
65
- 'nazione mittente': 'sender_country',
66
  'nazione destinatario': 'consignee_country',
67
  'valore spedizione': 'shipment_value',
68
- 'tipo cliente': 'client_type',
69
  'classe spedizione': 'shipment_class',
70
  'addebito oneri': 'duty_charge',
71
  'tipo imballo': 'packaging_type',
@@ -76,7 +74,6 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
76
  'numero cellulare destinario': 'consignee_mobile_number',
77
  'is_ass_percentuale': 'is_insured_percentage',
78
  'barcode_servizio_rit': 'barcode_service_return',
79
- 'id_tipo_spedizione': 'shipment_type_id',
80
  'dettaglio_bs': 'detail_bs',
81
  'note 2': 'notes_2',
82
  'codice ufficio frazionario': 'fractional_office_code',
@@ -98,133 +95,134 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
98
  df_shipment_order['orderref'] = df_shipment_order['orderref'].astype(str).str.strip()
99
 
100
  # Pre-processa df_shipment_order per prendere la prima occorrenza di ogni orderRef.
101
- # Questo gestisce la "testata si ripete solo una volta" per ordine.
102
  df_shipment_order_unique = df_shipment_order.drop_duplicates(subset=['orderref'], keep='first').copy()
103
 
104
- # Fai la join: ogni riga di df_shipping_mark (che è una LDV) verrà arricchita
105
- # con i dettagli dell'ordine presi dalla testata unica.
106
- # 'how=left' assicura che tutte le shipping mark vengano mantenute.
107
- df_merged = pd.merge(df_shipping_mark, df_shipment_order_unique, on='orderref', how='left')
 
 
108
 
109
  # Dizionario per i valori di default/fissi per i campi obbligatori o predeterminati
110
- default_fixed_values = {
111
- # Dati Mittente Fissi (come richiesto)
112
- 'sender_contact_name': 'POSTE LOGISTICS',
113
- 'sender_street_type': 'VIA',
114
- 'sender_address': 'DA MODIFICARE', # Sarà concatenato con il numero civico
115
- 'sender_house_number': '19',
116
- 'sender_city': 'VIDIGULFO',
117
- 'sender_province': 'PV',
118
- 'sender_zip_code': '27040', # Esempio CAP per Vidigulfo
119
- 'sender_phone': '00000000000', # Default phone for sender if not provided
120
- 'sender_email': 'INFO@POSTELOGISTICS.COM', # Esempio email mittente
121
- 'sender_fiscal_id': '00000000000000000000', # Esempio fiscale mittente
122
- 'sender_filler': '', # Campo filler per il blocco mittente iniziale
123
-
124
- # Valori fissi specifici dal documento o richieste utente
 
 
 
 
 
 
 
125
  'currency': 'EU',
126
  'client_type': 'MGCS',
127
- 'sender_country': 'ITA', # Fisso per Poste Logistics, Italia
128
- 'id_tipo_spedizione': 'N', # Richiesta utente
129
-
130
- # Valori di default per campi obbligatori se non presenti e non fissi
131
- 'consignee_contact': 'SCONOSCIUTO',
132
- 'consignee_contact_name': 'SCONOSCIUTO',
133
- 'consignee_street_type': 'VIA',
134
- 'consignee_address': 'INDIRIZZO SCONOSCIUTO',
135
- 'consignee_house_number': 'SN', # Senza Numero
136
- 'consignee_phone': '00000000000',
137
- 'consignee_zip_code': '00000',
138
- 'consignee_city': 'CITTA SCONOSCIUTA',
139
- 'consignee_province': 'XX',
140
- 'num_packages': 1, # Default 1 collo se non specificato
141
- 'package_weight': 1, # Default 1 grammo se non specificato
142
- 'service_code': 'STD', # Esempio di default per servizio
143
- 'branch_code': '00', # Esempio di default per filiale
144
- 'is_insured_percentage': 'N', # Default Non assicurata
145
- 'undelivered_action': 'R' # Default Restituire
146
  }
147
 
148
- # Definizione dettagliata del layout del file .dat con nome campo, lunghezza, tipo di padding, decimali, obbligatorietà
 
149
  dat_format_spec = [
150
- # Campi Mittente Assunti (per occupare i primi 334 caratteri non specificati)
151
- {'name': 'sender_contact_name', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
152
- {'name': 'sender_street_type', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
153
- {'name': 'sender_address', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
154
- {'name': 'sender_house_number', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
155
- {'name': 'sender_city', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
156
- {'name': 'sender_province', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
157
- {'name': 'sender_zip_code', 'length': 9, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
158
- {'name': 'sender_phone', 'length': 15, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
159
- {'name': 'sender_email', 'length': 50, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
160
- {'name': 'sender_fiscal_id', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
161
- {'name': 'sender_filler', 'length': 148, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False}, # Filler per arrivare a 334 caratteri totali per il blocco mittente
162
-
163
- # Dettagli Destinatario (Campi 21-28, dal documento)
164
- {'name': 'consignee_contact', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
165
- {'name': 'consignee_contact_name', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False}, # Aggiunto, spesso distinto da contact
166
- {'name': 'consignee_street_type', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
167
- {'name': 'consignee_address', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
168
- {'name': 'consignee_house_number', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
169
- {'name': 'consignee_phone', 'length': 15, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
170
- {'name': 'consignee_zip_code', 'length': 9, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
171
- {'name': 'consignee_city', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
172
- {'name': 'consignee_province', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
 
 
 
 
 
 
 
173
 
174
  # Dettagli Colli e Spedizione (Campi 29-33) - zero-padded a sinistra
175
- {'name': 'num_packages', 'length': 3, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': True},
176
- {'name': 'package_weight', 'length': 7, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': True},
177
- {'name': 'package_width', 'length': 5, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': False},
178
- {'name': 'package_height', 'length': 5, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': False},
179
- {'name': 'package_depth', 'length': 5, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': False},
180
 
181
  # Servizio e Riferimenti (Campi 34-36)
182
- {'name': 'service_code', 'length': 3, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
183
- {'name': 'product_ref_code', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
184
- {'name': 'consignee_ref_code', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
185
 
186
  # Valuta, Contrassegno, Note, Assicurazione (Campi 37-41)
187
- {'name': 'currency', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
188
- {'name': 'cash_on_delivery_amount', 'length': 9, 'numeric_pad_zero': True, 'decimals': 2, 'mandatory': False},
189
- {'name': 'payment_type', 'length': 3, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
190
- {'name': 'notes', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
191
- {'name': 'insured_amount', 'length': 10, 'numeric_pad_zero': True, 'decimals': 2, 'mandatory': False},
192
 
193
  # Identificativo Collo e Filiale (Campi 42-43)
194
- {'name': 'shippingmark', 'length': 25, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True}, # Sarà prefissato con 'CLI000000009'
195
- {'name': 'branch_code', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
196
 
197
  # Contenuto e Paesi (Campi 44-48)
198
- {'name': 'content_description', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
199
- {'name': 'content_type', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
200
- {'name': 'sender_country', 'length': 3, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
201
- {'name': 'consignee_country', 'length': 3, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
202
- {'name': 'shipment_value', 'length': 10, 'numeric_pad_zero': True, 'decimals': 2, 'mandatory': False},
203
 
204
  # Tipo Cliente e Classe Spedizione (Campi 49-50)
205
- {'name': 'client_type', 'length': 4, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
206
- {'name': 'shipment_class', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
207
 
208
  # Oneri, Imballo, Fiscale (Campi 51-53)
209
- {'name': 'duty_charge', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
210
- {'name': 'packaging_type', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
211
- {'name': 'fiscal_id', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
212
 
213
  # Barcode Servizio e Contatti Aggiuntivi (Campi 54-57)
214
- {'name': 'barcode_service', 'length': 3, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
215
- {'name': 'consignee_email', 'length': 50, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
216
- {'name': 'consignee_mobile_prefix', 'length': 4, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
217
- {'name': 'consignee_mobile_number', 'length': 10, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
218
-
219
- # Altri Campi (Campi 58-65)
220
- {'name': 'is_insured_percentage', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
221
- {'name': 'barcode_service_return', 'length': 3, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
222
- {'name': 'id_tipo_spedizione', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True},
223
- {'name': 'detail_bs', 'length': 6, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
224
- {'name': 'notes_2', 'length': 50, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
225
- {'name': 'fractional_office_code', 'length': 11, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
226
- {'name': 'customer_post_box', 'length': 11, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False},
227
- {'name': 'undelivered_action', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False}
228
  ]
229
 
230
  # Genera il contenuto del file .dat
@@ -232,72 +230,52 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
232
  for index, row in df_merged.iterrows():
233
  record_parts = []
234
 
235
- # Pre-processa i campi mittente per la costruzione del record
236
- current_sender_address = f"{default_fixed_values['sender_street_type'].strip()} {default_fixed_values['sender_address'].strip()} {default_fixed_values['sender_house_number'].strip()}"
237
- current_sender_address = current_sender_address.strip()
238
- # Tronca se troppo lunga
239
- current_sender_address = current_sender_address[:dat_format_spec[2]['length']]
240
-
241
-
242
  for field_spec in dat_format_spec:
243
  field_name = field_spec['name']
244
  length = field_spec['length']
245
  is_numeric_zero_padded = field_spec['numeric_pad_zero']
246
  dec_places = field_spec['decimals']
247
  is_mandatory = field_spec['mandatory']
 
 
 
248
 
249
- value = None
250
-
251
- # Gestione dei campi mittente fissi
252
- if field_name == 'sender_contact_name':
253
- value = default_fixed_values['sender_contact_name']
254
- elif field_name == 'sender_street_type':
255
- value = default_fixed_values['sender_street_type']
256
- elif field_name == 'sender_address':
257
- value = current_sender_address # Usa l'indirizzo combinato
258
- elif field_name == 'sender_house_number':
259
- value = default_fixed_values['sender_house_number']
260
- elif field_name == 'sender_city':
261
- value = default_fixed_values['sender_city']
262
- elif field_name == 'sender_province':
263
- value = default_fixed_values['sender_province']
264
- elif field_name == 'sender_zip_code':
265
- value = default_fixed_values['sender_zip_code']
266
- elif field_name == 'sender_phone':
267
- value = default_fixed_values['sender_phone']
268
- elif field_name == 'sender_email':
269
- value = default_fixed_values['sender_email']
270
- elif field_name == 'sender_fiscal_id':
271
- value = default_fixed_values['sender_fiscal_id']
272
- elif field_name == 'sender_filler':
273
- value = default_fixed_values['sender_filler']
274
- # Gestione degli altri campi fissi da default_fixed_values
275
- elif field_name in default_fixed_values and field_name not in ['sender_address', 'sender_street_type', 'sender_house_number', 'sender_city', 'sender_province', 'sender_zip_code', 'sender_phone', 'sender_email', 'sender_fiscal_id', 'sender_filler']:
276
- value = default_fixed_values[field_name]
277
- else:
278
- value = row.get(field_name)
279
-
280
- # Gestione dei valori mancanti (NaN) in pandas
281
- if pd.isna(value) or value == '':
282
- if is_mandatory:
283
- # Se è obbligatorio e mancante, usa un default basato sul tipo di campo
284
- if is_numeric_zero_padded:
285
- value = '0' * length # Default numerico (zero)
286
- else:
287
- value = ' ' * length # Default stringa (spazi)
288
  else:
289
- value = '' # Se non obbligatorio, lascia vuoto
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
  # Conversione in stringa e pulizia iniziale
292
- value_str = str(value).strip()
293
 
294
- # Gestione speciale per 'shippingmark' (campo 42) per aggiungere il prefisso CLI
295
- if field_name == 'shippingmark':
296
- if value_str:
297
- value_str = f"CLI000000009{value_str}"
298
  else:
299
- value_str = "CLI000000009"
300
 
 
301
  if is_numeric_zero_padded:
302
  try:
303
  clean_value = value_str.replace(',', '.') # Sostituisce virgole con punti
@@ -308,8 +286,8 @@ def generate_poste_dat_file(df_shipping_mark_input, df_shipment_order_input):
308
  else: # Campi numerici interi
309
  formatted_value = str(int(float(clean_value))).rjust(length, '0')
310
  except ValueError:
311
- formatted_value = '0'.rjust(length, '0') # Se la conversione fallisce, riempi con zeri
312
- else: # Default: allinea a sinistra con spazi
313
  formatted_value = value_str.ljust(length)
314
 
315
  # Trunca se la stringa è più lunga della lunghezza specificata
 
36
  # **IMPORTANTE**: Adatta queste chiavi ai nomi effettivi delle colonne
37
  # nel tuo file ShipmentOrder_.xlsx, ignorando maiuscole/minuscole per la chiave.
38
  column_mapping = {
39
+ 'rag. sociale destinatario': 'ragione_sociale_destinatario',
40
  'contatto destinatario': 'consignee_contact',
41
+ 'nome contatto presso il destinatario': 'consignee_contact_name', # Aggiunto, se distinto
42
  'tipo via destinatario': 'consignee_street_type',
43
  'via destinatario': 'consignee_address',
44
  'numero civico destinatario': 'consignee_house_number',
 
54
  'codice servizio': 'service_code',
55
  'codice rif. prodotto': 'product_ref_code',
56
  'codice rif. destinatario': 'consignee_ref_code',
 
57
  'importo contrassegno': 'cash_on_delivery_amount',
58
  'tipo pagamento': 'payment_type',
59
  'note': 'notes',
60
  'importo assicurata': 'insured_amount',
61
+ 'identificativo collo': 'package_id', # Sarà sovrascritto dalla 'shippingmark' specifica per riga
62
  'filiale': 'branch_code',
63
  'contenuto': 'content_description',
64
  'tipo contenuto': 'content_type',
 
65
  'nazione destinatario': 'consignee_country',
66
  'valore spedizione': 'shipment_value',
 
67
  'classe spedizione': 'shipment_class',
68
  'addebito oneri': 'duty_charge',
69
  'tipo imballo': 'packaging_type',
 
74
  'numero cellulare destinario': 'consignee_mobile_number',
75
  'is_ass_percentuale': 'is_insured_percentage',
76
  'barcode_servizio_rit': 'barcode_service_return',
 
77
  'dettaglio_bs': 'detail_bs',
78
  'note 2': 'notes_2',
79
  'codice ufficio frazionario': 'fractional_office_code',
 
95
  df_shipment_order['orderref'] = df_shipment_order['orderref'].astype(str).str.strip()
96
 
97
  # Pre-processa df_shipment_order per prendere la prima occorrenza di ogni orderRef.
 
98
  df_shipment_order_unique = df_shipment_order.drop_duplicates(subset=['orderref'], keep='first').copy()
99
 
100
+ # Fai la join: cruciale 'how=inner' per la join "secca"
101
+ df_merged = pd.merge(df_shipping_mark, df_shipment_order_unique, on='orderref', how='inner')
102
+
103
+ if df_merged.empty:
104
+ st.warning("Nessun record corrispondente trovato dopo la join. Assicurati che 'orderRef' sia presente e corrisponda in entrambi i file.")
105
+ return None, None
106
 
107
  # Dizionario per i valori di default/fissi per i campi obbligatori o predeterminati
108
+ # Basato sul nuovo tracciato fornito
109
+ fixed_and_default_values = {
110
+ # Campi fissi Mittente (Campi 1-18)
111
+ 'codice_identificativo_cliente': 'CLI000000009',
112
+ 'id_ldv_fixed': 'FIL01',
113
+ 'tipo_spedizione_fixed': 'N',
114
+ 'attivazione_ritiro': 'N',
115
+ 'stampa_ldv': 'N',
116
+ 'data_spedizione': datetime.now().strftime("%Y%m%d"), # AAAAMMGG
117
+ 'ritiro_mp': 'P',
118
+ 'codice_indirizzo_mittente': 'IND00000001',
119
+ 'ragione_sociale_mittente': 'POSTE LOGISTICS',
120
+ 'contatto_mittente': 'POSTELOGISTIC',
121
+ 'tipo_via_mittente': 'VIA',
122
+ 'via_mittente': 'DA MODIFICARE', # Usato per combinare con civico
123
+ 'numero_civico_mittente': '19', # Usato per combinare con via
124
+ 'numero_telefono_mittente': '60898001',
125
+ 'cap_mittente': '70019',
126
+ 'localita_mittente': 'VIDIGULFO',
127
+ 'provincia_mittente': 'PV',
128
+
129
+ # Altri campi fissi o con default obbligatorio
130
  'currency': 'EU',
131
  'client_type': 'MGCS',
132
+ 'sender_country': 'ITA',
133
+ 'service_code': 'STD', # Default se non specificato dal file
134
+ 'branch_code': '00', # Default se non specificato dal file
135
+ 'is_insured_percentage': 'N', # Default se non specificato dal file
136
+ 'undelivered_action': 'R' # Default se non specificato dal file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  }
138
 
139
+ # Definizione dettagliata del layout del file .dat con nome campo, lunghezza, tipo di padding, decimali, obbligatorietà, source_type
140
+ # Source_type: 'fixed' (valore dal dizionario fixed_and_default_values), 'file' (valore dal DataFrame unito), 'derived' (calcolato)
141
  dat_format_spec = [
142
+ # BLOCCO MITTENTE FISSO (Campi 1-18 da nuova tabella)
143
+ {'name': 'codice_identificativo_cliente', 'length': 12, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 1
144
+ {'name': 'id_ldv_fixed', 'length': 42, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 2
145
+ {'name': 'tipo_spedizione_fixed', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 3
146
+ {'name': 'attivazione_ritiro', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 4
147
+ {'name': 'stampa_ldv', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 5
148
+ {'name': 'orderref', 'length': 25, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'file'}, # 6 (Numero Riferimento Cliente - dal file)
149
+ {'name': 'data_spedizione', 'length': 8, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 7 (Data corrente)
150
+ {'name': 'ritiro_mp', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 8
151
+ {'name': 'codice_indirizzo_mittente', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 9
152
+ {'name': 'ragione_sociale_mittente', 'length': 40, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 10
153
+ {'name': 'contatto_mittente', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 11
154
+ {'name': 'tipo_via_mittente', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 12
155
+ {'name': 'via_mittente_completa', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'derived'}, # 13+14 (Via + Civico)
156
+ {'name': 'numero_telefono_mittente', 'length': 15, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 15
157
+ {'name': 'cap_mittente', 'length': 9, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 16
158
+ {'name': 'localita_mittente', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 17
159
+ {'name': 'provincia_mittente', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 18
160
+
161
+ # Dettagli Destinatario (Campi 19, e poi 21-65 dalla vecchia numerazione)
162
+ {'name': 'ragione_sociale_destinatario','length': 40, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'file'}, # 19
163
+ {'name': 'consignee_contact', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'file'}, # 21 (from old 21)
164
+ {'name': 'consignee_contact_name', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # Aggiunto, se distinto
165
+ {'name': 'consignee_street_type', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'file'}, # 22
166
+ {'name': 'consignee_address', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'file'}, # 23
167
+ {'name': 'consignee_house_number', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'file'}, # 24
168
+ {'name': 'consignee_phone', 'length': 15, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'file'}, # 25
169
+ {'name': 'consignee_zip_code', 'length': 9, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'file'}, # 26
170
+ {'name': 'consignee_city', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'file'}, # 27
171
+ {'name': 'consignee_province', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'file'}, # 28
172
 
173
  # Dettagli Colli e Spedizione (Campi 29-33) - zero-padded a sinistra
174
+ {'name': 'num_packages', 'length': 3, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': True, 'source_type': 'file'}, # 29
175
+ {'name': 'package_weight', 'length': 7, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': True, 'source_type': 'file'}, # 30
176
+ {'name': 'package_width', 'length': 5, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 31
177
+ {'name': 'package_height', 'length': 5, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 32
178
+ {'name': 'package_depth', 'length': 5, 'numeric_pad_zero': True, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 33
179
 
180
  # Servizio e Riferimenti (Campi 34-36)
181
+ {'name': 'service_code', 'length': 3, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'file'}, # 34 (Default from previous context)
182
+ {'name': 'product_ref_code', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 35
183
+ {'name': 'consignee_ref_code', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 36
184
 
185
  # Valuta, Contrassegno, Note, Assicurazione (Campi 37-41)
186
+ {'name': 'currency', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 37 (Constant: EU)
187
+ {'name': 'cash_on_delivery_amount', 'length': 9, 'numeric_pad_zero': True, 'decimals': 2, 'mandatory': False, 'source_type': 'file'}, # 38
188
+ {'name': 'payment_type', 'length': 3, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 39
189
+ {'name': 'notes', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 40
190
+ {'name': 'insured_amount', 'length': 10, 'numeric_pad_zero': True, 'decimals': 2, 'mandatory': False, 'source_type': 'file'}, # 41
191
 
192
  # Identificativo Collo e Filiale (Campi 42-43)
193
+ {'name': 'shippingmark', 'length': 25, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'derived'}, # 42 (Prefixed with CLI000000009)
194
+ {'name': 'branch_code', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'file'}, # 43 (Default from previous context)
195
 
196
  # Contenuto e Paesi (Campi 44-48)
197
+ {'name': 'content_description', 'length': 30, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 44
198
+ {'name': 'content_type', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 45
199
+ {'name': 'sender_country', 'length': 3, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'fixed'}, # 46 (ITA Fixed)
200
+ {'name': 'consignee_country', 'length': 3, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 47
201
+ {'name': 'shipment_value', 'length': 10, 'numeric_pad_zero': True, 'decimals': 2, 'mandatory': False, 'source_type': 'file'}, # 48
202
 
203
  # Tipo Cliente e Classe Spedizione (Campi 49-50)
204
+ {'name': 'client_type', 'length': 4, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': True, 'source_type': 'fixed'}, # 49 (Constant: MGCS)
205
+ {'name': 'shipment_class', 'length': 5, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 50
206
 
207
  # Oneri, Imballo, Fiscale (Campi 51-53)
208
+ {'name': 'duty_charge', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 51
209
+ {'name': 'packaging_type', 'length': 2, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 52
210
+ {'name': 'fiscal_id', 'length': 20, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 53
211
 
212
  # Barcode Servizio e Contatti Aggiuntivi (Campi 54-57)
213
+ {'name': 'barcode_service', 'length': 3, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 54
214
+ {'name': 'consignee_email', 'length': 50, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 55
215
+ {'name': 'consignee_mobile_prefix', 'length': 4, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 56
216
+ {'name': 'consignee_mobile_number', 'length': 10, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 57
217
+
218
+ # Altri Campi (Campi 58-65) - Nota: Campo 60 (id_tipo_spedizione) ora mappato al Campo 3
219
+ {'name': 'is_insured_percentage', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 58
220
+ {'name': 'barcode_service_return', 'length': 3, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 59
221
+ {'name': 'detail_bs', 'length': 6, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 61
222
+ {'name': 'notes_2', 'length': 50, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 62
223
+ {'name': 'fractional_office_code', 'length': 11, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 63
224
+ {'name': 'customer_post_box', 'length': 11, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'}, # 64
225
+ {'name': 'undelivered_action', 'length': 1, 'numeric_pad_zero': False, 'decimals': 0, 'mandatory': False, 'source_type': 'file'} # 65
 
226
  ]
227
 
228
  # Genera il contenuto del file .dat
 
230
  for index, row in df_merged.iterrows():
231
  record_parts = []
232
 
 
 
 
 
 
 
 
233
  for field_spec in dat_format_spec:
234
  field_name = field_spec['name']
235
  length = field_spec['length']
236
  is_numeric_zero_padded = field_spec['numeric_pad_zero']
237
  dec_places = field_spec['decimals']
238
  is_mandatory = field_spec['mandatory']
239
+ source_type = field_spec['source_type']
240
+
241
+ value_to_format = ''
242
 
243
+ if source_type == 'fixed':
244
+ if field_name == 'via_mittente_completa':
245
+ # Costruisci l'indirizzo completo del mittente
246
+ full_address = f"{fixed_and_default_values['tipo_via_mittente'].strip()} {fixed_and_default_values['via_mittente'].strip()} {fixed_and_default_values['numero_civico_mittente'].strip()}"
247
+ value_to_format = full_address.strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  else:
249
+ value_to_format = fixed_and_default_values.get(field_name, '')
250
+ elif source_type == 'file':
251
+ value_to_format = row.get(field_name)
252
+ # Gestisci NaN dopo la merge, se il campo dal file non è presente per quel record
253
+ if pd.isna(value_to_format):
254
+ value_to_format = ''
255
+ elif source_type == 'derived':
256
+ if field_name == 'shippingmark':
257
+ # Campo 42: Identificativo collo (shippingMark) prefissato
258
+ original_shipping_mark = row.get('shippingmark', '')
259
+ if pd.isna(original_shipping_mark):
260
+ original_shipping_mark = ''
261
+ value_to_format = f"CLI000000009{str(original_shipping_mark).strip()}"
262
+ # Altri campi derivati se ce ne fossero
263
+ elif field_name == 'data_spedizione':
264
+ value_to_format = datetime.now().strftime("%Y%m%d")
265
+ else:
266
+ value_to_format = '' # Fallback per derivati non implementati
267
 
268
  # Conversione in stringa e pulizia iniziale
269
+ value_str = str(value_to_format).strip()
270
 
271
+ # Se il campo è obbligatorio e il valore è ancora vuoto dopo aver cercato, usa il default
272
+ if is_mandatory and not value_str:
273
+ if is_numeric_zero_padded:
274
+ value_str = '0' * length
275
  else:
276
+ value_str = ' ' * length # Riempie con spazi se stringa obbligatoria vuota
277
 
278
+ # Formattazione finale (allineamento, padding)
279
  if is_numeric_zero_padded:
280
  try:
281
  clean_value = value_str.replace(',', '.') # Sostituisce virgole con punti
 
286
  else: # Campi numerici interi
287
  formatted_value = str(int(float(clean_value))).rjust(length, '0')
288
  except ValueError:
289
+ formatted_value = '0'.rjust(length, '0') # Fallback se la conversione fallisce
290
+ else:
291
  formatted_value = value_str.ljust(length)
292
 
293
  # Trunca se la stringa è più lunga della lunghezza specificata