Vai al contenuto

Fase 2 - Specifica tecnica catalogo moduli

Data audit: 2026-06-30

Documenti collegati:

  • docs/processo/inventario_fase1_catalogo_moduli_tenant.md
  • docs/processo/fase1b_catalogo_moduli_business.md

Scopo

Questa fase definisce il modello tecnico target per rendere i moduli SafeOps attivabili per tenant con menu, sottomenu, feature, profili, regole, quantita, scadenze e costi.

Il documento e' una specifica. Non contiene codice applicativo e non modifica il runtime.

Obiettivi tecnici

  1. Introdurre un catalogo moduli canonico.
  2. Collegare ogni modulo a menu, feature, profili, dipendenze, piani e listini.
  3. Mantenere compatibilita con TenantModuleEntitlement, TenantFeatureFlag, TenantRoleProfile, ServiceCatalog e CommercialeListino*.
  4. Evitare una migrazione big-bang del menu.
  5. Separare modulo tenant, servizio commerciale e servizio venduto a cliente finale.
  6. Rendere espliciti limiti e stati.
  7. Preparare un enforcement uniforme su menu, route, API e job.

Principio di rollout

Il nuovo catalogo deve partire come fonte descrittiva e di orchestrazione, non come sostituzione immediata.

Sequenza consigliata:

  1. creare catalogo e mapping;
  2. popolare seed iniziali;
  3. leggere catalogo in parallelo agli entitlement esistenti;
  4. generare entitlement legacy dalle subscription;
  5. spostare gradualmente i menu da hardcoded a dichiarativi;
  6. attivare enforcement unificato solo modulo per modulo.

Modello dati target

module_catalog

Anagrafica canonica dei moduli.

Campo Tipo logico Note
id integer PK
module_code string Unico, es. safe_ras, core_documentale
legacy_module_code string nullable Es. ras, documentale, commerciale
name string Nome business
description text nullable Descrizione
module_type enum/string core, sellable, addon, integration, internal
category string Es. platform, operations, safecondo, training
owner string nullable Owner funzionale
is_active boolean Visibile/gestibile nel catalogo
is_tenant_visible boolean Visibile ai tenant admin
is_sellable boolean Usabile in offerte/piani
requires_subscription boolean Richiede subscription tenant
default_status string active, disabled, trial
sort_order integer Ordinamento
metadata_json json/text Estensioni
created_at datetime Audit
updated_at datetime Audit

Vincoli:

  • unique su module_code;
  • indice su legacy_module_code;
  • indice su module_type, is_active, is_sellable.

module_dependency

Dipendenze tra moduli.

Campo Tipo logico Note
id integer PK
module_code string Modulo dipendente
depends_on_module_code string Modulo richiesto
dependency_type string required, optional, recommended, addon_parent
enforcement string warn, block_activation, block_runtime
notes text nullable Note
created_at datetime Audit
updated_at datetime Audit

Vincoli:

  • unique su (module_code, depends_on_module_code, dependency_type);
  • indici su module_code, depends_on_module_code.

module_menu_item

Menu dichiarativi per modulo.

Campo Tipo logico Note
id integer PK
module_code string Modulo owner
parent_key string nullable Chiave menu padre
menu_key string Chiave stabile, non label
label string Testo menu
category_label string nullable Compatibilita FAB/menu attuale
href string nullable URL
endpoint string nullable Endpoint Flask/FAB
icon string nullable Icona
sort_order integer Ordinamento
required_feature_code string nullable Feature richiesta
required_profile_code string nullable Profilo richiesto
required_permission string nullable Permesso/FAB action
min_status string Es. active, readonly
readonly_behavior string show, hide, disable_writes
is_active boolean Menu attivo
metadata_json json/text Estensioni
created_at datetime Audit
updated_at datetime Audit

Vincoli:

  • unique su menu_key;
  • indice su module_code;
  • indice su parent_key;
  • indice su required_feature_code.

Nota: menu_key deve diventare la fonte stabile. Le label devono essere solo presentazione.

module_feature

