Architettura
Layering
┌─────────────────────────────────────────────────────────────────────┐
│ COMPONENTI UI (Vue 3) │
│ │
│ SpreadsheetEditor FogliListView │
│ ├─ SpreadsheetToolbar ├─ FogliFilters │
│ ├─ SpreadsheetErpPanel ├─ FogliListItem (grid/list) │
│ ├─ SpreadsheetVersioniPanel├─ FogliCreateDialog │
│ ├─ SpreadsheetAclDialog └─ FogliShareDialog │
│ ├─ SpreadsheetLockIndicator │
│ └─ FortuneSheetWorkbook ← unico componente che importa │
│ @fortune-sheet/vue │
└──────────────────────────┬──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ COMPOSABLES (Vue 3 + state) │
│ │
│ useSpreadsheet useFogliList useFogliAcl │
│ useSpreadsheetERP useSpreadsheetVersioni │
│ useGlobalFilters usePzetaFogliOptions │
│ useTheme │
└──────────────────────────┬──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ ADAPTER (isolato da Fortune Sheet) │
│ │
│ FortuneSheetAdapter (implements IFortuneSheetAdapter) │
│ ├─ initialize / destroy │
│ ├─ loadSnapshot / getSnapshot │
│ ├─ getCellValue / setCellValue / setRange / setCellError │
│ ├─ undo / redo / canUndo / canRedo │
│ ├─ registerCustomFunctions (ERP-native) │
│ └─ onChange / offChange (debounced 500ms) │
└──────────────────────────┬──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ SERVICES (PostgREST + RPC) │
│ │
│ FogliPostgrestService — CRUD fogli, versioni, ACL, catalogo │
│ LockService — acquisisciLock/rilasciaLock/rinnovaLock │
│ ErpDataService — fetch dati ERP da viste PostgREST │
└─────────────────────────────────────────────────────────────────────┘
Adapter Pattern
Il punto critico dell'architettura: nessun componente né composable importa direttamente @fortune-sheet/core o @fortune-sheet/vue, eccetto FortuneSheetAdapter e FortuneSheetWorkbook.
Componenti / Composables
│
│ parla solo di:
│ - FortuneSheetSnapshot (Record<string, unknown> opaco)
│ - CellValue (string | number | boolean | null)
│ - SpreadsheetChangeEvent
▼
IFortuneSheetAdapter ◄── interfaccia
▲
│
FortuneSheetAdapter (implementazione)
│
└─► @fortune-sheet/core (dipendenza confinata)
Vantaggi:
- Test: si può mockare l'
IFortuneSheetAdaptersenza caricare l'engine pesante - Swappabilità: in futuro si può rimpiazzare Fortune Sheet con un altro engine senza toccare componenti/composables
- Tree-shaking: i path che non importano l'adapter non trascinano Fortune Sheet
State management
Lo stato del foglio è centralizzato in FoglioState (gestito da useSpreadsheet):
interface FoglioState {
idfolio: number | null
nome: string
permessoEffettivo: PermessoFoglio | null // 'proprietario' | 'scrittura' | 'lettura'
isDirty: boolean
isSaving: boolean
isLoading: boolean
lockInfo: LockInfo | null
filtriGlobali: GlobalFilters
ultimaRevisione: number | null
lastSavedAt: Date | null
}
I composables specializzati (useSpreadsheetERP, useGlobalFilters, useSpreadsheetVersioni, useFogliAcl) hanno il loro stato interno e comunicano via prop/event con SpreadsheetEditor.
Lock pessimistico
Il modello di concorrenza è lock pessimistico (Pessimistic Offline Lock):
1. Apertura foglio → useSpreadsheet.load(idfolio)
2. acquisisciLock() → RPC POST /rpc/acquisisci_lock_foglio
├─ successo === true:
│ └─ heartbeatInterval ogni 5 minuti → rinnovaLock()
└─ successo === false (LOCK_OCCUPIED):
└─ apri in modalità "view" forzata (read-only UI)
3. Chiusura editor (onUnmounted) → rilasciaLock()
Il DB applica un timeout (default 30 minuti). Il client lo rinnova ogni 5 minuti tramite heartbeat.
ACL — tre livelli
1. Proprietario → tutto (read/write/delete/share/eliminate)
2. Scrittura → read/write (no share, no delete)
3. Lettura → read-only (UI editor in mode='view')
Il permesso_effettivo è calcolato server-side dalla vista v_foglidicalcolo_lista (combinazione di idproprietario, righe in foglidicalcoloacl, ispubblico).
Versioning
Ogni salvataggio crea una nuova riga in foglidicalcoloversioni con numerorevisione incrementale e snapshot JSONB completo Fortune Sheet. Il numero massimo di versioni è configurabile (maxVersioni, default 10).
salva() → numeroRevisioneNuovo = (state.ultimaRevisione ?? 0) + 1
→ INSERT INTO foglidicalcoloversioni (..., snapshot, numerorevisione)
→ state.ultimaRevisione = numeroRevisioneNuovo
→ state.isDirty = false
Ripristino → RPC ripristina_versione_foglio(p_idversione) clona lo snapshot come nuova versione corrente.
Sorgenti ERP
Le sorgenti ERP sono viste PostgREST registrate nel catalogo foglidicalcolosorgenticatalogo con:
codice— identificativo univoco (es.'fatture_riepilogo')endpoint— path PostgREST (es./v_fatture_riepilogo)parametri— schema dei filtri dichiarato come JSON
Un foglio ha 0..N DataSourceConfig che riferiscono il catalogo + cella destinazione + filtri specifici + flag usaFiltriGlobali per ereditare i filtri globali del foglio (periodo, idcliente, ...).
foglio.sorgentierp[]:
├─ { codice: 'fatture_riepilogo', cellaDestinazione: 'A1', filtri: {...}, usaFiltriGlobali: true }
├─ { codice: 'ordini_riepilogo', cellaDestinazione: 'A50', filtri: {...} }
└─ ...
useSpreadsheetERP.refreshAll() fa fetch di tutte le sorgenti in parallelo via ErpDataService.fetchSorgente() e scrive i risultati nelle celle target tramite adapter.setRange().
Schema postgrest
Header standard: Accept-Profile: foglidicalcolo
Content-Profile: foglidicalcolo
Vista lista: GET /v_foglidicalcolo_lista?...
Vista ricerca: GET /v_foglidicalcolo_ricerca?tsv_search=plfts(italian).{q}
Detail: GET /v_foglidicalcolo_dettaglio?idfolio=eq.{id}
Crea: POST /foglidicalcolo
Aggiorna: PATCH /foglidicalcolo?idfolio=eq.{id}
Versioni: GET /foglidicalcoloversioni?idfolio=eq.{id}&order=numerorevisione.desc
Snapshot: GET /foglidicalcoloversioni?idversione=eq.{id}&select=snapshot
Salva versione: POST /foglidicalcoloversioni
ACL: GET/POST/PATCH/DELETE /foglidicalcoloacl
Catalogo: GET /foglidicalcolosorgenticatalogo?isattivo=eq.true&order=ordinamento
RPC lock: POST /rpc/acquisisci_lock_foglio
RPC duplica: POST /rpc/duplica_foglio
RPC ripristina: POST /rpc/ripristina_versione_foglio
Vincoli e best practice
- Usa
SpreadsheetEditorcome orchestratore — gestisce mount, lock, autosave, panel ERP/versioni/ACL - Configura
maxVersioniin base allo storage disponibile (snapshot JSONB possono essere grandi) - Esponi viste PostgREST tipizzate per ogni sorgente ERP, non tabelle dirette
- Usa filtri globali (
GlobalFilters) per sorgenti omogenee temporalmente
- Importare direttamente
@fortune-sheet/core— usaFortuneSheetAdaptero le sue API esposte - Modificare lo snapshot JSONB lato applicazione (è opaco — usalo solo come blob)
- Bypassare il
LockService— sempreacquisisciLockprima di modifiche concorrenti-sensitive - Esporre tabelle ERP raw via PostgREST (sicurezza, schema instability)
Vue FogliDiCalcolo
Plugin Vue 3 per fogli di calcolo enterprise basato su Fortune Sheet, con integrazione dati ERP via PostgREST, versioning JSONB, lock pessimistico e ACL per utente.
ERP via PostgREST
Configurazione del catalogo sorgenti ERP, query string PostgREST, filtri globali e specifici, refresh automatico, mapping cella destinazione.