Generatore di ID sequenziali con azzeramento annuale - Supporto multi-prefisso

Prev Next

To download the Yearly Resetting Sequential ID Generator - Multi-Prefix Support, visit: [tulip](https://<span translate=).co/library/apps/yearly-resetting-sequential-id-generator---multi-prefix-support/">Library

In questo articolo, uno dei nostri sviluppatori della libreria, Tamás, illustra le scelte di sviluppo alla base dell’app modulare “Generatore di ID sequenziali con azzeramento annuale – Supporto multi-prefisso” disponibile nella libreria.

Perché l’ho realizzata in questo modo

Ho progettato questo blocco logico per risolvere un problema molto concreto: generare ID in modo affidabile, facile da riutilizzare e veloce da implementare in molte app.Il mio obiettivo era che l’ID stesso contenesse tutto il necessario per calcolare quello successivo. Ogni parte dell’ID ha un ruolo ben definito: * il prefisso indica a quale sequenza appartiene l’ID * l’anno separa la sequenza nel tempo * il numero indica l’ordine all’interno di quell’anno

Grazie a questa struttura, non ho dovuto introdurre alcuna logica di azzeramento. L’ultimo ID esistente contiene già tutte le informazioni necessarie per generare quello successivo. Finché l’ultimo ID è valido, l’ID successivo può sempre essere calcolato in modo prevedibile.

Ho incluso l’anno direttamente nell’ID perché separa naturalmente i record nel tempo. Ciò consente inoltre alla numerazione di ricominciare automaticamente ogni anno senza bisogno di alcun azzeramento programmato o aggiornamento manuale.

Il prefisso funge da confine tra sequenze diverse. Ciò rende possibile gestire più sequenze di numerazione indipendenti all’interno della stessa tabella. Cambiare il prefisso significa semplicemente continuare una sequenza diversa.

Ho aggiunto una validazione perché l’intero approccio si basa su una struttura degli ID coerente. Se un ID esistente non segue il formato previsto, continuare la sequenza potrebbe portare a risultati confusi o incoerenti. Preferisco interrompere tempestivamente il processo, rendere visibile il problema ed evitare di generare ID errati.

Ho scelto una ricerca basata su connettori in modo che il blocco logico possa funzionare con tabelle diverse senza modificare la logica di base. Una volta configurato il connettore, è sufficiente fornire l’ID della tabella e il prefisso, e lo stesso blocco logico può essere riutilizzato in più punti.

Per me, l’obiettivo non era solo generare ID, ma creare un modello riutilizzabile in cui il formato stesso dell’ID definisca come funziona la sequenzializzazione. È questo che mantiene la logica semplice, prevedibile e facile da riutilizzare in diverse app.

Cosa fa questo blocco logico

Questo blocco logico genera ID sequenziali che si azzerano ogni anno con la seguente struttura:prefisso + anno + numero progressivo

Esempio: TEST20240001 TEST20240002

Quando cambia l’anno, la numerazione ricomincia automaticamente da 1: TEST20250001

Nella stessa tabella possono coesistere più prefissi. Ciascun prefisso mantiene una propria sequenza indipendente: ORD20240001 ORD20240002 INV20240001 INV20240002

Il blocco logico garantisce: * formato ID coerente * azzeramento automatico annuale * numerazione indipendente per ogni prefisso * rilevamento tempestivo di ID storici non validi * rilevamento tempestivo di overflow numerico

Perché il connettore fa parte del progetto

Prima di generare l’ID successivo, il blocco logico necessita dell’ultimo ID esistente per il prefisso specificato. Il connettore esegue una ricerca filtrata: * filtra i record in cui l’ID inizia con il prefisso * ordina in ordine decrescente in base a _sequenceNumber * restituisce l’ultimo record

Poiché il prefisso fa parte del filtro, ogni prefisso ha un proprio contatore progressivo anche all’interno della stessa tabella. Ciò mantiene le sequenze isolate e prevedibili.

Come è strutturata l’implementazione

L’implementazione effettiva utilizza tre trigger. Questa struttura è intenzionale. Volevo che il blocco logico fosse facile da seguire dall’inizio alla fine, mantenendo comunque la logica principale separata e riutilizzabile. Quindi, invece di distribuire la logica su molti trigger modificabili, l’ho organizzata in tre fasi:1. CONFIGURAZIONE UTENTE: FASE DI INPUT 2. MAGIC: convalida + generazione dell’ID 3. CONFIGURAZIONE UTENTE: FASE DI OUTPUT

Questo garantisce un buon equilibrio tra usabilità e controllo.* Il primo trigger è dove si definiscono gli input.* Il secondo trigger contiene la logica interna che normalmente non deve essere modificata affinché il blocco logico funzioni.* Il terzo trigger è dove si decide cosa fare con il risultato generato.

Ciò significa che normalmente non è necessario intervenire all’interno del trigger della logica centrale. È sufficiente configurare il lato di input e quello di output. Questa è stata una delle decisioni chiave in fase di progettazione: rendere il blocco logico riutilizzabile esponendo solo le parti che solitamente devono essere modificate.

Trigger 1 – CONFIGURAZIONE UTENTE: FASE DI INGRESSO

Questo è il primo trigger con cui si interagisce. Il suo scopo è definire i valori di input necessari al blocco logico prima che possa essere generato qualsiasi ID. In pratica, questo trigger viene utilizzato per definire:* Il prefisso: _configPrefix* La lunghezza dell’ID: _configIdNumberFormat* L’ID della tabella passato al connettore: tableId

Cosa significa ciascun valore

_configPrefix: è il testo inserito all’inizio di ogni ID generato. Si utilizza per definire quale sequenza si desidera generare.

Esempi:* ORD* INV* TEST

Se _configPrefix = ORD, il blocco logico prende in considerazione solo gli ID esistenti che iniziano con ORD e continua solo quella sequenza. Pertanto, questo valore svolge due funzioni contemporaneamente:* definisce il prefisso visibile nell’ID finale* definisce quali ID storici il connettore debba utilizzare come fonte per il numero successivo

Ciò significa che prefissi diversi nella stessa tabella producono sequenze diverse**._configIdNumberFormat**

Questo parametro definisce la lunghezza della parte numerica dell’ID. Va impostato come stringa riempita con zeri. Esempi:000000000000000000

Il numero di zeri definisce la larghezza della sezione numerica.

Esempi:0000 → 0001, 0002, 0003 ... 9999 000000 → 000001, 000002 ... 999999

Questo valore non imposta il numero effettivo, ma definisce il formato in cui il numero deve rientrare. Lo utilizzo per due scopi: * per aggiungere zeri iniziali al numero generato * per verificare se il numero successivo rientra ancora nella lunghezza consentita

tableId

Questo valore definisce quale tabella il connettore deve interrogare.

In pratica, è proprio questo che rende il blocco logico riutilizzabile su tabelle diverse. È possibile copiare lo stesso blocco logico e indirizzarlo verso un’altra tabella modificando solo questo valore nel trigger di configurazione.

Il blocco logico non effettua autonomamente la ricerca nelle tabelle. Passa tableId e _configPrefix al connettore, e il connettore restituisce l’ultimo ID corrispondente da quella tabella.

Come utilizzarli insieme

Questi tre valori definiscono insieme il comportamento del generatore:* tableId indica al blocco logico dove cercare* _configPrefix gli indica quale sequenza continuare* _configIdNumberFormat gli indica come deve apparire la parte numerica

Guida pratica alla configurazione

Quando si configura il blocco logico, assicurarsi che:* tableId punti alla tabella corretta* _configPrefix corrisponda al prefisso che si desidera continuare* _configIdNumberFormat corrisponda alla lunghezza del numero che si desidera applicare

Se si modifica il prefisso, non si continua più la vecchia sequenza. Si avvia o si continua una sequenza diversa, specifica per quel prefisso. Se in seguito si aumenta la lunghezza del formato numerico, il blocco logico si aspetterà quel formato più ampio durante la convalida degli ID esistenti in quella tabella e nell’ambito di quel prefisso.Il connettore recupera l’ID più recente corrispondente al prefisso e lo memorizza in _stateLastId. Se non esiste ancora alcun ID, LINK() garantisce che il generatore possa comunque produrre il primo ID valido.

Perché il connettore fa parte del progetto

Il connettore non è solo un dettaglio tecnico. Fa parte della strategia di riutilizzo. Utilizzo il connettore per leggere l’ultimo ID esistente dalla tabella di destinazione. Il connettore riceve l’ID della tabella come input, applica il filtro del prefisso, ordina i record in base a _sequenceNumber in ordine decrescente e restituisce l’ultimo record corrispondente.

Ciò è importante per due motivi.

In primo luogo, consente al blocco logico di continuare la numerazione partendo dallo stato effettivo della tabella.

In secondo luogo, rende il blocco logico molto più facile da riutilizzare. Se si copia il pulsante in un’altra app, non è necessario riscrivere la logica. È sufficiente indirizzare il connettore alla tabella corretta e fornire la configurazione necessaria. Questo è il modello pratico alla base della realizzazione: * creare il pulsante una sola volta * impostare l’input della tabella nel connettore * definire il prefisso e il formato del numero * riutilizzarlo ovunque sia necessario

Si è trattato di una scelta deliberata per supportare una creazione più rapida delle app.

Cosa fa il connettore nel blocco logico?

Ecco cosa succede:* il connettore riceve l’ID della tabella* il connettore riceve il prefisso* il connettore filtra i record in base al prefisso* il connettore ordina i record in ordine decrescente in base a _sequenceNumber* il connettore restituisce l’ultimo ID corrispondente

Il connettore supporta anche un caso d’uso molto importante: entità multiple nella stessa tabella.

Se la stessa tabella contiene record come: ORD20240001 MAT20240011

allora il blocco logico non dovrebbe trattarli come un’unica sequenza condivisa. Ogni prefisso dovrebbe proseguire solo a partire dal proprio valore più recente.

Pertanto, il connettore filtra prima in base al prefisso. Ciò significa che i valori successivi diventano: ORD → ORD20240002 MAT → MAT20240012

Ho scelto questo modello perché entità diverse possono coesistere nella stessa tabella, ma ciò non significa che debbano condividere lo stesso numero progressivo.

Vale anche la pena precisare chiaramente un requisito di configurazione: il connettore richiede l’autenticazione, quindi è necessario generare un token e aggiungerlo alle impostazioni di autenticazione del connettore. Considero questa operazione come parte del lavoro di configurazione, non come parte della logica del blocco logico stesso, ma è comunque un aspetto importante per rendere il blocco logico utilizzabile.

Trigger 2 – MAGIC: convalida + generazione dell’ID

Questo trigger costituisce la logica centrale del blocco logico. Esegue tre operazioni in sequenza: 1. verifica se l’ultimo ID corrispondente abbia la struttura prevista; 2. verifica se il numero successivo rientri ancora nella lunghezza numerica configurata; 3. genera l’ID successivo

Ho raggruppato questi passaggi in un unico trigger poiché appartengono allo stesso flusso decisionale. Prima di creare un nuovo ID, voglio rispondere a tre domande: * l’ID precedente è strutturalmente sicuro da utilizzare? * il numero successivo rientrerebbe ancora nel formato? * se sì, quale dovrebbe essere il prossimo ID?

Convalida del formato

Expression

REGEX_REPLACE(LINK(Variable._stateLastId.id, Variable._configPrefix + DATETIMETOTEXT(App info.Current Date and Time,'YYYY') + Variable._configIdNumberFormat),'^' + Variable._configPrefix + '[0-9]{4}' + '[0-9]{' + LEN(Variable._configIdNumberFormat) + '}$','ok')

Cosa fa questa expression

Questo expression verifica se l’ID più recente corrisponde esattamente alla struttura prevista dal blocco logico. Funziona nel seguente ordine: 1. LINK(...) prende Variable._stateLastId.id quando il connettore ha restituito un valore. 2. Se non esiste ancora alcun ID, LINK(...) utilizza un valore di ripiego costruito a partire da: * Variable._configPrefix * DATETIMETOTEXT(...,'YYYY') * Variable._configIdNumberFormat3. Tale valore di riserva viene incluso affinché la convalida disponga comunque di una struttura valida da valutare anche quando si tratta del primo ID generato.4. Variable._configPrefix definisce il prefisso esatto con cui l’ID deve iniziare.5. DATETIMETOTEXT(...,'YYYY') non viene utilizzato come valore di corrispondenza all’interno dell’espressione regolare stessa, ma fa parte della struttura di riserva affinché questa assuma la forma di un vero ID.6. LEN(Variable._configIdNumberFormat) definisce quante cifre deve contenere la parte numerica.7. REGEX_REPLACE(...) verifica quindi l’intera stringa rispetto a questo modello: * prefisso * esattamente 4 cifre per l’anno * esattamente N cifre per la parte numerica8. Se la stringa completa corrisponde, l’intero valore viene sostituito con «ok».9. Se non corrisponde, il valore originale rimane invariato.10. Il trigger verifica quindi se il risultato è uguale a «ok». In caso contrario, l’esecuzione si interrompe.

Quindi questo expression non estrae valori per il calcolo. Il suo ruolo è verificare che l’ID abbia la struttura prevista prima che venga applicata qualsiasi logica numerica.

Perché ho scelto questa soluzione

Ho scelto di convalidare la struttura completa dell’ID prima di applicare qualsiasi logica numerica. L’ho costruito in questo modo perché il resto del trigger presuppone posizioni fisse:* prefisso all’inizio* anno immediatamente dopo il prefisso* numero immediatamente dopo l’anno

Se l’ID precedente non rispetta tale schema, l’incremento a partire da esso risulterebbe inaffidabile.

Ho anche scelto di includere qui il fallback LINK(...) in modo che il primo ID generato non necessiti di una logica di convalida separata. Lo stesso percorso di convalida funziona indipendentemente dall’esistenza o meno di un ID precedente. Ho intenzionalmente interrotto il trigger invece di tentare di correggere gli ID non conformi. La correzione automatica nasconderebbe i problemi nei dati e potrebbe creare una sequenza che sembra valida ma è basata su un input errato.

Protezione da overflow

Expression

LEN(IF(MID(LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat), LEN(Variable._configPrefix), 4) = DATETIMETOTEXT(App info.Data e ora correnti,'YYYY'),RIGHT(Variable._configIdNumberFormat+ TOTEXT(TEXTTOINTEGER(REGEX_REPLACE(LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat),'^' + Variable._configPrefix + '[0-9]{4}','')) + 1), LEN(Variable._configIdNumberFormat)), RIGHT(Variable._configIdNumberFormat + '1', LEN(Variable._configIdNumberFormat))))