Associazione tra modulo e feature flag.

Campo Tipo logico Note
id integer PK
module_code string Modulo
feature_code string Es. feature.ras.extrabit_zip_export
name string Nome leggibile
description text nullable Descrizione
default_enabled boolean Default catalogo
included_in_plan_code string nullable Piano minimo
is_billable_addon boolean Feature fatturabile separatamente
created_at datetime Audit
updated_at datetime Audit

Vincoli:

  • unique su (module_code, feature_code);
  • indice su feature_code.

Runtime:

  • la tabella non sostituisce subito TenantFeatureFlag;
  • definisce a quale modulo appartiene una feature;
  • TenantFeatureFlag resta la decisione tenant-specific.

module_profile

Profili suggeriti o richiesti da un modulo.

Campo Tipo logico Note
id integer PK
module_code string Modulo
profile_code string Profilo
profile_role string admin, operator, readonly, external, technical
default_enabled boolean Abilitato di default quando il modulo e' attivo
default_readonly boolean Readonly di default
created_at datetime Audit
updated_at datetime Audit

Vincoli:

  • unique su (module_code, profile_code);
  • indice su profile_code.

Adapter:

  • legge/scrive TenantRoleProfile;
  • genera default per TenantRoleProfileEntitlement quando si attiva un modulo.

module_plan

Piani commerciali associati a un modulo.

Campo Tipo logico Note
id integer PK
module_code string Modulo
plan_code string trial, base, pro, enterprise, addon_*
name string Nome piano
description text nullable Descrizione
billing_cycle string monthly, yearly, one_time, usage
is_active boolean Piano attivo
is_trial boolean Trial
trial_days integer nullable Durata trial
default_currency string Es. EUR
sort_order integer Ordinamento
metadata_json json/text Estensioni
created_at datetime Audit
updated_at datetime Audit

Vincoli:

  • unique su (module_code, plan_code);
  • indice su module_code, is_active.

module_price

Prezzi base per modulo/piano. Non sostituisce subito i listini verticali RAS.

Campo Tipo logico Note
id integer PK
module_code string Modulo
plan_code string Piano
price_code string Codice prezzo
amount decimal Importo
currency string EUR
billing_cycle string monthly, yearly, one_time, usage
valid_from date nullable Validita
valid_to date nullable Validita
is_active boolean Stato
external_price_ref string nullable Ponte verso listino esterno
metadata_json json/text Estensioni
created_at datetime Audit
updated_at datetime Audit

Vincoli:

  • unique su (module_code, plan_code, price_code);
  • indice su validita e stato.

module_service_mapping

Mapping tra catalogo moduli e service code legacy/canonici.

Campo Tipo logico Note
id integer PK
module_code string Modulo
plan_code string nullable Piano
canonical_service_code string Es. SAFE_RAS_REDAZIONE
legacy_service_code string opzionale Es. RAS_CONDOMINIO_RED; DB normalizzato a stringa vuota per unicita MySQL
source_system string service_catalog, commerciale_listino, manual, legacy
source_ref string nullable ID o codice sorgente
workflow_type string nullable Es. ras_quote, dvr
is_active boolean Stato
created_at datetime Audit
updated_at datetime Audit

Vincoli:

  • unique su (canonical_service_code, legacy_service_code, source_system);
  • indice su module_code;
  • indice su legacy_service_code.

Nota MySQL: legacy_service_code e' opzionale a livello logico, ma nello schema fisico non deve essere NULL quando partecipa al vincolo unique. Usare stringa vuota come valore normalizzato.

tenant_module_subscription

Subscription tenant al modulo/piano. Questa e' la fonte commerciale-operativa futura.

Campo Tipo logico Note
id integer PK
tenant_key string Tenant
module_code string Modulo canonico
plan_code string nullable Piano
status string trial, active, readonly, suspended, expired, disabled
enabled boolean Flag rapido
valid_from datetime nullable Decorrenza
valid_to datetime nullable Scadenza
grace_until datetime nullable Periodo di grazia
billing_cycle string nullable Ciclo
agreed_price decimal nullable Prezzo concordato
currency string EUR
auto_renew boolean Rinnovo automatico
source string manual, service_catalog, commerciale, migration, trial
source_ref string nullable ID legacy o contratto
notes text nullable Note
created_by string nullable Audit
updated_by string nullable Audit
created_at datetime Audit
updated_at datetime Audit

