@primocaredentgroup/customer-accounts
v0.4.0
Published
Convex component per la gestione clienti esterni del laboratorio — PrimoLabcore
Downloads
0
Readme
@primocaredentgroup/customer-accounts
Componente Convex riusabile per l'anagrafica clienti del laboratorio: account, contatti, sedi, profili operativi, note, task, attività e richieste dal portale.
Prerequisiti
- Node.js 20+
- Convex
^1.43.0 - accesso npm a
@primocaredentgroup/migration-kit@^2.2.4, peer richiesto dal component source - un provider di autenticazione configurato nell'app host
Installazione
npm install @primocaredentgroup/customer-accountsRegistra il componente mantenendo l'handle canonico customerAccounts:
// convex/convex.config.ts
import { defineApp } from "convex/server";
import customerAccounts from "@primocaredentgroup/customer-accounts/convex.config.js";
const app = defineApp();
app.use(customerAccounts);
export default app;Le capability di migrazione dichiarano componentName: "customerAccounts".
Un alias di mount differente richiede supporto esplicito del migration kit.
Boundary host autenticato
Il browser non deve chiamare riferimenti components.customerAccounts e non
deve fornire userId, authSubject, email o ruoli per autorizzarsi. L'host
deriva l'identità da ctx.auth, applica il proprio RBAC e usa exposeApi come
unico boundary pubblico per le operazioni CRM dello staff.
// convex/customerAccounts.ts
import {
CustomerAccountsClient,
exposeApi,
} from "@primocaredentgroup/customer-accounts";
import { components } from "./_generated/api.js";
export const customerAccountsClientForActor = (tokenIdentifier: string) =>
new CustomerAccountsClient(components.customerAccounts, {
callerTokenIdentifier: tokenIdentifier,
});
export const {
createAccount,
updateAccount,
getAccountByCustomerCode,
paginateAccounts,
addAccountContact,
addAccountLocation,
getAccountOverview,
// ...esporta soltanto le funzioni richieste dall'app
} = exposeApi(components.customerAccounts, {
getActorContext: async (ctx, permission) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Autenticazione richiesta");
await authorizeCustomerAccountsPermission(identity, permission);
return { tokenIdentifier: identity.tokenIdentifier };
},
});exposeApi espone intenzionalmente solo l'API CRM dello staff. Le operazioni
portale restano nel client backend perché l'associazione utente → account è
proprietà dell'host. Prima di chiamarle, l'host deve risolvere server-side
l'accountId consentito e creare il client con il tokenIdentifier corrente.
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Autenticazione richiesta");
const accountId = await resolvePortalAccountForIdentity(ctx, identity);
const client = customerAccountsClientForActor(identity.tokenIdentifier);
return await client.portal.createOrderRequest(ctx, {
accountId,
title: args.title,
});Le letture di dettaglio portale usate dal client sono sempre scoped per
accountId. I vecchi riferimenti raw non scoped restano disponibili soltanto
per compatibilità con la 0.3 e sono deprecati.
Liste e paginazione
Le API legacy list* continuano a restituire array, ma sono bounded:
- default 100 record;
- massimo 200;
- se esistono più record del limite richiesto, generano un errore esplicito che
indica la corrispondente API
paginate*.
Per sincronizzazioni, selettori completi e dataset non banali usa sempre le API
paginate con paginationOpts Convex. Il numero di elementi per pagina deve
essere compreso tra 1 e 200.
const firstPage = await client.accounts.paginate(ctx, {
isActive: true,
paginationOpts: { numItems: 100, cursor: null },
});Le API paginate* usano cursori compatibili con il boundary dei componenti;
trattali sempre come stringhe opache. searchAccountsPaginated usa un cursore
dedicato legato ai filtri della ricerca e una finestra bounded di massimo 1.000
risultati. Per questa API gli advanced options endCursor, maximumRowsRead e
maximumBytesRead non sono supportati.
Il filtro legacy tag richiede membership in un array e non può produrre una
paginazione corretta con lo schema attuale. È supportato solo da listAccounts
con scan bounded; non è accettato da paginateAccounts.
Per operazioni idempotenti non enumerare gli account: usa
getAccountByCustomerCode. customerCode viene trim-mato in creazione e
lookup, conserva il case ed è l'identità stabile e immutabile del master
cliente.
Tabelle isolate
| Tabella | Responsabilità |
| --- | --- |
| lookupValues | Lookup configurabili |
| accounts | Anagrafica master cliente |
| accountContacts | Contatti dell'account |
| accountLocations | Sedi dell'account |
| accountOperationalProfiles | Profilo operativo 1:1 |
| accountNotes | Note interne |
| accountTasks | Task e follow-up |
| accountActivities | Timeline audit |
| portalClinicExternalLinks | Associazioni clinica esterna → account |
| portalOrderRequests | Richieste del portale |
| portalOrderAttachments | Allegati delle richieste |
API per dominio
Il client CustomerAccountsClient raggruppa le operazioni nei domini:
accounts: create/update/lifecycle/get/list/paginate/search;contactselocations: lifecycle, primario unico, list/paginate;operationalProfiles: upsert 1:1 e get;notes,tasks,activities: timeline operativa bounded/paginata;lookups: lifecycle, lookup diretto, list/paginate;overview: aggregato bounded dell'account;portal: link cliniche, richieste e allegati con ownership verificata.
Tutti i validator degli argomenti, documenti, pagine e ritorni sono esportati
da @primocaredentgroup/customer-accounts/validators e
@primocaredentgroup/customer-accounts/returns.
I campi opzionali delle mutation update possono essere:
- omessi per lasciarli invariati;
- valorizzati per aggiornarli;
- impostati a
nullper cancellarli, dove previsto dal validator.
Il client generico non permette di creare lookup con isSystem: true: quel
flag rende il record non disattivabile e richiede una capability privilegiata
progettata esplicitamente dall'host. I seed dimostrativi devono creare lookup
normali.
Migration kit
La 0.4 espone una sola row verificata:
CustomerAccount.Identity, identitàcustomerCode.
Contatti, sedi e transazioni portale sono rinviati finché non esistono chiavi esterne stabili e un contratto esplicito per le relazioni possedute dall'host.
Con migration-kit 2.2.x configura il modulo npm esplicitamente:
// migration-kit.config.ts
export default {
componentGlobs: ["convex/components/*/migration.ts"],
modules: ["@primocaredentgroup/customer-accounts/migration"],
};La capability supporta preview e commit, batch fino a 100 record e un upsert
diretto idempotente. preview non scrive. La row dichiara però
reconcileStrategy: "skip": nel flusso orchestrato, un externalId già
mappato viene conteggiato come skipped e la capability non viene richiamata.
Per riconciliare dati sorgente modificati serve rimuovere consapevolmente la
mapping o introdurre una futura capability di reconcile. Il customerCode
passato come externalId deve coincidere esattamente con
input.customerCode, essere non vuoto e non avere spazi esterni.
Upgrade dalla 0.3
Prima di installare la 0.4 su un deployment esistente esegui un preflight in sola lettura e blocca il rollout se trovi:
customerCodevuoti, con spazi esterni o duplicati esatti;customerTypevuoti o con spazi esterni (la 0.4 normalizza i nuovi valori e i filtri, ma non riscrive automaticamente le righe legacy);- duplicati
(category, code)nei lookup; - più profili operativi per account;
- più contatti o sedi primarie attive per account;
- link portale duplicati per account/clinica;
- richieste portale in bozza create con
subject, email o user id invece deltokenIdentifiercanonico: il confronto ownership della 0.4 è esatto, quindi completale prima dell'upgrade oppure esegui un backfill con una mapping identità verificata; - metadata attività non conformi al record JSON flat supportato: primitive o array di primitive, senza oggetti annidati.
La release aggiunge indici a tabelle esistenti. Sui deployment PrimoCare verificati il dataset è piccolo; per un'installazione con tabelle grandi applica il rollout staged prescritto da Convex: prima indici staged, backfill/verifica, poi indici attivi e codice che li interroga.
Non eseguire migrazioni o deploy di produzione come parte dell'installazione
npm. Prima usa preview, conserva un export/backup, applica batch bounded e
verifica mapping e conteggi prima/dopo. Un retry orchestrato di identità già
mappate deve risultare skipped; il test di upsert con lo stesso ID riguarda la
capability diretta, non sostituisce questa verifica dell'orchestratore.
Sviluppo del package
npm install
npm run verify
npm pack --dry-runverify esegue build, test e typecheck usando i binding ufficiali tracciati nel
repository. Dopo una modifica alla superficie Convex, il maintainer esegue
separatamente npm run codegen, verifica il diff dei binding e solo dopo lancia
verify. Il publish richiede il normale 2FA npm dell'organizzazione.
