ERP via PostgREST
Catalogo sorgenti
Una sorgente ERP è una vista PostgREST tipizzata registrata nella tabella foglidicalcolosorgenticatalogo:
INSERT INTO foglidicalcolo.foglidicalcolosorgenticatalogo (
codice, nome, descrizione, endpoint, parametri, isattivo, ordinamento
) VALUES (
'fatture_riepilogo',
'Riepilogo fatture',
'Fatture per periodo, tipologia operazione, cliente',
'/v_fatture_riepilogo',
'{
"tipooperazione": { "tipo": "select", "label": "Tipo operazione",
"options": [{"value":"V","label":"Vendita"}, {"value":"A","label":"Acquisto"}] },
"periodo": { "tipo": "daterange", "label": "Periodo", "required": false },
"idcliente": { "tipo": "integer", "label": "Cliente" }
}'::jsonb,
true,
10
);
La libreria carica il catalogo via FogliPostgrestService.catalogo() la prima volta che il pannello ERP si apre (cache in useSpreadsheetERP.catalogo).
DataSourceConfig nel foglio
Ogni foglio dichiara N sorgenti attive in foglidicalcolo.sorgentierp (campo JSONB):
interface DataSourceConfig {
codice: string // riferisce SorgenteCatalogo.codice
cellaDestinazione: string // es. "A1", "C5"
sheetIndex: number // tab Fortune Sheet (0-based)
filtri: Record<string, unknown> // filtri specifici di questa istanza
usaFiltriGlobali: boolean // ereditare dai filtri globali del foglio?
limitRighe?: number // default 100
}
Esempio:
const sorgenti: DataSourceConfig[] = [
{
codice: 'fatture_riepilogo',
cellaDestinazione: 'A1',
sheetIndex: 0,
filtri: { tipooperazione: 'V' },
usaFiltriGlobali: true, // eredita periodo + idcliente
limitRighe: 500,
},
{
codice: 'ordini_riepilogo',
cellaDestinazione: 'A1',
sheetIndex: 1, // tab "Ordini"
filtri: {},
usaFiltriGlobali: true,
limitRighe: 500,
},
]
Filtri globali
I GlobalFilters del foglio sono persistiti nel campo filtriglobali JSONB e applicati a tutte le sorgenti con usaFiltriGlobali: true.
interface GlobalFilters {
periodo?: { da: string; a: string } // ISO 8601, es. "2026-01-01"
idcliente?: number | null
idreparto?: number | null
testoLibero?: string
[key: string]: unknown // estendibile
}
Il composable useGlobalFilters autosalva i cambi nel DB (debounced 1000ms) e useSpreadsheetERP fa refresh debounced 500ms quando i filtri cambiano.
Query string PostgREST generata
ErpDataService.fetchSorgente() costruisce la query string combinando filtri specifici e globali. Mapping:
| Chiave input | Output query string |
|---|---|
dataDa (da periodo.da se usaFiltriGlobali) | datadocumento=gte.{val} |
dataA (da periodo.a) | datadocumento=lte.{val} |
| altre chiavi | {key}=eq.{val} |
limit | escluso dalla query string (usa header Range) |
null / undefined | escluse |
Esempio risolto:
config.filtri: { tipooperazione: 'V' }
filtriGlobali: { periodo: { da: '2026-01-01', a: '2026-12-31' }, idcliente: 42 }
usaFiltriGlobali: true
GET /v_fatture_riepilogo?tipooperazione=eq.V&datadocumento=gte.2026-01-01&datadocumento=lte.2026-12-31&idcliente=eq.42
Headers: Range: 0-499, Prefer: count=exact
Paginazione e count
ErpDataService usa headers PostgREST per gestire paginazione e conteggio totale:
Range: 0-{limitRighe - 1}
Prefer: count=exact
Risposta:
Content-Range: 0-499/4231
→ totaleRighe = 4231, righe.length = 500. Il consumer (useSpreadsheetERP) memorizza il totale in ErpDataSource.totaleRighe.
Scrittura nelle celle
Dopo il fetch, useSpreadsheetERP chiama l'adapter per scrivere i risultati:
// pseudo-code interno
const range: CellValue[][] = righe.map((row) =>
Object.values(row) as CellValue[]
)
adapter.setRange(startRow, startCol, range, sheetIndex)
cellaDestinazione (es. "C5") viene convertita in (row, col) zero-based.
Refresh
Tre meccanismi attivano un refresh:
| Trigger | Cosa fa |
|---|---|
refreshAll() esplicito (es. click toolbar) | Fetch in parallelo di tutte le sorgenti |
Cambio filtriGlobali (watcher reattivo) | debouncedRefreshAll (500ms) |
refreshInterval impostato (es. 60s) | setInterval(refreshAll, interval * 1000) |
In caso di errore, l'errore va in useSpreadsheetERP.errors[codice] e ErpDataSource.error. Le altre sorgenti continuano comunque.
Esempio end-to-end
<script setup lang="ts">
import { ref } from 'vue'
import type { ErpRefreshEvent, SpreadsheetSaveEvent } from '@pzeta/vue-foglidicalcolo'
const foglioId = ref<number>(42)
function onErpRefresh(event: ErpRefreshEvent) {
console.log('Sorgenti ricaricate:', event.sources)
console.log('Errori:', event.errors)
}
function onSave(event: SpreadsheetSaveEvent) {
console.log(`Salvata rev. ${event.revisione}`)
}
</script>
<template>
<SpreadsheetEditor
:foglio-id="foglioId"
height="700px"
show-erp-panel
show-global-filters
:refresh-interval="60"
@erp-refresh="onErpRefresh"
@save="onSave"
/>
</template>
Funzioni ERP-native
In aggiunta al fetch via panel, l'adapter può registrare funzioni formula ERP nel motore Fortune Sheet:
=ERP.FATTURATO("V"; "2026-01-01"; "2026-12-31"; 42)
=ERP.ORDINI(42; "2026-01-01"; "2026-12-31")
=ERP.SCADENZE("2026-01-01"; "2026-01-31")
Vedi erp-functions. Le funzioni di default lanciano Not implemented — il consumer deve fornire l'implementazione concreta che chiama ErpDataService.
Architettura
Layering di @pzeta/vue-foglidicalcolo — adapter Fortune Sheet, composables, services PostgREST, lock e ACL. Pattern Adapter per disaccoppiare il workbook engine.
Installazione
Installazione di @pzeta/vue-foglidicalcolo, peer dependencies, registrazione del plugin PzetaFogliDiCalcolo e opzioni di configurazione.