Vincoli:

  • unique consigliato su (tenant_key, module_code) per la prima versione;
  • se in futuro servono piu piani contemporanei, passare a unique su (tenant_key, module_code, plan_code, source_ref);
  • indice su tenant_key, module_code, status, valid_to.

tenant_module_limit

Limiti quantitativi per tenant/modulo.

Campo Tipo logico Note
id integer PK
tenant_key string Tenant
module_code string Modulo
limit_key string Es. users.max, storage.bytes
limit_value decimal/string Valore
unit string nullable count, bytes, monthly, days
scope string tenant, user, cliente, module
scope_ref string opzionale ID utente/cliente se scoped; DB normalizzato a stringa vuota per unicita MySQL
valid_from datetime nullable Validita
valid_to datetime nullable Validita
source string plan, override, manual, legacy
is_active boolean Stato
notes text nullable Note
created_at datetime Audit
updated_at datetime Audit

Vincoli:

  • unique su (tenant_key, module_code, limit_key, scope, scope_ref);
  • indice su tenant_key, module_code, limit_key.

Nota MySQL: scope_ref e' opzionale a livello logico, ma nello schema fisico non deve essere NULL quando partecipa al vincolo unique. Usare stringa vuota come valore normalizzato.

tenant_module_rule

Regole configurabili per tenant/modulo.

Campo Tipo logico Note
id integer PK
tenant_key string Tenant
module_code string Modulo
rule_key string Codice regola
rule_type string boolean, threshold, workflow, pricing, visibility, integration
config_json json/text Configurazione validata
status string active, disabled, readonly
priority integer Precedenza
valid_from datetime nullable Validita
valid_to datetime nullable Validita
notes text nullable Note
created_at datetime Audit
updated_at datetime Audit

Vincoli:

  • unique su (tenant_key, module_code, rule_key);
  • indice su tenant_key, module_code, status.

tenant_module_activation_event

Registro eventi attivazione/modifica.

Campo Tipo logico Note
id integer PK
tenant_key string Tenant
module_code string Modulo
event_type string created, activated, suspended, expired, renewed, plan_changed, limit_changed
old_status string nullable Stato precedente
new_status string nullable Stato nuovo
payload_json json/text Dettaglio evento
created_by string nullable Utente/processo
created_at datetime Audit

Indici:

  • tenant_key, module_code, event_type, created_at.

Adapter verso tabelle esistenti

Adapter TenantModuleEntitlement

Responsabilita:

  • mantenere il runtime compatibile con is_module_enabled;
  • generare o aggiornare entitlement legacy quando cambia tenant_module_subscription;
  • leggere gli entitlement esistenti durante la transizione.

Regola proposta:

Subscription Entitlement legacy
active enabled=True, status=active
trial enabled=True, status=trial
readonly enabled=True, status=readonly
suspended enabled=False, status=suspended
expired oltre grace enabled=False, status=expired
disabled enabled=False, status=disabled

Nota critica: eliminare progressivamente i commit impliciti nelle letture di stato. La lettura non deve creare righe in produzione; i seed devono essere job espliciti.

Adapter TenantFeatureFlag

Responsabilita:

  • associare feature al modulo tramite module_feature;
  • applicare default di piano;
  • mantenere override tenant-specific.

Precedenza proposta:

  1. override esplicito in TenantFeatureFlag;
  2. piano attivo su tenant_module_subscription;
  3. default module_feature;
  4. default globale disabilitato.

Adapter profili

Responsabilita:

  • associare profili a moduli tramite module_profile;
  • attivare profili tenant consigliati quando si attiva un modulo;
  • non rimuovere automaticamente profili utente gia assegnati senza preview.