Cosa fa questa expression

Questa expression e calcola quale sarebbe la parte numerica successiva e poi ne misura la lunghezza. Funziona in questo ordine: 1. LINK(...) utilizza l’ultimo ID, se presente. In caso contrario, ricorre a Variable._configPrefix + Variable._configIdNumberFormat. 2. MID(..., LEN(Variable._configPrefix), 4) legge i 4 caratteri immediatamente successivi al prefisso. Questi vengono considerati come la parte relativa all’anno dell’ID precedente. 3. DATETIMETOTEXT(...,'YYYY') fornisce l’anno corrente per il confronto. 4. IF(...) decide se la parte numerica debba continuare o essere azzerata: * stesso anno → incremento * anno diverso → ricomincia da 15. Nel ramo di incremento, REGEX_REPLACE(...) rimuove il prefisso e l’anno a 4 cifre dall’inizio dell’ID precedente, in modo che rimanga solo la parte numerica.6. TEXTTOINTEGER(...) + 1 converte quel testo numerico in un numero e lo incrementa.7. TOTEXT(...) riconverte il nuovo numero in testo.8. Variable._configIdNumberFormat + ... antepone al valore il formato con zeri configurato.9. RIGHT(..., LEN(Variable._configIdNumberFormat)) mantiene solo il numero di cifre richiesto.10. Nel ramo di reset, RIGHT(Variable._configIdNumberFormat + '1', LEN(...)) crea il primo numero consentito per un nuovo anno.11. LEN(...) misura la lunghezza della porzione numerica risultante.

