Panoramica struttura

Il file database.json è il cuore della configurazione di TIMS. Contiene tutto ciò che serve al sistema per funzionare: connessione al DB, autenticazione, definizione di ogni query e le relative regole di visualizzazione e modifica.

{ "connection": { ... }, // Connessione SQL Server "auth_table": "dbo.AnUtenti", // Tabella autenticazione "auth_fields": { ... }, // Mapping campi login "session": { ... }, // Configurazione sessione "security": { ... }, // Regole sicurezza "groups": [ ... ], // Gruppi di query (il cuore) "ui_settings": { ... } // Impostazioni interfaccia }

Connection

Parametri di connessione al database SQL Server.

ParametroValore attualeNote
serverems.tilog.itHost del SQL Server
databaseEMS_PremiNome database
usersaUtente SQL
options.encrypttrueCrittografia connessione
options.trustServerCertificatetrueAccetta certificati self-signed
options.connectionTimeout15000 msTimeout connessione
options.requestTimeout15000 msTimeout richieste
options.pool.max10Max connessioni pool

Autenticazione

L'autenticazione usa la tabella dbo.AnUtenti. Ecco il mapping dei campi:

Chiave JSONCampo DBDescrizione
usernameLoginCampo login utente
passwordPasswordCampo password
levelTipoUtenteLivello accesso numerico
activeAbilitatoUtente attivo (1/0)
deletedCancellatoUtente cancellato (1/0)
user_idIDID univoco utente
display_nameUtenteNome visualizzato
codsoccodsocCodice società assegnata

Livelli utente

LivelloRuoloAccesso
100AdministratorTutto
90AdministratorTutto (incl. Configurazione, Locazioni)
50SuperUserQuery standard + bottoni custom (es. Chiudi Ordine, Stampa Etichette IN)
10StandardUserSolo query con min_level ≤ 10
Regola: Ogni query ha un min_level. L'utente vede solo le query dove il suo TipoUtente ≥ min_level della query. Anche i custom_buttons hanno il proprio min_level.

Session & Security

Session

ParametroValoreNote
timeout3600 s (1 ora)Durata sessione
securefalseCookie secure flag (true in prod con HTTPS)
sameSitestrictProtezione CSRF
namePHPSESSIDNome cookie sessione (valore configurato db_session_id non applicato: manca session_name() in config.php)

Security

ParametroValoreDescrizione
max_login_attempts5Tentativi login prima del blocco
lockout_duration300 s (5 min)Durata blocco account
password_min_length4Lunghezza minima password
session_regeneratetrueRigenera session ID al login

UI Settings

ParametroValore
themedark
sidebar_width250px
default_page_size25 righe
date_formatDD/MM/YYYY
currency_symbol€
decimal_places2

Tipi di query

simple type: simple

Query singola, tabella piatta. Ha una sola sql a livello root della query. Usata per: movimenti, locazioni, giacenza, articoli, clienti/fornitori, bordereau, opzioni, utenti, progressivi.

{ "id": "articoli", "label": "Articoli", "type": "simple", "min_level": 0, "sql": "SELECT ... FROM AnArticoli WHERE ...", "primary_key": "codArticolo", "editable_fields": ["descrizione1"], "required_fields": [], "readonly_fields": ["codArticolo", ...], "display_options": { ... } }
master_detail type: master_detail

Struttura a due livelli: testata (master) + righe (detail). La query detail usa @masterId come parametro per filtrare le righe della testata espansa. Usata per: ordini_in, ordini_out.

{ "id": "ordini_in", "type": "master_detail", "master": { "sql": "SELECT ... FROM OfOrdiniInTES ...", "primary_key": "id", "hidden_fields": ["id", "codSoc", "Cancellato"], "editable_fields": [...], "readonly_fields": [...], "required_fields": [...] }, "detail": { "sql": "SELECT ... WHERE idOfOrdiniInTES = @masterId", "join_field": "idOfOrdiniInTES", "hidden_fields": ["idOfOrdiniInTES"], ... } }
join_field indica il campo nella tabella detail che si collega alla primary_key del master. Il placeholder @masterId nella SQL viene sostituito a runtime con l'ID della riga master espansa.

Logica campi

Ogni query (o ogni sezione master/detail) definisce 4 categorie di campi:

ProprietàSignificatoRegola
editable_fields Campi modificabili dall'utente Appariranno come input editabili nella UI di modifica
readonly_fields Campi in sola lettura Visibili ma non modificabili
required_fields Campi obbligatori Devono avere un valore per salvare. Sottoinsieme di editable_fields
Campi nascosti Solo per master_detail. Presenti nei dati ma non mostrati in griglia (es. ID, codSoc)
primary_key — Identifica il record per le operazioni di UPDATE. Può essere una stringa singola ("id") o un array per chiavi composite (["codSoc", "NomeProgr"]). Se non presente, la query è solo in lettura.

Insert rules (regole di inserimento)

La proprietà insert_rules permette di definire regole di validazione complesse per la creazione di nuovi record. Viene valutata lato server prima di eseguire l'INSERT.

RegolaTipo valoreDescrizione
requiredarray di campiCampi obbligatori: devono avere un valore non vuoto
at_least_one_truearray di arrayPer ogni gruppo: almeno uno dei campi deve essere = 1 (true)
conditional_requiredarray di regoleSe il campo if_field = if_value allora then_required deve essere valorizzato
duplicate_checkarray di regoleVerifica unicità: il campo indicato non deve esistere già per la stessa società
auto_fieldsoggetto chiave-valoreCampi valorizzati automaticamente dal server: @session.username, @session.codsoc, ecc.
"insert_rules": { "required": ["RagioneSociale"], "at_least_one_true": [["Cli", "Forn", "Vett"]], "conditional_required": [ { "if_field": "Cli", "if_value": 1, "then_required": ["codCli"] }, { "if_field": "Forn", "if_value": 1, "then_required": ["codForn"] } ], "duplicate_check": [ { "field": "codCli", "condition_field": "Cli", "message": "Codice cliente già esistente" } ], "auto_fields": { "codSoc": "@session.codsoc", "utenteCreazione": "@session.username", "utenteUltModifica": "@session.username" } }
Dove usato: Attualmente applicato alla query clienti_fornitori. Può essere aggiunto a qualsiasi query di tipo simple con insert abilitato.

Display options

OpzioneDefaultDescrizione
rows_per_page50Righe per pagina nella griglia
allow_exporttrueAbilita export (Excel, PDF, WhatsApp)
show_record_counttrueMostra conteggio record totali
expand_icon"+"Solo master_detail: icona espansione
collapse_icon"-"Solo master_detail: icona compressione
edit_icon"🔧"Solo master_detail: icona modifica

Highlight fields

Dentro la sezione detail di una master_detail, puoi definire regole di evidenziazione condizionale sulle righe.

"highlight_fields": { "daControllare": { // nome campo da controllare "condition": "equals", // tipo condizione "value": 1, // valore che attiva l'highlight "style": "warning" // stile CSS applicato (warning = giallo) } }
Dove usato: ordini_in detail → daControllare = 1 evidenzia la riga · ordini_out detail → ORR_InPrelievo = 1 evidenzia la riga.

Custom buttons

Pulsanti personalizzati associati a una query. Attualmente definiti solo per ordini_in.

Chiudi Ordini min_level: 50 selection: multiple
ProprietàValore
idchiudi_ordini_in
iconfas fa-lock
colorwarning
visibletrue
confirm"Sei sicuro di voler chiudere {count} ordini selezionati?"
action_typesql
action.sqlUPDATE OfOrdiniInTES SET OrdineChiuso = 1, dataChiusura = GETDATE() WHERE id IN (@selectedIds) AND codSoc = @codSoc
refresh_aftertrue
Stampa DDT min_level: 10 selection: single
ProprietàValore
idstampa_ddt_in
visiblefalse (nascosto al momento)
action_typeapi_call
endpointprintflow/api/generate.php
methodPOST
paramstemplate: ddt_in, id: @selectedId
response_typedownload
action_type "sql" esegue una query SQL diretta. "api_call" chiama un endpoint PHP. I placeholder @selectedIds, @selectedId, @codSoc vengono sostituiti a runtime.
Stampa Bordereau (dropdown menu) min_level: 10 selection: single

I bottoni di tipo dropdown mostrano un menu a discesa con più azioni. Ogni item del menu è una vera azione (api_call o custom_handler). Usato per la stampa bordereau:

ItemHandler / Endpoint
Stampa Lista di Caricoapi/print_bordereau.php
Bordereau per Vettoreapi/print_bordereau_vettore.php

Struttura di un custom_button

{ "id": "nome_univoco", "label": "Testo Bottone", "icon": "fas fa-icon", "color": "primary|warning|danger|success", "visible": true|false, "min_level": 50, "confirm": "Messaggio conferma con {count}", "selection_mode": "single|multiple|none", "same_group_field": "NomeCampo", // opzionale: tutte le righe selezionate devono avere lo stesso valore "action_type": "sql|api_call|custom_handler|dropdown", "custom_handler": "nomeFunzione", // per action_type: custom_handler "action": { // se sql: "sql": "UPDATE ... WHERE id IN (@selectedIds) AND codSoc = @codSoc", "refresh_after": true, // se api_call: "endpoint": "path/to/api.php", "method": "GET|POST", "params": { "key": "value", "id": "@selectedId" }, "response_type": "download|json" }, // se dropdown: "items": [ { "id": "item1", "label": "Voce 1", "icon": "fas fa-...", "action_type": "api_call", ... }, { "id": "item2", "label": "Voce 2", ... } ] }
same_group_field: Se impostato, impedisce di eseguire l'azione su righe selezionate con valori diversi per quel campo. Esempio: con same_group_field: "Codice" non puoi chiudere contemporaneamente bordereau con codici diversi.

Filtro codSoc (Società)

Di default, ogni query viene filtrata automaticamente per il codSoc dell'utente loggato. Il sistema aggiunge AND codSoc = @userCodSoc alla WHERE.

Eccezione: skip_codsoc_filter — Se una query ha "skip_codsoc_filter": true, il filtro società NON viene aggiunto. Usato per tabelle che non hanno il campo codSoc, come AnLocazioni e OrBordereau.

Query con skip_codsoc_filter: true:

locazioni bordereau

Gruppo: Magazzino

movimenti simple level: 0

Movimenti di carico/scarico magazzino. Tabella: StMovimenti JOIN AnArticoli.

Tutti i campi readonly. Nessun campo editabile. Solo visualizzazione.

Campi

IdcodArticolodescrizioneQtacodSoccodCausaleNumeroOrdineDdtDataDdtLottoDataScadenza
locazioni simple level: 90 skip_codsoc

Anagrafica locazioni magazzino. Tabella: AnLocazioni. Solo Admin (90+). Non ha campo codSoc.

Tutti i campi readonly.

Campi

codMagFiscodMagLogCorsiaPosizionePianoAliasPickingOScortaPrelevabileMultiArticoloPesoMaxLungLargAltezzadescrizione
giacenza simple level: 0

Giacenza articoli in magazzino. Vista: vStockArticoliPosizioni (filtro Qta > 0).

Tutti i campi readonly.

Campi

codArticolodescrizioneLottoDataScadenzaIdPalletQtaQtaImpegnatacodMagFiscodMagLogCorsiaPosizionePianoAlias

Gruppo: Anagrafiche

articoli simple level: 0

Anagrafica articoli. Tabella: AnArticoli (Cancellato = 0). Primary key: codArticolo.

Campo editabile

descrizione1

Campi readonly

codArticolodescrizioneQtaConfQtaPezzoPesoLordoPesoNettogestLottogestDtScadenzaCodUMAbilitatoCancellato
clienti_fornitori simple level: 0 (lettura) / 50 (scrittura)

Anagrafica clienti, fornitori e vettori. Tabella: AnCliForn (Cancellato = 0). Primary key: IdCliOForn (hidden).

Campi editabili

codClicodFornRagioneSocialeCliFornVettindirizzoLocalitaProvinciaCAPNazionetelefonoemail

Campi nascosti

Insert rules (validazione creazione)

required: RagioneSociale
at_least_one_true: [Cli, Forn, Vett] — almeno uno dei ruoli deve essere attivo
conditional_required: se Cli=1 → codCli obbligatorio; se Forn=1 o Vett=1 → codForn obbligatorio
duplicate_check: codCli unico tra clienti, codForn unico tra fornitori/vettori
auto_fields: codSoc, codAnSubTipoAn, utenteCreazione, utenteUltModifica (popolati automaticamente dal server)

Gruppo: Spedizioni

ordini_in — Ordini In Ingresso master_detail level: 0

MASTER — OfOrdiniInTES

JOIN con AnCliForn su IdAnCliForn = IdCliOForn. Primary key: id.

Campi nascosti:

Campi editabili:

OrdineFornitoreProgressivoArrivoDataArrivoPrevistoNoteOrdineNoteLogisticaNoteDDTNoteTrasportoDDTDataDDTOrdineChiuso

Campo obbligatorio:

OrdineFornitore

Campi readonly:

dataChiusuradataCreazionedataUltModificaEsitoInviatodataInvioEsitoRagioneSocialecodForn

DETAIL — OfOrdiniInRIG

JOIN con AnArticoli su IdAnArticolo = ID. Join field: idOfOrdiniInTES.

Campi editabili:

LottoAttesoDtScadAttesaQtaAttesaQtaCaricataRigaCompletataNoteRigaSubRigavaloreDatiExtraIn1valoreDatiExtraIn2valoreDatiExtraIn3valoreDatiExtraIn4daControllare

Campi obbligatori:

QtaAttesaRiga

Highlight: daControllare = 1 → stile warning

Custom buttons: "Chiudi Ordine" (min_level 50, selezione multipla, custom_handler: chiudiOrdineIn, file: assets/js/chiudi-ordine-in.js, API: api/chiudi_ordine_in.php) · "Stampa Etichette IN" (min_level 10, visible: true, custom_handler: stampaEtichetteIn, file: assets/js/etichette-in.js, API: api/etichette_in.php) · "Stampa DDT" (min_level 10, visible: false, api_call: api/print_ordini_in.php).
missioni_ldp — Missioni (LdP) master_detail level: 0

MASTER — OrListaPCOTes

JOIN con OrOrdiniOutTes su Id = idOrListaPCOTes. Primary key: id. Sola lettura.

DataListaNumeroOrdineNumBollaDataBollaInPrelievo

DETAIL — OrListaPCORig

JOIN con AnArticoli e AnLocazioni. Join field: idOrListaPCOTes. Sola lettura.

codArticolodescrizioneLottoIdPalletQtaDaPrelevareQtaPrelevataQtaOutOfStockcodMagFiscodMagLogCorsiaPosizionePiano
Custom buttons: "Stampa LdP" (min_level 10, visible: true, api_call: api/print_ldp.php) · Stampa multipla via api/print_ldp_Multi.php · Stampa automatica via api/printing_ajax.php.
bordereau simple level: 0 (read) / 50 (write) skip_codsoc

Gestione bordereau spedizioni. Tabella: OrBordereau. Non ha filtro codSoc. Ha custom_create: "bordereau" e custom_delete: "bordereau" per la gestione speciale di creazione e cancellazione.

Campi visualizzati (readonly)

IDID_AnCliFornAbilitatoCancellatodataCreazioneutenteCreazionedataChiusuracodSocCodiceNote
Custom buttons:
Stampa bordereau (dropdown, min_level 10) — menu con: "Stampa Lista di Carico" → api/print_bordereau.php, "Bordereau per Vettore" → api/print_bordereau_vettore.php

Chiudi Bordereau (min_level 50, selection: multiple, same_group_field: "Codice") — handler: chiudiBordereau() in assets/js/chiudi-bordereau.js. Verifica dataChiusura IS NULL. API: api/bordereau.php?action=close

Cancella Bordereau (min_level 50, selection: multiple, same_group_field: "Codice") — handler: deleteBordereau() in assets/js/app.js. Transazione atomica: Cancellato=1 + scollega ordini collegati. API: api/bordereau.php?action=delete
ordini_out — Ordini di Spedizione master_detail level: 0

MASTER — OrOrdiniOutTes

JOIN con AnCliForn su IdAnCli = IdCliOForn. Primary key: id.

Campi nascosti:

Campi editabili:

NumeroOrdineNumBollaDataBollaDestRagSocDestIndirizzoDestLocalitaDestCAPDestProvinciaDestNazioneNoteOrdineNoteLogisticaNoteDDTNoteTrasportoValoreContrassegnotipoIncassoContrassegnoNumFatturadataConsRichParamTesLibero1ParamTesLibero2ParamTesLibero3

Campo obbligatorio:

NumeroOrdine

Campi readonly:

dataCreazionedataUltModificadataChiusuraEsitoInviatodataInvioEsitoRagioneSocialeLocalitaCAPProvinciaNazioneAnnoidOrListaPCOTesInPrelievo

DETAIL — OrOrdiniOutRig

JOIN con AnArticoli su IdAnArticolo = ID. Join field: IdOrOrdiniOutTes.

Campi editabili:

NumRigaOrdineCodiceRiservaPalletCodiceRiservaPosizioneORR_InPrelievoLottoDataScadenzaMatricolaIdPalletvaloreVariante1valoreVariante2valoreVariante3valoreVariante4valoreVariante5valoreVariante6valoreVariante7valoreVariante8QtaNoteRigaOrdineQtaPrelevata

Campi obbligatori:

QtaNumRigaOrdine

Highlight: ORR_InPrelievo = 1 → stile warning


Gruppo: Configurazione

opzioni simple level: 90

Opzioni di configurazione sistema. Tabella: AnOpzioni. Tutti i campi readonly.

Campi

OpzioneCodSocIdAnUtenteValoredataCreazioneAbilitatoCancellato
utenti simple level: 90

Anagrafica utenti. Tabella: AnUtenti. Primary key: ID.

Campo editabile

Abilitato

Campi readonly

codSocUtenteLoginPasswordTipoCancellatodataCreazioneTipoUtenteTokenRfMenu
progressivi simple level: 90

Anagrafica progressivi. Tabella: AnProgr. Primary key composita: ["codSoc", "NomeProgr"]. Limitata a TOP 200.

Campi editabili

ProgrAbilitatoPrefisso

Campi readonly

codSocNomeProgrdataCreazionedataUltModificautenteCreazioneutenteUltModificaCancellatoFormato

Mappa tabelle DB

Riepilogo di tutte le tabelle/viste referenziate nel database.json.

Tabella / VistaUsata inTipoNote
StMovimentimovimentiTabellaJOIN con AnArticoli su IdAnArticolo
AnLocazionilocazioniTabellaNiente codSoc (skip_codsoc_filter)
vStockArticoliPosizionigiacenzaVistaFiltro Qta > 0
AnArticoliarticoli, ordini_in detail, ordini_out detail, movimentiTabellaCancellato = 0 in anagrafica
AnCliFornclienti_fornitori, ordini_in master, ordini_out masterTabellaJOIN su IdCliOForn o IdAnCliForn
OfOrdiniInTESordini_in masterTabellaTestata ordini ingresso
OfOrdiniInRIGordini_in detailTabellaRighe ordini ingresso
OrOrdiniOutTesordini_out masterTabellaTestata ordini spedizione
OrOrdiniOutRigordini_out detailTabellaRighe ordini spedizione
OrBordereaubordereauTabellaskip_codsoc_filter
AnOpzioniopzioniTabellaConfigurazioni sistema
AnUtentiutenti, autenticazioneTabellaLogin + livelli accesso
AnProgrprogressiviTabellaPK composita, TOP 200
TimsUserGridPreferencespreferenze griglia (non in database.json)TabellaPreferenze colonne per utente: user_id, codsoc, query_id, visible_fields (JSON), field_order (JSON). Gestita da api/user_preferences.php

Feature flags (Features.php)

Il sistema supporta feature flags per abilitare/disabilitare funzionalità specifiche per singola società (codSoc). La configurazione è in C:\inetpub\config_secure\features_config.php, che non è nel repository per sicurezza.

// features_config.php return [ 'TL01' => [ 'vedi_note_riga_ordine' => true, 'stampa_etichette_personalizzate' => [ 'enabled' => true, 'articoli_prefix' => ['ART', 'BRL'] // solo per questi prefissi codArticolo ] ], 'BER' => [ 'vedi_note_riga_ordine' => false ] ];
MetodoFirmaDescrizione
Features::enabled()(codSoc, feature): boolRitorna true se la feature è attiva per la società
Features::matchesPrefix()(codSoc, feature, codArticolo): boolRitorna true se il codArticolo inizia con uno dei prefix abilitati per la feature
Default: Se una feature non è definita nella configurazione, enabled() restituisce false. Ogni feature è OFF per default.
Uso tipico in API:
if (Features::enabled($codsoc, 'mia_feature')) { ... }
if (Features::matchesPrefix($codsoc, 'feature', $codArticolo)) { ... }

Encoding (Encoding.php)

SQL Server con collation Latin1_General_CI_AS restituisce i campi VARCHAR in encoding Windows-1252 anziché UTF-8. La classe Encoding in includes/Encoding.php gestisce la conversione esplicita prima dell'output JSON.

Attenzione: La conversione NON è applicata globalmente per evitare doppie conversioni su campi NVARCHAR (che SQL Server restituisce già in UTF-8). Va applicata esplicitamente sulle query che usano campi VARCHAR.
// Uso in un endpoint API $rows = $db->fetchAll($sql, $params); $rows = Encoding::toUtf8($rows); // conversione esplicita echo json_encode($rows);
MetodoDescrizione
Encoding::toUtf8($data)Conversione ricorsiva: stringhe non-UTF8 vengono convertite da Windows-1252. Array, null, int, float, bool vengono gestiti automaticamente.

Logica di conversione

stringa UTF-8 valida → invariata stringa non-UTF-8 → converti da Windows-1252 array → applica ricorsivamente su ogni elemento int / float / null / bool / object → invariati

Preferenze griglia utente

Il sistema salva le preferenze colonne di ogni utente (visibilità e ordine) per ogni query, persistendo in database. Non fa parte della configurazione database.json ma è un layer applicativo sopra di esso.

ComponenteRuolo
assets/js/grid-preferences.jsModal drag-drop (SortableJS) per riordinare e nascondere colonne
api/user_preferences.phpGET: carica preferenze · POST: salva · DELETE: ripristina default
dbo.TimsUserGridPreferencesTabella DB con campi: user_id, codsoc, query_id, visible_fields (JSON), field_order (JSON)
includes/Auth.php → loadUserGridPreferences()Carica le preferenze al login e le rende disponibili al frontend
applyUserGridPrefs(queryId, columns)Funzione JS che filtra e riordina le colonne prima del rendering griglia
Scope: Le preferenze sono specifiche per combinazione utente + codSoc + queryId. Lo stesso utente può avere layout diversi su query diverse e su società diverse.
Ripristino: Il pulsante "Ripristina default" nel modal esegue una DELETE su api/user_preferences.php?query_id=X e ricarica la griglia con le colonne originali del database.json.

Checklist: aggiungere una nuova query

Quando devi inserire una nuova query nel database.json, segui questi passaggi:

1. Scegli il gruppo

Decidi in quale gruppo inserire la query (Magazzino, Anagrafiche, Ordini, Configurazione) oppure crea un nuovo gruppo con name, icon e description.

2. Definisci i campi base

{ "id": "nome_univoco_senza_spazi", "label": "Label mostrata in sidebar", "description": "Descrizione della query", "min_level": 0, // 0=tutti, 10=standard, 50=super, 90=admin "type": "simple" // oppure "master_detail" }

3. Scrivi la SQL

Per simple: un'unica proprietà "sql".

Per master_detail: master.sql e detail.sql (con @masterId).

Attenzione ai JOIN: Usa sempre il prefisso tabella per evitare colonne ambigue (es. OfOrdiniInTES.codSoc, non solo codSoc).

4. Configura i campi

editable_fields — solo i campi che l'utente può modificare.
readonly_fields — tutti gli altri campi visibili.
required_fields — sottoinsieme dei editabili, obbligatori per il salvataggio.
hidden_fields — (solo master_detail) campi nei dati ma non in griglia (ID, codSoc, Cancellato).
primary_key — necessaria se vuoi abilitare UPDATE. Stringa o array.

5. Gestisci il filtro codSoc

Se la tabella NON ha il campo codSoc, aggiungi "skip_codsoc_filter": true.

5b. (Opzionale) Insert rules

Se la query ha regole di validazione per la creazione di nuovi record, definisci insert_rules con le regole: required, at_least_one_true, conditional_required, duplicate_check, auto_fields. Vedi la sezione Insert rules.

6. Display options

"display_options": { "rows_per_page": 50, "allow_export": true, "show_record_count": true }

7. (Opzionale) Highlight fields

Solo per detail di master_detail. Definisci campo, condizione, valore e stile.

8. (Opzionale) Custom buttons

Aggiungi bottoni con azioni SQL o API call. Ricorda min_level e selection_mode.

9. Template completo (copia e incolla)

// === SIMPLE === { "id": "CAMBIO_ID", "label": "CAMBIO_LABEL", "description": "CAMBIO_DESCRIZIONE", "min_level": 0, "type": "simple", "sql": "SELECT campo1, campo2 FROM TABELLA WHERE Cancellato = 0", "primary_key": "campo1", "skip_codsoc_filter": false, "editable_fields": ["campo2"], "required_fields": [], "readonly_fields": ["campo1"], "display_options": { "rows_per_page": 50, "allow_export": true, "show_record_count": true } } // === MASTER_DETAIL === { "id": "CAMBIO_ID", "label": "CAMBIO_LABEL", "description": "CAMBIO_DESCRIZIONE", "min_level": 0, "type": "master_detail", "master": { "sql": "SELECT t.id, t.campo1 FROM TABELLA_TES t WHERE t.Cancellato = 0", "primary_key": "id", "hidden_fields": ["id", "codSoc"], "editable_fields": ["campo1"], "readonly_fields": [], "required_fields": ["campo1"] }, "detail": { "sql": "SELECT r.campo1, r.campo2 FROM TABELLA_RIG r WHERE r.idTes = @masterId", "join_field": "idTes", "hidden_fields": ["idTes"], "editable_fields": ["campo2"], "readonly_fields": ["campo1"], "required_fields": ["campo2"] }, "display_options": { "rows_per_page": 50, "allow_export": true, "show_record_count": true, "expand_icon": "+", "collapse_icon": "-", "edit_icon": "🔧" } }