Moduli custom
Il registry dei moduli definisce la struttura: quali moduli esistono e quali campi hanno. Qui si crea un modulo e se ne modella lo schema; i record che ci vivono dentro si gestiscono con le API dei record generici.
Cambiare la struttura (creare moduli, aggiungere o eliminare campi, cambiare tipo) richiede
il ruolo amministratore. La sola lettura della definizione è accessibile a ogni
utente autenticato. Vedi anche le convenzioni per errori ed envelope.
Ogni richiesta richiede l'header Authorization: Bearer <accessToken>.
01L'oggetto Modulo
La definizione completa di un modulo (quella restituita da GET /api/modules/:moduleKey
e dalle mutazioni) è un oggetto con la riga module, l'elenco
groups (le sezioni) e l'elenco fields (il catalogo campi).
| Attributo | Tipo | Descrizione |
|---|---|---|
| module.module_key | string | Chiave univoca (slug); usata negli URL /api/records/:moduleKey. |
| module.label_singular | string | Etichetta al singolare (es. Ticket). |
| module.label_plural | string | Etichetta al plurale. |
| module.icon | string | null | Icona del modulo (opzionale). |
| module.description | string | null | Descrizione libera. |
| module.is_custom | boolean | true per i moduli personalizzati (i soli modificabili). |
| groups | array | Sezioni del form: id, group_key, label, sort_order. |
| fields | array<Campo> | Il catalogo campi: vedi lo schema Campo. |
{ "module": { "module_key": "tickets", "label_singular": "Ticket", "label_plural": "Ticket", "icon": null, "description": null, "is_custom": true }, "groups": [ { "id": 4, "group_key": "dettagli", "label": "Dettagli", "sort_order": 0 } ], "fields": [ { "field_key": "titolo", "field_label": "Titolo", "data_type": "text", "is_required": true, … } ] }
02Campo (field)
Ogni voce di fields descrive un campo del modulo. In creazione i campi
si passano nella forma abbreviata (key, label,
type, required, config);
in lettura tornano con le colonne persistite (field_key,
field_label, data_type…).
| Attributo | Tipo | Descrizione |
|---|---|---|
| field_key | string | Chiave del campo (slug); è la proprietà usata nel body dei record. |
| field_label | string | Etichetta mostrata nel form. |
| data_type | string | Tipo del campo: vedi Tipi di campo. |
| is_required | boolean | true se obbligatorio in creazione. |
| config.options | string[] | Opzioni statiche: obbligatorio per select; opzioni iniziali per picklist. |
| config.target | string | Modulo puntato, obbligatorio per lookup: core (accounts/contacts/leads) o un modulo custom attivo (anche il modulo stesso). |
| sort_order | integer | Ordine di visualizzazione. |
| group_id | integer | null | Sezione a cui il campo appartiene. |
03Endpoint
| Metodo | Path | Cosa fa | |
|---|---|---|---|
| GET | /api/modules | auth | Elenco dei moduli (base + custom). |
| GET | /api/modules/:moduleKey | auth | Definizione completa di un modulo (campi, gruppi). |
| POST | /api/modules | admin | Crea un nuovo modulo custom. |
| PATCH | /api/modules/:moduleKey | admin | Rinomina / aggiorna il modulo (etichette, icona, descrizione). |
| DELETE | /api/modules/:moduleKey | admin | Elimina il modulo (con conferma della chiave). |
| POST | /api/modules/:moduleKey/fields | admin | Aggiunge un campo. |
| PATCH | /api/modules/:moduleKey/fields/:fieldId | admin | Modifica un campo (etichetta, required, config). |
| POST | /api/modules/:moduleKey/fields/:fieldId/type | admin | Cambia il tipo di un campo (con conversione dati). |
| PATCH | /api/modules/:moduleKey/fields/:fieldId/placement | admin | Sposta un campo in un gruppo e ne fissa l'ordine. |
| DELETE | /api/modules/:moduleKey/fields/:fieldId | admin | Elimina un campo (?purge=true rimuove anche i dati). |
| POST | /api/modules/:moduleKey/field-groups | admin | Crea un gruppo di campi (sezione del form). |
| PATCH | /api/modules/:moduleKey/field-groups/:groupId | admin | Rinomina / riordina un gruppo. |
| DELETE | /api/modules/:moduleKey/field-groups/:groupId | admin | Elimina un gruppo. |
04Elencare i moduli
Restituisce i moduli base più i moduli custom leggibili dal ruolo (i moduli custom su cui non hai permesso di lettura vengono omessi).
Risposta · 200{ "success": true, "data": { "items": [ { "module_key": "accounts", "label_plural": "Account", "is_custom": false }, { "module_key": "tickets", "label_plural": "Ticket", "is_custom": true } ] } }
05Definizione di un modulo
| Parametro | Tipo | Descrizione |
|---|---|---|
| moduleKey | string | Chiave del modulo (es. tickets). |
| Stato | Corpo |
|---|---|
| 200 | { success: true, data: { module, groups, fields } }: vedi L'oggetto Modulo. |
| 403 | FORBIDDEN: modulo custom non leggibile dal tuo ruolo. |
| 404 | NOT_FOUND: modulo inesistente. |
06Creare un modulo
Definisci la chiave del modulo (usata poi negli URL /api/records/:moduleKey),
le etichette singolare/plurale e i campi iniziali. La chiave viene normalizzata a slug e non può
essere una riservata (accounts, contacts,
deals…).
| Campo | Tipo | Note | |
|---|---|---|---|
| moduleKey | string | opzionale | Chiave desiderata; se assente è derivata da labelPlural. Slug ^[a-z][a-z0-9_]{1,40}$. |
| labelSingular | string | richiesto | Etichetta al singolare. |
| labelPlural | string | richiesto | Etichetta al plurale. |
| icon | string | opzionale | Icona del modulo. |
| description | string | opzionale | Descrizione libera. |
| groups | array | opzionale | Sezioni: [{ "label": "Dettagli" }], nell'ordine di visualizzazione. |
| fields | array<Campo> | opzionale | Campi iniziali: vedi Campo. Ogni groupIndex punta a groups. |
curl -X POST https://crm.tuodominio.it/api/modules \ -H "Authorization: Bearer <accessToken>" \ -H "Content-Type: application/json" \ -d '{ "moduleKey": "tickets", "labelSingular": "Ticket", "labelPlural": "Ticket", "fields": [ { "key": "titolo", "label": "Titolo", "type": "text", "required": true } ] }'
const res = await fetch("https://crm.tuodominio.it/api/modules", { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ moduleKey: "tickets", labelSingular: "Ticket", labelPlural: "Ticket", fields: [{ key: "titolo", label: "Titolo", type: "text", required: true }], }), }); const { data } = await res.json();
{ "success": true, "data": { "module": { "module_key": "tickets", … }, "groups": [], "fields": [ … ] } }
| Stato | Codice | Quando |
|---|---|---|
| 201 | · | Modulo creato; nel corpo la definizione completa. |
| 422 | VALIDATION_ERROR | Etichette mancanti, chiave non valida o riservata, campo select senza opzioni, lookup con target non valido. |
| 403 | FORBIDDEN | Chiamante senza ruolo amministratore. |
07Aggiungere un campo
Aggiunge un campo a un modulo custom esistente. Se il campo è
required e il modulo ha già dei record, devi passare
defaultValue per riempirli (backfill).
| Campo | Tipo | Note | |
|---|---|---|---|
| label | string | richiesto | Etichetta; la field_key è lo slug derivato. |
| type | string | opzionale | Tipo del campo (default text). Vedi Tipi. |
| required | boolean | opzionale | Obbligatorietà (default false). |
| config | object | opzionale | options per select/picklist, target per lookup, prefix/padding/next per autonumber. |
| defaultValue | any | opzionale | Valore di backfill per i record esistenti quando required. |
| groupId | integer | opzionale | Sezione in cui collocare il campo. |
curl -X POST https://crm.tuodominio.it/api/modules/tickets/fields \ -H "Authorization: Bearer <accessToken>" \ -H "Content-Type: application/json" \ -d '{ "label": "Priorità", "type": "select", "config": { "options": ["bassa","alta"] } }'
const res = await fetch("https://crm.tuodominio.it/api/modules/tickets/fields", { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ label: "Priorità", type: "select", config: { options: ["bassa", "alta"] } }), }); const { data } = await res.json();
{ "success": true, "data": { "field": { "field_key": "priorita", "data_type": "select", … }, "definition": { … } } }
| Stato | Codice | Quando |
|---|---|---|
| 201 | · | Campo aggiunto; nel corpo il campo e la definizione aggiornata. |
| 422 | VALIDATION_ERROR | Etichetta mancante, tipo non supportato, chiave duplicata, modulo base (non personalizzato), o campo obbligatorio su record esistenti senza defaultValue. |
| 403 | FORBIDDEN | Chiamante senza ruolo amministratore. |
| 404 | NOT_FOUND | Modulo inesistente. |
08Tipi di campo
Ogni campo ha un type tra i dieci tipi supportati:
| Tipo | Uso |
|---|---|
| text · textarea | Testo breve / lungo. |
| number | Valore numerico. |
| date · datetime | Data / data e ora. |
| checkbox | Sì / No (booleano). |
| picklist | Elenco a scelta gestito (opzioni riusabili, vedi Picklist). Le opzioni iniziali si passano in config.options. |
| select | Elenco a scelta con opzioni statiche in config.options (almeno una, obbligatoria). |
| lookup | Riferimento a un record di un altro modulo. config.target: un modulo core (accounts, contacts, leads) oppure un modulo custom attivo: anche il modulo stesso (auto-riferimento, es. padre/figlio). Il valore è l'id del record collegato, validato all'inserimento. |
| autonumber | Progressivo assegnato dal server alla creazione (mai richiesto in input, anche se il campo è obbligatorio). config: prefix (es. TICKET-), padding (zero-fill, max 12) e next (primo valore, default 1). I valori forniti da import restano invariati. |
09Cambiare il tipo di un campo
Cambia il data_type di un campo esistente, convertendo i dati già
presenti. La conversione avviene solo se tutti i valori sono rappresentabili
nel nuovo tipo; altrimenti la richiesta è rifiutata e nessun dato viene toccato.
curl -X POST https://crm.tuodominio.it/api/modules/tickets/fields/42/type \ -H "Authorization: Bearer <accessToken>" \ -H "Content-Type: application/json" \ -d '{ "newType": "number" }'
| Stato | Codice | Quando |
|---|---|---|
| 200 | · | Tipo cambiato; nel corpo la definizione aggiornata. |
| 422 | VALIDATION_ERROR | Tipo non supportato, o alcuni record hanno valori non convertibili (nei details: conteggio ed esempi). |
| 403 | FORBIDDEN | Chiamante senza ruolo amministratore. |
10Azzerare il contatore di un autonumber
Imposta il prossimo valore che il campo autonumber assegnerà
(config.next). Body opzionale
{ "next": 100 } (default 1). L'operazione è tracciata
nell'audit log (module_field_counter_reset).
Riportare il contatore indietro può produrre numeri duplicati rispetto ai record già esistenti: l'unicità del progressivo non è imposta dal database.
11Gruppi di campi
I campi possono essere organizzati in gruppi (le sezioni del form di
dettaglio). I gruppi si creano, rinominano, riordinano ed eliminano con gli endpoint
/field-groups; lo spostamento di un campo tra gruppi usa
PATCH …/fields/:fieldId/placement.
curl -X POST https://crm.tuodominio.it/api/modules/tickets/field-groups \ -H "Authorization: Bearer <accessToken>" \ -H "Content-Type: application/json" \ -d '{ "label": "Dettagli" }'
Eliminare un campo con ?purge=true, o eliminare un modulo, rimuove i
dati associati in modo irreversibile: fai un backup se il modulo contiene record importanti
(vedi Backup). L'eliminazione di un modulo richiede
di ridigitarne la chiave come conferma.