Precedenza proposta:

  1. assegnazione utente esplicita;
  2. entitlement profilo tenant;
  3. default modulo/piano;
  4. readonly globale se subscription readonly.

Adapter ServiceCatalog

Responsabilita:

  • ponte verso catalogo prezzi semplice;
  • supportare attivazioni tenant gia esistenti;
  • non diventare subito il modello completo dei moduli.

Mapping consigliato:

  • ServiceCatalog.code -> module_service_mapping.canonical_service_code o legacy_service_code;
  • TenantServiceSubscription -> tenant_module_subscription quando il servizio rappresenta un modulo/piano tenant;
  • mantenere TenantServiceSubscription per servizi non ancora migrati.

Adapter CommercialeListino*

Responsabilita:

  • lasciare i listini RAS verticali dove sono;
  • collegare i service code RAS al catalogo tramite module_service_mapping;
  • non generalizzare subito il motore listini RAS a tutti i moduli.

Mapping iniziale:

Legacy Canonico Modulo
AMM_PORTALE_RAS SAFE_RAS_ADMIN_PORTAL safe_admin_portal / safe_ras
AMM_RAS_SCAD_DOC SAFE_RAS_ADMIN_DOC_EXPIRY safe_admin_portal / safe_ras
RAS_CONDOMINIO SAFE_RAS_CONDOMINIO safe_ras
RAS_CONDOMINIO_RED SAFE_RAS_REDAZIONE safe_ras
RAS_CONDOMINIO_RIAL SAFE_RAS_RIALLINEAMENTO safe_ras

Adapter menu runtime

Responsabilita:

  • generare menu da module_menu_item per i moduli migrati;
  • lasciare menu hardcoded per i moduli non migrati;
  • usare menu_key stabile, non label testuali, come fonte di policy;
  • mantenere compatibilita temporanea con policy label-based.

Fasi:

  1. shadow mode: calcolare menu dichiarativo e confrontarlo con menu reale;
  2. hybrid mode: alcuni moduli leggono da catalogo;
  3. catalog mode: modulo completamente dichiarativo;
  4. cleanup: rimuovere condizioni hardcoded per il modulo migrato.

Enforcement target

Ogni accesso a modulo/route dovrebbe valutare:

  1. tenant valido;
  2. subscription modulo valida;
  3. entitlement legacy coerente;
  4. feature richiesta attiva;
  5. profilo utente compatibile;
  6. permission FAB/route compatibile;
  7. stato non scaduto;
  8. readonly rispettato;
  9. limiti quantitativi rispettati;
  10. dipendenze runtime soddisfatte.

Il menu puo nascondere voci, ma non deve essere l'unico controllo.

Stati canonici

Stato Significato runtime Note
trial Abilitato con scadenza Limiti bassi e data obbligatoria
active Abilitato pieno Stato normale
readonly Lettura consentita, scritture bloccate Deve valere anche per API/import/job
suspended Accesso bloccato temporaneamente Recuperabile
expired Scaduto Puo avere grace in readonly
disabled Disabilitato Stato amministrativo

Limiti canonici iniziali

Limit key Tipo Moduli candidati
users.max count tutti
storage.bytes bytes documentale, tecnici, mailops
documents.max count documentale, RAS, DVR
exports.monthly count/month documentale, RAS
practices.monthly count/month RAS, DVR, tecnici
tickets.monthly count/month ticketing
courses.active count formazione
students.active count formazione
integrations.enabled count/list OSM, WooCommerce, SSO

Seed iniziale consigliato

module_catalog

module_code legacy type
core_anagrafiche anagrafiche core
core_documentale documentale sellable o core
core_catalogo_servizi service_catalog internal
safe_ras ras sellable
safe_commerciale commerciale sellable
safe_formazione formazione sellable
safe_tecnici tecnici sellable
safe_interventi interventi internal o alias di safe_tecnici
safe_ticketing tickets sellable
safe_calendar calendar addon
safe_file_space tecnico_file_space addon
safe_mailops mailops addon
safe_dvr dvr addon
safe_compila safecondo_compila addon
safe_admin_portal amministratore_condominio addon
integration_osm osm integration

