feat(02-01): Account and AccountingPeriod Prisma models + COA definition
- Add AccountType, NormalBalance, PeriodStatus enums to schema - Add Account model with tenant scoping, code/name/type/normalBalance/parentId - Add AccountingPeriod model with year/month/status/closedAt/closedById - Create ISP_CHART_OF_ACCOUNTS with 28 accounts across all 5 types (1000-5000 ranges) - Create accounting-period.ts with getOpenPeriod, closePeriod, isDateInClosedPeriod - Extend TENANT_SCOPED_MODELS with account and accountingPeriod - Add full query extension blocks for account and accountingPeriod in withTenantContext - Run migration: 20260304144656_add_accounting_models
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AccountType" AS ENUM ('ASSET', 'LIABILITY', 'EQUITY', 'REVENUE', 'EXPENSE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "NormalBalance" AS ENUM ('DEBIT', 'CREDIT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PeriodStatus" AS ENUM ('OPEN', 'CLOSED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Account" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"accountType" "AccountType" NOT NULL,
|
||||
"normalBalance" "NormalBalance" NOT NULL,
|
||||
"parentId" TEXT,
|
||||
"isSystemAccount" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AccountingPeriod" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"year" INTEGER NOT NULL,
|
||||
"month" INTEGER NOT NULL,
|
||||
"status" "PeriodStatus" NOT NULL DEFAULT 'OPEN',
|
||||
"closedAt" TIMESTAMP(3),
|
||||
"closedById" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "AccountingPeriod_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Account_tenantId_idx" ON "Account"("tenantId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Account_tenantId_code_key" ON "Account"("tenantId", "code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AccountingPeriod_tenantId_idx" ON "AccountingPeriod"("tenantId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "AccountingPeriod_tenantId_year_month_key" ON "AccountingPeriod"("tenantId", "year", "month");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Account" ADD CONSTRAINT "Account_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Account"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AccountingPeriod" ADD CONSTRAINT "AccountingPeriod_closedById_fkey" FOREIGN KEY ("closedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -39,6 +39,24 @@ enum Role {
|
||||
CLIENT
|
||||
}
|
||||
|
||||
enum AccountType {
|
||||
ASSET
|
||||
LIABILITY
|
||||
EQUITY
|
||||
REVENUE
|
||||
EXPENSE
|
||||
}
|
||||
|
||||
enum NormalBalance {
|
||||
DEBIT
|
||||
CREDIT
|
||||
}
|
||||
|
||||
enum PeriodStatus {
|
||||
OPEN
|
||||
CLOSED
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MODELS
|
||||
// =============================================================================
|
||||
@@ -69,6 +87,56 @@ model Tenant {
|
||||
users User[]
|
||||
}
|
||||
|
||||
/// An Account represents a node in the Chart of Accounts tree.
|
||||
/// Account balances are NEVER stored — they are always derived from journal entry sums.
|
||||
/// System accounts (isSystemAccount=true) are auto-created from ISP_CHART_OF_ACCOUNTS
|
||||
/// and cannot be deleted by tenant admins.
|
||||
model Account {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
/// Account code (e.g., "1010" for Cash on Hand). Unique per tenant.
|
||||
code String
|
||||
name String
|
||||
accountType AccountType
|
||||
normalBalance NormalBalance
|
||||
/// Optional parent account id for hierarchical COA (e.g., 1010 parent = 1000)
|
||||
parentId String?
|
||||
parent Account? @relation("AccountSubAccounts", fields: [parentId], references: [id])
|
||||
children Account[] @relation("AccountSubAccounts")
|
||||
/// COA accounts seeded at tenant creation cannot be deleted by admins
|
||||
isSystemAccount Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
/// Account codes must be unique within a tenant
|
||||
@@unique([tenantId, code])
|
||||
/// RLS-ready index — always present on tenant-scoped models
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
/// An AccountingPeriod represents one calendar month for a tenant.
|
||||
/// Once CLOSED, no journal entries may be posted to that period.
|
||||
/// Periods are created on-demand (first time an entry needs one).
|
||||
model AccountingPeriod {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
year Int
|
||||
month Int
|
||||
status PeriodStatus @default(OPEN)
|
||||
/// Timestamp when the period was closed (null if OPEN)
|
||||
closedAt DateTime?
|
||||
/// The admin user who closed this period
|
||||
closedById String?
|
||||
closedBy User? @relation(fields: [closedById], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
/// One period per month per tenant
|
||||
@@unique([tenantId, year, month])
|
||||
/// RLS-ready index — always present on tenant-scoped models
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
/// A User belongs to a Tenant (or is a super-admin with no tenant).
|
||||
/// Email uniqueness is enforced per-tenant, not globally.
|
||||
/// Super-admins have isSuperAdmin=true and tenantId=null.
|
||||
@@ -89,6 +157,9 @@ model User {
|
||||
isActive Boolean @default(true)
|
||||
isSuperAdmin Boolean @default(false)
|
||||
|
||||
/// Accounting periods this user has closed (as administrator)
|
||||
closedPeriods AccountingPeriod[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
|
||||
131
src/lib/accounting/accounting-period.ts
Normal file
131
src/lib/accounting/accounting-period.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
// =============================================================================
|
||||
// Accounting Period Management
|
||||
// =============================================================================
|
||||
//
|
||||
// Accounting periods represent calendar months. A period is OPEN by default.
|
||||
// Once CLOSED, no journal entries may be posted to that period (enforced in 02-02).
|
||||
//
|
||||
// Periods are created on-demand — the first call to getOpenPeriod() for a given
|
||||
// month/year creates the period record if it doesn't already exist.
|
||||
// =============================================================================
|
||||
|
||||
import { PrismaClient, PeriodStatus } from "@prisma/client";
|
||||
|
||||
/**
|
||||
* Minimal Prisma client interface that supports accountingPeriod operations.
|
||||
* Accepts a full PrismaClient or a transaction client (Prisma.$transaction callback arg).
|
||||
*/
|
||||
type PrismaLike = Pick<PrismaClient, "accountingPeriod">;
|
||||
|
||||
/**
|
||||
* Finds an existing OPEN period for the given month/year, or creates one if
|
||||
* it doesn't exist yet. Throws if the period for that month is already CLOSED.
|
||||
*
|
||||
* @param db - A Prisma client or transaction client
|
||||
* @param tenantId - The tenant scoping this period
|
||||
* @param year - Calendar year (e.g., 2026)
|
||||
* @param month - Calendar month, 1-12
|
||||
* @returns The OPEN AccountingPeriod record
|
||||
* @throws Error if period for month/year exists but is CLOSED
|
||||
*/
|
||||
export async function getOpenPeriod(
|
||||
db: PrismaLike,
|
||||
tenantId: string,
|
||||
year: number,
|
||||
month: number
|
||||
) {
|
||||
// Try to find an existing period for this month
|
||||
const existing = await db.accountingPeriod.findFirst({
|
||||
where: { tenantId, year, month },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
if (existing.status === PeriodStatus.CLOSED) {
|
||||
throw new Error(
|
||||
`Accounting period ${year}-${String(month).padStart(2, "0")} is closed. ` +
|
||||
`No journal entries may be posted to a closed period.`
|
||||
);
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
// Create the period if it doesn't exist yet
|
||||
return db.accountingPeriod.create({
|
||||
data: {
|
||||
tenantId,
|
||||
year,
|
||||
month,
|
||||
status: PeriodStatus.OPEN,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes an accounting period. Sets status to CLOSED, records the timestamp
|
||||
* and the ID of the admin who performed the close.
|
||||
*
|
||||
* @param db - A Prisma client or transaction client
|
||||
* @param periodId - The UUID of the AccountingPeriod to close
|
||||
* @param closedById - The UUID of the User performing the close action
|
||||
* @returns The updated (closed) AccountingPeriod record
|
||||
* @throws Error if the period is already closed
|
||||
*/
|
||||
export async function closePeriod(
|
||||
db: PrismaLike,
|
||||
periodId: string,
|
||||
closedById: string
|
||||
) {
|
||||
const period = await db.accountingPeriod.findFirst({
|
||||
where: { id: periodId },
|
||||
});
|
||||
|
||||
if (!period) {
|
||||
throw new Error(`Accounting period not found: ${periodId}`);
|
||||
}
|
||||
|
||||
if (period.status === PeriodStatus.CLOSED) {
|
||||
throw new Error(
|
||||
`Accounting period ${period.year}-${String(period.month).padStart(2, "0")} ` +
|
||||
`is already closed.`
|
||||
);
|
||||
}
|
||||
|
||||
return db.accountingPeriod.update({
|
||||
where: { id: periodId },
|
||||
data: {
|
||||
status: PeriodStatus.CLOSED,
|
||||
closedAt: new Date(),
|
||||
closedById,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the accounting period containing the given date is CLOSED.
|
||||
*
|
||||
* Used by journal entry posting logic (02-02) to prevent entries in closed periods.
|
||||
*
|
||||
* @param db - A Prisma client or transaction client
|
||||
* @param tenantId - The tenant to check
|
||||
* @param date - The date to check (uses its year and month)
|
||||
* @returns true if the period is CLOSED, false if OPEN or not yet created
|
||||
*/
|
||||
export async function isDateInClosedPeriod(
|
||||
db: PrismaLike,
|
||||
tenantId: string,
|
||||
date: Date
|
||||
): Promise<boolean> {
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1; // JS months are 0-indexed
|
||||
|
||||
const period = await db.accountingPeriod.findFirst({
|
||||
where: { tenantId, year, month },
|
||||
});
|
||||
|
||||
if (!period) {
|
||||
// Period not created yet — it's implicitly open
|
||||
return false;
|
||||
}
|
||||
|
||||
return period.status === PeriodStatus.CLOSED;
|
||||
}
|
||||
279
src/lib/accounting/chart-of-accounts.ts
Normal file
279
src/lib/accounting/chart-of-accounts.ts
Normal file
@@ -0,0 +1,279 @@
|
||||
// =============================================================================
|
||||
// ISP Chart of Accounts Definition
|
||||
// =============================================================================
|
||||
//
|
||||
// This module defines the standard Chart of Accounts (COA) for ISP businesses
|
||||
// using NetForge. Every new tenant gets this COA auto-provisioned on signup.
|
||||
//
|
||||
// DOUBLE-ENTRY ACCOUNTING RULES:
|
||||
// - DEBIT increases ASSET and EXPENSE accounts (normal balance = DEBIT)
|
||||
// - CREDIT increases LIABILITY, EQUITY, and REVENUE accounts (normal balance = CREDIT)
|
||||
// - Account balances are NEVER stored — always derived from journal entry sums
|
||||
//
|
||||
// ACCOUNT CODE RANGES:
|
||||
// 1000s = Assets
|
||||
// 2000s = Liabilities
|
||||
// 3000s = Equity
|
||||
// 4000s = Revenue
|
||||
// 5000s = Expenses
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Account type enum mirroring the Prisma AccountType enum.
|
||||
* Exported for use in code that runs outside the Prisma context (e.g., seeding scripts).
|
||||
*/
|
||||
export type AccountType = "ASSET" | "LIABILITY" | "EQUITY" | "REVENUE" | "EXPENSE";
|
||||
|
||||
/**
|
||||
* Normal balance enum mirroring the Prisma NormalBalance enum.
|
||||
* Determines whether a debit or credit increases the account balance.
|
||||
* - DEBIT: assets and expenses increase with debits
|
||||
* - CREDIT: liabilities, equity, and revenue increase with credits
|
||||
*/
|
||||
export type NormalBalance = "DEBIT" | "CREDIT";
|
||||
|
||||
/**
|
||||
* A single account definition in the ISP Chart of Accounts.
|
||||
*/
|
||||
export interface COAAccountDefinition {
|
||||
/** Account code (e.g., "1010"). Must be unique within a tenant. */
|
||||
code: string;
|
||||
/** Human-readable account name (e.g., "Cash on Hand"). */
|
||||
name: string;
|
||||
/** The broad classification of this account. */
|
||||
accountType: AccountType;
|
||||
/** Whether debits or credits increase this account's balance. */
|
||||
normalBalance: NormalBalance;
|
||||
/**
|
||||
* Optional parent account code. If set, this account is a sub-account
|
||||
* of the named parent. Parent must appear earlier in the array so it
|
||||
* can be resolved to a parentId during seeding.
|
||||
*/
|
||||
parentCode?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The standard ISP Chart of Accounts.
|
||||
*
|
||||
* Organized by account type:
|
||||
* - Assets (1000s): What the ISP owns or is owed
|
||||
* - Liabilities (2000s): What the ISP owes to others
|
||||
* - Equity (3000s): Owner's stake in the business
|
||||
* - Revenue (4000s): Income from ISP operations
|
||||
* - Expenses (5000s): Costs of running the ISP
|
||||
*
|
||||
* All 5 standard accounting types are represented.
|
||||
* Subscriber Credits (1150) is a contra-asset (reduces AR).
|
||||
*/
|
||||
export const ISP_CHART_OF_ACCOUNTS: COAAccountDefinition[] = [
|
||||
// ---------------------------------------------------------------------------
|
||||
// ASSETS (1000s) — Normal balance: DEBIT
|
||||
// ---------------------------------------------------------------------------
|
||||
{
|
||||
code: "1000",
|
||||
name: "Current Assets",
|
||||
accountType: "ASSET",
|
||||
normalBalance: "DEBIT",
|
||||
},
|
||||
{
|
||||
code: "1010",
|
||||
name: "Cash on Hand",
|
||||
accountType: "ASSET",
|
||||
normalBalance: "DEBIT",
|
||||
parentCode: "1000",
|
||||
},
|
||||
{
|
||||
code: "1020",
|
||||
name: "Cash in Bank",
|
||||
accountType: "ASSET",
|
||||
normalBalance: "DEBIT",
|
||||
parentCode: "1000",
|
||||
},
|
||||
{
|
||||
code: "1100",
|
||||
name: "Accounts Receivable",
|
||||
accountType: "ASSET",
|
||||
normalBalance: "DEBIT",
|
||||
parentCode: "1000",
|
||||
},
|
||||
{
|
||||
code: "1150",
|
||||
name: "Subscriber Credits",
|
||||
accountType: "ASSET",
|
||||
normalBalance: "CREDIT", // Contra-asset: reduces accounts receivable
|
||||
parentCode: "1000",
|
||||
},
|
||||
{
|
||||
code: "1200",
|
||||
name: "Equipment Inventory",
|
||||
accountType: "ASSET",
|
||||
normalBalance: "DEBIT",
|
||||
parentCode: "1000",
|
||||
},
|
||||
{
|
||||
code: "1300",
|
||||
name: "Prepaid Expenses",
|
||||
accountType: "ASSET",
|
||||
normalBalance: "DEBIT",
|
||||
parentCode: "1000",
|
||||
},
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// LIABILITIES (2000s) — Normal balance: CREDIT
|
||||
// ---------------------------------------------------------------------------
|
||||
{
|
||||
code: "2000",
|
||||
name: "Current Liabilities",
|
||||
accountType: "LIABILITY",
|
||||
normalBalance: "CREDIT",
|
||||
},
|
||||
{
|
||||
code: "2010",
|
||||
name: "Accounts Payable",
|
||||
accountType: "LIABILITY",
|
||||
normalBalance: "CREDIT",
|
||||
parentCode: "2000",
|
||||
},
|
||||
{
|
||||
code: "2100",
|
||||
name: "Unearned Revenue",
|
||||
accountType: "LIABILITY",
|
||||
normalBalance: "CREDIT", // Prepaid subscriber payments not yet earned
|
||||
parentCode: "2000",
|
||||
},
|
||||
{
|
||||
code: "2200",
|
||||
name: "Taxes Payable",
|
||||
accountType: "LIABILITY",
|
||||
normalBalance: "CREDIT",
|
||||
parentCode: "2000",
|
||||
},
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// EQUITY (3000s) — Normal balance: CREDIT
|
||||
// ---------------------------------------------------------------------------
|
||||
{
|
||||
code: "3000",
|
||||
name: "Owner's Equity",
|
||||
accountType: "EQUITY",
|
||||
normalBalance: "CREDIT",
|
||||
},
|
||||
{
|
||||
code: "3010",
|
||||
name: "Owner's Capital",
|
||||
accountType: "EQUITY",
|
||||
normalBalance: "CREDIT",
|
||||
parentCode: "3000",
|
||||
},
|
||||
{
|
||||
code: "3020",
|
||||
name: "Retained Earnings",
|
||||
accountType: "EQUITY",
|
||||
normalBalance: "CREDIT",
|
||||
parentCode: "3000",
|
||||
},
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// REVENUE (4000s) — Normal balance: CREDIT
|
||||
// ---------------------------------------------------------------------------
|
||||
{
|
||||
code: "4000",
|
||||
name: "Operating Revenue",
|
||||
accountType: "REVENUE",
|
||||
normalBalance: "CREDIT",
|
||||
},
|
||||
{
|
||||
code: "4010",
|
||||
name: "Subscription Revenue",
|
||||
accountType: "REVENUE",
|
||||
normalBalance: "CREDIT",
|
||||
parentCode: "4000",
|
||||
},
|
||||
{
|
||||
code: "4020",
|
||||
name: "Installation Fees",
|
||||
accountType: "REVENUE",
|
||||
normalBalance: "CREDIT",
|
||||
parentCode: "4000",
|
||||
},
|
||||
{
|
||||
code: "4030",
|
||||
name: "Reconnection Fees",
|
||||
accountType: "REVENUE",
|
||||
normalBalance: "CREDIT",
|
||||
parentCode: "4000",
|
||||
},
|
||||
{
|
||||
code: "4090",
|
||||
name: "Other Revenue",
|
||||
accountType: "REVENUE",
|
||||
normalBalance: "CREDIT",
|
||||
parentCode: "4000",
|
||||
},
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// EXPENSES (5000s) — Normal balance: DEBIT
|
||||
// ---------------------------------------------------------------------------
|
||||
{
|
||||
code: "5000",
|
||||
name: "Operating Expenses",
|
||||
accountType: "EXPENSE",
|
||||
normalBalance: "DEBIT",
|
||||
},
|
||||
{
|
||||
code: "5010",
|
||||
name: "Salary Expense",
|
||||
accountType: "EXPENSE",
|
||||
normalBalance: "DEBIT",
|
||||
parentCode: "5000",
|
||||
},
|
||||
{
|
||||
code: "5020",
|
||||
name: "Technician Compensation",
|
||||
accountType: "EXPENSE",
|
||||
normalBalance: "DEBIT",
|
||||
parentCode: "5000",
|
||||
},
|
||||
{
|
||||
code: "5030",
|
||||
name: "Equipment Expense",
|
||||
accountType: "EXPENSE",
|
||||
normalBalance: "DEBIT",
|
||||
parentCode: "5000",
|
||||
},
|
||||
{
|
||||
code: "5040",
|
||||
name: "Internet Bandwidth",
|
||||
accountType: "EXPENSE",
|
||||
normalBalance: "DEBIT",
|
||||
parentCode: "5000",
|
||||
},
|
||||
{
|
||||
code: "5050",
|
||||
name: "Office Supplies",
|
||||
accountType: "EXPENSE",
|
||||
normalBalance: "DEBIT",
|
||||
parentCode: "5000",
|
||||
},
|
||||
{
|
||||
code: "5060",
|
||||
name: "Utilities",
|
||||
accountType: "EXPENSE",
|
||||
normalBalance: "DEBIT",
|
||||
parentCode: "5000",
|
||||
},
|
||||
{
|
||||
code: "5070",
|
||||
name: "Depreciation",
|
||||
accountType: "EXPENSE",
|
||||
normalBalance: "DEBIT",
|
||||
parentCode: "5000",
|
||||
},
|
||||
{
|
||||
code: "5090",
|
||||
name: "Other Expense",
|
||||
accountType: "EXPENSE",
|
||||
normalBalance: "DEBIT",
|
||||
parentCode: "5000",
|
||||
},
|
||||
];
|
||||
@@ -30,7 +30,7 @@ import { prisma } from "@/lib/prisma";
|
||||
* Extend this list as new models are added in later phases:
|
||||
* e.g., "subscriber", "invoice", "servicePlan", "payment"
|
||||
*/
|
||||
export const TENANT_SCOPED_MODELS = ["user"] as const;
|
||||
export const TENANT_SCOPED_MODELS = ["user", "account", "accountingPeriod"] as const;
|
||||
|
||||
export type TenantScopedModel = (typeof TENANT_SCOPED_MODELS)[number];
|
||||
|
||||
@@ -162,6 +162,198 @@ export function withTenantContext(tenantId: string) {
|
||||
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);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user