Architektur: Craftvia-Domänenmodell, Verträge und Team-Schnitte
- 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>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
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")}`;
|
||||
}
|
||||
Reference in New Issue
Block a user