Il trigger confronta questa lunghezza con quella consentita da Variable._configIdNumberFormat. Se la porzione numerica successiva fosse troppo lunga, l’esecuzione si interrompe.

Perché ho scelto questa soluzione

Ho scelto di calcolare il numero successivo nello stesso modo in cui viene effettivamente generato l’ID, invece di utilizzare una formula separata per indovinare quando il numero supererebbe la lunghezza consentita.

L’ho strutturato in questo modo perché volevo che il controllo dell’overflow seguisse esattamente le stesse regole della generazione reale dell’ID. Ciò riduce la possibilità che la logica di convalida e quella di generazione si discostino in seguito.

Fermarsi qui è più sicuro che continuare. Se il valore numerico successivo non rientra più nella lunghezza configurata, l’ID risultante violerebbe la struttura che hai configurato.

Ad esempio, se il formato numerico è 0000, allora 9999 è valido ma 10000 non lo è. Voglio che il blocco logico lo rilevi prima della creazione del record, non dopo.

Logica di generazione dell’ID

Expression

Variable._configPrefix + DATETIMETOTEXT(App info.Data e ora correnti,'AAAA')+ IF(MID(LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat),LEN(Variable._configPrefix),4) = DATETIMETOTEXT(Informazioni app.Data e ora correnti,'YYYY'),RIGHT(Variable._configIdNumberFormat+ TOTEXT(TEXTTOINTEGER(REGEX_REPLACE(LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat),'^' + Variable._configPrefix + '[0-9]{4}','')) + 1),LEN(Variable._configIdNumberFormat)),RIGHT(Variable._configIdNumberFormat + '1', LEN(Variable._configIdNumberFormat)))

