import { prisma } from "@/lib/prisma"; // ============================================================================= // Tenant-Scoped Prisma Client // ============================================================================= // // ARCHITECTURE: // This module provides a Prisma client extended with query-level extensions // that automatically inject the current tenant's ID into all reads, writes, // and deletes on tenant-scoped models. // // This is the PRIMARY enforcement layer for multi-tenancy. // PostgreSQL RLS (see migration: add-rls-policies) is DEFENSE-IN-DEPTH — // it catches application bugs if this middleware is somehow bypassed. // // USAGE: // const tenantPrisma = withTenantContext(tenantId); // const users = await tenantPrisma.user.findMany(); // auto-filters by tenantId // // EXTENDING: // When adding new tenant-scoped models (Subscriber, Invoice, Plan, etc.), // add the model name to TENANT_SCOPED_MODELS below and extend the $extends // block in withTenantContext() following the same pattern. // ============================================================================= /** * List of Prisma model names that are scoped to a tenant. * Models NOT in this list (like Tenant itself) bypass tenant filtering. * * Extend this list as new models are added in later phases: * e.g., "subscriber", "invoice", "servicePlan", "payment" */ export const TENANT_SCOPED_MODELS = ["user", "account", "accountingPeriod", "subscriber", "servicePlan", "tenantSettings", "journalEntry", "journalEntryLine", "invoice", "invoiceLine", "payment", "paymentAllocation", "zone", "zoneAssignment", "ticket", "ticketCategory", "collection", "collectionAllocation", "remittance", "jobOrder"] as const; export type TenantScopedModel = (typeof TENANT_SCOPED_MODELS)[number]; /** * Returns a Prisma client extended with tenant-scoped query filtering. * * All operations on tenant-scoped models automatically include the tenantId: * - findMany / findFirst: where clause gets tenantId injected * - findUnique: converted to findFirst with tenantId guard * - create: data gets tenantId set * - update / updateMany: where clause gets tenantId added * - delete / deleteMany: where clause gets tenantId added * - upsert: where clause gets tenantId added, create data gets tenantId set * * @param tenantId - The tenant UUID to scope all queries to */ export function withTenantContext(tenantId: string) { return prisma.$extends({ query: { user: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, // findUnique requires an exact unique key — we can't simply inject // tenantId there. Route through findFirst to enforce tenant scoping. async findUnique({ args, query }) { // Check if tenantId is already part of a compound unique key // If so, let Prisma handle it. Otherwise, use findFirst with tenant guard. if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { // Use findFirst to enforce tenant boundary return prisma.user.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.user.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { // Remove the nested `tenant` relation object when injecting tenantId // (they are mutually exclusive in Prisma's UncheckedCreateInput) // eslint-disable-next-line @typescript-eslint/no-unused-vars const { tenant: _tenant, ...rest } = args.data as typeof args.data & { tenant?: unknown }; args.data = { ...rest, tenantId } as typeof args.data; return query(args); }, async createMany({ args, query }) { if (Array.isArray(args.data)) { args.data = args.data.map((item) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { tenant: _t, ...rest } = item as typeof item & { tenant?: unknown }; return { ...rest, tenantId } as typeof item; }); } else { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { tenant: _t, ...rest } = args.data as typeof args.data & { tenant?: unknown }; args.data = { ...rest, tenantId } as typeof args.data; } return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async delete({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async upsert({ args, query }) { args.where = { ...args.where, tenantId } as typeof args.where; // eslint-disable-next-line @typescript-eslint/no-unused-vars const { tenant: _t, ...createRest } = args.create as typeof args.create & { tenant?: unknown }; args.create = { ...createRest, tenantId } as typeof args.create; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, account: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.account.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.account.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async createMany({ args, query }) { if (Array.isArray(args.data)) { args.data = args.data.map((item) => ({ ...item, tenantId })) as typeof args.data; } else { args.data = { ...args.data, tenantId } as typeof args.data; } return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async delete({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async upsert({ args, query }) { args.where = { ...args.where, tenantId } as typeof args.where; args.create = { ...args.create, tenantId } as typeof args.create; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, accountingPeriod: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.accountingPeriod.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.accountingPeriod.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async createMany({ args, query }) { if (Array.isArray(args.data)) { args.data = args.data.map((item) => ({ ...item, tenantId })) as typeof args.data; } else { args.data = { ...args.data, tenantId } as typeof args.data; } return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async delete({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async upsert({ args, query }) { args.where = { ...args.where, tenantId } as typeof args.where; args.create = { ...args.create, tenantId } as typeof args.create; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, subscriber: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.subscriber.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.subscriber.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async createMany({ args, query }) { if (Array.isArray(args.data)) { args.data = args.data.map((item) => ({ ...item, tenantId })) as typeof args.data; } else { args.data = { ...args.data, tenantId } as typeof args.data; } return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async delete({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async upsert({ args, query }) { args.where = { ...args.where, tenantId } as typeof args.where; args.create = { ...args.create, tenantId } as typeof args.create; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, servicePlan: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.servicePlan.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.servicePlan.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async createMany({ args, query }) { if (Array.isArray(args.data)) { args.data = args.data.map((item) => ({ ...item, tenantId })) as typeof args.data; } else { args.data = { ...args.data, tenantId } as typeof args.data; } return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async delete({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async upsert({ args, query }) { args.where = { ...args.where, tenantId } as typeof args.where; args.create = { ...args.create, tenantId } as typeof args.create; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, tenantSettings: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.tenantSettings.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.tenantSettings.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async upsert({ args, query }) { args.where = { ...args.where, tenantId } as typeof args.where; args.create = { ...args.create, tenantId } as typeof args.create; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, journalEntry: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.journalEntry.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.journalEntry.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async delete({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, journalEntryLine: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.journalEntryLine.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, invoice: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.invoice.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.invoice.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async createMany({ args, query }) { if (Array.isArray(args.data)) { args.data = args.data.map((item) => ({ ...item, tenantId })) as typeof args.data; } else { args.data = { ...args.data, tenantId } as typeof args.data; } return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async delete({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, invoiceLine: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, payment: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.payment.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, paymentAllocation: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async createMany({ args, query }) { if (Array.isArray(args.data)) { args.data = args.data.map((item) => ({ ...item, tenantId })) as typeof args.data; } else { args.data = { ...args.data, tenantId } as typeof args.data; } return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, zone: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.zone.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.zone.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async createMany({ args, query }) { if (Array.isArray(args.data)) { args.data = args.data.map((item) => ({ ...item, tenantId })) as typeof args.data; } else { args.data = { ...args.data, tenantId } as typeof args.data; } return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async delete({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async upsert({ args, query }) { args.where = { ...args.where, tenantId } as typeof args.where; args.create = { ...args.create, tenantId } as typeof args.create; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, zoneAssignment: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.zoneAssignment.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.zoneAssignment.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async createMany({ args, query }) { if (Array.isArray(args.data)) { args.data = args.data.map((item) => ({ ...item, tenantId })) as typeof args.data; } else { args.data = { ...args.data, tenantId } as typeof args.data; } return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async delete({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async upsert({ args, query }) { args.where = { ...args.where, tenantId } as typeof args.where; args.create = { ...args.create, tenantId } as typeof args.create; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, ticketCategory: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.ticketCategory.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.ticketCategory.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async createMany({ args, query }) { if (Array.isArray(args.data)) { args.data = args.data.map((item) => ({ ...item, tenantId })) as typeof args.data; } else { args.data = { ...args.data, tenantId } as typeof args.data; } return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async delete({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async upsert({ args, query }) { args.where = { ...args.where, tenantId } as typeof args.where; args.create = { ...args.create, tenantId } as typeof args.create; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, ticket: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.ticket.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.ticket.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async createMany({ args, query }) { if (Array.isArray(args.data)) { args.data = args.data.map((item) => ({ ...item, tenantId })) as typeof args.data; } else { args.data = { ...args.data, tenantId } as typeof args.data; } return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async delete({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async upsert({ args, query }) { args.where = { ...args.where, tenantId } as typeof args.where; args.create = { ...args.create, tenantId } as typeof args.create; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, collection: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.collection.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.collection.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async createMany({ args, query }) { if (Array.isArray(args.data)) { args.data = args.data.map((item) => ({ ...item, tenantId })) as typeof args.data; } else { args.data = { ...args.data, tenantId } as typeof args.data; } return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async delete({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async upsert({ args, query }) { args.where = { ...args.where, tenantId } as typeof args.where; args.create = { ...args.create, tenantId } as typeof args.create; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, collectionAllocation: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.collectionAllocation.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.collectionAllocation.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async createMany({ args, query }) { if (Array.isArray(args.data)) { args.data = args.data.map((item) => ({ ...item, tenantId })) as typeof args.data; } else { args.data = { ...args.data, tenantId } as typeof args.data; } return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async delete({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async upsert({ args, query }) { args.where = { ...args.where, tenantId } as typeof args.where; args.create = { ...args.create, tenantId } as typeof args.create; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, remittance: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.remittance.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.remittance.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async createMany({ args, query }) { if (Array.isArray(args.data)) { args.data = args.data.map((item) => ({ ...item, tenantId })) as typeof args.data; } else { args.data = { ...args.data, tenantId } as typeof args.data; } return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async delete({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async upsert({ args, query }) { args.where = { ...args.where, tenantId } as typeof args.where; args.create = { ...args.create, tenantId } as typeof args.create; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, jobOrder: { async findMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirst({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findFirstOrThrow({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async findUnique({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { return prisma.jobOrder.findFirst({ ...args, where: { ...args.where, tenantId }, }); } return query(args); }, async findUniqueOrThrow({ args, query }) { if (args.where && "id" in args.where && !("tenantId" in (args.where as object))) { const result = await prisma.jobOrder.findFirst({ ...args, where: { ...args.where, tenantId }, }); if (!result) { throw new Error("Record not found"); } return result; } return query(args); }, async create({ args, query }) { args.data = { ...args.data, tenantId } as typeof args.data; return query(args); }, async createMany({ args, query }) { if (Array.isArray(args.data)) { args.data = args.data.map((item) => ({ ...item, tenantId })) as typeof args.data; } else { args.data = { ...args.data, tenantId } as typeof args.data; } return query(args); }, async update({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async updateMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async delete({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async deleteMany({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async upsert({ args, query }) { args.where = { ...args.where, tenantId } as typeof args.where; args.create = { ...args.create, tenantId } as typeof args.create; return query(args); }, async count({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async aggregate({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, async groupBy({ args, query }) { args.where = { ...args.where, tenantId }; return query(args); }, }, }, }); } /** * Alias for withTenantContext — exported under both names as per plan spec. * Use either name; they are identical. */ export const createTenantPrisma = withTenantContext; /** * Sets the PostgreSQL session variable used by RLS policies for the duration * of the provided transaction or connection. * * RLS ARCHITECTURE NOTE: * The Prisma client connects as the database OWNER, which bypasses RLS * by default (PostgreSQL superusers and table owners bypass RLS unless * FORCE ROW LEVEL SECURITY is set). This means RLS is NOT the primary * enforcement — withTenantContext() (application-level Prisma extensions) * is the primary enforcement layer. * * This function enables RLS enforcement for code paths that explicitly opt in, * or in future scenarios where a non-owner role is used for queries. * * Usage (inside a Prisma transaction): * await prisma.$transaction(async (tx) => { * await setTenantRLS(tx, tenantId); * // ... queries here will be RLS-enforced * }); * * @param tx - A Prisma client or transaction client * @param tenantId - The tenant UUID to set as the current RLS context */ export async function setTenantRLS( tx: { $executeRawUnsafe: (query: string, ...args: unknown[]) => Promise }, tenantId: string ): Promise { // SET LOCAL scopes the variable to the current transaction await tx.$executeRawUnsafe( `SET LOCAL "app.current_tenant_id" = $1`, tenantId ); }