LockService
Costruttore
class LockService {
constructor(private readonly baseUrl: string)
}
Service per lock pessimistico sui fogli. Quattro operazioni, tre RPC e una read.
API
acquisisciLock(idfolio)
async acquisisciLock(idfolio: number): Promise<AcquisisciLockResult>
RPC POST /rpc/acquisisci_lock_foglio con { p_idfolio }. Acquisisce un lock di 30 minuti (durata gestita server-side).
interface AcquisisciLockResult {
successo: boolean
idutente_lock?: number // se successo === true
datalock?: string // ISO 8601
scadenzalock?: string // ISO 8601, ~30 min nel futuro
errore?: 'LOCK_OCCUPIED' // se successo === false
}
Comportamento:
- Se nessun lock attivo → crea il lock per
current_user, ritornasuccesso: true - Se lock esistente di altro utente → ritorna
{ successo: false, errore: 'LOCK_OCCUPIED' } - Se lock esistente dello stesso utente → estende la scadenza (idempotente)
- Se lock scaduto (
scadenzalock < now()) → sovrascrive silenziosamente
rilasciaLock(idfolio)
async rilasciaLock(idfolio: number): Promise<boolean>
RPC POST /rpc/rilascia_lock_foglio con { p_idfolio }. Elimina la riga lock se l'utente corrente è owner.
Side-effects:
- Se
current_user === idutente_lock→ DELETE lock - Altrimenti → no-op (silent fail per evitare error leak)
Ritorna sempre true se la chiamata HTTP è riuscita.
rinnovaLock(idfolio)
async rinnovaLock(idfolio: number): Promise<string>
RPC POST /rpc/rinnova_lock_foglio con { p_idfolio }. Estende scadenzalock a now() + 30 min. Usato come heartbeat ogni 5 minuti dal composable useSpreadsheet.
Ritorna la nuova scadenzalock (ISO 8601 string).
Lato server, accetta solo se current_user === idutente_lock.
leggiStatoLock(idfolio)
async leggiStatoLock(idfolio: number): Promise<LockInfo>
GET sulla vista v_foglidicalcolo_lista filtrata su idfolio=eq.{id}. Ritorna lo stato corrente lock mappato in camelCase:
interface LockInfo {
idfolio: number
idutenteLock: number | null // ← mappato da idutente_lock
datalock: string | null
scadenzalock: string | null
islocked: boolean // true se lock attivo non scaduto
}
Usato per polling manuale o per leggere lo stato senza tentare di acquisirlo.
Heartbeat pattern
// In useSpreadsheet
let heartbeatInterval: ReturnType<typeof setInterval> | null = null
async function startHeartbeat(idfolio: number) {
heartbeatInterval = setInterval(async () => {
try {
const nuovaScadenza = await lockService.rinnovaLock(idfolio)
// aggiorna lockInfo.scadenzalock localmente
} catch (err) {
// lock perso (es. invalidato da admin)
stopHeartbeat()
}
}, 5 * 60 * 1000) // ogni 5 min
}
function stopHeartbeat() {
if (heartbeatInterval) {
clearInterval(heartbeatInterval)
heartbeatInterval = null
}
}
Logica islocked
Il campo islocked in v_foglidicalcolo_lista è calcolato server-side come:
CASE
WHEN scadenzalock IS NULL THEN false
WHEN scadenzalock < now() THEN false
WHEN idutente_lock = current_user_id() THEN false -- vediamo "no lock esterno"
ELSE true
END
islocked === false, chi non lo possiede vede islocked === true. Combinato con idutente_lock non-null, questo permette di distinguere "ho io il lock" (lockInfo !== null && !islocked) da "lock di altri" (islocked === true).Esempio: acquisizione con fallback
import { LockService } from '@pzeta/vue-foglidicalcolo'
const lockSvc = new LockService('https://api.example.com/rest')
const result = await lockSvc.acquisisciLock(42)
if (result.successo) {
console.log(`Lock acquisito, scade ${result.scadenzalock}`)
// ... apri editor in modalità edit
} else if (result.errore === 'LOCK_OCCUPIED') {
console.warn('Foglio bloccato da altro utente')
const stato = await lockSvc.leggiStatoLock(42)
console.log(`Owner: utente ${stato.idutenteLock}, scade ${stato.scadenzalock}`)
// ... apri editor in modalità view forzata
}
Pulizia lock orfani
I lock vengono auto-puliti dalla logica scadenzalock < now() lato DB. Lock fantasma (es. utente chiuso bruscamente, heartbeat interrotto) scadono entro 30 minuti. Il prossimo acquisisciLock di un altro utente li sovrascrive silenziosamente.
forza_rilascio_lock) — la libreria non la include perché richiede autorizzazione speciale.Tipi correlati
LockInfoAcquisisciLockResultuseSpreadsheet— consumatore del lock con heartbeatSpreadsheetLockIndicator— UI dello stato lock
FortuneSheetAdapter
Adapter che incapsula @fortune-sheet/core. Lifecycle initialize/destroy, snapshot I/O, API celle, undo/redo, funzioni ERP-native, eventi.
ERP functions (formule)
Placeholder per funzioni formula ERP-native registrabili nel motore Fortune Sheet (=ERP.FATTURATO, =ERP.ORDINI, ...). Da implementare nel consumer.