Getting Started

Architettura

Layering di @pzeta/vue-foglidicalcolo — adapter Fortune Sheet, composables, services PostgREST, lock e ACL. Pattern Adapter per disaccoppiare il workbook engine.

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'IFortuneSheetAdapter senza 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

DO
  • Usa SpreadsheetEditor come orchestratore — gestisce mount, lock, autosave, panel ERP/versioni/ACL
  • Configura maxVersioni in 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
DON'T
  • Importare direttamente @fortune-sheet/core — usa FortuneSheetAdapter o le sue API esposte
  • Modificare lo snapshot JSONB lato applicazione (è opaco — usalo solo come blob)
  • Bypassare il LockService — sempre acquisisciLock prima di modifiche concorrenti-sensitive
  • Esporre tabelle ERP raw via PostgREST (sicurezza, schema instability)