lmb-fe/pages/order_page.py
2026-03-13 23:54:16 +01:00

270 lines
10 KiB
Python

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()