pasquale991 commited on
Commit
fbff78a
·
verified ·
1 Parent(s): 2855799

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +71 -115
src/streamlit_app.py CHANGED
@@ -7,7 +7,6 @@ import os
7
  import pytz
8
 
9
  # --- Inizializzazione Session State ---
10
- # Queste variabili mantengono lo stato dell'applicazione tra i refresh
11
  if 'file_uploader_key_json' not in st.session_state:
12
  st.session_state.file_uploader_key_json = 'json_uploader_0'
13
  if 'file_uploader_key_customers' not in st.session_state:
@@ -18,7 +17,6 @@ if 'customers_df' not in st.session_state:
18
  st.session_state.customers_df = pd.DataFrame()
19
 
20
  # --- CSS personalizzato ---
21
- # Stili per rendere l'interfaccia più gradevole
22
  st.markdown(
23
  """
24
  <style>
@@ -54,20 +52,6 @@ UTC_TIMEZONE = pytz.utc
54
 
55
  # Funzione di conversione per Outbound
56
  def convert_json_to_excel_data_outbound(json_file_content: str, customers_df_input: pd.DataFrame = None):
57
- """
58
- Converte il contenuto di un file JSON di tipo 'Ordine Outbound' in un DataFrame Pandas,
59
- arricchendo i dati con informazioni da un DataFrame di clienti fornito.
60
-
61
- Args:
62
- json_file_content (str): Il contenuto del file JSON come stringa.
63
- customers_df_input (pd.DataFrame, optional): DataFrame contenente l'anagrafica dei clienti.
64
- Deve includere 'code', 'company.code' e altri campi partner.
65
- Defaults to None.
66
-
67
- Returns:
68
- pd.DataFrame: Un DataFrame Pandas con i dati estratti e formattati,
69
- o None se si verifica un errore bloccante.
70
- """
71
  try:
72
  json_data = json.loads(json_file_content)
73
 
@@ -80,23 +64,23 @@ def convert_json_to_excel_data_outbound(json_file_content: str, customers_df_inp
80
 
81
  order_number = outbound_info.get('orderNumber')
82
  order_type_json = outbound_info.get('orderType')
83
- delivery_type_fixed = "DELIVERY" # Valore fisso
84
 
85
- order_doc_date_str = outbound_info.get('orderDocDate')
86
- date_detail_json = outbound_info.get('orderDateDetail')
87
- due_date_str = outbound_info.get('orderDate')
88
 
89
  reason_code = outbound_info.get('reasonCode')
90
  payment_info_value = outbound_info.get('paymentInfo', {}).get('value')
91
 
92
  delivery_customer = outbound_info.get('deliveryCustomer', {})
93
  customer_code = delivery_customer.get('code')
94
- customer_mail = delivery_customer.get('refMail')
95
 
96
- # Consignee data (from JSON - destinatario della merce)
97
  consignee_name = delivery_customer.get('name')
98
  consignee_address_base = delivery_customer.get('address', '')
99
- consignee_address_number = delivery_customer.get('addressNumber', '')
100
 
101
  consignee_address = f"{consignee_address_base} {consignee_address_number}".strip() if consignee_address_base or consignee_address_number else ""
102
 
@@ -105,21 +89,21 @@ def convert_json_to_excel_data_outbound(json_file_content: str, customers_df_inp
105
  consignee_zip_code = delivery_customer.get('zipCode')
106
  consignee_country = delivery_customer.get('country')
107
 
108
- # Customer data (from Excel lookup - cliente effettivo che ha fatto l'ordine)
109
  customer_name = ""
110
- customer_address = ""
111
  customer_city = ""
112
  customer_province = ""
113
  customer_zip_code = ""
114
  customer_country = ""
115
 
116
- # Esegue la lookup solo se customers_df_input è fornito e non vuoto
117
  if customers_df_input is not None and not customers_df_input.empty and customer_code and company_code_json:
118
 
119
  # Filtra per 'code' e 'company.code' (assicurandosi che siano stringhe)
120
  # Aggiunto controllo per tipo stringa prima del confronto
121
  customer_data = customers_df_input[
122
- (customers_df_input['code'].astype(str) == str(customer_code)) &
123
  (customers_df_input['company.code'].astype(str) == str(company_code_json))
124
  ]
125
 
@@ -148,8 +132,8 @@ def convert_json_to_excel_data_outbound(json_file_content: str, customers_df_inp
148
  return None # Errore bloccante se l'Excel clienti non è disponibile
149
 
150
  # Note fields
151
- note_ddt = outbound_info.get('noteDdt')
152
- note_warehouse = outbound_info.get('noteWarehouse')
153
 
154
  note_delivery = outbound_info.get('noteDelivery')
155
  note_shipment = outbound_info.get('noteShipment')
@@ -158,23 +142,22 @@ def convert_json_to_excel_data_outbound(json_file_content: str, customers_df_inp
158
  booking_info = outbound_info.get('bookingInfo')
159
 
160
  # Formattazione delle date
161
- order_date_formatted = None
162
  if order_doc_date_str:
163
  try:
164
- # Rimuovi 'Z' per compatibilità con fromisoformat se presente
165
  dt_object = datetime.fromisoformat(order_doc_date_str.replace('Z', ''))
166
  order_date_formatted = dt_object.strftime('%d/%m/%Y')
167
  except ValueError:
168
- order_date_formatted = order_doc_date_str # Usa la stringa originale se il parsing fallisce
169
  st.warning(f"Attenzione: Impossibile parsare 'orderDocDate' '{order_doc_date_str}'. Verrà usata la stringa originale.")
170
 
171
  due_date_formatted = None
172
- if due_date_str:
173
  try:
174
  dt_object = datetime.fromisoformat(due_date_str.replace('Z', ''))
175
  due_date_formatted = dt_object.strftime('%d/%m/%Y')
176
  except ValueError:
177
- due_date_formatted = due_date_str # Usa la stringa originale se il parsing fallisce
178
  st.warning(f"Attenzione: Impossibile parsare 'orderDate' (dueDate) '{due_date_str}'. Verrà usata la stringa originale.")
179
 
180
  details = outbound_info.get('details', [])
@@ -185,38 +168,38 @@ def convert_json_to_excel_data_outbound(json_file_content: str, customers_df_inp
185
  'siteCode': site_code_json,
186
  'companyCode': company_code_json,
187
  'orderNumber': order_number,
188
- 'orderType': order_type_json,
189
- 'deliveryType': delivery_type_fixed,
190
- 'orderDate': order_date_formatted,
191
- 'dateDetail': date_detail_json,
192
  'dueDate': due_date_formatted,
193
  'reasonCode': reason_code,
194
- 'paymentValue': payment_info_value,
195
  'customer.code': customer_code,
196
- 'customer.name': customer_name,
197
- 'customer.address': customer_address,
198
- 'customer.city': customer_city,
199
- 'customer.province': customer_province,
200
- 'customer.zipCode': customer_zip_code,
201
- 'customer.country': customer_country,
202
- 'consignee.name': consignee_name,
203
- 'consignee.address': consignee_address,
204
- 'consignee.city': consignee_city,
205
- 'consignee.province': consignee_province,
206
- 'consignee.zipCode': consignee_zip_code,
207
- 'consignee.country': consignee_country,
208
- 'customer.mail': customer_mail,
209
  'details.positionId': item.get('positionId'),
210
- 'details.itemNumber': item.get('itemCode'),
211
- 'details.requestedQt': item.get('quantity'),
212
  'details.lotId': item.get('lotId'),
213
  'details.stockOwner': item.get('stockOwner'),
214
- 'details.magCli': item.get('warehouse'),
215
  'details.availability': item.get('availability'),
216
  'details.accountingStatus': item.get('accountingStatus'),
217
  'details.refOrder': item.get('refOrder'),
218
- 'noteDDT': note_ddt,
219
- 'noteMag': note_warehouse,
220
  'noteDelivery': note_delivery,
221
  'noteShipment': note_shipment,
222
  'reasonCodeShp': reason_code_shp,
@@ -226,7 +209,7 @@ def convert_json_to_excel_data_outbound(json_file_content: str, customers_df_inp
226
  processed_data_for_this_file.append(row)
227
 
228
  if not processed_data_for_this_file:
229
- return pd.DataFrame() # Ritorna un DataFrame vuoto se non ci sono dettagli
230
 
231
  return pd.DataFrame(processed_data_for_this_file)
232
 
@@ -244,17 +227,11 @@ def convert_json_to_excel_data_outbound(json_file_content: str, customers_df_inp
244
  st.title("Convertitore JSON Outbound a Template Excel Outbound")
245
  st.write("Questo strumento converte uno o più file JSON di ordini Outbound in un unico foglio Excel formattato, utilizzando un'anagrafica partner esterna.")
246
 
247
- ---
248
-
249
- ### 1. Carica Excel con Anagrafica Partner
250
-
251
- Qui puoi caricare il file Excel che contiene i dati dei tuoi clienti. È essenziale per arricchire le informazioni provenienti dai JSON.
252
-
253
- ```python
254
  uploaded_customers_file = st.file_uploader(
255
  "Scegli il file Excel con l'anagrafica dei partner",
256
  type=["xlsx", "xls"],
257
- accept_multiple_files=False,
258
  key=st.session_state.file_uploader_key_customers
259
  )
260
 
@@ -264,12 +241,12 @@ customers_df = st.session_state.customers_df
264
  if uploaded_customers_file:
265
  try:
266
  # Leggi l'Excel partendo dalla riga 2 (indice 1) per le intestazioni
267
- temp_customers_df = pd.read_excel(uploaded_customers_file, header=1)
268
 
269
  # Colonne richieste nell'Excel clienti
270
  required_excel_cols = [
271
- 'code', 'company.code', 'partner.name', 'partner.address.address',
272
- 'partner.address.addressNumber', 'partner.address.city',
273
  'partner.address.province', 'partner.address.zipCode', 'partner.address.country'
274
  ]
275
 
@@ -296,13 +273,7 @@ if uploaded_customers_file:
296
  # Aggiorna la variabile locale customers_df ogni volta che il session_state cambia
297
  customers_df = st.session_state.customers_df
298
 
299
- ---
300
-
301
- ### 2. Carica i tuoi file JSON degli Ordini Outbound
302
-
303
- Qui puoi caricare uno o più file JSON. **Ricorda**: l'uploader è attivo solo dopo aver caricato un file Excel di anagrafica valido.
304
-
305
- ```python
306
  uploaded_json_files = st.file_uploader(
307
  "Scegli uno o più file JSON di tipo 'Ordine_Outbound'",
308
  type=["json"],
@@ -321,11 +292,10 @@ if uploaded_json_files and st.session_state.customer_excel_loaded:
321
  for uploaded_file in uploaded_json_files:
322
  file_name = uploaded_file.name
323
 
324
- # Verifica che il nome del file inizi con "Ordine_Outbound"
325
  if not file_name.lower().startswith("ordine_outbound"):
326
  st.error(f"Errore: Il file '{file_name}' non inizia con 'Ordine_Outbound'. Verrà ignorato.")
327
  has_blocking_error = True
328
- continue # Passa al prossimo file
329
 
330
  try:
331
  json_content_bytes = uploaded_file.read()
@@ -334,7 +304,7 @@ if uploaded_json_files and st.session_state.customer_excel_loaded:
334
  # Passa customers_df dallo session state alla funzione di conversione
335
  df_this_file = convert_json_to_excel_data_outbound(json_content_string, st.session_state.customers_df)
336
 
337
- if df_this_file is not None and not df_this_file.empty:
338
  all_processed_data.append(df_this_file)
339
  elif df_this_file is None: # Se la funzione ritorna None, c'è stato un errore bloccante
340
  has_blocking_error = True
@@ -344,24 +314,22 @@ if uploaded_json_files and st.session_state.customer_excel_loaded:
344
  st.error(f"Si è verificato un errore imprevisto durante la lettura o l'elaborazione del file '{file_name}': {e}")
345
  has_blocking_error = True
346
 
347
- # --- Generazione del file Excel finale ---
348
  if not has_blocking_error and all_processed_data:
349
- # Concatena tutti i DataFrame elaborati in uno solo
350
  final_df = pd.concat(all_processed_data, ignore_index=True)
351
  st.success(f"Elaborazione di {len(all_processed_data)} file JSON completata! Pronto per il download.")
352
 
353
  # --- Post-processing per la formattazione Excel ---
354
 
355
- # Definizione delle colonne di testata che devono essere azzerate per le righe duplicate dello stesso ordine
356
  header_columns = [
357
  'siteCode', 'companyCode', 'orderNumber', 'orderType', 'deliveryType',
358
  'orderDate', 'dateDetail', 'dueDate', 'reasonCode',
359
- 'paymentValue',
360
  'customer.code', 'customer.name', 'customer.address', 'customer.city',
361
  'customer.province', 'customer.zipCode', 'customer.country',
362
  'consignee.name', 'consignee.address', 'consignee.city', 'consignee.province',
363
  'consignee.zipCode', 'consignee.country', 'customer.mail',
364
- 'noteDDT', 'noteMag', 'noteDelivery', 'noteShipment',
365
  'reasonCodeShp', 'infoDelivery', 'bookingInfo'
366
  ]
367
 
@@ -371,23 +339,23 @@ if uploaded_json_files and st.session_state.customer_excel_loaded:
371
  # Crea una maschera booleana per identificare le righe duplicate per orderNumber
372
  rows_to_clear_headers = final_df.duplicated(subset=['orderNumber'], keep='first')
373
 
374
- # Azzerare le colonne di testata per le righe identificate (lascia solo la prima occorrenza)
375
  final_df.loc[rows_to_clear_headers, header_columns] = ""
376
 
377
  # --- Creazione file Excel ---
378
  output = io.BytesIO()
379
  writer = pd.ExcelWriter(output, engine='xlsxwriter')
380
 
381
- sheet_name_excel = 'Delivery' # Nome del foglio Excel
382
 
383
  workbook = writer.book
384
- worksheet = workbook.add_worksheet(sheet_name_excel)
385
 
386
- # Lista delle colonne finali nell'ordine desiderato per l'Excel
387
  final_columns_order = [
388
  'siteCode', 'companyCode', 'orderNumber', 'orderType', 'deliveryType',
389
  'orderDate', 'dateDetail', 'dueDate', 'reasonCode',
390
- 'paymentValue',
391
  'customer.code', 'customer.name', 'customer.address', 'customer.city',
392
  'customer.province', 'customer.zipCode', 'customer.country',
393
  'consignee.name', 'consignee.address', 'consignee.city', 'consignee.province',
@@ -395,30 +363,29 @@ if uploaded_json_files and st.session_state.customer_excel_loaded:
395
  'details.positionId', 'details.itemNumber', 'details.requestedQt',
396
  'details.lotId', 'details.stockOwner', 'details.magCli', 'details.availability',
397
  'details.accountingStatus', 'details.refOrder',
398
- 'noteDDT', 'noteMag', 'noteDelivery', 'noteShipment',
399
  'reasonCodeShp', 'infoDelivery', 'bookingInfo'
400
  ]
401
 
402
  # Assicurati che il DataFrame abbia tutte le colonne nell'ordine desiderato
403
- # Aggiungi colonne mancanti con valori vuoti
404
  for col in final_columns_order:
405
  if col not in final_df.columns:
406
- final_df[col] = ""
407
  final_df = final_df[final_columns_order]
408
 
409
- # Larghezze delle colonne aggiornate per l'Excel
410
  col_widths = {
411
  'siteCode': 10, 'companyCode': 15, 'orderNumber': 15, 'orderType': 10, 'deliveryType': 12,
412
- 'orderDate': 12, 'dateDetail': 15, 'dueDate': 12, 'reasonCode': 12,
413
- 'paymentValue': 15,
414
- 'customer.code': 15, 'customer.name': 30, 'customer.address': 30, 'customer.city': 15,
415
  'customer.province': 10, 'customer.zipCode': 10, 'customer.country': 10,
416
  'consignee.name': 30, 'consignee.address': 30, 'consignee.city': 15, 'consignee.province': 10,
417
  'consignee.zipCode': 10, 'consignee.country': 10, 'customer.mail': 25,
418
  'details.positionId': 12, 'details.itemNumber': 20, 'details.requestedQt': 15,
419
  'details.lotId': 10, 'details.stockOwner': 15, 'details.magCli': 15, 'details.availability': 15,
420
  'details.accountingStatus': 18, 'details.refOrder': 15,
421
- 'noteDDT': 20, 'noteMag': 20, 'noteDelivery': 20, 'noteShipment': 20,
422
  'reasonCodeShp': 15, 'infoDelivery': 20, 'bookingInfo': 20
423
  }
424
 
@@ -427,27 +394,26 @@ if uploaded_json_files and st.session_state.customer_excel_loaded:
427
 
428
  # Scrivi i nomi delle colonne nella riga 2 (indice 1 in xlsxwriter)
429
  for col_num, col_name in enumerate(final_df.columns.values):
430
- worksheet.write(1, col_num, col_name)
431
 
432
  # Imposta le larghezze delle colonne
433
  if col_name in col_widths:
434
  worksheet.set_column(col_num, col_num, col_widths[col_name])
435
  else:
436
- worksheet.set_column(col_num, col_num, 15) # Larghezza di default
437
-
438
  # Applica il formato data alle colonne 'orderDate' e 'dueDate'
439
- if col_name in ['orderDate', 'dueDate']:
440
  worksheet.set_column(col_num, col_num, col_widths[col_name], date_format)
441
 
442
  # Scrivi i dati a partire dalla riga 3 (indice 2 in xlsxwriter)
443
  final_df.to_excel(writer, sheet_name=sheet_name_excel, index=False, header=False, startrow=2)
444
 
445
  writer.close()
446
- output.seek(0) # Riporta il "puntatore" all'inizio del file BytesIO
447
 
448
- # Genera un nome di file unico con timestamp
449
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
450
- output_file_name = f"DeliveryOrders_{timestamp}.xlsx"
451
 
452
  st.download_button(
453
  label="Scarica file Excel Unificato",
@@ -462,20 +428,10 @@ if uploaded_json_files and st.session_state.customer_excel_loaded:
462
  else:
463
  st.info("Carica i tuoi file per iniziare.")
464
 
465
- ---
466
-
467
- ### Pulisci Input
468
-
469
- Questo pulsante ti permette di resettare l'applicazione e caricare nuovi file.
470
-
471
- ```python
472
  # --- Funzione per il reset ---
473
  def clear_inputs():
474
- """Resetta lo stato dell'applicazione, pulendo gli input e i dati caricati."""
475
- # Aggiorna le chiavi degli uploader per forzarne il reset visivo
476
  st.session_state.file_uploader_key_json = f'json_uploader_{datetime.now().timestamp()}'
477
- # Aggiungi un piccolo offset per assicurare che la chiave sia diversa
478
- st.session_state.file_uploader_key_customers = f'customers_uploader_{datetime.now().timestamp() + 1}'
479
  st.session_state.customer_excel_loaded = False
480
  st.session_state.customers_df = pd.DataFrame() # Resetta il DataFrame dell'anagrafica
481
 
 
7
  import pytz
8
 
9
  # --- Inizializzazione Session State ---
 
10
  if 'file_uploader_key_json' not in st.session_state:
11
  st.session_state.file_uploader_key_json = 'json_uploader_0'
12
  if 'file_uploader_key_customers' not in st.session_state:
 
17
  st.session_state.customers_df = pd.DataFrame()
18
 
19
  # --- CSS personalizzato ---
 
20
  st.markdown(
21
  """
22
  <style>
 
52
 
53
  # Funzione di conversione per Outbound
54
  def convert_json_to_excel_data_outbound(json_file_content: str, customers_df_input: pd.DataFrame = None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  try:
56
  json_data = json.loads(json_file_content)
57
 
 
64
 
65
  order_number = outbound_info.get('orderNumber')
66
  order_type_json = outbound_info.get('orderType')
67
+ delivery_type_fixed = "DELIVERY"
68
 
69
+ order_doc_date_str = outbound_info.get('orderDocDate')
70
+ date_detail_json = outbound_info.get('orderDateDetail')
71
+ due_date_str = outbound_info.get('orderDate')
72
 
73
  reason_code = outbound_info.get('reasonCode')
74
  payment_info_value = outbound_info.get('paymentInfo', {}).get('value')
75
 
76
  delivery_customer = outbound_info.get('deliveryCustomer', {})
77
  customer_code = delivery_customer.get('code')
78
+ customer_mail = delivery_customer.get('refMail')
79
 
80
+ # Consignee data (from JSON)
81
  consignee_name = delivery_customer.get('name')
82
  consignee_address_base = delivery_customer.get('address', '')
83
+ consignee_address_number = delivery_customer.get('addressNumber', '')
84
 
85
  consignee_address = f"{consignee_address_base} {consignee_address_number}".strip() if consignee_address_base or consignee_address_number else ""
86
 
 
89
  consignee_zip_code = delivery_customer.get('zipCode')
90
  consignee_country = delivery_customer.get('country')
91
 
92
+ # Customer data (from Excel lookup)
93
  customer_name = ""
94
+ customer_address = ""
95
  customer_city = ""
96
  customer_province = ""
97
  customer_zip_code = ""
98
  customer_country = ""
99
 
100
+ # Esegui la lookup solo se customers_df_input è fornito e non vuoto
101
  if customers_df_input is not None and not customers_df_input.empty and customer_code and company_code_json:
102
 
103
  # Filtra per 'code' e 'company.code' (assicurandosi che siano stringhe)
104
  # Aggiunto controllo per tipo stringa prima del confronto
105
  customer_data = customers_df_input[
106
+ (customers_df_input['code'].astype(str) == str(customer_code)) &
107
  (customers_df_input['company.code'].astype(str) == str(company_code_json))
108
  ]
109
 
 
132
  return None # Errore bloccante se l'Excel clienti non è disponibile
133
 
134
  # Note fields
135
+ note_ddt = outbound_info.get('noteDdt')
136
+ note_warehouse = outbound_info.get('noteWarehouse')
137
 
138
  note_delivery = outbound_info.get('noteDelivery')
139
  note_shipment = outbound_info.get('noteShipment')
 
142
  booking_info = outbound_info.get('bookingInfo')
143
 
144
  # Formattazione delle date
145
+ order_date_formatted = None
146
  if order_doc_date_str:
147
  try:
 
148
  dt_object = datetime.fromisoformat(order_doc_date_str.replace('Z', ''))
149
  order_date_formatted = dt_object.strftime('%d/%m/%Y')
150
  except ValueError:
151
+ order_date_formatted = order_doc_date_str
152
  st.warning(f"Attenzione: Impossibile parsare 'orderDocDate' '{order_doc_date_str}'. Verrà usata la stringa originale.")
153
 
154
  due_date_formatted = None
155
+ if due_date_str:
156
  try:
157
  dt_object = datetime.fromisoformat(due_date_str.replace('Z', ''))
158
  due_date_formatted = dt_object.strftime('%d/%m/%Y')
159
  except ValueError:
160
+ due_date_formatted = due_date_str
161
  st.warning(f"Attenzione: Impossibile parsare 'orderDate' (dueDate) '{due_date_str}'. Verrà usata la stringa originale.")
162
 
163
  details = outbound_info.get('details', [])
 
168
  'siteCode': site_code_json,
169
  'companyCode': company_code_json,
170
  'orderNumber': order_number,
171
+ 'orderType': order_type_json,
172
+ 'deliveryType': delivery_type_fixed,
173
+ 'orderDate': order_date_formatted,
174
+ 'dateDetail': date_detail_json,
175
  'dueDate': due_date_formatted,
176
  'reasonCode': reason_code,
177
+ 'paymentValue': payment_info_value,
178
  'customer.code': customer_code,
179
+ 'customer.name': customer_name,
180
+ 'customer.address': customer_address,
181
+ 'customer.city': customer_city,
182
+ 'customer.province': customer_province,
183
+ 'customer.zipCode': customer_zip_code,
184
+ 'customer.country': customer_country,
185
+ 'consignee.name': consignee_name,
186
+ 'consignee.address': consignee_address,
187
+ 'consignee.city': consignee_city,
188
+ 'consignee.province': consignee_province,
189
+ 'consignee.zipCode': consignee_zip_code,
190
+ 'consignee.country': consignee_country,
191
+ 'customer.mail': customer_mail,
192
  'details.positionId': item.get('positionId'),
193
+ 'details.itemNumber': item.get('itemCode'),
194
+ 'details.requestedQt': item.get('quantity'),
195
  'details.lotId': item.get('lotId'),
196
  'details.stockOwner': item.get('stockOwner'),
197
+ 'details.magCli': item.get('warehouse'),
198
  'details.availability': item.get('availability'),
199
  'details.accountingStatus': item.get('accountingStatus'),
200
  'details.refOrder': item.get('refOrder'),
201
+ 'noteDDT': note_ddt,
202
+ 'noteMag': note_warehouse,
203
  'noteDelivery': note_delivery,
204
  'noteShipment': note_shipment,
205
  'reasonCodeShp': reason_code_shp,
 
209
  processed_data_for_this_file.append(row)
210
 
211
  if not processed_data_for_this_file:
212
+ return pd.DataFrame()
213
 
214
  return pd.DataFrame(processed_data_for_this_file)
215
 
 
227
  st.title("Convertitore JSON Outbound a Template Excel Outbound")
228
  st.write("Questo strumento converte uno o più file JSON di ordini Outbound in un unico foglio Excel formattato, utilizzando un'anagrafica partner esterna.")
229
 
230
+ st.header("1. Carica Excel con Anagrafica Partner")
 
 
 
 
 
 
231
  uploaded_customers_file = st.file_uploader(
232
  "Scegli il file Excel con l'anagrafica dei partner",
233
  type=["xlsx", "xls"],
234
+ accept_multiple_files=False,
235
  key=st.session_state.file_uploader_key_customers
236
  )
237
 
 
241
  if uploaded_customers_file:
242
  try:
243
  # Leggi l'Excel partendo dalla riga 2 (indice 1) per le intestazioni
244
+ temp_customers_df = pd.read_excel(uploaded_customers_file, header=1)
245
 
246
  # Colonne richieste nell'Excel clienti
247
  required_excel_cols = [
248
+ 'code', 'company.code', 'partner.name', 'partner.address.address',
249
+ 'partner.address.addressNumber', 'partner.address.city',
250
  'partner.address.province', 'partner.address.zipCode', 'partner.address.country'
251
  ]
252
 
 
273
  # Aggiorna la variabile locale customers_df ogni volta che il session_state cambia
274
  customers_df = st.session_state.customers_df
275
 
276
+ st.header("2. Carica i tuoi file JSON degli Ordini Outbound")
 
 
 
 
 
 
277
  uploaded_json_files = st.file_uploader(
278
  "Scegli uno o più file JSON di tipo 'Ordine_Outbound'",
279
  type=["json"],
 
292
  for uploaded_file in uploaded_json_files:
293
  file_name = uploaded_file.name
294
 
 
295
  if not file_name.lower().startswith("ordine_outbound"):
296
  st.error(f"Errore: Il file '{file_name}' non inizia con 'Ordine_Outbound'. Verrà ignorato.")
297
  has_blocking_error = True
298
+ continue
299
 
300
  try:
301
  json_content_bytes = uploaded_file.read()
 
304
  # Passa customers_df dallo session state alla funzione di conversione
305
  df_this_file = convert_json_to_excel_data_outbound(json_content_string, st.session_state.customers_df)
306
 
307
+ if df_this_file is not None and not df_this_file.empty:
308
  all_processed_data.append(df_this_file)
309
  elif df_this_file is None: # Se la funzione ritorna None, c'è stato un errore bloccante
310
  has_blocking_error = True
 
314
  st.error(f"Si è verificato un errore imprevisto durante la lettura o l'elaborazione del file '{file_name}': {e}")
315
  has_blocking_error = True
316
 
 
317
  if not has_blocking_error and all_processed_data:
 
318
  final_df = pd.concat(all_processed_data, ignore_index=True)
319
  st.success(f"Elaborazione di {len(all_processed_data)} file JSON completata! Pronto per il download.")
320
 
321
  # --- Post-processing per la formattazione Excel ---
322
 
323
+ # Definizione delle colonne di testata
324
  header_columns = [
325
  'siteCode', 'companyCode', 'orderNumber', 'orderType', 'deliveryType',
326
  'orderDate', 'dateDetail', 'dueDate', 'reasonCode',
327
+ 'paymentValue',
328
  'customer.code', 'customer.name', 'customer.address', 'customer.city',
329
  'customer.province', 'customer.zipCode', 'customer.country',
330
  'consignee.name', 'consignee.address', 'consignee.city', 'consignee.province',
331
  'consignee.zipCode', 'consignee.country', 'customer.mail',
332
+ 'noteDDT', 'noteMag', 'noteDelivery', 'noteShipment',
333
  'reasonCodeShp', 'infoDelivery', 'bookingInfo'
334
  ]
335
 
 
339
  # Crea una maschera booleana per identificare le righe duplicate per orderNumber
340
  rows_to_clear_headers = final_df.duplicated(subset=['orderNumber'], keep='first')
341
 
342
+ # Azzerare le colonne di testata per le righe identificate
343
  final_df.loc[rows_to_clear_headers, header_columns] = ""
344
 
345
  # --- Creazione file Excel ---
346
  output = io.BytesIO()
347
  writer = pd.ExcelWriter(output, engine='xlsxwriter')
348
 
349
+ sheet_name_excel = 'Delivery' # Nome del foglio modificato
350
 
351
  workbook = writer.book
352
+ worksheet = workbook.add_worksheet(sheet_name_excel)
353
 
354
+ # Lista delle colonne finali nell'ordine desiderato
355
  final_columns_order = [
356
  'siteCode', 'companyCode', 'orderNumber', 'orderType', 'deliveryType',
357
  'orderDate', 'dateDetail', 'dueDate', 'reasonCode',
358
+ 'paymentValue',
359
  'customer.code', 'customer.name', 'customer.address', 'customer.city',
360
  'customer.province', 'customer.zipCode', 'customer.country',
361
  'consignee.name', 'consignee.address', 'consignee.city', 'consignee.province',
 
363
  'details.positionId', 'details.itemNumber', 'details.requestedQt',
364
  'details.lotId', 'details.stockOwner', 'details.magCli', 'details.availability',
365
  'details.accountingStatus', 'details.refOrder',
366
+ 'noteDDT', 'noteMag', 'noteDelivery', 'noteShipment',
367
  'reasonCodeShp', 'infoDelivery', 'bookingInfo'
368
  ]
369
 
370
  # Assicurati che il DataFrame abbia tutte le colonne nell'ordine desiderato
 
371
  for col in final_columns_order:
372
  if col not in final_df.columns:
373
+ final_df[col] = ""
374
  final_df = final_df[final_columns_order]
375
 
376
+ # Larghezze delle colonne aggiornate
377
  col_widths = {
378
  'siteCode': 10, 'companyCode': 15, 'orderNumber': 15, 'orderType': 10, 'deliveryType': 12,
379
+ 'orderDate': 12, 'dateDetail': 15, 'dueDate': 12, 'reasonCode': 12,
380
+ 'paymentValue': 15,
381
+ 'customer.code': 15, 'customer.name': 30, 'customer.address': 30, 'customer.city': 15,
382
  'customer.province': 10, 'customer.zipCode': 10, 'customer.country': 10,
383
  'consignee.name': 30, 'consignee.address': 30, 'consignee.city': 15, 'consignee.province': 10,
384
  'consignee.zipCode': 10, 'consignee.country': 10, 'customer.mail': 25,
385
  'details.positionId': 12, 'details.itemNumber': 20, 'details.requestedQt': 15,
386
  'details.lotId': 10, 'details.stockOwner': 15, 'details.magCli': 15, 'details.availability': 15,
387
  'details.accountingStatus': 18, 'details.refOrder': 15,
388
+ 'noteDDT': 20, 'noteMag': 20, 'noteDelivery': 20, 'noteShipment': 20,
389
  'reasonCodeShp': 15, 'infoDelivery': 20, 'bookingInfo': 20
390
  }
391
 
 
394
 
395
  # Scrivi i nomi delle colonne nella riga 2 (indice 1 in xlsxwriter)
396
  for col_num, col_name in enumerate(final_df.columns.values):
397
+ worksheet.write(1, col_num, col_name)
398
 
399
  # Imposta le larghezze delle colonne
400
  if col_name in col_widths:
401
  worksheet.set_column(col_num, col_num, col_widths[col_name])
402
  else:
403
+ worksheet.set_column(col_num, col_num, 15)
404
+
405
  # Applica il formato data alle colonne 'orderDate' e 'dueDate'
406
+ if col_name in ['orderDate', 'dueDate']:
407
  worksheet.set_column(col_num, col_num, col_widths[col_name], date_format)
408
 
409
  # Scrivi i dati a partire dalla riga 3 (indice 2 in xlsxwriter)
410
  final_df.to_excel(writer, sheet_name=sheet_name_excel, index=False, header=False, startrow=2)
411
 
412
  writer.close()
413
+ output.seek(0)
414
 
 
415
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
416
+ output_file_name = f"DeliveryOrders_{timestamp}.xlsx" # Nome del file di output
417
 
418
  st.download_button(
419
  label="Scarica file Excel Unificato",
 
428
  else:
429
  st.info("Carica i tuoi file per iniziare.")
430
 
 
 
 
 
 
 
 
431
  # --- Funzione per il reset ---
432
  def clear_inputs():
 
 
433
  st.session_state.file_uploader_key_json = f'json_uploader_{datetime.now().timestamp()}'
434
+ st.session_state.file_uploader_key_customers = f'customers_uploader_{datetime.now().timestamp() + 1}' # Assicurati che le chiavi siano diverse
 
435
  st.session_state.customer_excel_loaded = False
436
  st.session_state.customers_df = pd.DataFrame() # Resetta il DataFrame dell'anagrafica
437