Gestionale per la finanza agevolata in ambito vitivinicolo
L'applicativo attorno a cui girano gli altri sistemi
- Periodo
- dal 2018
- Ruolo
- sviluppo
- Tecnologie
- Python · Django · PostgreSQL · Celery · Vue.js · jQuery · Bootstrap · Ansible
- Stato
- in produzione
a cosa serve
Un progetto di finanza agevolata dura anni, mette insieme più aziende sotto un capofila e vive di una regola sola: una spesa vale se è stata prevista in un piano, autorizzata prima di essere sostenuta e documentata dopo. Alla fine va rendicontata nella forma prevista da regole scritte altrove, che cambiano da un'annualità alla successiva.
Fatto a mano, questo lavoro sta in fogli di calcolo per i piani, cartelle condivise per i documenti e scambi di posta per le autorizzazioni. Regge finché i progetti sono pochi, e si rompe sulla domanda più semplice che qualcuno possa fare: questa spesa era autorizzata, da chi, e dov'è il documento che la giustifica.
cosa fa
L'applicativo tiene il piano finanziario di ogni progetto, segue ogni singola spesa lungo il suo ciclo di vita, sorveglia i documenti che la giustificano e produce quelli da consegnare. È anche il punto da cui passano gli altri sistemi descritti in queste pagine — la scrittura dei progetti, l'analisi dei documenti, le risposte sulla documentazione — e il posto dove si decide cosa farne: i modelli osservano ed estraggono altrove, qui si calcola l'esito.
Ha una parte di AI propria, ed è l'ultima sezione di questa scheda: il manuale utente è indicizzato come base di conoscenza e risponde alle domande di chi sta guardando una schermata.
Ci ho lavorato insieme ad altri: è troppo esteso per essere attribuito a una singola persona. Scomporlo per moduli sarebbe più corretto ma impraticabile, quindi questa scheda lo racconta nel suo insieme.
una spesa è una macchina a stati
L'entità centrale è l'ordine: una spesa che qualcuno propone, qualcun altro autorizza, un fornitore esegue e un terzo rendiconta. Attraversa dieci stati, e ogni passaggio registra chi l'ha fatto e quando.
Il passaggio dalla compilazione alla valutazione trasforma una bozza in un documento: genera il codice definitivo dell'ordine, che unisce progetto, impresa, progressivo e mercato. Se la generazione del codice fallisce lo stato non avanza, perché un ordine in valutazione senza codice sarebbe irrintracciabile da quel momento in poi, e nessun controllo successivo se ne accorgerebbe.
la retromarcia è il punto dove il sistema tocca il mondo
Un ordine può sempre tornare in lavorazione, e serve che possa: le cose cambiano, e un sistema che obbliga ad annullare e riscrivere produce doppioni che poi qualcuno deve spiegare. Ma da un certo punto in avanti l'ordine è già uscito verso il gestionale contabile a valle, e tornare indietro smette di essere una scrittura locale.
Le due uscite scartate erano simmetriche e sbagliate per ragioni opposte. Vietare la retromarcia oltre la fatturabilità avrebbe spostato tutto sugli annullamenti, che è la stessa perdita di storia con un nome diverso. Concederla sempre avrebbe lasciato il sistema a valle disallineato in silenzio, che è peggio, perché il disallineamento si scopre a fine progetto quando i totali non tornano.
La soluzione adottata è più noiosa: la retromarcia è bloccata se il documento a valle esiste davvero, e l'esistenza si verifica interrogando quel sistema invece di fidarsi di un flag locale. Un flag locale è la registrazione di un'intenzione, non di un fatto, e in mezzo alle due cose c'è ogni errore di rete mai capitato. Solo un amministratore può forzare il blocco, lasciando una nota che dice cosa andrà verificato dall'altra parte.
il ruolo non è il permesso
L'accesso è diviso per ruolo, con permessi distinti. Chi amministra vede tutto, compresa la gestione degli utenti. Chi segue i progetti e gli ordini nella loro parte operativa ha un ruolo dedicato a quello. Chi lavora solo sulle bozze iniziali di progetti e ordini vede una porzione più stretta. I fornitori entrano in sezioni proprie, pensate per loro. Sono quattro tagli sullo stesso dominio, ciascuno con quello che gli serve e nient'altro.
Il ruolo però non basta a dire chi può cosa, e questa è la scelta di progettazione che si nota di meno e regge di più. Quasi tutte le azioni che fanno avanzare un ordine non richiedono un ruolo: richiedono di essere associati all'azienda capofila di quel progetto. La stessa persona, con lo stesso ruolo, ha poteri diversi su due progetti diversi.
Il motivo è che il ruolo è una proprietà dell'utente, mentre il potere di far avanzare una spesa è una proprietà della coppia utente-progetto. Tenerli separati evita la soluzione che si vede più spesso, cioè moltiplicare i ruoli finché ce n'è uno per ogni combinazione: quel percorso finisce con venti ruoli che nessuno sa più assegnare, e con la verifica dei permessi copiata in ogni vista.
Sulla stessa linea sta il perimetro di visibilità — quali aziende e quali ordini un utente vede — calcolato in un punto solo e applicato a tutte le liste. La ragione per cui sta in un punto solo è che ogni copia di quella regola è un posto in cui, il giorno che cambia, qualcuno si dimenticherà di cambiarla, e il difetto che ne esce è del tipo peggiore: qualcuno vede dati che non gli spettano e nessun test se ne accorge.
i semafori si calcolano, la conferma no
I documenti di un ordine sono divisi in tre aree — amministrativa, tecnica, finanziaria — e ciascuna ha un semaforo che si ricalcola da sé a ogni evento che possa cambiarlo: il salvataggio di una voce, la validazione di un file, la propagazione di una lettera di incarico, la cancellazione di un documento già validato, il riallineamento in blocco. I cinque punti di innesco chiamano la stessa funzione, e il semaforo non è mai scritto a mano da nessuna parte: è derivato, e ricalcolarlo da zero costa meno che tenerlo aggiornato per differenze.
Gli stati del semaforo sono tre, e il terzo è quello interessante. Incompleta e completa sono ovvi; completa con riserva serve ai casi in cui un documento c'è ma qualcosa non torna, e la lavorazione non può fermarsi lì ad aspettare. Un ordine che avanza mentre un'area è in riserva se la porta dietro con un contrassegno proprio: la riserva resta leggibile a valle invece di dissolversi nel passaggio di stato, che è il modo in cui normalmente le eccezioni spariscono.
Il passaggio finale, quello che dichiara un ordine rendicontato, non si accontenta dei semafori: richiede tre conferme messe a mano, una per area, ciascuna registrata con l'utente e la data. Il motivo è che il calcolo sa dire se i documenti ci sono, non se vanno bene, e le due cose sono state tenute separate di proposito. Il semaforo è un aiuto alla lettura; la firma resta di una persona, con un nome accanto.
le regole cambiano ogni anno, quindi non stanno nel codice
Le voci che compongono un piano finanziario — azioni, subazioni, tipi di costo — cambiano da un'annualità alla successiva. E i progetti delle annualità precedenti restano aperti per anni: non esiste un momento in cui si può migrare tutto alla regola nuova, perché quel momento richiederebbe che nessun progetto vecchio sia più in corso, e non succede mai.
La scelta è stata trattare la tassonomia come dato, non come codice: le voci sono righe di tabella con l'anno di validità, e aggiungere un'annualità è un'operazione di popolamento che non tocca il codice né richiede un rilascio. Stessa cosa per le percentuali del piano e per le soglie dei controlli di riepilogo, differenziate per fascia di anni.
Il limite di questa scelta è dichiarato, e resta il punto che invecchia peggio: quello che è dato è la tassonomia, non la formula. Il calcolo degli importi ha due rami diversi, prima e dopo il 2023, perché in quel passaggio è cambiata la struttura del calcolo e non solo i suoi valori — una voce che prima si otteneva come percentuale di altre ha iniziato a essere inserita direttamente. Un ramo nel codice per ogni riforma è sostenibile finché le riforme sono rare, e finora lo sono state; il giorno in cui non lo saranno, il posto dove intervenire è già identificato, ed è questo.
tre copie dello stesso budget
Il budget di ogni subazione esiste in tre versioni parallele: quello di progettazione, che è la somma delle voci di costo; quello approvato, congelato nel momento in cui il progetto entra in gestione; quello di gestione, che si muove con gli spostamenti successivi. Un quarto campo tiene separatamente memoria degli spostamenti di budget da un'azienda all'altra, e non viene azzerato quando una variante riallinea il budget di gestione.
Tenere quattro numeri dove sembra bastarne uno è una duplicazione voluta, e la ragione è la domanda che arriva sempre: di quanto ci si è scostati da ciò che era stato approvato. Un valore solo, aggiornato in place, a quella domanda non sa più rispondere il giorno dopo il primo aggiornamento. Il quarto campo esiste perché uno spostamento fra aziende diverse e uno spostamento dentro la stessa azienda hanno conseguenze diverse per chi rendiconta, e sommarli in un numero unico li renderebbe indistinguibili proprio quando serve distinguerli.
l'ecosistema
Attorno al gestionale gira un insieme di servizi che si parlano fra loro, ed è questo a renderlo un sistema più che un applicativo isolato.
L'anagrafica d'impresa vive in un servizio esterno dedicato, e la regola che la governa sta in una riga: il gestionale la mostra e non la scrive. Prima di aggiungere un campo a un'azienda si verifica che non esista già lì. La regola sembra pedante finché non si è vista la sua violazione: due ragioni sociali diverse per la stessa impresa, ciascuna corretta nel suo applicativo, e nessun modo di sapere quale sia quella buona senza chiedere all'azienda.
La scrittura dei progetti ha un database separato: il piano finanziario viene copiato lì una volta e nessun dato economico torna indietro. L'analisi dei documenti riceve i file caricati e restituisce fatti, che qui diventano semafori. Il sistema RAG risponde alle domande sulla documentazione. Pacchetti interni condivisi portano le stesse utilità — client per i modelli linguistici, gestione dei file, connettori verso il servizio delle anagrafiche — dentro le applicazioni che ne hanno bisogno, invece di riscriverle in ognuna. n8n fa da trasporto per i flussi che girano in background, senza logica propria.
i due sistemi a valle non si rompono allo stesso modo
A valle ci sono due gestionali contabili di terze parti, e ogni progetto dichiara quale usare. Uno si raggiunge via API REST con un token per database, l'altro interrogando il suo database FileMaker: due protocolli diversi per lo stesso scambio, ordini in uscita e fatture in entrata.
La differenza che conta non è il protocollo, è cosa succede quando l'invio fallisce. Verso il primo, un errore riporta l'ordine allo stato precedente: si è tentato di uscire e non si è usciti, quindi il gestionale finge di non averci provato, e l'operatore ritenta. Verso il secondo l'errore viene registrato e mostrato, ma l'ordine avanza comunque.
È un'asimmetria vera, e la dichiaro perché è il genere di cosa che si scopre in produzione e non in fase di progetto: significa che per il secondo sistema esiste una finestra in cui il gestionale crede di aver trasmesso e l'altro non ha ricevuto, e che quella finestra la chiude una persona guardando, non il codice. Il secondo sistema è anche l'unico dei due che può cambiare un dato del gestionale in senso inverso — il tasso di cambio, quando diventa definitivo — e quella sincronizzazione è interrogata a ogni passaggio di stato invece di essere attesa: chiedere costa una chiamata, aspettare costa un ordine fatturato al cambio sbagliato.
il manuale è una base di conoscenza, e risponde
La parte di AI che appartiene al gestionale è un assistente sulla guida utente. Non risponde sui dati: risponde sul manuale, a chi sta guardando una schermata, e sa quale schermata è.
Il corpus è la documentazione utente scritta nello stesso repository del codice e versionata con esso — trentasei pagine su cinque aree, che coprono per intero quello che l'utente più comune vede a menu. La scelta di tenerla lì invece che in uno spazio documentale a parte ha una conseguenza pratica: chi modifica una schermata ha la guida di quella schermata nello stesso albero di file, e aggiornarla è parte dello stesso intervento, non un compito successivo che qualcuno prima o poi farà.
il ruolo lo mette il server
Ogni pagina del manuale dichiara a quali ruoli è destinata, e la ricerca filtra su quel campo. Il vocabolario ammesso è di tre valori soli, senza alcun valore jolly: chi serve più ruoli li elenca tutti. Un jolly sarebbe l'unico modo per far trapelare una pagina per distrazione, e non vale il risparmio di tre caratteri.
I ruoli del gestionale sono più dei tre valori del corpus, quindi in mezzo c'è una traduzione. Vive in un punto solo, e il ruolo di partenza è letto dal profilo dell'utente autenticato lato server. Non è una precauzione formale: il servizio che risponde autentica il sistema chiamante e non la persona, quindi un ruolo che arrivasse dal browser non sarebbe un filtro, sarebbe un campo a scelta di chi chiede.
Una pagina senza il campo del ruolo, o con un valore fuori vocabolario, non viene indicizzata e la cosa viene segnalata. Le due alternative erano entrambe peggiori: indicizzarla senza filtro la rende visibile a chiunque, cioè una fuga; indicizzarla e nasconderla in silenzio produce contenuto morto che nessuno si accorge sia sparito. Scartare e protestare è l'unica opzione in cui l'errore resta visibile. Il controllo sta anche a monte, dove le pagine si scrivono, per una ragione precisa: un refuso nel nome del ruolo lascia la pagina scritta, indicizzata e invisibile proprio a chi doveva leggerla, e il primo a saperlo sarebbe un utente che riceve «non ho trovato» su un tema che il manuale copre.
Il vocabolario autoritativo però non sta qui: sta nel servizio che indicizza, ed è modificabile da lì. Quello del gestionale è una copia, e le due possono divergere in silenzio. È scritto nel contratto insieme all'accordo che lo rende innocuo — chi cambia il vocabolario lo annuncia all'altro lato — perché un limite dichiarato con la sua contromisura è un rischio governato, mentre un limite non scritto è una sorpresa in attesa.
tre risposte possibili, e una si butta
La risposta del servizio può arrivare in tre forme, e il widget le tratta in modo diverso.
| Risposta | Significato | Cosa fa il widget |
|---|---|---|
| Con le fonti | fondata sulle guide che elenca | mostra la risposta e i titoli |
| Con l'elenco delle fonti vuoto | il manuale è stato interrogato e non ha riscontri | mostra la risposta, con una nota esplicita |
| Senza il campo delle fonti | la ricerca non è stata tentata | scarta l'output e segnala un errore |
Il terzo caso non è un esito, è un'anomalia di contratto: la richiesta è costruita in modo che la ricerca sia sempre tentata, quindi se quel campo manca la risposta è il modello che parla a memoria di un manuale che non ha letto. Per un assistente sul manuale, una procedura inventata con tono assertivo è il danno peggiore fra i due possibili — l'utente la segue. Il controllo sta nel servizio che compone la risposta e non nella vista che la mostra, così nessuna schermata futura può dimenticarsene.
Da distinguere con attenzione dal secondo caso, che è invece una risposta legittima: il modello dice che non ha riscontri, ed è una cosa che deve poter dire.
la schermata da cui arriva la domanda
Chi apre il widget è dentro una pagina precisa, e quell'informazione prima si buttava via. Serve alle domande deittiche — «cosa significa questa colonna?», «a cosa serve questo pulsante?» — che da un pannello dentro la pagina sono le più naturali e prima erano senza risposta, perché il servizio non sapeva cosa fosse «questo».
Il legame lo dichiara il corpus, non una mappa nel codice. Ogni guida elenca le schermate che copre, e il gestionale legge quel campo e ne costruisce l'indice inverso. Non c'è nessuna tabella da mantenere a mano, e la manutenzione sta in capo a chi scrive la guida, cioè all'unica persona che sa di quale schermata sta parlando. Il precedente che ha suggerito questa forma è nello stesso database: una tabella nata anni prima per legare le schermate ai testi di aiuto contiene oggi una riga, che è il destino di ogni struttura che chiede a qualcuno di ricordarsi di popolarla.
Tre scelte, tutte con una ragione asimmetrica dietro.
- Contesto, non filtro. La schermata orienta la risposta, non restringe la ricerca. Una schermata sbagliata orienta male una risposta; un filtro sbagliato la fa sparire, e chi chiede non ha modo di distinguere «non c'è» da «non te lo mostro».
- Attraversa il confine solo il titolo in lingua utente, non l'identificatore tecnico della vista. Metterlo nella richiesta significherebbe infilare un nome tecnico dentro un prompt che vieta i nomi tecnici, e il canale che sembrava neutro non lo è: finisce comunque dentro il testo che il modello legge, in forma strutturata, cioè più incline a essere citato.
- I titoli si filtrano per ruolo anche qui. Il filtro della ricerca vive dall'altra parte e non tocca i titoli, che li inietta il gestionale. Senza filtrarli, su una schermata condivisa un professional si ritrovava come contesto la guida scritta per il fornitore.
E una soglia oltre la quale non si passa più nulla. Non è un problema di lunghezza: troncare a due un elenco di dieci guide produce una selezione arbitraria — l'ordine è quello dei file — che il contesto presenta come la descrizione della schermata. Quando dieci guide dichiaravano la stessa schermata, la causa vera si è rivelata un'altra: l'identificatore era troppo grossolano, perché una sola rotta serviva quattro aree che per chi guarda sono quattro pagine diverse. Distinguendole, il problema non nasce, e la soglia torna a essere quello che deve essere — una rete che non scatta, non la cura.
i suggerimenti non sono i titoli delle guide
Il pannello propone alcune domande già scritte, e il primo tentativo usava il titolo della guida che copre la schermata, perché era il dato già disponibile e gratuito.
Sulla pagina da cui passano tutti, l'unico suggerimento diventava «Controllare il budget del progetto», che su un pulsante si legge come la richiesta di eseguire quel controllo sui propri dati: una promessa che l'assistente non può mantenere, dato che risponde sul manuale e non sul database. La causa non era una svista ma la convenzione del corpus, dove il titolo è per definizione il nome di un compito, verbo all'infinito più oggetto. Ottimo per una guida, sbagliato per un suggerimento.
Ora le domande si scrivono a mano, schermata per schermata, e dove non ci sono il pannello non propone nulla. Nessun ripiego automatico: qualunque ripiego derivato dai dati esistenti riporterebbe lo stesso difetto con un'altra faccia.
i report non passano dalla richiesta
I riepiloghi in foglio di calcolo — ordini, budget, contributi, fatture per valuta — non si generano dentro la richiesta HTTP. La vista mette in coda un lavoro, il lavoro produce il file e lo deposita in uno spazio temporaneo intestato all'utente, la pagina interroga lo stato e scarica quando è pronto.
La ragione è banale e si è vista una volta sola per essere capita: un riepilogo su un progetto grande impiega più del tempo massimo concesso a una richiesta, e finisce con un errore del proxy dopo aver comunque consumato tutto il lavoro. Spostarlo in coda toglie il limite e in più rende l'attesa visibile, mentre prima era indistinguibile da un blocco — e un utente che non distingue le due cose ricarica la pagina, facendo ripartire il calcolo da capo.
limiti dichiarati
- Non è un lavoro individuale, e la scheda non prova a spartirlo. Le parti che ho progettato e scritto per intero sono raccontate nelle schede degli altri sistemi.
- I controlli di riepilogo esistono in due motori: quello nuovo e quello precedente, tenuto per le annualità vecchie. Convivono da tempo ed è debito riconosciuto, non una scelta.
- Il calcolo degli importi ha due rami, prima e dopo il 2023. Finché restano aperti progetti delle annualità vecchie il ramo vecchio non si può togliere, e nessuno può dire quando sarà.
- Il disallineamento con il secondo sistema contabile non ha una riconciliazione automatica. Oggi lo chiude una persona che guarda.
- L'assistente sul manuale è acceso per un solo account. È un cancello di collaudo, e finché regge la pagina del manuale che spiega l'assistente descrive a chiunque la trovi qualcosa che quasi nessuno ha ancora.
- Il controllo che varrebbe più di tutti sull'assistente non c'è: verificare che ogni domanda suggerita recuperi davvero la propria guida fra le fonti. Oggi si controlla che i riferimenti esistano, non che la ricerca li trovi.
stato
In produzione. Il deploy passa da Ansible, che gestisce le migrazioni del database, l'installazione dei requisiti e l'aggiornamento dei pacchetti interni; gira su macchine dedicate, con un proxy davanti e il database su una macchina separata. Lo stack è Python e Django, con Vue.js, jQuery e Bootstrap sul frontend, PostgreSQL come database e Celery per i lavori in coda.