import streamlit as st import pandas as pd import json from datetime import datetime import pytz import io import os # --- Inizializzazione Session State --- if 'file_uploader_key' not in st.session_state: st.session_state.file_uploader_key = 0 # --- CSS personalizzato per sfondo giallo e testo blu --- st.markdown( """ """, unsafe_allow_html=True ) # Definizione del fuso orario per l'Italia ITALY_TIMEZONE = pytz.timezone("Europe/Rome") UTC_TIMEZONE = pytz.utc # Funzione di conversione def convert_json_to_excel_data(json_file_content: str): try: json_data = json.loads(json_file_content) company_code = json_data.get('companyCode') site_code_json = json_data.get('siteCode') order_info = json_data.get('order', {}) doc_number = order_info.get('orderNumber') partner_code = order_info.get('partnerCode') order_date_str = order_info.get('orderDate') order_date_formatted = None if order_date_str: try: dt_object_naive = datetime.fromisoformat(order_date_str.replace('Z', '')) dt_object_utc = UTC_TIMEZONE.localize(dt_object_naive) dt_object_italy = dt_object_utc.astimezone(ITALY_TIMEZONE) # La stringa di formattazione rimane corretta per Python order_date_formatted = dt_object_italy.strftime('%d/%m/%Y %H:%M:%S') except ValueError: order_date_formatted = order_date_str st.warning(f"Attenzione: Impossibile parsare la data '{order_date_str}' per un file. Verrà usata la stringa originale.") except Exception as tz_error: st.warning(f"Attenzione: Errore nella conversione del fuso orario per '{order_date_str}': {tz_error}. Verrà usata la stringa originale.") order_date_formatted = order_date_str details = order_info.get('details', []) processed_data_for_this_file = [] for item in details: cancel_pos_oda_val = item.get('cancelPosOdA') if cancel_pos_oda_val is None or \ (isinstance(cancel_pos_oda_val, bool) and not cancel_pos_oda_val) or \ (isinstance(cancel_pos_oda_val, str) and cancel_pos_oda_val.lower() == 'false'): positions_status_value = 'CREATED' else: positions_status_value = 'CANCELLED' row = { 'site.code': site_code_json, 'docNumber': doc_number, 'company.code': company_code, 'supplier.partnerRef.code': partner_code, 'supplier.partnerRef.company.code': company_code, 'docDate': order_date_formatted, 'status': 'CREATED', 'positions.status': positions_status_value, 'positions.positionId': item.get('positionId'), 'positions.stock.item.itemNumber': item.get('itemCode'), 'positions.stock.quantity': item.get('quantity'), 'positions.stock.accountingInfo.stockOwner': item.get('stockOwner'), 'positions.stock.accountingInfo.costCenter': item.get('warehouse'), 'positions.stock.accountingInfo.availability': item.get('availability'), 'positions.stock.item.company.code': company_code, 'positions.spare.string1': item.get('refOrder'), 'positions.reason.code': item.get('reasonCode'), 'positions.reason.company.code': company_code, } processed_data_for_this_file.append(row) if not processed_data_for_this_file: return pd.DataFrame() return pd.DataFrame(processed_data_for_this_file) except json.JSONDecodeError as e: st.error(f"Errore nel file JSON: {e}. Assicurati che il contenuto sia un JSON valido.") return None except KeyError as ke: st.error(f"Errore: Chiave JSON mancante o struttura inattesa: {ke}. Assicurati che il JSON abbia la struttura attesa (es. 'order.details').") return None except Exception as e: st.error(f"Si è verificato un errore generico durante la conversione di un file JSON: {e}") return None # --- Interfaccia Streamlit --- st.title("Convertitore JSON Oda a Template Excel Oda") st.write("Carica i tuoi file JSON degli ordini ODA e scarica il foglio Excel formattato.") st.markdown("**(Il Site Code verrà letto direttamente dal JSON)**") st.header("1. Carica i tuoi file JSON") uploaded_files = st.file_uploader( "Scegli uno o più file JSON di tipo 'Ordine_Acquisto'", type=["json"], accept_multiple_files=True, key=st.session_state.file_uploader_key ) all_processed_data = [] if uploaded_files: st.info(f"Caricati {len(uploaded_files)} file. Elaborazione in corso...") conversion_successful = True for uploaded_file in uploaded_files: file_name = uploaded_file.name if not file_name.lower().startswith("ordine_acquisto"): st.error(f"Errore: Il file '{file_name}' non inizia con 'Ordine_Acquisto'. Verrà ignorato.") conversion_successful = False continue try: json_content_bytes = uploaded_file.read() json_content_string = json_content_bytes.decode('utf-8') df_this_file = convert_json_to_excel_data(json_content_string) if df_this_file is not None: all_processed_data.append(df_this_file) else: conversion_successful = False st.error(f"Impossibile convertire il file '{file_name}'. Controlla i log per i dettagli.") except Exception as e: st.error(f"Si è verificato un errore imprevisto durante la lettura o l'elaborazione del file '{file_name}': {e}") conversion_successful = False if all_processed_data: final_df = pd.concat(all_processed_data, ignore_index=True) st.success(f"Elaborazione di {len(all_processed_data)} file JSON completata! Pronto per il download.") # --- Post-processing per la formattazione Excel --- # Definizione delle colonne di testata header_columns = [ 'site.code', 'docNumber', 'company.code', 'supplier.partnerRef.code', 'supplier.partnerRef.company.code', 'docDate', 'status' ] # Ordina il DataFrame per docNumber e positionId per raggruppare gli ordini final_df = final_df.sort_values(by=['docNumber', 'positions.positionId']).reset_index(drop=True) # Crea una maschera booleana per identificare le righe duplicate per docNumber rows_to_clear_headers = final_df.duplicated(subset=['docNumber'], keep='first') # Azzerare le colonne di testata per le righe identificate final_df.loc[rows_to_clear_headers, header_columns] = "" # --- Creazione file Excel --- output = io.BytesIO() writer = pd.ExcelWriter(output, engine='xlsxwriter') sheet_name_excel = 'PurchaseOrder' workbook = writer.book worksheet = writer.sheets.get(sheet_name_excel) if worksheet is None: worksheet = workbook.add_worksheet(sheet_name_excel) # Lista delle colonne finali nell'ordine desiderato final_columns_order = [ 'site.code', 'docNumber', 'company.code', 'supplier.partnerRef.code', 'supplier.partnerRef.company.code', 'docDate', 'status', 'positions.status', 'positions.positionId', 'positions.stock.item.itemNumber', 'positions.stock.quantity', 'positions.stock.accountingInfo.stockOwner', 'positions.stock.accountingInfo.costCenter', 'positions.stock.accountingInfo.availability', 'positions.stock.item.company.code', 'positions.spare.string1', 'positions.reason.code', 'positions.reason.company.code', ] # Assicurati che il DataFrame abbia tutte le colonne nell'ordine desiderato for col in final_columns_order: if col not in final_df.columns: final_df[col] = "" final_df = final_df[final_columns_order] # Larghezze delle colonne aggiornate col_widths = { 'site.code': 10, 'docNumber': 15, 'company.code': 15, 'supplier.partnerRef.code': 18, 'supplier.partnerRef.company.code': 25, 'docDate': 20, # Lascia 20, è abbastanza larga 'status': 10, 'positions.status': 12, 'positions.positionId': 12, 'positions.stock.item.itemNumber': 25, 'positions.stock.quantity': 10, 'positions.stock.accountingInfo.stockOwner': 18, 'positions.stock.accountingInfo.costCenter': 18, 'positions.stock.accountingInfo.availability': 15, 'positions.stock.item.company.code': 25, 'positions.spare.string1': 15, 'positions.reason.code': 15, 'positions.reason.company.code': 25, } # *** NUOVA PARTE: Formattazione della colonna docDate in Excel *** # Crea un formato numerico personalizzato per data e ora completa date_time_format = workbook.add_format({'num_format': 'dd/mm/yyyy hh:mm:ss'}) # Scrivi i nomi delle colonne nella riga 2 (indice 1 in xlsxwriter) for col_num, col_name in enumerate(final_df.columns.values): worksheet.write(1, col_num, col_name) # Imposta le larghezze delle colonne if col_name in col_widths: worksheet.set_column(col_num, col_num, col_widths[col_name]) else: worksheet.set_column(col_num, col_num, 15) # Applica il formato data/ora alla colonna 'docDate' if col_name == 'docDate': # +1 perché stiamo scrivendo da startrow=2 (quindi la colonna dati inizia da riga 2) # Il +1 per l'indice della colonna non serve, è già l'indice corretto worksheet.set_column(col_num, col_num, col_widths[col_name], date_time_format) # Scrivi i dati a partire dalla riga 3 (indice 2 in xlsxwriter) final_df.to_excel(writer, sheet_name=sheet_name_excel, index=False, header=False, startrow=2) writer.close() output.seek(0) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") output_file_name = f"PurchaseOrder_{timestamp}.xlsx" st.download_button( label="Scarica file Excel Unificato", data=output.getvalue(), file_name=output_file_name, mime="application/vnd.openxmlformats-officedocument.spreadsheet1.sheet" ) elif not conversion_successful: st.error("Nessun file valido è stato elaborato con successo. Controlla i messaggi di errore sopra.") else: st.info("Nessun dato valido estratto dai file selezionati.") else: st.info("Carica i tuoi file JSON per iniziare.") # --- Funzione per il reset --- def clear_inputs(): st.session_state.file_uploader_key += 1 st.markdown("---") st.button("Pulisci Input (RESET)", on_click=clear_inputs) st.markdown("Sviluppato con Streamlit e Python.")