Cosa fa questa expression

Questa è l’ expression che costruisce l’ID finale. Funziona in questo ordine: 1. Variable._configPrefix aggiunge il prefisso configurato all’inizio dell’ID. 2. DATETIMETOTEXT(App info.Current Date and Time,'YYYY') aggiunge l’anno corrente a 4 cifre. 3. LINK(...) utilizza l’ultimo ID restituito dal connettore, se presente. Se il connettore non restituisce un ID, utilizza Variable._configPrefix + Variable._configIdNumberFormat come struttura di riserva. 4. MID(..., LEN(Variable._configPrefix), 4) legge i 4 caratteri immediatamente successivi al prefisso. Questi vengono considerati come la parte relativa all’anno dell’ID precedente.5. DATETIMETOTEXT(...,'YYYY') viene utilizzato nuovamente qui in modo che l’ expression e possa decidere se l’ID precedente appartiene all’anno corrente.6. IF(...) controlla i due possibili percorsi: * stesso anno → continuare la numerazione * anno diverso → azzerare a 17. Nel ramo dello stesso anno, REGEX_REPLACE(...) rimuove il prefisso e l’anno a 4 cifre dall’inizio dell’ID precedente, in modo che rimanga solo la parte numerica.8. TEXTTOINTEGER(...) + 1 ** incrementa quel valore numerico.9. TOTEXT (...) riconverte il valore incrementato in testo.10. Variable._configIdNumberFormat + ... antepone il formato con zeri configurato al nuovo valore.11. RIGHT (..., LEN(Variable._configIdNumberFormat)) mantiene solo il numero di cifre richiesto, in modo che la parte numerica abbia sempre una lunghezza fissa.12. Nel ramo di reset, RIGHT (Variable._configIdNumberFormat + '1', LEN(...))** crea il primo numero di una nuova sequenza annuale.13. Il prefisso, l’anno corrente e il numero formattato vengono concatenati nell’ID finale.

