- Migration 0002_craftvia_domain: 27 Fachtabellen inkl. RLS (enable_tenant_rls) - TENANT_MODELS (db.ts, backup/topology.ts) um alle Fachmodelle ergänzt - moduleGuard liefert DB-autoritative Rechte; ServiceCtx für Domänen-Services - Verträge: Statusmaschine, Events, Nummernkreise, Sichtbarkeits-Scopes, Job-Queues + Worker, KI-Provider-Interfaces, Sync-Envelope - docs/craftvia/ARCHITEKTUR.md mit Lanes, Ownership und DoD Gate: tsc, lint, build, 22/22 Tests grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
42 lines
1.5 KiB
TypeScript
42 lines
1.5 KiB
TypeScript
import type { TenantDb } from "@/server/db";
|
|
|
|
export type NumberKey = "customer" | "work_order" | "emergency" | "report";
|
|
|
|
const DEFAULT_PREFIX: Record<NumberKey, string> = {
|
|
customer: "K-",
|
|
work_order: "A-",
|
|
emergency: "N-",
|
|
report: "B-",
|
|
};
|
|
|
|
/**
|
|
* Allocate the next number of a tenant sequence, e.g. "A-00042".
|
|
* The increment is a single UPDATE … RETURNING (atomic); the very first call per key
|
|
* creates the row and retries once if a concurrent call created it first.
|
|
*/
|
|
export async function nextNumber(db: TenantDb, tenantId: string, key: NumberKey): Promise<string> {
|
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
const existing = await db.numberSequence.findFirst({ where: { tenantId, key }, select: { id: true } });
|
|
if (existing) {
|
|
const seq = await db.numberSequence.update({
|
|
where: { id: existing.id },
|
|
data: { nextValue: { increment: 1 } },
|
|
});
|
|
return format(seq.prefix, seq.nextValue - 1, seq.padding);
|
|
}
|
|
try {
|
|
const seq = await db.numberSequence.create({
|
|
data: { tenantId, key, prefix: DEFAULT_PREFIX[key], nextValue: 2 },
|
|
});
|
|
return format(seq.prefix, 1, seq.padding);
|
|
} catch (err) {
|
|
if ((err as { code?: string }).code !== "P2002") throw err; // unique race → retry via update path
|
|
}
|
|
}
|
|
throw new Error(`could not allocate number for ${key}`);
|
|
}
|
|
|
|
function format(prefix: string, value: number, padding: number): string {
|
|
return `${prefix}${String(value).padStart(padding, "0")}`;
|
|
}
|