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

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +115 -78
src/streamlit_app.py CHANGED
@@ -7,6 +7,7 @@ import os
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,6 +18,7 @@ if 'customers_df' not in st.session_state:
17
  st.session_state.customers_df = pd.DataFrame()
18
 
19
  # --- CSS personalizzato ---
 
20
  st.markdown(
21
  """
22
  <style>
@@ -52,6 +54,20 @@ UTC_TIMEZONE = pytz.utc
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,23 +80,23 @@ def convert_json_to_excel_data_outbound(json_file_content: str, customers_df_inp
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,21 +105,21 @@ def convert_json_to_excel_data_outbound(json_file_content: str, customers_df_inp
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,8 +148,8 @@ def convert_json_to_excel_data_outbound(json_file_content: str, customers_df_inp
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,69 +158,65 @@ def convert_json_to_excel_data_outbound(json_file_content: str, customers_df_inp
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', [])
164
 
165
  processed_data_for_this_file = []
166
  for item in details:
167
- # Logica per cancelPosOda
168
- cancel_pos_oda_value = item.get('cancelPosOda', False) # Assume default a False se non presente
169
- cancel_pos_oda_excel = 'X' if cancel_pos_oda_value is True else '' # Metti 'X' se True, altrimenti vuoto
170
-
171
  row = {
172
  'siteCode': site_code_json,
173
  'companyCode': company_code_json,
174
  'orderNumber': order_number,
175
- 'orderType': order_type_json,
176
- 'deliveryType': delivery_type_fixed,
177
- 'orderDate': order_date_formatted,
178
- 'dateDetail': date_detail_json,
179
  'dueDate': due_date_formatted,
180
  'reasonCode': reason_code,
181
- 'paymentValue': payment_info_value,
182
  'customer.code': customer_code,
183
- 'customer.name': customer_name,
184
- 'customer.address': customer_address,
185
- 'customer.city': customer_city,
186
- 'customer.province': customer_province,
187
- 'customer.zipCode': customer_zip_code,
188
- 'customer.country': customer_country,
189
- 'consignee.name': consignee_name,
190
- 'consignee.address': consignee_address,
191
- 'consignee.city': consignee_city,
192
- 'consignee.province': consignee_province,
193
- 'consignee.zipCode': consignee_zip_code,
194
- 'consignee.country': consignee_country,
195
- 'customer.mail': customer_mail,
196
  'details.positionId': item.get('positionId'),
197
- 'details.itemNumber': item.get('itemCode'),
198
- 'details.requestedQt': item.get('quantity'),
199
  'details.lotId': item.get('lotId'),
200
  'details.stockOwner': item.get('stockOwner'),
201
- 'details.magCli': item.get('warehouse'),
202
  'details.availability': item.get('availability'),
203
  'details.accountingStatus': item.get('accountingStatus'),
204
  'details.refOrder': item.get('refOrder'),
205
- 'cancelPosOda': cancel_pos_oda_excel, # Nuovo campo
206
- 'noteDDT': note_ddt,
207
- 'noteMag': note_warehouse,
208
  'noteDelivery': note_delivery,
209
  'noteShipment': note_shipment,
210
  'reasonCodeShp': reason_code_shp,
@@ -214,7 +226,7 @@ def convert_json_to_excel_data_outbound(json_file_content: str, customers_df_inp
214
  processed_data_for_this_file.append(row)
215
 
216
  if not processed_data_for_this_file:
217
- return pd.DataFrame()
218
 
219
  return pd.DataFrame(processed_data_for_this_file)
220
 
@@ -232,11 +244,17 @@ def convert_json_to_excel_data_outbound(json_file_content: str, customers_df_inp
232
  st.title("Convertitore JSON Outbound a Template Excel Outbound")
233
  st.write("Questo strumento converte uno o più file JSON di ordini Outbound in un unico foglio Excel formattato, utilizzando un'anagrafica partner esterna.")
234
 
235
- st.header("1. Carica Excel con Anagrafica Partner")
 
 
 
 
 
 
236
  uploaded_customers_file = st.file_uploader(
237
  "Scegli il file Excel con l'anagrafica dei partner",
238
  type=["xlsx", "xls"],
239
- accept_multiple_files=False,
240
  key=st.session_state.file_uploader_key_customers
241
  )
242
 
@@ -246,12 +264,12 @@ customers_df = st.session_state.customers_df
246
  if uploaded_customers_file:
247
  try:
248
  # Leggi l'Excel partendo dalla riga 2 (indice 1) per le intestazioni
249
- temp_customers_df = pd.read_excel(uploaded_customers_file, header=1)
250
 
251
  # Colonne richieste nell'Excel clienti
252
  required_excel_cols = [
253
- 'code', 'company.code', 'partner.name', 'partner.address.address',
254
- 'partner.address.addressNumber', 'partner.address.city',
255
  'partner.address.province', 'partner.address.zipCode', 'partner.address.country'
256
  ]
257
 
@@ -278,7 +296,13 @@ if uploaded_customers_file:
278
  # Aggiorna la variabile locale customers_df ogni volta che il session_state cambia
279
  customers_df = st.session_state.customers_df
280
 
281
- st.header("2. Carica i tuoi file JSON degli Ordini Outbound")
 
 
 
 
 
 
282
  uploaded_json_files = st.file_uploader(
283
  "Scegli uno o più file JSON di tipo 'Ordine_Outbound'",
284
  type=["json"],
@@ -297,10 +321,11 @@ if uploaded_json_files and st.session_state.customer_excel_loaded:
297
  for uploaded_file in uploaded_json_files:
298
  file_name = uploaded_file.name
299
 
 
300
  if not file_name.lower().startswith("ordine_outbound"):
301
  st.error(f"Errore: Il file '{file_name}' non inizia con 'Ordine_Outbound'. Verrà ignorato.")
302
  has_blocking_error = True
303
- continue
304
 
305
  try:
306
  json_content_bytes = uploaded_file.read()
@@ -309,7 +334,7 @@ if uploaded_json_files and st.session_state.customer_excel_loaded:
309
  # Passa customers_df dallo session state alla funzione di conversione
310
  df_this_file = convert_json_to_excel_data_outbound(json_content_string, st.session_state.customers_df)
311
 
312
- if df_this_file is not None and not df_this_file.empty:
313
  all_processed_data.append(df_this_file)
314
  elif df_this_file is None: # Se la funzione ritorna None, c'è stato un errore bloccante
315
  has_blocking_error = True
@@ -319,22 +344,24 @@ if uploaded_json_files and st.session_state.customer_excel_loaded:
319
  st.error(f"Si è verificato un errore imprevisto durante la lettura o l'elaborazione del file '{file_name}': {e}")
320
  has_blocking_error = True
321
 
 
322
  if not has_blocking_error and all_processed_data:
 
323
  final_df = pd.concat(all_processed_data, ignore_index=True)
324
  st.success(f"Elaborazione di {len(all_processed_data)} file JSON completata! Pronto per il download.")
325
 
326
  # --- Post-processing per la formattazione Excel ---
327
 
328
- # Definizione delle colonne di testata
329
  header_columns = [
330
  'siteCode', 'companyCode', 'orderNumber', 'orderType', 'deliveryType',
331
  'orderDate', 'dateDetail', 'dueDate', 'reasonCode',
332
- 'paymentValue',
333
  'customer.code', 'customer.name', 'customer.address', 'customer.city',
334
  'customer.province', 'customer.zipCode', 'customer.country',
335
  'consignee.name', 'consignee.address', 'consignee.city', 'consignee.province',
336
  'consignee.zipCode', 'consignee.country', 'customer.mail',
337
- 'noteDDT', 'noteMag', 'noteDelivery', 'noteShipment',
338
  'reasonCodeShp', 'infoDelivery', 'bookingInfo'
339
  ]
340
 
@@ -344,23 +371,23 @@ if uploaded_json_files and st.session_state.customer_excel_loaded:
344
  # Crea una maschera booleana per identificare le righe duplicate per orderNumber
345
  rows_to_clear_headers = final_df.duplicated(subset=['orderNumber'], keep='first')
346
 
347
- # Azzerare le colonne di testata per le righe identificate
348
  final_df.loc[rows_to_clear_headers, header_columns] = ""
349
 
350
  # --- Creazione file Excel ---
351
  output = io.BytesIO()
352
  writer = pd.ExcelWriter(output, engine='xlsxwriter')
353
 
354
- sheet_name_excel = 'Delivery' # Nome del foglio modificato
355
 
356
  workbook = writer.book
357
- worksheet = workbook.add_worksheet(sheet_name_excel)
358
 
359
- # Lista delle colonne finali nell'ordine desiderato (aggiunto 'cancelPosOda')
360
  final_columns_order = [
361
  'siteCode', 'companyCode', 'orderNumber', 'orderType', 'deliveryType',
362
  'orderDate', 'dateDetail', 'dueDate', 'reasonCode',
363
- 'paymentValue',
364
  'customer.code', 'customer.name', 'customer.address', 'customer.city',
365
  'customer.province', 'customer.zipCode', 'customer.country',
366
  'consignee.name', 'consignee.address', 'consignee.city', 'consignee.province',
@@ -368,31 +395,30 @@ if uploaded_json_files and st.session_state.customer_excel_loaded:
368
  'details.positionId', 'details.itemNumber', 'details.requestedQt',
369
  'details.lotId', 'details.stockOwner', 'details.magCli', 'details.availability',
370
  'details.accountingStatus', 'details.refOrder',
371
- 'cancelPosOda', # Nuovo campo aggiunto qui
372
- 'noteDDT', 'noteMag', 'noteDelivery', 'noteShipment',
373
  'reasonCodeShp', 'infoDelivery', 'bookingInfo'
374
  ]
375
 
376
  # Assicurati che il DataFrame abbia tutte le colonne nell'ordine desiderato
 
377
  for col in final_columns_order:
378
  if col not in final_df.columns:
379
- final_df[col] = ""
380
  final_df = final_df[final_columns_order]
381
 
382
- # Larghezze delle colonne aggiornate (aggiunto 'cancelPosOda')
383
  col_widths = {
384
  'siteCode': 10, 'companyCode': 15, 'orderNumber': 15, 'orderType': 10, 'deliveryType': 12,
385
- 'orderDate': 12, 'dateDetail': 15, 'dueDate': 12, 'reasonCode': 12,
386
- 'paymentValue': 15,
387
- 'customer.code': 15, 'customer.name': 30, 'customer.address': 30, 'customer.city': 15,
388
  'customer.province': 10, 'customer.zipCode': 10, 'customer.country': 10,
389
  'consignee.name': 30, 'consignee.address': 30, 'consignee.city': 15, 'consignee.province': 10,
390
  'consignee.zipCode': 10, 'consignee.country': 10, 'customer.mail': 25,
391
  'details.positionId': 12, 'details.itemNumber': 20, 'details.requestedQt': 15,
392
  'details.lotId': 10, 'details.stockOwner': 15, 'details.magCli': 15, 'details.availability': 15,
393
  'details.accountingStatus': 18, 'details.refOrder': 15,
394
- 'cancelPosOda': 10, # Larghezza per il nuovo campo
395
- 'noteDDT': 20, 'noteMag': 20, 'noteDelivery': 20, 'noteShipment': 20,
396
  'reasonCodeShp': 15, 'infoDelivery': 20, 'bookingInfo': 20
397
  }
398
 
@@ -401,26 +427,27 @@ if uploaded_json_files and st.session_state.customer_excel_loaded:
401
 
402
  # Scrivi i nomi delle colonne nella riga 2 (indice 1 in xlsxwriter)
403
  for col_num, col_name in enumerate(final_df.columns.values):
404
- worksheet.write(1, col_num, col_name)
405
 
406
  # Imposta le larghezze delle colonne
407
  if col_name in col_widths:
408
  worksheet.set_column(col_num, col_num, col_widths[col_name])
409
  else:
410
- worksheet.set_column(col_num, col_num, 15)
411
-
412
  # Applica il formato data alle colonne 'orderDate' e 'dueDate'
413
- if col_name in ['orderDate', 'dueDate']:
414
  worksheet.set_column(col_num, col_num, col_widths[col_name], date_format)
415
 
416
  # Scrivi i dati a partire dalla riga 3 (indice 2 in xlsxwriter)
417
  final_df.to_excel(writer, sheet_name=sheet_name_excel, index=False, header=False, startrow=2)
418
 
419
  writer.close()
420
- output.seek(0)
421
 
 
422
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
423
- output_file_name = f"DeliveryOrders_{timestamp}.xlsx" # Nome del file di output
424
 
425
  st.download_button(
426
  label="Scarica file Excel Unificato",
@@ -435,10 +462,20 @@ if uploaded_json_files and st.session_state.customer_excel_loaded:
435
  else:
436
  st.info("Carica i tuoi file per iniziare.")
437
 
 
 
 
 
 
 
 
438
  # --- Funzione per il reset ---
439
  def clear_inputs():
 
 
440
  st.session_state.file_uploader_key_json = f'json_uploader_{datetime.now().timestamp()}'
441
- st.session_state.file_uploader_key_customers = f'customers_uploader_{datetime.now().timestamp() + 1}' # Assicurati che le chiavi siano diverse
 
442
  st.session_state.customer_excel_loaded = False
443
  st.session_state.customers_df = pd.DataFrame() # Resetta il DataFrame dell'anagrafica
444
 
 
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
  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
 
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
 
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
  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
  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
  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', [])
181
 
182
  processed_data_for_this_file = []
183
  for item in details:
 
 
 
 
184
  row = {
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
  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
  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
  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
  # 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
  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
  # 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
  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
  # 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
  '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
 
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
  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