Pertanto, l’output finale ha sempre questa struttura:prefisso + anno + numero a lunghezza fissa

Perché ho scelto questa soluzione

Ho scelto di ricavare l’ID successivo interamente dall’ultimo ID reale presente nella tabella. L’ho strutturato in questo modo perché l’ID precedente contiene già tutto ciò di cui la logica ha bisogno:* a quale prefisso appartiene la sequenza* a quale anno appartiene* qual era l’ultimo valore numerico

Ciò significa che non ho bisogno di una tabella contatore separata, di un campo anno memorizzato o di un azzeramento annuale programmato.

Ho scelto inoltre una formattazione a lunghezza fissa perché mantiene la struttura prevedibile e più facile da convalidare.

Il compromesso è che l’ expression e dipende da una struttura rigida degli ID. Ecco perché la fase di convalida viene prima di tutto. Ho accettato questo compromesso perché un contratto rigido e leggibile è più utile in questo caso che cercare di supportare molti formati di input poco definiti.

Trigger 3 – CONFIGURAZIONE UTENTE: FASE DI OUTPUT

Questo è l’ultimo trigger nel blocco logico. Il suo scopo è definire cosa deve accadere con l’ID generato una volta che _stateOutput contiene un valore valido.

Il punto importante qui è che questa fase è stata volutamente lasciata aperta per l’utente.