Nota: safe_interventi puo essere evitato come modulo pubblico e trattato come legacy alias di safe_tecnici.

Piano migrazione concettuale

Step 1 - Tabelle e seed

  • creare tabelle catalogo;
  • seed moduli;
  • seed dipendenze;
  • seed mapping service code;
  • nessun cambio runtime.

Step 2 - Vista controllo

  • nuova vista control-plane "Catalogo Moduli";
  • vista "Attivazioni Moduli";
  • lettura combinata catalogo + entitlement + service subscription;
  • solo diagnostica o edit limitato.

Step 3 - Sync verso entitlement

  • se si attiva una subscription, aggiornare TenantModuleEntitlement;
  • non leggere ancora dal nuovo catalogo per autorizzare tutto;
  • logging eventi in tenant_module_activation_event.

Step 4 - Feature e profili

  • collegare feature a moduli;
  • applicare default feature per piano;
  • suggerire profili da abilitare;
  • preview prima di modificare assegnazioni utente.

Step 5 - Menu shadow

  • calcolare menu dichiarativo in parallelo;
  • confrontare con menu effettivo;
  • produrre report differenze per tenant/profilo.

Step 6 - Primo modulo migrato

Modulo candidato: safe_file_space o safe_mailops, per blast radius ridotto.

Motivo:

  • menu limitato;
  • dipendenze chiare;
  • limiti semplici;
  • minore rischio rispetto a RAS/commerciale.

Step 7 - Moduli complessi

Migrare dopo:

  • core_documentale;
  • safe_tecnici;
  • safe_formazione;
  • safe_commerciale;
  • safe_ras.

RAS va lasciato dopo commerciale/documentale perche' dipende da entrambi.

Invarianti da rispettare

  1. Nessuna lettura deve creare righe o fare commit.
  2. I service code legacy devono restare validi finche esistono offerte/contratti/documenti che li usano.
  3. La disattivazione menu non equivale a blocco autorizzativo.
  4. Il readonly deve essere valutato anche sulle azioni POST/API/job.
  5. Le dipendenze devono bloccare nuove attivazioni incoerenti, ma non rompere tenant legacy senza migrazione guidata.
  6. I moduli core non devono essere disattivabili accidentalmente da pannelli commerciali.
  7. Ogni sync automatico deve scrivere un evento audit.

Test e verifiche richieste in fase implementativa

Quando si passera al codice, servono test su:

  • attivazione modulo -> entitlement legacy aggiornato;
  • scadenza subscription -> stato expired o readonly con grace;
  • feature inclusa nel piano -> feature flag effettiva;
  • override feature tenant -> prevale sul default piano;
  • profilo abilitato da modulo -> visibilita menu coerente;
  • menu nascosto -> route diretta negata;
  • readonly -> POST/API/job bloccati;
  • limite superato -> operazione negata con messaggio chiaro;
  • mapping legacy service code -> canonico;
  • shadow menu -> differenze tracciate.

Decisioni ancora aperte

  1. core_documentale deve essere vendibile o core incluso in tutti i piani?
  2. safe_interventi deve esistere come modulo separato o essere alias interno di safe_tecnici?
  3. Il primo unique su tenant_module_subscription deve consentire un solo piano per modulo/tenant?
  4. ServiceCatalog deve diventare solo adapter o essere evoluto nel catalogo prezzi generale?
  5. Quale modulo e' il primo candidato per migrazione menu dichiarativa?
  6. Quanto deve durare la compatibilita label-based delle policy menu?

Conclusione fase 2

La direzione consigliata e' introdurre un catalogo moduli come livello canonico sopra le tabelle attuali. Le tabelle esistenti non vanno eliminate: vanno collegate tramite adapter e poi migrate gradualmente.

Il primo risultato tecnico utile non e' riscrivere RAS o commerciale, ma creare una fonte unica che dica cosa e' un modulo, quali menu possiede, quali feature/profili richiede, quali piani lo vendono e quali limiti lo governano.