import streamlit as st import pandas as pd import json from datetime import datetime import io import os import pytz # Assicurati che pytz sia nel requirements.txt # --- Inizializzazione Session State --- if 'file_uploader_key_json' not in st.session_state: st.session_state.file_uploader_key_json = 0 if 'file_uploader_key_customers' not in st.session_state: st.session_state.file_uploader_key_customers = 0 # --- CSS personalizzato --- st.markdown( """ """, unsafe_allow_html=True ) # Definizione del fuso orario per l'Italia (per coerenza, anche se le date Outbound sembrano solo data) ITALY_TIMEZONE = pytz.timezone("Europe/Rome") UTC_TIMEZONE = pytz.utc # Funzione di conversione per Outbound def convert_json_to_excel_data_outbound(json_file_content: str, customers_df: pd.DataFrame = None): try: json_data = json.loads(json_file_content) # Campi a livello radice site_code_json = json_data.get('siteCode') company_code_json = json_data.get('companyCode') # Campi sotto 'outbound' outbound_info = json_data.get('outbound', {}) order_number = outbound_info.get('orderNumber') order_type_json = outbound_info.get('orderType') delivery_type_fixed = "DELIVERY" # Valore fisso order_doc_date_str = outbound_info.get('orderDocDate') order_date_detail = outbound_info.get('orderDateDetail') due_date_str = outbound_info.get('orderDate') # Corrisponde a 'dueDate' reason_code = outbound_info.get('reasonCode') service_fixed = "PDB" # Valore fisso service_type_fixed = "Standard" # Valore fisso payment_info_value = outbound_info.get('paymentInfo', {}).get('value') delivery_customer = outbound_info.get('deliveryCustomer', {}) customer_code = delivery_customer.get('code') customer_mail = delivery_customer.get('refMail') # customer.mail dal JSON # Consignee data (from JSON) consignee_name = delivery_customer.get('name') consignee_address = delivery_customer.get('address') consignee_city = delivery_customer.get('city') consignee_province = delivery_customer.get('province') consignee_zip_code = delivery_customer.get('zipCode') consignee_country = delivery_customer.get('country') # Customer data (from Excel lookup) customer_name = "" customer_address = "" customer_city = "" customer_province = "" customer_zip_code = "" customer_country = "" # Esegui la lookup solo se customers_df è fornito e non vuoto if customers_df is not None and not customers_df.empty and customer_code and company_code_json: # Assicurati che le colonne di join siano dello stesso tipo # customers_df['Code'] = customers_df['Code'].astype(str) # customers_df['company.code'] = customers_df['company.code'].astype(str) # Filtra per Code e company.code customer_data = customers_df[ (customers_df['Code'].astype(str) == str(customer_code)) & (customers_df['company.code'].astype(str) == str(company_code_json)) ] if not customer_data.empty: # Prendiamo la prima riga se ce ne sono più di una customer_row = customer_data.iloc[0] customer_name = customer_row.get('partner.name', '') customer_address = customer_row.get('partner.address', '') customer_city = customer_row.get('partner.address.city', '') customer_province = customer_row.get('partner.address.province', '') customer_zip_code = customer_row.get('partner.address.zipCode', '') customer_country = customer_row.get('partner.address.country', '') else: st.warning(f"Attenzione: Dettagli cliente non trovati nell'Excel per Code: '{customer_code}' e Company: '{company_code_json}'.") # Note fields note_ddt = outbound_info.get('noteDdt') note_warehouse = outbound_info.get('noteWarehouse') note_delivery = outbound_info.get('noteDelivery') note_shipment = outbound_info.get('noteShipment') reason_code_shp = outbound_info.get('reasonCodeShp') info_delivery = outbound_info.get('infoDelivery') booking_info = outbound_info.get('bookingInfo') # Formattazione delle date (Outbound sembra avere solo date, senza ora) order_doc_date_formatted = None if order_doc_date_str: try: # Rimuovi 'Z' se presente e converti a datetime senza info fuso orario, poi formatta dt_object = datetime.fromisoformat(order_doc_date_str.replace('Z', '')) order_doc_date_formatted = dt_object.strftime('%d/%m/%Y') except ValueError: order_doc_date_formatted = order_doc_date_str st.warning(f"Attenzione: Impossibile parsare 'orderDocDate' '{order_doc_date_str}'. Verrà usata la stringa originale.") due_date_formatted = None if due_date_str: # Questo è il campo 'orderDate' del JSON che diventa 'dueDate' try: dt_object = datetime.fromisoformat(due_date_str.replace('Z', '')) due_date_formatted = dt_object.strftime('%d/%m/%Y') except ValueError: due_date_formatted = due_date_str st.warning(f"Attenzione: Impossibile parsare 'orderDate' (dueDate) '{due_date_str}'. Verrà usata la stringa originale.") details = outbound_info.get('details', []) processed_data_for_this_file = [] for item in details: row = { 'siteCode': site_code_json, 'companyCode': company_code_json, 'orderNumber': order_number, 'orderType': order_type_json, 'deliveryType': delivery_type_fixed, 'orderDocDate': order_doc_date_formatted, 'orderDateDetail': order_date_detail, 'dueDate': due_date_formatted, 'reasonCode': reason_code, 'service': service_fixed, 'serviceType': service_type_fixed, 'paymentInfo.value': payment_info_value, 'customer.code': customer_code, 'customer.name': customer_name, # Da Excel 'customer.address': customer_address, # Da Excel 'customer.city': customer_city, # Da Excel 'customer.province': customer_province, # Da Excel 'customer.zipCode': customer_zip_code, # Da Excel 'customer.country': customer_country, # Da Excel 'consignee.name': consignee_name, # Dal JSON 'consignee.address': consignee_address, # Dal JSON 'consignee.city': consignee_city, # Dal JSON 'consignee.province': consignee_province, # Dal JSON 'consignee.zipCode': consignee_zip_code, # Dal JSON 'consignee.country': consignee_country, # Dal JSON 'customer.mail': customer_mail, # Dal JSON 'details.positionId': item.get('positionId'), 'details.itemNumber': item.get('itemCode'), # Rinominato da itemCode 'details.requestedQt': item.get('quantity'), # Rinominato da quantity 'details.lotId': item.get('lotId'), 'details.stockOwner': item.get('stockOwner'), 'details.magCli': item.get('warehouse'), # Rinominato da warehouse 'details.availability': item.get('availability'), 'details.accountingStatus': item.get('accountingStatus'), 'details.refOrder': item.get('refOrder'), 'noteDdt': note_ddt, 'noteWarehouse': note_warehouse, 'noteDelivery': note_delivery, 'noteShipment': note_shipment, 'reasonCodeShp': reason_code_shp, 'infoDelivery': info_delivery, 'bookingInfo': booking_info } 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.") 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 Outbound a Template Excel Outbound") st.write("Carica i tuoi file JSON degli ordini Outbound e un file Excel con i dati dei clienti.") st.header("1. Carica il file Excel dei Clienti") st.info("Questo file Excel deve contenere le colonne: `Code`, `company.code`, `partner.name`, `partner.address`, `partner.address.city`, `partner.address.province`, `partner.address.zipCode`, `partner.address.country`.") uploaded_customers_file = st.file_uploader( "Scegli il file Excel con i dettagli dei Clienti", type=["xlsx", "xls"], key=st.session_state.file_uploader_key_customers ) customers_df = pd.DataFrame() if uploaded_customers_file: try: customers_df = pd.read_excel(uploaded_customers_file) # Rinomina le colonne per facilitare l'accesso, se necessario, o assicurati che i nomi corrispondano # Es: customers_df.rename(columns={'Codice': 'Code', 'Nome': 'partner.name'}, inplace=True) # Assicurati che le colonne per il merge siano stringhe per evitare problemi di tipo customers_df['Code'] = customers_df['Code'].astype(str) customers_df['company.code'] = customers_df['company.code'].astype(str) st.success("File Excel Clienti caricato con successo.") # Debug: st.dataframe(customers_df.head()) except Exception as e: st.error(f"Errore nel caricamento del file Excel Clienti: {e}. Assicurati che sia un file Excel valido e che le colonne siano corrette.") customers_df = pd.DataFrame() # Reset in caso di errore st.header("2. Carica i tuoi file JSON degli Ordini Outbound") uploaded_json_files = st.file_uploader( "Scegli uno o più file JSON di tipo 'Ordine_Outbound'", type=["json"], accept_multiple_files=True, key=st.session_state.file_uploader_key_json ) all_processed_data = [] if uploaded_json_files and not customers_df.empty: st.info(f"Caricati {len(uploaded_json_files)} file JSON. Elaborazione in corso...") conversion_successful = True for uploaded_file in uploaded_json_files: file_name = uploaded_file.name if not file_name.lower().startswith("ordine_outbound"): st.error(f"Errore: Il file '{file_name}' non inizia con 'Ordine_Outbound'. Verrà ignorato.") conversion_successful = False continue try: json_content_bytes = uploaded_file.read() json_content_string = json_content_bytes.decode('utf-8') # Passa customers_df alla funzione di conversione df_this_file = convert_json_to_excel_data_outbound(json_content_string, customers_df) if df_this_file is not None and not df_this_file.empty: all_processed_data.append(df_this_file) elif df_this_file is None: 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 = [ 'siteCode', 'companyCode', 'orderNumber', 'orderType', 'deliveryType', 'orderDocDate', 'orderDateDetail', 'dueDate', 'reasonCode', 'service', 'serviceType', 'paymentInfo.value', 'customer.code', 'customer.name', 'customer.address', 'customer.city', 'customer.province', 'customer.zipCode', 'customer.country', 'consignee.name', 'consignee.address', 'consignee.city', 'consignee.province', 'consignee.zipCode', 'consignee.country', 'customer.mail', 'noteDdt', 'noteWarehouse', 'noteDelivery', 'noteShipment', 'reasonCodeShp', 'infoDelivery', 'bookingInfo' ] # Ordina il DataFrame per orderNumber e details.positionId per raggruppare gli ordini final_df = final_df.sort_values(by=['orderNumber', 'details.positionId']).reset_index(drop=True) # Crea una maschera booleana per identificare le righe duplicate per orderNumber rows_to_clear_headers = final_df.duplicated(subset=['orderNumber'], 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 = 'OutboundOrder' 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 = [ 'siteCode', 'companyCode', 'orderNumber', 'orderType', 'deliveryType', 'orderDocDate', 'orderDateDetail', 'dueDate', 'reasonCode', 'service', 'serviceType', 'paymentInfo.value', 'customer.code', 'customer.name', 'customer.address', 'customer.city', 'customer.province', 'customer.zipCode', 'customer.country', 'consignee.name', 'consignee.address', 'consignee.city', 'consignee.province', 'consignee.zipCode', 'consignee.country', 'customer.mail', 'details.positionId', 'details.itemNumber', 'details.requestedQt', 'details.lotId', 'details.stockOwner', 'details.magCli', 'details.availability', 'details.accountingStatus', 'details.refOrder', 'noteDdt', 'noteWarehouse', 'noteDelivery', 'noteShipment', 'reasonCodeShp', 'infoDelivery', 'bookingInfo' ] # 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 (adattate per Outbound) col_widths = { 'siteCode': 10, 'companyCode': 15, 'orderNumber': 15, 'orderType': 10, 'deliveryType': 12, 'orderDocDate': 12, 'orderDateDetail': 15, 'dueDate': 12, 'reasonCode': 12, # Date solo gg/mm/aaaa 'service': 10, 'serviceType': 12, 'paymentInfo.value': 15, 'customer.code': 15, 'customer.name': 30, 'customer.address': 30, 'customer.city': 15, 'customer.province': 10, 'customer.zipCode': 10, 'customer.country': 10, 'consignee.name': 30, 'consignee.address': 30, 'consignee.city': 15, 'consignee.province': 10, 'consignee.zipCode': 10, 'consignee.country': 10, 'customer.mail': 25, 'details.positionId': 12, 'details.itemNumber': 20, 'details.requestedQt': 15, 'details.lotId': 10, 'details.stockOwner': 15, 'details.magCli': 15, 'details.availability': 15, 'details.accountingStatus': 18, 'details.refOrder': 15, 'noteDdt': 20, 'noteWarehouse': 20, 'noteDelivery': 20, 'noteShipment': 20, 'reasonCodeShp': 15, 'infoDelivery': 20, 'bookingInfo': 20 } # Formato data per le colonne di data (solo data, senza ora) date_format = workbook.add_format({'num_format': 'dd/mm/yyyy'}) # 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 alle colonne 'orderDocDate' e 'dueDate' if col_name in ['orderDocDate', 'dueDate']: worksheet.set_column(col_num, col_num, col_widths[col_name], date_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"OutboundOrders_{timestamp}.xlsx" st.download_button( label="Scarica file Excel Unificato", data=output.getvalue(), file_name=output_file_name, mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ) elif uploaded_json_files and customers_df.empty: st.warning("Per elaborare i file JSON, devi prima caricare un file Excel con i dettagli dei clienti.") elif not conversion_successful: st.error("Nessun file valido è stato elaborato con successo. Controlla i messaggi di errore sopra.") else: st.info("Carica i tuoi file JSON per iniziare.") # --- Funzione per il reset --- def clear_inputs(): st.session_state.file_uploader_key_json += 1 st.session_state.file_uploader_key_customers += 1 st.markdown("---") st.button("Pulisci Input (RESET)", on_click=clear_inputs) st.markdown("Sviluppato con Streamlit e Python.")