Se _stateOutput non è vuoto, potete decidere quale azione il blocco logico debba compiere successivamente. Nell’attuale configurazione, il trigger mostra un messaggio guida che spiega i passaggi successivi: * aggiungere la tabella configurata all’app * sostituire la fase del messaggio con un trigger “Crea record” * utilizzare _stateOutput come ID del nuovo record

L’ho strutturato in questo modo perché il comportamento in uscita può variare a seconda dei casi d’uso.

Potresti voler: * creare immediatamente un record * memorizzare prima l’ID in un’altra variabile * utilizzare l’ID all’interno di un flusso di lavoro più lungo * verificare condizioni aggiuntive prima di scrivere nella tabella

Invece di impostare in modo rigido un unico comportamento di output, ho mantenuto configurabile la fase di output.

Ecco perché la configurazione visibile all’utente è presente su entrambe le estremità del blocco logico:* la struttura di input è configurabile* il comportamento di output è configurabile* la logica di base rimane al centro

Approccio alla denominazione delle variabili

Le variabili interne utilizzano il prefisso trattino basso:* _configPrefix* _configIdNumberFormat* _stateLastId* _stateOutput

Questo aiuta a distinguere rapidamente tra i valori di configurazione e le variabili di elaborazione interne.

Limiti di progettazione

Il blocco logico presuppone questa struttura:prefisso + anno + numero. Ogniprefisso mantiene una propria sequenza indipendente.

Il blocco logico non supporta un unico contatore condiviso tra più prefissi.

Se l’anno o le sezioni numeriche vengono spostate all’interno dell’ID, l’ expressions e deve essere adeguato di conseguenza.

Sintesi delle decisioni di progettazione

Se riduco l’intero blocco logico alle decisioni fondamentali che ne stanno alla base, queste sono:* Volevo un riutilizzo rapido, quindi il blocco logico è guidato dalla configurazione.* Volevo che la tabella fosse la fonte di verità, quindi l’ID successivo si basa sempre sull’ultimo record effettivo.* Volevo che la logica rimanesse gestibile, quindi il calcolo è centralizzato in un unico punto.* Volevo che più entità in un’unica tabella funzionassero in modo pulito, quindi ogni prefisso ha una propria sequenza indipendente.* Volevo che la struttura dell’ID rimanesse coerente nel tempo, quindi l’anno fa parte dell’ID e la sequenza si azzera automaticamente quando cambia l’anno.* Volevo proteggere la tabella da schemi errati, quindi la convalida del formato e i controlli di overflow bloccano gli ID errati prima che venga creato un record.* Volevo che il flusso del trigger rimanesse leggibile, quindi le responsabilità sono suddivise in passaggi chiari: configurazione, convalida, generazione, output.

Ecco perché il blocco logico ha questa forma. Non si tratta solo di un generatore di ID. È un modello riutilizzabile per la creazione controllata di ID che cerca di bilanciare semplicità, riutilizzabilità e protezione dei dati, mantenendo al contempo la segmentazione annuale integrata direttamente nella struttura dell’ID.

Partecipa

Entra a far parte della comunità per condividere miglioramenti, proporre unità aggiuntive, segnalare problemi o discutere le decisioni architetturali.