import requests import streamlit as st from functions import ( API_BASE, _auth_headers, build_order_payload, create_client, fetch_clients, fetch_presets, is_water_inci, make_empty_ingredient_df, validate_order, ) from functions_ui import display_orderData st.set_page_config(page_title="Creazione Ordine", layout="wide") st.title("Creazione Ordine") # --- Session state init --- if "order_presets" not in st.session_state: st.session_state.order_presets = None if "order_clients" not in st.session_state: st.session_state.order_clients = None if "order_submitted" not in st.session_state: st.session_state.order_submitted = False if "order_result" not in st.session_state: st.session_state.order_result = None if "ingredient_df" not in st.session_state: st.session_state.ingredient_df = make_empty_ingredient_df() if "new_client_mode" not in st.session_state: st.session_state.new_client_mode = False # --------------------------------------------------------------------------- # 1. Istruzioni # --------------------------------------------------------------------------- with st.expander("Istruzioni per la compilazione", expanded=False): st.markdown(""" ### Come compilare l'ordine **Campi obbligatori:** - **Nome del cliente**: Selezionare un cliente esistente dal menu a tendina, oppure scegliere *"+ Nuovo cliente..."* per inserirne uno nuovo. - **Nome del prodotto**: Il nome commerciale del prodotto cosmetico. - **Preset di esposizione**: Selezionare il preset che descrive le condizioni di utilizzo del prodotto (es. crema viso leave-on, shampoo rinse-off, ecc.). I preset si creano dalla pagina *Esposizione*. **Tabella ingredienti:** - **INCI**: Nome INCI dell'ingrediente (facoltativo, ma consigliato). Se si inserisce *AQUA*, *Water*, *Eau* o varianti, la valutazione tossicologica viene automaticamente saltata e il CAS puo essere lasciato vuoto. - **CAS**: Numero CAS nel formato standard (es. `56-81-5`). Ogni riga deve contenere **un solo** CAS number. - **Percentuale (%)**: Percentuale in peso dell'ingrediente nella formulazione. Sono ammessi fino a 6 decimali (es. `0.000200`). - **Colorante / CAS speciale**: Selezionare per coloranti (CI xxxxx) o sostanze con codici non-standard. Il formato CAS non verra validato. - **Salta tossicologia**: Selezionare per ingredienti che non richiedono valutazione tossicologica. Viene impostato automaticamente per AQUA/Water. **Regole:** - La somma delle percentuali deve essere esattamente **100%** (tolleranza: +/- 0.01%). - E possibile aggiungere o rimuovere righe dalla tabella cliccando i pulsanti in basso. - Compilare tutti i campi obbligatori prima di inviare l'ordine. """) # --------------------------------------------------------------------------- # 2. Carica ordine esistente # --------------------------------------------------------------------------- st.markdown("---") st.subheader("Carica ordine esistente") col_lookup, col_lookup_btn = st.columns([3, 1]) with col_lookup: order_id_input = st.number_input( "ID Ordine", min_value=1, step=1, value=None, placeholder="es. 42", help="Inserire l'ID numerico di un ordine esistente per precompilare il modulo.", ) with col_lookup_btn: st.write("") st.write("") lookup_clicked = st.button("Carica", disabled=order_id_input is None) if lookup_clicked: st.info( f"Funzionalita in sviluppo. L'ordine con ID {order_id_input} " "verra caricato automaticamente quando l'endpoint API sara disponibile." ) # --------------------------------------------------------------------------- # 3. Caricamento dati da API (preset + clienti) # --------------------------------------------------------------------------- if st.session_state.order_presets is None: st.session_state.order_presets = fetch_presets() if st.session_state.order_clients is None: st.session_state.order_clients = fetch_clients() preset_names = st.session_state.order_presets client_list = st.session_state.order_clients client_names = [c["nome_cliente"] for c in client_list] # --------------------------------------------------------------------------- # 4. Form fields # --------------------------------------------------------------------------- st.markdown("---") st.subheader("Dati ordine") col_left, col_right = st.columns(2) with col_left: dropdown_options = client_names + ["+ Nuovo cliente..."] selected_client_option = st.selectbox("Nome del cliente *", options=dropdown_options) client_name = "" if selected_client_option == "+ Nuovo cliente...": new_client_name = st.text_input( "Nome nuovo cliente", placeholder="es. Cosmetica Italia S.r.l.", ) if new_client_name.strip(): if st.button("Aggiungi cliente"): success = create_client(new_client_name.strip()) if success: st.success(f"Cliente '{new_client_name.strip()}' aggiunto.") st.session_state.order_clients = fetch_clients() st.rerun() else: st.error("Errore nella creazione del cliente.") client_name = new_client_name.strip() else: client_name = selected_client_option product_name = st.text_input("Nome del prodotto *", placeholder="es. Crema idratante viso") with col_right: if preset_names: selected_preset = st.selectbox("Preset di esposizione *", options=preset_names) else: st.warning("Nessun preset disponibile. Creare un preset nella pagina Esposizione.") selected_preset = None col_reload_p, col_reload_c = st.columns(2) with col_reload_p: if st.button("Ricarica preset", key="reload_presets"): st.session_state.order_presets = fetch_presets() st.rerun() with col_reload_c: if st.button("Ricarica clienti", key="reload_clients"): st.session_state.order_clients = fetch_clients() st.rerun() # --------------------------------------------------------------------------- # 5. Tabella ingredienti # --------------------------------------------------------------------------- st.markdown("---") st.subheader("Ingredienti") column_config = { "inci": st.column_config.TextColumn( "INCI", help="Nome INCI (facoltativo). Inserire AQUA/Water per saltare automaticamente la tossicologia.", width="medium", ), "cas": st.column_config.TextColumn( "CAS", help="Numero CAS (es. 56-81-5). Puo essere vuoto per AQUA/Water.", width="medium", ), "percentage": st.column_config.NumberColumn( "Percentuale (%)", help="Percentuale in peso (fino a 6 decimali)", min_value=0.0, max_value=100.0, format="%.6f", width="small", ), "is_colorante": st.column_config.CheckboxColumn( "Colorante", help="Coloranti o CAS speciali: bypassa la validazione del formato CAS", default=False, width="small", ), "skip_tox": st.column_config.CheckboxColumn( "Salta Tox", help="Salta la valutazione tossicologica (automatico per AQUA/Water)", default=False, width="small", ), } edited_df = st.data_editor( st.session_state.ingredient_df, column_config=column_config, num_rows="dynamic", width="stretch", hide_index=True, key="ingredients_editor", ) _aqua_count = sum(is_water_inci(v) for v in edited_df["inci"].fillna("")) if _aqua_count > 0: st.info(f"{_aqua_count} ingrediente/i rilevato/i come AQUA/Water: tossicologia saltata automaticamente.") # --------------------------------------------------------------------------- # 6. Validazione # --------------------------------------------------------------------------- errors = validate_order(client_name, product_name, selected_preset, edited_df) if errors: st.markdown("---") for err in errors: st.error(err) # --------------------------------------------------------------------------- # 7. Submit # --------------------------------------------------------------------------- st.markdown("---") can_submit = (len(errors) == 0) and (not st.session_state.order_submitted) if st.button("Invia Ordine", type="primary", disabled=not can_submit): payload = build_order_payload(client_name, product_name, selected_preset, edited_df) st.session_state.order_result = payload with st.spinner("Invio ordine in corso..."): try: resp = requests.post( f"{API_BASE}/orders/create", json=payload, headers=_auth_headers(), timeout=30, ) result = resp.json() if result.get("success"): st.session_state.order_submitted = True id_ordine = result.get("id_ordine") st.success(f"Ordine #{id_ordine} creato. Elaborazione avviata in background.") display_orderData(payload) else: st.error(result.get("error") or result.get("detail", "Errore nella creazione dell'ordine")) except requests.ConnectionError: st.error("Impossibile connettersi all'API. Verifica che il server sia attivo.") except Exception as e: st.error(f"Errore: {e}") # --------------------------------------------------------------------------- # 8. Debug JSON # --------------------------------------------------------------------------- if st.session_state.order_result is not None: with st.expander("Debug - Raw JSON"): st.json(st.session_state.order_result) # --------------------------------------------------------------------------- # 9. Reset # --------------------------------------------------------------------------- st.markdown("---") if st.button("Nuovo Ordine", key="reset_order"): st.session_state.order_submitted = False st.session_state.order_result = None st.session_state.ingredient_df = make_empty_ingredient_df() if "ingredients_editor" in st.session_state: del st.session_state["ingredients_editor"] st.rerun()