pasquale991 commited on
Commit
7da7801
·
verified ·
1 Parent(s): a6df0d2

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +301 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,303 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
1
  import streamlit as st
2
+ import pandas as pd
3
+ import json
4
+ from datetime import datetime
5
+ import io
6
+ import os
7
+
8
+ # --- Inizializzazione Session State ---
9
+ if 'site_code_input' not in st.session_state:
10
+ st.session_state.site_code_input = ""
11
+ if 'file_uploader_key' not in st.session_state:
12
+ st.session_state.file_uploader_key = 0
13
+
14
+ # --- CSS personalizzato ---
15
+ st.markdown(
16
+ """
17
+ <style>
18
+ .stApp {
19
+ background-color: #FFFFCC;
20
+ background-attachment: fixed;
21
+ }
22
+ h1, h2 {
23
+ color: #1E90FF;
24
+ }
25
+ p, label {
26
+ color: #000080;
27
+ }
28
+ .stButton>button {
29
+ color: white;
30
+ background-color: #1E90FF;
31
+ border: none;
32
+ border-radius: 5px;
33
+ padding: 10px 20px;
34
+ font-size: 16px;
35
+ }
36
+ .stButton>button:hover {
37
+ background-color: #4169E1;
38
+ }
39
+ </style>
40
+ """,
41
+ unsafe_allow_html=True
42
+ )
43
+
44
+ # Funzione di conversione
45
+ def convert_json_to_excel_data(json_file_content: str):
46
+ try:
47
+ json_data = json.loads(json_file_content)
48
+
49
+ # Campi a livello radice
50
+ site_code_json = json_data.get('siteCode')
51
+ company_code_json = json_data.get('companyCode')
52
+
53
+ # Campi sotto 'outbound'
54
+ outbound_info = json_data.get('outbound', {})
55
+
56
+ order_number = outbound_info.get('orderNumber')
57
+ order_doc_date_str = outbound_info.get('orderDocDate')
58
+ order_type_json = outbound_info.get('orderType') # Dal JSON è "CL"
59
+
60
+ # Fissi: deliveryType (non nel JSON), service (nel JSON ma fisso PDB), serviceType (nel JSON ma fisso Standard)
61
+ delivery_type_fixed = "DELIVERY"
62
+ service_fixed = "PDB"
63
+ service_type_fixed = "Standard"
64
+
65
+ order_date_detail = outbound_info.get('orderDateDetail')
66
+ order_date_str = outbound_info.get('orderDate') # Questo sarà 'dueDate'
67
+ reason_code = outbound_info.get('reasonCode')
68
+ payment_info_value = outbound_info.get('paymentInfo', {}).get('value')
69
+
70
+ delivery_customer = outbound_info.get('deliveryCustomer', {})
71
+ customer_code = delivery_customer.get('code')
72
+ consignee_name = delivery_customer.get('name')
73
+ consignee_address = delivery_customer.get('address')
74
+ consignee_city = delivery_customer.get('city')
75
+ consignee_province = delivery_customer.get('province')
76
+ consignee_zip_code = delivery_customer.get('zipCode')
77
+ consignee_country = delivery_customer.get('country')
78
+ customer_mail = delivery_customer.get('refMail')
79
+
80
+ note_ddt = outbound_info.get('noteDdt')
81
+ note_warehouse = outbound_info.get('noteWarehouse')
82
+ note_delivery = outbound_info.get('noteDelivery')
83
+ note_shipment = outbound_info.get('noteShipment')
84
+ reason_code_shp = outbound_info.get('reasonCodeShp')
85
+ info_delivery = outbound_info.get('infoDelivery')
86
+ booking_info = outbound_info.get('bookingInfo')
87
+
88
+ # Formattazione delle date
89
+ order_doc_date_formatted = None
90
+ if order_doc_date_str:
91
+ try:
92
+ dt_object = datetime.fromisoformat(order_doc_date_str.replace('Z', '+00:00'))
93
+ order_doc_date_formatted = dt_object.strftime('%d/%m/%Y')
94
+ except ValueError:
95
+ order_doc_date_formatted = order_doc_date_str
96
+ st.warning(f"Attenzione: Impossibile parsare 'orderDocDate' '{order_doc_date_str}'. Verrà usata la stringa originale.")
97
+
98
+ due_date_formatted = None
99
+ if order_date_str:
100
+ try:
101
+ dt_object = datetime.fromisoformat(order_date_str.replace('Z', '+00:00'))
102
+ due_date_formatted = dt_object.strftime('%d/%m/%Y')
103
+ except ValueError:
104
+ due_date_formatted = order_date_str
105
+ st.warning(f"Attenzione: Impossibile parsare 'orderDate' '{order_date_str}'. Verrà usata la stringa originale.")
106
+
107
+
108
+ details = outbound_info.get('details', [])
109
+
110
+ processed_data_for_this_file = []
111
+ for item in details:
112
+ row = {
113
+ 'siteCode': site_code_json,
114
+ 'companyCode': company_code_json,
115
+ 'orderNumber': order_number,
116
+ 'orderType': order_type_json, # Mappa da 'outbound.orderType' (CL nel JSON, non fisso DELIVERY)
117
+ 'deliveryType': delivery_type_fixed, # Fisso a 'DELIVERY' come da richiesta immagine
118
+ 'orderDocDate': order_doc_date_formatted,
119
+ 'orderDateDetail': order_date_detail,
120
+ 'dueDate': due_date_formatted,
121
+ 'reasonCode': reason_code,
122
+ 'service': service_fixed, # Fisso a 'PDB' come da richiesta immagine
123
+ 'serviceType': service_type_fixed, # Fisso a 'Standard' come da richiesta immagine
124
+ 'paymentInfo.value': payment_info_value,
125
+ 'customer.code': customer_code,
126
+ 'consignee.name': consignee_name,
127
+ 'consignee.address': consignee_address,
128
+ 'consignee.city': consignee_city,
129
+ 'consignee.province': consignee_province,
130
+ 'consignee.zipCode': consignee_zip_code,
131
+ 'consignee.country': consignee_country,
132
+ 'customer.mail': customer_mail,
133
+ 'details.positionId': item.get('positionId'),
134
+ 'details.itemNumber': item.get('itemCode'),
135
+ 'details.requestedQt': item.get('quantity'),
136
+ 'details.lotId': item.get('lotId'),
137
+ 'details.stockOwner': item.get('stockOwner'),
138
+ 'details.magCli': item.get('warehouse'),
139
+ 'details.availability': item.get('availability'),
140
+ 'details.accountingStatus': item.get('accountingStatus'),
141
+ 'details.refOrder': item.get('refOrder'),
142
+ 'noteDdt': note_ddt,
143
+ 'noteWarehouse': note_warehouse,
144
+ 'noteDelivery': note_delivery,
145
+ 'noteShipment': note_shipment,
146
+ 'reasonCodeShp': reason_code_shp,
147
+ 'infoDelivery': info_delivery,
148
+ 'bookingInfo': booking_info
149
+ }
150
+ processed_data_for_this_file.append(row)
151
+
152
+ if not processed_data_for_this_file:
153
+ return pd.DataFrame()
154
+
155
+ return pd.DataFrame(processed_data_for_this_file)
156
+
157
+ except json.JSONDecodeError as e:
158
+ st.error(f"Errore nel file JSON: {e}. Assicurati che il contenuto sia un JSON valido.")
159
+ return None
160
+ except KeyError as ke:
161
+ st.error(f"Errore: Chiave JSON mancante o struttura inattesa: {ke}. Assicurati che il JSON abbia la struttura attesa.")
162
+ return None
163
+ except Exception as e:
164
+ st.error(f"Si è verificato un errore generico durante la conversione di un file JSON: {e}")
165
+ return None
166
+
167
+ # --- Interfaccia Streamlit ---
168
+ st.title("Convertitore JSON Outbound a Template Excel Outbound")
169
+ st.write("Carica i tuoi file JSON degli ordini Outbound e scarica il foglio Excel formattato.")
170
+
171
+ # Non chiediamo siteCode all'utente se è già nel JSON, a meno che non vogliamo sovrascriverlo
172
+ # st.header("1. Qual è il Site Code? Inseriscilo qui:")
173
+ # user_site_code = st.text_input("Site Code", key="site_code_input")
174
+ st.markdown("**(Il Site Code verrà letto direttamente dal JSON)**")
175
+
176
+
177
+ st.header("1. Carica i tuoi file JSON")
178
+ # Passiamo la chiave dinamica al file_uploader
179
+ uploaded_files = st.file_uploader(
180
+ "Scegli uno o più file JSON di tipo 'Ordine_Outbound'",
181
+ type=["json"],
182
+ accept_multiple_files=True,
183
+ key=st.session_state.file_uploader_key # Usa la chiave dinamica qui
184
+ )
185
+
186
+ all_processed_data = []
187
+
188
+ if uploaded_files:
189
+ st.info(f"Caricati {len(uploaded_files)} file. Elaborazione in corso...")
190
+
191
+ conversion_successful = True
192
+
193
+ for uploaded_file in uploaded_files:
194
+ file_name = uploaded_file.name
195
+
196
+ # Verifica che il nome del file inizi con "Ordine_Outbound"
197
+ if not file_name.lower().startswith("ordine_outbound"):
198
+ st.error(f"Errore: Il file '{file_name}' non inizia con 'Ordine_Outbound'. Verrà ignorato.")
199
+ conversion_successful = False
200
+ continue
201
+
202
+ try:
203
+ json_content_bytes = uploaded_file.read()
204
+ json_content_string = json_content_bytes.decode('utf-8')
205
+
206
+ df_this_file = convert_json_to_excel_data(json_content_string)
207
+
208
+ if df_this_file is not None and not df_this_file.empty: # Assicurati che il DataFrame non sia vuoto
209
+ all_processed_data.append(df_this_file)
210
+ elif df_this_file is None: # C'è stato un errore e la funzione ha restituito None
211
+ conversion_successful = False
212
+ st.error(f"Impossibile convertire il file '{file_name}'. Controlla i log per i dettagli.")
213
+ # else: df_this_file era vuoto, nessun errore ma nessun dato da aggiungere
214
+
215
+ except Exception as e:
216
+ st.error(f"Si è verificato un errore imprevisto durante la lettura o l'elaborazione del file '{file_name}': {e}")
217
+ conversion_successful = False
218
+
219
+ if all_processed_data:
220
+ final_df = pd.concat(all_processed_data, ignore_index=True)
221
+ st.success(f"Elaborazione di {len(all_processed_data)} file JSON completata! Pronto per il download.")
222
+
223
+ output = io.BytesIO()
224
+ writer = pd.ExcelWriter(output, engine='xlsxwriter')
225
+
226
+ sheet_name_excel = 'OutboundOrder' # Nome del foglio Excel
227
+ final_df.to_excel(writer, sheet_name=sheet_name_excel, index=False)
228
+
229
+ workbook = writer.book
230
+ worksheet = writer.sheets[sheet_name_excel]
231
+
232
+ # Ordine delle colonne e larghezze per l'Excel Outbound
233
+ # Basato sul mapping fornito, con siteCode/companyCode in testa
234
+ final_columns_order = [
235
+ 'siteCode', 'companyCode', 'orderNumber', 'orderType', 'deliveryType',
236
+ 'orderDocDate', 'orderDateDetail', 'dueDate', 'reasonCode',
237
+ 'service', 'serviceType', 'paymentInfo.value', 'customer.code',
238
+ 'consignee.name', 'consignee.address', 'consignee.city', 'consignee.province',
239
+ 'consignee.zipCode', 'consignee.country', 'customer.mail',
240
+ 'details.positionId', 'details.itemNumber', 'details.requestedQt',
241
+ 'details.lotId', 'details.stockOwner', 'details.magCli', 'details.availability',
242
+ 'details.accountingStatus', 'details.refOrder',
243
+ 'noteDdt', 'noteWarehouse', 'noteDelivery', 'noteShipment',
244
+ 'reasonCodeShp', 'infoDelivery', 'bookingInfo'
245
+ ]
246
+
247
+ # Filtra solo le colonne effettivamente presenti nel DataFrame dopo la conversione
248
+ # e riordinale
249
+ cols_present_and_ordered = [col for col in final_columns_order if col in final_df.columns]
250
+ final_df = final_df[cols_present_and_ordered]
251
+
252
+ # Larghezze colonne ottimizzate per Outbound
253
+ col_widths = {
254
+ 'siteCode': 10, 'companyCode': 15, 'orderNumber': 15, 'orderType': 10, 'deliveryType': 12,
255
+ 'orderDocDate': 15, 'orderDateDetail': 15, 'dueDate': 12, 'reasonCode': 12,
256
+ 'service': 10, 'serviceType': 12, 'paymentInfo.value': 15, 'customer.code': 15,
257
+ 'consignee.name': 30, 'consignee.address': 30, 'consignee.city': 15, 'consignee.province': 10,
258
+ 'consignee.zipCode': 10, 'consignee.country': 10, 'customer.mail': 25,
259
+ 'details.positionId': 12, 'details.itemNumber': 20, 'details.requestedQt': 15,
260
+ 'details.lotId': 10, 'details.stockOwner': 15, 'details.magCli': 15, 'details.availability': 15,
261
+ 'details.accountingStatus': 18, 'details.refOrder': 15,
262
+ 'noteDdt': 20, 'noteWarehouse': 20, 'noteDelivery': 20, 'noteShipment': 20,
263
+ 'reasonCodeShp': 15, 'infoDelivery': 20, 'bookingInfo': 20
264
+ }
265
+
266
+ # Applica le intestazioni e le larghezze delle colonne
267
+ for col_num, col_name in enumerate(final_df.columns.values):
268
+ worksheet.write(0, col_num, col_name)
269
+ if col_name in col_widths:
270
+ worksheet.set_column(col_num, col_num, col_widths[col_name])
271
+ else:
272
+ worksheet.set_column(col_num, col_num, 15) # Larghezza di default
273
+
274
+ writer.close()
275
+ output.seek(0)
276
+
277
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
278
+ output_file_name = f"OrdiniOutbound_{timestamp}.xlsx" # Nome del file con timestamp
279
+
280
+ st.download_button(
281
+ label="Scarica file Excel Unificato",
282
+ data=output.getvalue(),
283
+ file_name=output_file_name,
284
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
285
+ )
286
+ elif not conversion_successful:
287
+ st.error("Nessun file valido è stato elaborato con successo. Controlla i messaggi di errore sopra.")
288
+ else:
289
+ st.info("Nessun dato valido estratto dai file selezionati.")
290
+ else:
291
+ st.info("Carica i tuoi file JSON per iniziare.")
292
+
293
+
294
+ # --- Funzione per il reset: incrementa la chiave del file_uploader ---
295
+ def clear_inputs():
296
+ st.session_state.file_uploader_key += 1
297
+ st.session_state.site_code_input = ""
298
+ st.rerun()
299
+
300
+ st.markdown("---")
301
+ st.button("Pulisci Input (RESET)", on_click=clear_inputs)
302
 
303
+ st.markdown("Sviluppato con Streamlit e Python.")