Quando una modifica alla configurazione o ai dati aziendali può influire su altri utenti, un’applicazione può richiedere che una persona la proponga e un’altra la esamini prima che diventi effettiva. Progettare questo processo come un flusso di approvazione delle modifiche in un SaaS PHP richiede più che aggiungere un pulsante «approva»: bisogna separare la proposta dal dato corrente, proteggere ogni transizione e definire cosa accade se il contesto cambia mentre la richiesta è in attesa.
L’obiettivo è fare in modo che il sistema possa rispondere, in modo verificabile, a cosa è stato richiesto, chi l’ha esaminato, con quali permessi e se è stato effettivamente applicato. La soluzione deve essere proporzionata al rischio: un flusso non necessario aggiunge attrito, mentre uno insufficiente può consentire modifiche errate o difficili da tracciare.
Decidere quali modifiche richiedono l’approvazione

Inizia classificando le modifiche in base all’impatto, all’ambito e alla possibilità di correggerle. Non tutti i dati modificabili richiedono una revisione. Un utente che modifica una preferenza personale a basso impatto può salvare direttamente la modifica. Potrebbe invece essere ragionevole richiedere l’approvazione per modificare una regola di fatturazione, una configurazione condivisa da un’organizzazione o un dato master che influisce su più processi.
Definisci criteri di prodotto espliciti. Per esempio, chiediti se la modifica interessa più utenti di chi la propone, se può modificare importi o permessi, se una seconda persona è richiesta da una policy interna e quanto costerebbe correggerla. Documenta anche le eccezioni e chi può modificarle. Evita di trasformare l’approvazione in un meccanismo generico per operazioni non pertinenti al caso: l’ambito qui riguarda le modifiche alla configurazione o ai dati aziendali.
La decisione deve riflettersi sul server, non soltanto nell’interfaccia. Nascondere un pulsante non impedisce a qualcuno di chiamare direttamente un endpoint. L’API deve determinare se il tipo di modifica richiede l’approvazione e applicare la regola sia alle operazioni singole, sia alle azioni massive o alle integrazioni che possono modificare lo stesso dato.
Separare la richiesta dal dato corrente
Non sovrascrivere il dato corrente quando crei una proposta. Salva una richiesta indipendente che contenga almeno l’organizzazione, la risorsa interessata, i valori proposti, lo stato, chi l’ha creata e quando. A seconda del prodotto, può essere necessario salvare anche una motivazione, l’origine della richiesta, una scadenza o la versione del dato originale.
Per rappresentare i valori, puoi salvare campi tipizzati oppure una struttura serializzata convalidata dal server. In entrambi i casi, evita di accettare un payload arbitrario come se fosse un aggiornamento diretto. Convalida lo schema, i campi consentiti, i formati e le regole di business quando ricevi la proposta. Ripeti le convalide pertinenti al momento dell’applicazione, perché le condizioni potrebbero essere cambiate durante l’attesa.
Questo design consente di confrontare il valore corrente con quello proposto senza alterare l’esperienza degli altri utenti. Permette anche alla schermata di revisione di mostrare una differenza comprensibile. Non presentare come corrente un valore che è soltanto in attesa: chiarisci quale dato è stato applicato e quale è la proposta.
Modellare stati e transizioni espliciti
Definisci un insieme ristretto di stati con significati precisi. Una sequenza comune può includere proposed (proposta), in_review (in revisione), approved (approvata), rejected (rifiutata), expired (scaduta) e applied (applicata). Non tutti i prodotti hanno bisogno dello stato di revisione: se basta una decisione immediata, lo si può omettere. L’importante è che ogni stato descriva una situazione operativa e non sia un’etichetta decorativa.
Specifica le transizioni valide. Per esempio, una proposta può passare alla revisione oppure essere rifiutata; una richiesta in revisione può essere approvata o rifiutata; un’approvazione può essere applicata se è ancora valida. Una richiesta scaduta non dovrebbe essere approvata seguendo lo stesso percorso di una richiesta attiva. Le azioni non consentite devono restituire una risposta coerente, anche se l’interfaccia non offre tale opzione.
«Approvata» e «applicata» non sono equivalenti. L’approvazione registra una decisione; l’applicazione modifica il dato corrente. Mantenere entrambe le fasi permette di rilevare gli errori di applicazione e mostrare le richieste approvate che richiedono ancora attenzione. Se il processo può essere completato in un’unica transazione breve, la decisione e la scrittura possono essere coordinate, ma il modello deve continuare a distinguere ciò che è accaduto.
Verificare i permessi a ogni transizione
Autorizza ogni azione sul server in base all’organizzazione, al ruolo, alla risorsa e allo stato corrente. Non dare per scontato che aver potuto creare una richiesta conceda anche il permesso di approvarla o applicarla. Non fidarti dell’identificativo dell’organizzazione inviato dal client: ricava l’ambito da una sessione o da una credenziale convalidata e verifica che la richiesta e la risorsa appartengano allo stesso ambito.
Se l’auto-approvazione è vietata, verifica che l’identità di chi approva sia diversa da quella di chi ha proposto la modifica. Questa regola deve essere applicata durante la transizione, non soltanto nella vista. Definisci inoltre cosa accade alle richieste degli amministratori, degli utenti di servizio e dei processi automatizzati; le eccezioni richiedono una policy deliberata, non un’omissione accidentale.
Un’approvazione dovrebbe essere associata a una proposta specifica. Se il suo contenuto cambia, l’approvazione precedente non è più valida: crea una nuova versione della richiesta oppure riportala a uno stato che richieda una revisione. Modificare silenziosamente i valori dopo l’approvazione rompe la corrispondenza tra ciò che è stato esaminato e ciò che è stato applicato.
Gestire concorrenza, scadenza e applicazione
Mentre una richiesta è in attesa, un altro attore potrebbe modificare il dato originale. Per rilevare questa situazione, registra la versione della risorsa o i valori pertinenti quando viene creata la proposta. Al momento dell’applicazione, confronta la versione attesa con quella corrente. Se non corrispondono, non sovrascrivere senza criterio la nuova modifica: blocca l’applicazione, segnala un conflitto o richiedi una nuova revisione secondo le regole del prodotto.
In un database relazionale, una transazione può proteggere la lettura della richiesta, il controllo del suo stato e l’aggiornamento della risorsa. Il pattern specifico dipende dal motore e dal livello di concorrenza; può essere appropriato un lock pessimistico oppure un aggiornamento condizionale basato sulla versione. In ogni caso, la transizione deve essere atomica: due richieste simultanee non dovrebbero applicare due volte la stessa modifica.
Rendi l’applicazione idempotente oppure rifiuta i retry in modo sicuro. Se vengono aggiornati dati nel database e inoltre viene inviata una notifica o pubblicato un evento, valuta la possibilità di registrare l’evento in sospeso nella stessa transazione ed elaborarlo in seguito. Questo riduce il rischio di mostrare una notifica di successo per una scrittura che non è stata confermata.
Definisci anche una policy di scadenza: per quanto tempo una proposta può restare attiva, cosa ne determina la scadenza e se può essere rinnovata. La scadenza deve essere verificata sul server al momento dell’approvazione e dell’applicazione; non basta modificare lo stato tramite un task pianificato. Se un ritardo rende inaffidabili i valori o le condizioni, richiedi una nuova proposta.
Sottoporre ad audit e gestire operativamente il flusso
Conserva un registro di chi ha proposto, approvato, rifiutato e applicato ogni modifica, includendo date, identificativi dell’organizzazione e della risorsa, esito e contesto necessario per un’indagine. Salva un riferimento alla versione proposta e alla decisione. Il registro di audit non dovrebbe poter essere modificato dalle normali funzioni di modifica delle richieste. Limita i dati registrati a quelli necessari e applica le policy di accesso e conservazione del prodotto.
L’interfaccia operativa dovrebbe aiutare a trovare le richieste in sospeso, scadute, bloccate da conflitti oppure approvate ma non applicate. Spiega il motivo del blocco e offri un percorso sicuro per risolverlo; non trasformare un errore di applicazione in un’approvazione automatica. Le notifiche possono segnalare nuove attività e decisioni, ma sono un supporto: lo stato persistito e consultabile è la fonte di verità.
Testare invarianti, permessi ed errori

I test devono verificare le regole che non devono mai essere violate. Una richiesta non approvata non modifica il dato corrente; una persona senza accesso all’organizzazione non può leggerla né cambiarne lo stato; l’auto-approvazione non riesce se è vietata; una richiesta approvata non può essere applicata con un contenuto diverso da quello esaminato. Aggiungi test per gli stati terminali, le richieste scadute e le transizioni non valide.
Testa anche le race condition tra due approvazioni o tra un’approvazione e una modifica del dato originale. Simula errori durante l’applicazione e verifica che la richiesta non venga contrassegnata come applicata se la scrittura non è stata confermata. Se esiste un task che fa scadere le richieste, verifica che sia sicuro eseguirlo più volte. Questi test trasformano le regole di business in garanzie verificabili e aiutano a mantenere il flusso quando cambiano i permessi o il modello dei dati.
Come criterio di progettazione, ogni fase deve avere un responsabile, una condizione verificabile e un risultato osservabile. Se il team non sa spiegare chi è autorizzato a ogni transizione, cosa accade in caso di modifica concorrente o come ricostruire una decisione, il flusso deve ancora essere definito prima di arrivare in produzione.



