Remove multi-tenant company_id scoping from entire codebase

Drop company_id column from all 22 domain tables via migration.
Remove companyId from JWT payload, auth plugins, all service method
signatures (~215 occurrences), all route handlers (~105 occurrences),
test runner, test suites, and frontend auth store/types.

The company table stays as store settings (name, timezone). Tenant
isolation in a SaaS deployment would be at the database level (one
DB per customer) not the application level.

All 107 API tests pass. Zero TSC errors across all packages.
This commit is contained in:
Ryan Moon
2026-03-29 14:58:33 -05:00
parent 55f8591cf1
commit d36c6f7135
35 changed files with 353 additions and 511 deletions

View File

@@ -33,15 +33,13 @@ async function generateUniqueNumber(
db: PostgresJsDatabase<any>,
table: typeof accounts | typeof members,
column: typeof accounts.accountNumber | typeof members.memberNumber,
companyId: string,
companyIdColumn: Column,
): Promise<string> {
for (let attempt = 0; attempt < 10; attempt++) {
const num = String(Math.floor(100000 + Math.random() * 900000))
const [existing] = await db
.select({ id: table.id })
.from(table)
.where(and(eq(companyIdColumn, companyId), eq(column, num)))
.where(eq(column, num))
.limit(1)
if (!existing) return num
}
@@ -58,13 +56,12 @@ function normalizeAddress(address?: { street?: string; city?: string; state?: st
}
export const AccountService = {
async create(db: PostgresJsDatabase<any>, companyId: string, input: AccountCreateInput) {
const accountNumber = await generateUniqueNumber(db, accounts, accounts.accountNumber, companyId, accounts.companyId)
async create(db: PostgresJsDatabase<any>, input: AccountCreateInput) {
const accountNumber = await generateUniqueNumber(db, accounts, accounts.accountNumber)
const [account] = await db
.insert(accounts)
.values({
companyId,
accountNumber,
name: input.name,
email: input.email,
@@ -78,38 +75,38 @@ export const AccountService = {
return account
},
async getById(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async getById(db: PostgresJsDatabase<any>, id: string) {
const [account] = await db
.select()
.from(accounts)
.where(and(eq(accounts.id, id), eq(accounts.companyId, companyId)))
.where(eq(accounts.id, id))
.limit(1)
return account ?? null
},
async update(db: PostgresJsDatabase<any>, companyId: string, id: string, input: AccountUpdateInput) {
async update(db: PostgresJsDatabase<any>, id: string, input: AccountUpdateInput) {
const [account] = await db
.update(accounts)
.set({ ...input, updatedAt: new Date() })
.where(and(eq(accounts.id, id), eq(accounts.companyId, companyId)))
.where(eq(accounts.id, id))
.returning()
return account ?? null
},
async softDelete(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async softDelete(db: PostgresJsDatabase<any>, id: string) {
const [account] = await db
.update(accounts)
.set({ isActive: false, updatedAt: new Date() })
.where(and(eq(accounts.id, id), eq(accounts.companyId, companyId)))
.where(eq(accounts.id, id))
.returning()
return account ?? null
},
async list(db: PostgresJsDatabase<any>, companyId: string, params: PaginationInput) {
const baseWhere = and(eq(accounts.companyId, companyId), eq(accounts.isActive, true))
async list(db: PostgresJsDatabase<any>, params: PaginationInput) {
const baseWhere = eq(accounts.isActive, true)
const accountSearch = params.q
? buildSearchCondition(params.q, [accounts.name, accounts.email, accounts.phone, accounts.accountNumber])
@@ -156,7 +153,6 @@ export const AccountService = {
export const MemberService = {
async create(
db: PostgresJsDatabase<any>,
companyId: string,
input: {
accountId: string
firstName: string
@@ -171,7 +167,7 @@ export const MemberService = {
) {
// isMinor: explicit flag wins, else derive from DOB, else false
const minor = input.isMinor ?? (input.dateOfBirth ? isMinor(input.dateOfBirth) : false)
const memberNumber = await generateUniqueNumber(db, members, members.memberNumber, companyId, members.companyId)
const memberNumber = await generateUniqueNumber(db, members, members.memberNumber)
// Inherit email, phone, address from account if not provided
const [account] = await db
@@ -187,7 +183,6 @@ export const MemberService = {
const [member] = await db
.insert(members)
.values({
companyId,
memberNumber,
accountId: input.accountId,
firstName: input.firstName,
@@ -210,25 +205,21 @@ export const MemberService = {
return member
},
async getById(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async getById(db: PostgresJsDatabase<any>, id: string) {
const [member] = await db
.select()
.from(members)
.where(and(eq(members.id, id), eq(members.companyId, companyId)))
.where(eq(members.id, id))
.limit(1)
return member ?? null
},
async list(db: PostgresJsDatabase<any>, companyId: string, params: PaginationInput) {
const baseWhere = eq(members.companyId, companyId)
async list(db: PostgresJsDatabase<any>, params: PaginationInput) {
const searchCondition = params.q
? buildSearchCondition(params.q, [members.firstName, members.lastName, members.email, members.phone])
: undefined
const where = searchCondition ? and(baseWhere, searchCondition) : baseWhere
const sortableColumns: Record<string, Column> = {
first_name: members.firstName,
last_name: members.lastName,
@@ -239,7 +230,6 @@ export const MemberService = {
let query = db.select({
id: members.id,
accountId: members.accountId,
companyId: members.companyId,
firstName: members.firstName,
lastName: members.lastName,
dateOfBirth: members.dateOfBirth,
@@ -254,14 +244,14 @@ export const MemberService = {
})
.from(members)
.leftJoin(accounts, eq(members.accountId, accounts.id))
.where(where)
.where(searchCondition)
.$dynamic()
query = withSort(query, params.sort, params.order, sortableColumns, members.lastName)
query = withPagination(query, params.page, params.limit)
const [data, [{ total }]] = await Promise.all([
query,
db.select({ total: count() }).from(members).where(where),
db.select({ total: count() }).from(members).where(searchCondition),
])
return paginatedResponse(data, total, params.page, params.limit)
@@ -269,11 +259,10 @@ export const MemberService = {
async listByAccount(
db: PostgresJsDatabase<any>,
companyId: string,
accountId: string,
params: PaginationInput,
) {
const where = and(eq(members.companyId, companyId), eq(members.accountId, accountId))
const where = eq(members.accountId, accountId)
const sortableColumns: Record<string, Column> = {
first_name: members.firstName,
@@ -295,7 +284,6 @@ export const MemberService = {
async update(
db: PostgresJsDatabase<any>,
companyId: string,
id: string,
input: {
firstName?: string
@@ -319,27 +307,27 @@ export const MemberService = {
const [member] = await db
.update(members)
.set(updates)
.where(and(eq(members.id, id), eq(members.companyId, companyId)))
.where(eq(members.id, id))
.returning()
return member ?? null
},
async move(db: PostgresJsDatabase<any>, companyId: string, memberId: string, targetAccountId: string) {
const member = await this.getById(db, companyId, memberId)
async move(db: PostgresJsDatabase<any>, memberId: string, targetAccountId: string) {
const member = await this.getById(db, memberId)
if (!member) return null
const [updated] = await db
.update(members)
.set({ accountId: targetAccountId, updatedAt: new Date() })
.where(and(eq(members.id, memberId), eq(members.companyId, companyId)))
.where(eq(members.id, memberId))
.returning()
// If target account has no primary, set this member
const [targetAccount] = await db
.select()
.from(accounts)
.where(and(eq(accounts.id, targetAccountId), eq(accounts.companyId, companyId)))
.where(eq(accounts.id, targetAccountId))
.limit(1)
if (targetAccount && !targetAccount.primaryMemberId) {
await db
@@ -351,10 +339,10 @@ export const MemberService = {
return updated
},
async delete(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async delete(db: PostgresJsDatabase<any>, id: string) {
const [member] = await db
.delete(members)
.where(and(eq(members.id, id), eq(members.companyId, companyId)))
.where(eq(members.id, id))
.returning()
return member ?? null
@@ -362,11 +350,10 @@ export const MemberService = {
}
export const ProcessorLinkService = {
async create(db: PostgresJsDatabase<any>, companyId: string, input: ProcessorLinkCreateInput) {
async create(db: PostgresJsDatabase<any>, input: ProcessorLinkCreateInput) {
const [link] = await db
.insert(accountProcessorLinks)
.values({
companyId,
accountId: input.accountId,
processor: input.processor,
processorCustomerId: input.processorCustomerId,
@@ -375,17 +362,17 @@ export const ProcessorLinkService = {
return link
},
async getById(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async getById(db: PostgresJsDatabase<any>, id: string) {
const [link] = await db
.select()
.from(accountProcessorLinks)
.where(and(eq(accountProcessorLinks.id, id), eq(accountProcessorLinks.companyId, companyId)))
.where(eq(accountProcessorLinks.id, id))
.limit(1)
return link ?? null
},
async listByAccount(db: PostgresJsDatabase<any>, companyId: string, accountId: string, params: PaginationInput) {
const baseWhere = and(eq(accountProcessorLinks.companyId, companyId), eq(accountProcessorLinks.accountId, accountId))
async listByAccount(db: PostgresJsDatabase<any>, accountId: string, params: PaginationInput) {
const baseWhere = eq(accountProcessorLinks.accountId, accountId)
const searchCondition = params.q
? buildSearchCondition(params.q, [accountProcessorLinks.processorCustomerId, accountProcessorLinks.processor])
: undefined
@@ -408,26 +395,26 @@ export const ProcessorLinkService = {
return paginatedResponse(data, total, params.page, params.limit)
},
async update(db: PostgresJsDatabase<any>, companyId: string, id: string, input: ProcessorLinkUpdateInput) {
async update(db: PostgresJsDatabase<any>, id: string, input: ProcessorLinkUpdateInput) {
const [link] = await db
.update(accountProcessorLinks)
.set(input)
.where(and(eq(accountProcessorLinks.id, id), eq(accountProcessorLinks.companyId, companyId)))
.where(eq(accountProcessorLinks.id, id))
.returning()
return link ?? null
},
async delete(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async delete(db: PostgresJsDatabase<any>, id: string) {
const [link] = await db
.delete(accountProcessorLinks)
.where(and(eq(accountProcessorLinks.id, id), eq(accountProcessorLinks.companyId, companyId)))
.where(eq(accountProcessorLinks.id, id))
.returning()
return link ?? null
},
}
export const PaymentMethodService = {
async create(db: PostgresJsDatabase<any>, companyId: string, input: PaymentMethodCreateInput) {
async create(db: PostgresJsDatabase<any>, input: PaymentMethodCreateInput) {
// If this is the default, unset any existing default for this account
if (input.isDefault) {
await db
@@ -435,7 +422,6 @@ export const PaymentMethodService = {
.set({ isDefault: false })
.where(
and(
eq(accountPaymentMethods.companyId, companyId),
eq(accountPaymentMethods.accountId, input.accountId),
eq(accountPaymentMethods.isDefault, true),
),
@@ -445,7 +431,6 @@ export const PaymentMethodService = {
const [method] = await db
.insert(accountPaymentMethods)
.values({
companyId,
accountId: input.accountId,
processor: input.processor,
processorPaymentMethodId: input.processorPaymentMethodId,
@@ -459,17 +444,17 @@ export const PaymentMethodService = {
return method
},
async getById(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async getById(db: PostgresJsDatabase<any>, id: string) {
const [method] = await db
.select()
.from(accountPaymentMethods)
.where(and(eq(accountPaymentMethods.id, id), eq(accountPaymentMethods.companyId, companyId)))
.where(eq(accountPaymentMethods.id, id))
.limit(1)
return method ?? null
},
async listByAccount(db: PostgresJsDatabase<any>, companyId: string, accountId: string, params: PaginationInput) {
const baseWhere = and(eq(accountPaymentMethods.companyId, companyId), eq(accountPaymentMethods.accountId, accountId))
async listByAccount(db: PostgresJsDatabase<any>, accountId: string, params: PaginationInput) {
const baseWhere = eq(accountPaymentMethods.accountId, accountId)
const searchCondition = params.q
? buildSearchCondition(params.q, [accountPaymentMethods.cardBrand, accountPaymentMethods.lastFour])
: undefined
@@ -493,17 +478,16 @@ export const PaymentMethodService = {
return paginatedResponse(data, total, params.page, params.limit)
},
async update(db: PostgresJsDatabase<any>, companyId: string, id: string, input: PaymentMethodUpdateInput) {
async update(db: PostgresJsDatabase<any>, id: string, input: PaymentMethodUpdateInput) {
// If setting as default, unset existing default
if (input.isDefault) {
const existing = await this.getById(db, companyId, id)
const existing = await this.getById(db, id)
if (existing) {
await db
.update(accountPaymentMethods)
.set({ isDefault: false })
.where(
and(
eq(accountPaymentMethods.companyId, companyId),
eq(accountPaymentMethods.accountId, existing.accountId),
eq(accountPaymentMethods.isDefault, true),
),
@@ -514,26 +498,25 @@ export const PaymentMethodService = {
const [method] = await db
.update(accountPaymentMethods)
.set(input)
.where(and(eq(accountPaymentMethods.id, id), eq(accountPaymentMethods.companyId, companyId)))
.where(eq(accountPaymentMethods.id, id))
.returning()
return method ?? null
},
async delete(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async delete(db: PostgresJsDatabase<any>, id: string) {
const [method] = await db
.delete(accountPaymentMethods)
.where(and(eq(accountPaymentMethods.id, id), eq(accountPaymentMethods.companyId, companyId)))
.where(eq(accountPaymentMethods.id, id))
.returning()
return method ?? null
},
}
export const TaxExemptionService = {
async create(db: PostgresJsDatabase<any>, companyId: string, input: TaxExemptionCreateInput) {
async create(db: PostgresJsDatabase<any>, input: TaxExemptionCreateInput) {
const [exemption] = await db
.insert(taxExemptions)
.values({
companyId,
accountId: input.accountId,
certificateNumber: input.certificateNumber,
certificateType: input.certificateType,
@@ -546,17 +529,17 @@ export const TaxExemptionService = {
return exemption
},
async getById(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async getById(db: PostgresJsDatabase<any>, id: string) {
const [exemption] = await db
.select()
.from(taxExemptions)
.where(and(eq(taxExemptions.id, id), eq(taxExemptions.companyId, companyId)))
.where(eq(taxExemptions.id, id))
.limit(1)
return exemption ?? null
},
async listByAccount(db: PostgresJsDatabase<any>, companyId: string, accountId: string, params: PaginationInput) {
const baseWhere = and(eq(taxExemptions.companyId, companyId), eq(taxExemptions.accountId, accountId))
async listByAccount(db: PostgresJsDatabase<any>, accountId: string, params: PaginationInput) {
const baseWhere = eq(taxExemptions.accountId, accountId)
const searchCondition = params.q
? buildSearchCondition(params.q, [taxExemptions.certificateNumber, taxExemptions.certificateType, taxExemptions.issuingState])
: undefined
@@ -581,16 +564,16 @@ export const TaxExemptionService = {
return paginatedResponse(data, total, params.page, params.limit)
},
async update(db: PostgresJsDatabase<any>, companyId: string, id: string, input: TaxExemptionUpdateInput) {
async update(db: PostgresJsDatabase<any>, id: string, input: TaxExemptionUpdateInput) {
const [exemption] = await db
.update(taxExemptions)
.set({ ...input, updatedAt: new Date() })
.where(and(eq(taxExemptions.id, id), eq(taxExemptions.companyId, companyId)))
.where(eq(taxExemptions.id, id))
.returning()
return exemption ?? null
},
async approve(db: PostgresJsDatabase<any>, companyId: string, id: string, approvedBy: string) {
async approve(db: PostgresJsDatabase<any>, id: string, approvedBy: string) {
const [exemption] = await db
.update(taxExemptions)
.set({
@@ -599,12 +582,12 @@ export const TaxExemptionService = {
approvedAt: new Date(),
updatedAt: new Date(),
})
.where(and(eq(taxExemptions.id, id), eq(taxExemptions.companyId, companyId)))
.where(eq(taxExemptions.id, id))
.returning()
return exemption ?? null
},
async revoke(db: PostgresJsDatabase<any>, companyId: string, id: string, revokedBy: string, reason: string) {
async revoke(db: PostgresJsDatabase<any>, id: string, revokedBy: string, reason: string) {
const [exemption] = await db
.update(taxExemptions)
.set({
@@ -614,14 +597,14 @@ export const TaxExemptionService = {
revokedReason: reason,
updatedAt: new Date(),
})
.where(and(eq(taxExemptions.id, id), eq(taxExemptions.companyId, companyId)))
.where(eq(taxExemptions.id, id))
.returning()
return exemption ?? null
},
}
export const MemberIdentifierService = {
async create(db: PostgresJsDatabase<any>, companyId: string, input: MemberIdentifierCreateInput) {
async create(db: PostgresJsDatabase<any>, input: MemberIdentifierCreateInput) {
// If setting as primary, unset existing primary for this member
if (input.isPrimary) {
await db
@@ -638,7 +621,6 @@ export const MemberIdentifierService = {
const [identifier] = await db
.insert(memberIdentifiers)
.values({
companyId,
memberId: input.memberId,
type: input.type,
label: input.label,
@@ -655,8 +637,8 @@ export const MemberIdentifierService = {
return identifier
},
async listByMember(db: PostgresJsDatabase<any>, companyId: string, memberId: string, params: PaginationInput) {
const baseWhere = and(eq(memberIdentifiers.companyId, companyId), eq(memberIdentifiers.memberId, memberId))
async listByMember(db: PostgresJsDatabase<any>, memberId: string, params: PaginationInput) {
const baseWhere = eq(memberIdentifiers.memberId, memberId)
const searchCondition = params.q
? buildSearchCondition(params.q, [memberIdentifiers.value, memberIdentifiers.label, memberIdentifiers.issuingAuthority])
: undefined
@@ -679,18 +661,18 @@ export const MemberIdentifierService = {
return paginatedResponse(data, total, params.page, params.limit)
},
async getById(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async getById(db: PostgresJsDatabase<any>, id: string) {
const [identifier] = await db
.select()
.from(memberIdentifiers)
.where(and(eq(memberIdentifiers.id, id), eq(memberIdentifiers.companyId, companyId)))
.where(eq(memberIdentifiers.id, id))
.limit(1)
return identifier ?? null
},
async update(db: PostgresJsDatabase<any>, companyId: string, id: string, input: MemberIdentifierUpdateInput) {
async update(db: PostgresJsDatabase<any>, id: string, input: MemberIdentifierUpdateInput) {
if (input.isPrimary) {
const existing = await this.getById(db, companyId, id)
const existing = await this.getById(db, id)
if (existing) {
await db
.update(memberIdentifiers)
@@ -707,15 +689,15 @@ export const MemberIdentifierService = {
const [identifier] = await db
.update(memberIdentifiers)
.set({ ...input, updatedAt: new Date() })
.where(and(eq(memberIdentifiers.id, id), eq(memberIdentifiers.companyId, companyId)))
.where(eq(memberIdentifiers.id, id))
.returning()
return identifier ?? null
},
async delete(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async delete(db: PostgresJsDatabase<any>, id: string) {
const [identifier] = await db
.delete(memberIdentifiers)
.where(and(eq(memberIdentifiers.id, id), eq(memberIdentifiers.companyId, companyId)))
.where(eq(memberIdentifiers.id, id))
.returning()
return identifier ?? null
},

View File

@@ -26,7 +26,6 @@ export const FileService = {
async upload(
db: PostgresJsDatabase<any>,
storage: StorageProvider,
companyId: string,
input: {
data: Buffer
filename: string
@@ -54,7 +53,6 @@ export const FileService = {
.from(files)
.where(
and(
eq(files.companyId, companyId),
eq(files.entityType, input.entityType),
eq(files.entityId, input.entityId),
),
@@ -66,7 +64,7 @@ export const FileService = {
// Generate path
const fileId = randomUUID()
const ext = getExtension(input.contentType)
const path = `${companyId}/${input.entityType}/${input.entityId}/${input.category}-${fileId}.${ext}`
const path = `${input.entityType}/${input.entityId}/${input.category}-${fileId}.${ext}`
// Write to storage
await storage.put(path, input.data, input.contentType)
@@ -76,7 +74,6 @@ export const FileService = {
.insert(files)
.values({
id: fileId,
companyId,
path,
filename: input.filename,
contentType: input.contentType,
@@ -91,18 +88,17 @@ export const FileService = {
return file
},
async getById(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async getById(db: PostgresJsDatabase<any>, id: string) {
const [file] = await db
.select()
.from(files)
.where(and(eq(files.id, id), eq(files.companyId, companyId)))
.where(eq(files.id, id))
.limit(1)
return file ?? null
},
async listByEntity(
db: PostgresJsDatabase<any>,
companyId: string,
entityType: string,
entityId: string,
) {
@@ -111,7 +107,6 @@ export const FileService = {
.from(files)
.where(
and(
eq(files.companyId, companyId),
eq(files.entityType, entityType),
eq(files.entityId, entityId),
),
@@ -122,17 +117,16 @@ export const FileService = {
async delete(
db: PostgresJsDatabase<any>,
storage: StorageProvider,
companyId: string,
id: string,
) {
const file = await this.getById(db, companyId, id)
const file = await this.getById(db, id)
if (!file) return null
await storage.delete(file.path)
const [deleted] = await db
.delete(files)
.where(and(eq(files.id, id), eq(files.companyId, companyId)))
.where(eq(files.id, id))
.returning()
return deleted ?? null

View File

@@ -16,25 +16,25 @@ import {
} from '../utils/pagination.js'
export const CategoryService = {
async create(db: PostgresJsDatabase<any>, companyId: string, input: CategoryCreateInput) {
async create(db: PostgresJsDatabase<any>, input: CategoryCreateInput) {
const [category] = await db
.insert(categories)
.values({ companyId, ...input })
.values({ ...input })
.returning()
return category
},
async getById(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async getById(db: PostgresJsDatabase<any>, id: string) {
const [category] = await db
.select()
.from(categories)
.where(and(eq(categories.id, id), eq(categories.companyId, companyId)))
.where(eq(categories.id, id))
.limit(1)
return category ?? null
},
async list(db: PostgresJsDatabase<any>, companyId: string, params: PaginationInput) {
const baseWhere = and(eq(categories.companyId, companyId), eq(categories.isActive, true))
async list(db: PostgresJsDatabase<any>, params: PaginationInput) {
const baseWhere = eq(categories.isActive, true)
const searchCondition = params.q
? buildSearchCondition(params.q, [categories.name])
@@ -60,45 +60,45 @@ export const CategoryService = {
return paginatedResponse(data, total, params.page, params.limit)
},
async update(db: PostgresJsDatabase<any>, companyId: string, id: string, input: CategoryUpdateInput) {
async update(db: PostgresJsDatabase<any>, id: string, input: CategoryUpdateInput) {
const [category] = await db
.update(categories)
.set({ ...input, updatedAt: new Date() })
.where(and(eq(categories.id, id), eq(categories.companyId, companyId)))
.where(eq(categories.id, id))
.returning()
return category ?? null
},
async softDelete(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async softDelete(db: PostgresJsDatabase<any>, id: string) {
const [category] = await db
.update(categories)
.set({ isActive: false, updatedAt: new Date() })
.where(and(eq(categories.id, id), eq(categories.companyId, companyId)))
.where(eq(categories.id, id))
.returning()
return category ?? null
},
}
export const SupplierService = {
async create(db: PostgresJsDatabase<any>, companyId: string, input: SupplierCreateInput) {
async create(db: PostgresJsDatabase<any>, input: SupplierCreateInput) {
const [supplier] = await db
.insert(suppliers)
.values({ companyId, ...input })
.values({ ...input })
.returning()
return supplier
},
async getById(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async getById(db: PostgresJsDatabase<any>, id: string) {
const [supplier] = await db
.select()
.from(suppliers)
.where(and(eq(suppliers.id, id), eq(suppliers.companyId, companyId)))
.where(eq(suppliers.id, id))
.limit(1)
return supplier ?? null
},
async list(db: PostgresJsDatabase<any>, companyId: string, params: PaginationInput) {
const baseWhere = and(eq(suppliers.companyId, companyId), eq(suppliers.isActive, true))
async list(db: PostgresJsDatabase<any>, params: PaginationInput) {
const baseWhere = eq(suppliers.isActive, true)
const searchCondition = params.q
? buildSearchCondition(params.q, [suppliers.name, suppliers.contactName, suppliers.email])
@@ -123,20 +123,20 @@ export const SupplierService = {
return paginatedResponse(data, total, params.page, params.limit)
},
async update(db: PostgresJsDatabase<any>, companyId: string, id: string, input: SupplierUpdateInput) {
async update(db: PostgresJsDatabase<any>, id: string, input: SupplierUpdateInput) {
const [supplier] = await db
.update(suppliers)
.set({ ...input, updatedAt: new Date() })
.where(and(eq(suppliers.id, id), eq(suppliers.companyId, companyId)))
.where(eq(suppliers.id, id))
.returning()
return supplier ?? null
},
async softDelete(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async softDelete(db: PostgresJsDatabase<any>, id: string) {
const [supplier] = await db
.update(suppliers)
.set({ isActive: false, updatedAt: new Date() })
.where(and(eq(suppliers.id, id), eq(suppliers.companyId, companyId)))
.where(eq(suppliers.id, id))
.returning()
return supplier ?? null
},

View File

@@ -1,4 +1,4 @@
import { eq, and } from 'drizzle-orm'
import { eq } from 'drizzle-orm'
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
import { ForbiddenError } from '../lib/errors.js'
import {
@@ -14,46 +14,44 @@ function createLookupService(
systemSeeds: ReadonlyArray<{ slug: string; name: string; description: string; sortOrder: number }>,
) {
return {
async seedForCompany(db: PostgresJsDatabase<any>, companyId: string) {
async seedDefaults(db: PostgresJsDatabase<any>) {
const existing = await db
.select()
.from(table)
.where(and(eq(table.companyId, companyId), eq(table.isSystem, true)))
.where(eq(table.isSystem, true))
.limit(1)
if (existing.length > 0) return // already seeded
await db.insert(table).values(
systemSeeds.map((seed) => ({
companyId,
...seed,
isSystem: true,
})),
)
},
async list(db: PostgresJsDatabase<any>, companyId: string) {
async list(db: PostgresJsDatabase<any>) {
return db
.select()
.from(table)
.where(and(eq(table.companyId, companyId), eq(table.isActive, true)))
.where(eq(table.isActive, true))
.orderBy(table.sortOrder)
},
async getBySlug(db: PostgresJsDatabase<any>, companyId: string, slug: string) {
async getBySlug(db: PostgresJsDatabase<any>, slug: string) {
const [row] = await db
.select()
.from(table)
.where(and(eq(table.companyId, companyId), eq(table.slug, slug)))
.where(eq(table.slug, slug))
.limit(1)
return row ?? null
},
async create(db: PostgresJsDatabase<any>, companyId: string, input: LookupCreateInput) {
async create(db: PostgresJsDatabase<any>, input: LookupCreateInput) {
const [row] = await db
.insert(table)
.values({
companyId,
name: input.name,
slug: input.slug,
description: input.description,
@@ -64,12 +62,12 @@ function createLookupService(
return row
},
async update(db: PostgresJsDatabase<any>, companyId: string, id: string, input: LookupUpdateInput) {
async update(db: PostgresJsDatabase<any>, id: string, input: LookupUpdateInput) {
// Prevent modifying system rows' slug or system flag
const existing = await db
.select()
.from(table)
.where(and(eq(table.id, id), eq(table.companyId, companyId)))
.where(eq(table.id, id))
.limit(1)
if (!existing[0]) return null
@@ -80,16 +78,16 @@ function createLookupService(
const [row] = await db
.update(table)
.set(input)
.where(and(eq(table.id, id), eq(table.companyId, companyId)))
.where(eq(table.id, id))
.returning()
return row ?? null
},
async delete(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async delete(db: PostgresJsDatabase<any>, id: string) {
const existing = await db
.select()
.from(table)
.where(and(eq(table.id, id), eq(table.companyId, companyId)))
.where(eq(table.id, id))
.limit(1)
if (!existing[0]) return null
@@ -99,13 +97,13 @@ function createLookupService(
const [row] = await db
.delete(table)
.where(and(eq(table.id, id), eq(table.companyId, companyId)))
.where(eq(table.id, id))
.returning()
return row ?? null
},
async validateSlug(db: PostgresJsDatabase<any>, companyId: string, slug: string): Promise<boolean> {
const row = await this.getBySlug(db, companyId, slug)
async validateSlug(db: PostgresJsDatabase<any>, slug: string): Promise<boolean> {
const row = await this.getBySlug(db, slug)
return row !== null && row.isActive
},
}

View File

@@ -18,11 +18,10 @@ import {
import { UnitStatusService, ItemConditionService } from './lookup.service.js'
export const ProductService = {
async create(db: PostgresJsDatabase<any>, companyId: string, input: ProductCreateInput) {
async create(db: PostgresJsDatabase<any>, input: ProductCreateInput) {
const [product] = await db
.insert(products)
.values({
companyId,
...input,
price: input.price?.toString(),
minPrice: input.minPrice?.toString(),
@@ -32,17 +31,17 @@ export const ProductService = {
return product
},
async getById(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async getById(db: PostgresJsDatabase<any>, id: string) {
const [product] = await db
.select()
.from(products)
.where(and(eq(products.id, id), eq(products.companyId, companyId)))
.where(eq(products.id, id))
.limit(1)
return product ?? null
},
async list(db: PostgresJsDatabase<any>, companyId: string, params: PaginationInput) {
const baseWhere = and(eq(products.companyId, companyId), eq(products.isActive, true))
async list(db: PostgresJsDatabase<any>, params: PaginationInput) {
const baseWhere = eq(products.isActive, true)
const searchCondition = params.q
? buildSearchCondition(params.q, [products.name, products.sku, products.upc, products.brand])
@@ -72,17 +71,15 @@ export const ProductService = {
async update(
db: PostgresJsDatabase<any>,
companyId: string,
id: string,
input: ProductUpdateInput,
changedBy?: string,
) {
if (input.price !== undefined || input.minPrice !== undefined) {
const existing = await this.getById(db, companyId, id)
const existing = await this.getById(db, id)
if (existing) {
await db.insert(priceHistory).values({
productId: id,
companyId,
previousPrice: existing.price,
newPrice: input.price?.toString() ?? existing.price ?? '0',
previousMinPrice: existing.minPrice,
@@ -101,36 +98,35 @@ export const ProductService = {
const [product] = await db
.update(products)
.set(updates)
.where(and(eq(products.id, id), eq(products.companyId, companyId)))
.where(eq(products.id, id))
.returning()
return product ?? null
},
async softDelete(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async softDelete(db: PostgresJsDatabase<any>, id: string) {
const [product] = await db
.update(products)
.set({ isActive: false, updatedAt: new Date() })
.where(and(eq(products.id, id), eq(products.companyId, companyId)))
.where(eq(products.id, id))
.returning()
return product ?? null
},
}
export const InventoryUnitService = {
async create(db: PostgresJsDatabase<any>, companyId: string, input: InventoryUnitCreateInput) {
async create(db: PostgresJsDatabase<any>, input: InventoryUnitCreateInput) {
if (input.condition) {
const valid = await ItemConditionService.validateSlug(db, companyId, input.condition)
const valid = await ItemConditionService.validateSlug(db, input.condition)
if (!valid) throw new ValidationError(`Invalid condition: "${input.condition}"`)
}
if (input.status) {
const valid = await UnitStatusService.validateSlug(db, companyId, input.status)
const valid = await UnitStatusService.validateSlug(db, input.status)
if (!valid) throw new ValidationError(`Invalid status: "${input.status}"`)
}
const [unit] = await db
.insert(inventoryUnits)
.values({
companyId,
productId: input.productId,
locationId: input.locationId,
serialNumber: input.serialNumber,
@@ -144,25 +140,21 @@ export const InventoryUnitService = {
return unit
},
async getById(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async getById(db: PostgresJsDatabase<any>, id: string) {
const [unit] = await db
.select()
.from(inventoryUnits)
.where(and(eq(inventoryUnits.id, id), eq(inventoryUnits.companyId, companyId)))
.where(eq(inventoryUnits.id, id))
.limit(1)
return unit ?? null
},
async listByProduct(
db: PostgresJsDatabase<any>,
companyId: string,
productId: string,
params: PaginationInput,
) {
const where = and(
eq(inventoryUnits.companyId, companyId),
eq(inventoryUnits.productId, productId),
)
const where = eq(inventoryUnits.productId, productId)
const sortableColumns: Record<string, Column> = {
serial_number: inventoryUnits.serialNumber,
@@ -185,16 +177,15 @@ export const InventoryUnitService = {
async update(
db: PostgresJsDatabase<any>,
companyId: string,
id: string,
input: InventoryUnitUpdateInput,
) {
if (input.condition) {
const valid = await ItemConditionService.validateSlug(db, companyId, input.condition)
const valid = await ItemConditionService.validateSlug(db, input.condition)
if (!valid) throw new ValidationError(`Invalid condition: "${input.condition}"`)
}
if (input.status) {
const valid = await UnitStatusService.validateSlug(db, companyId, input.status)
const valid = await UnitStatusService.validateSlug(db, input.status)
if (!valid) throw new ValidationError(`Invalid status: "${input.status}"`)
}
@@ -204,7 +195,7 @@ export const InventoryUnitService = {
const [unit] = await db
.update(inventoryUnits)
.set(updates)
.where(and(eq(inventoryUnits.id, id), eq(inventoryUnits.companyId, companyId)))
.where(eq(inventoryUnits.id, id))
.returning()
return unit ?? null
},

View File

@@ -18,16 +18,16 @@ export const RbacService = {
await db.insert(permissions).values(toInsert)
},
/** Seed default roles for a company */
async seedRolesForCompany(db: PostgresJsDatabase<any>, companyId: string) {
/** Seed default roles */
async seedDefaultRoles(db: PostgresJsDatabase<any>) {
const existingRoles = await db
.select({ slug: roles.slug })
.from(roles)
.where(and(eq(roles.companyId, companyId), eq(roles.isSystem, true)))
.where(eq(roles.isSystem, true))
if (existingRoles.length > 0) return // already seeded
// Get all permission records for slug id mapping
// Get all permission records for slug -> id mapping
const allPerms = await db.select().from(permissions)
const permMap = new Map(allPerms.map((p) => [p.slug, p.id]))
@@ -35,7 +35,6 @@ export const RbacService = {
const [role] = await db
.insert(roles)
.values({
companyId,
name: roleDef.name,
slug: roleDef.slug,
description: roleDef.description,
@@ -91,9 +90,9 @@ export const RbacService = {
return db.select().from(permissions).orderBy(permissions.domain, permissions.action)
},
/** List roles for a company */
async listRoles(db: PostgresJsDatabase<any>, companyId: string, params: PaginationInput) {
const baseWhere = and(eq(roles.companyId, companyId), eq(roles.isActive, true))
/** List roles */
async listRoles(db: PostgresJsDatabase<any>, params: PaginationInput) {
const baseWhere = eq(roles.isActive, true)
const searchCondition = params.q
? buildSearchCondition(params.q, [roles.name, roles.slug])
@@ -120,11 +119,11 @@ export const RbacService = {
},
/** Get role with its permissions */
async getRoleWithPermissions(db: PostgresJsDatabase<any>, companyId: string, roleId: string) {
async getRoleWithPermissions(db: PostgresJsDatabase<any>, roleId: string) {
const [role] = await db
.select()
.from(roles)
.where(and(eq(roles.id, roleId), eq(roles.companyId, companyId)))
.where(eq(roles.id, roleId))
.limit(1)
if (!role) return null
@@ -141,13 +140,11 @@ export const RbacService = {
/** Create a custom role */
async createRole(
db: PostgresJsDatabase<any>,
companyId: string,
input: { name: string; slug: string; description?: string; permissionSlugs: string[] },
) {
const [role] = await db
.insert(roles)
.values({
companyId,
name: input.name,
slug: input.slug,
description: input.description,
@@ -156,7 +153,7 @@ export const RbacService = {
.returning()
await this.setRolePermissions(db, role.id, input.permissionSlugs)
return this.getRoleWithPermissions(db, companyId, role.id)
return this.getRoleWithPermissions(db, role.id)
},
/** Update role permissions (replace all) */
@@ -182,7 +179,6 @@ export const RbacService = {
/** Update a role */
async updateRole(
db: PostgresJsDatabase<any>,
companyId: string,
roleId: string,
input: { name?: string; description?: string; permissionSlugs?: string[] },
) {
@@ -194,22 +190,22 @@ export const RbacService = {
...(input.description !== undefined ? { description: input.description } : {}),
updatedAt: new Date(),
})
.where(and(eq(roles.id, roleId), eq(roles.companyId, companyId)))
.where(eq(roles.id, roleId))
}
if (input.permissionSlugs) {
await this.setRolePermissions(db, roleId, input.permissionSlugs)
}
return this.getRoleWithPermissions(db, companyId, roleId)
return this.getRoleWithPermissions(db, roleId)
},
/** Delete a custom role */
async deleteRole(db: PostgresJsDatabase<any>, companyId: string, roleId: string) {
async deleteRole(db: PostgresJsDatabase<any>, roleId: string) {
const [role] = await db
.select()
.from(roles)
.where(and(eq(roles.id, roleId), eq(roles.companyId, companyId)))
.where(eq(roles.id, roleId))
.limit(1)
if (!role) return null

View File

@@ -30,15 +30,13 @@ async function generateUniqueNumber(
db: PostgresJsDatabase<any>,
table: typeof repairTickets | typeof repairBatches,
column: typeof repairTickets.ticketNumber | typeof repairBatches.batchNumber,
companyId: string,
companyIdColumn: Column,
): Promise<string> {
for (let attempt = 0; attempt < 10; attempt++) {
const num = String(Math.floor(100000 + Math.random() * 900000))
const [existing] = await db
.select({ id: table.id })
.from(table)
.where(and(eq(companyIdColumn, companyId), eq(column, num)))
.where(eq(column, num))
.limit(1)
if (!existing) return num
}
@@ -46,15 +44,14 @@ async function generateUniqueNumber(
}
export const RepairTicketService = {
async create(db: PostgresJsDatabase<any>, companyId: string, input: RepairTicketCreateInput) {
async create(db: PostgresJsDatabase<any>, input: RepairTicketCreateInput) {
const ticketNumber = await generateUniqueNumber(
db, repairTickets, repairTickets.ticketNumber, companyId, repairTickets.companyId,
db, repairTickets, repairTickets.ticketNumber,
)
const [ticket] = await db
.insert(repairTickets)
.values({
companyId,
ticketNumber,
customerName: input.customerName,
customerPhone: input.customerPhone,
@@ -76,16 +73,16 @@ export const RepairTicketService = {
return ticket
},
async getById(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async getById(db: PostgresJsDatabase<any>, id: string) {
const [ticket] = await db
.select()
.from(repairTickets)
.where(and(eq(repairTickets.id, id), eq(repairTickets.companyId, companyId)))
.where(eq(repairTickets.id, id))
.limit(1)
return ticket ?? null
},
async list(db: PostgresJsDatabase<any>, companyId: string, params: PaginationInput, filters?: {
async list(db: PostgresJsDatabase<any>, params: PaginationInput, filters?: {
status?: string[]
conditionIn?: string[]
isBatch?: boolean
@@ -97,7 +94,7 @@ export const RepairTicketService = {
completedDateFrom?: string
completedDateTo?: string
}) {
const conditions: SQL[] = [eq(repairTickets.companyId, companyId)]
const conditions: SQL[] = []
if (params.q) {
const search = buildSearchCondition(params.q, [
@@ -128,7 +125,7 @@ export const RepairTicketService = {
if (filters?.completedDateFrom) conditions.push(gte(repairTickets.completedDate, new Date(filters.completedDateFrom)))
if (filters?.completedDateTo) conditions.push(lte(repairTickets.completedDate, new Date(filters.completedDateTo)))
const where = and(...conditions)
const where = conditions.length > 0 ? and(...conditions) : undefined
const sortableColumns: Record<string, Column> = {
ticket_number: repairTickets.ticketNumber,
@@ -151,8 +148,8 @@ export const RepairTicketService = {
return paginatedResponse(data, total, params.page, params.limit)
},
async listByBatch(db: PostgresJsDatabase<any>, companyId: string, batchId: string, params: PaginationInput) {
const baseWhere = and(eq(repairTickets.companyId, companyId), eq(repairTickets.repairBatchId, batchId))
async listByBatch(db: PostgresJsDatabase<any>, batchId: string, params: PaginationInput) {
const baseWhere = eq(repairTickets.repairBatchId, batchId)
const searchCondition = params.q
? buildSearchCondition(params.q, [repairTickets.ticketNumber, repairTickets.customerName, repairTickets.instrumentDescription])
: undefined
@@ -177,7 +174,7 @@ export const RepairTicketService = {
return paginatedResponse(data, total, params.page, params.limit)
},
async update(db: PostgresJsDatabase<any>, companyId: string, id: string, input: RepairTicketUpdateInput) {
async update(db: PostgresJsDatabase<any>, id: string, input: RepairTicketUpdateInput) {
const values: Record<string, unknown> = { ...input, updatedAt: new Date() }
if (input.estimatedCost !== undefined) values.estimatedCost = input.estimatedCost.toString()
if (input.promisedDate !== undefined) values.promisedDate = input.promisedDate ? new Date(input.promisedDate) : null
@@ -185,12 +182,12 @@ export const RepairTicketService = {
const [ticket] = await db
.update(repairTickets)
.set(values)
.where(and(eq(repairTickets.id, id), eq(repairTickets.companyId, companyId)))
.where(eq(repairTickets.id, id))
.returning()
return ticket ?? null
},
async updateStatus(db: PostgresJsDatabase<any>, companyId: string, id: string, status: string) {
async updateStatus(db: PostgresJsDatabase<any>, id: string, status: string) {
const updates: Record<string, unknown> = { status, updatedAt: new Date() }
if (status === 'ready' || status === 'picked_up' || status === 'delivered') {
updates.completedDate = new Date()
@@ -199,17 +196,17 @@ export const RepairTicketService = {
const [ticket] = await db
.update(repairTickets)
.set(updates)
.where(and(eq(repairTickets.id, id), eq(repairTickets.companyId, companyId)))
.where(eq(repairTickets.id, id))
.returning()
return ticket ?? null
},
async delete(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async delete(db: PostgresJsDatabase<any>, id: string) {
// Soft-cancel: set status to cancelled rather than hard delete
const [ticket] = await db
.update(repairTickets)
.set({ status: 'cancelled', updatedAt: new Date() })
.where(and(eq(repairTickets.id, id), eq(repairTickets.companyId, companyId)))
.where(eq(repairTickets.id, id))
.returning()
return ticket ?? null
},
@@ -254,19 +251,7 @@ export const RepairLineItemService = {
return paginatedResponse(data, total, params.page, params.limit)
},
async verifyOwnership(db: PostgresJsDatabase<any>, companyId: string, lineItemId: string): Promise<boolean> {
const [item] = await db
.select({ ticketCompanyId: repairTickets.companyId })
.from(repairLineItems)
.innerJoin(repairTickets, eq(repairLineItems.repairTicketId, repairTickets.id))
.where(and(eq(repairLineItems.id, lineItemId), eq(repairTickets.companyId, companyId)))
.limit(1)
return !!item
},
async update(db: PostgresJsDatabase<any>, companyId: string, id: string, input: RepairLineItemUpdateInput) {
if (!(await this.verifyOwnership(db, companyId, id))) return null
async update(db: PostgresJsDatabase<any>, id: string, input: RepairLineItemUpdateInput) {
const values: Record<string, unknown> = { ...input }
if (input.qty !== undefined) values.qty = input.qty.toString()
if (input.unitPrice !== undefined) values.unitPrice = input.unitPrice.toString()
@@ -281,9 +266,7 @@ export const RepairLineItemService = {
return item ?? null
},
async delete(db: PostgresJsDatabase<any>, companyId: string, id: string) {
if (!(await this.verifyOwnership(db, companyId, id))) return null
async delete(db: PostgresJsDatabase<any>, id: string) {
const [item] = await db
.delete(repairLineItems)
.where(eq(repairLineItems.id, id))
@@ -293,15 +276,14 @@ export const RepairLineItemService = {
}
export const RepairBatchService = {
async create(db: PostgresJsDatabase<any>, companyId: string, input: RepairBatchCreateInput) {
async create(db: PostgresJsDatabase<any>, input: RepairBatchCreateInput) {
const batchNumber = await generateUniqueNumber(
db, repairBatches, repairBatches.batchNumber, companyId, repairBatches.companyId,
db, repairBatches, repairBatches.batchNumber,
)
const [batch] = await db
.insert(repairBatches)
.values({
companyId,
batchNumber,
accountId: input.accountId,
locationId: input.locationId,
@@ -317,21 +299,19 @@ export const RepairBatchService = {
return batch
},
async getById(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async getById(db: PostgresJsDatabase<any>, id: string) {
const [batch] = await db
.select()
.from(repairBatches)
.where(and(eq(repairBatches.id, id), eq(repairBatches.companyId, companyId)))
.where(eq(repairBatches.id, id))
.limit(1)
return batch ?? null
},
async list(db: PostgresJsDatabase<any>, companyId: string, params: PaginationInput) {
const baseWhere = eq(repairBatches.companyId, companyId)
async list(db: PostgresJsDatabase<any>, params: PaginationInput) {
const searchCondition = params.q
? buildSearchCondition(params.q, [repairBatches.batchNumber, repairBatches.contactName, repairBatches.contactEmail])
: undefined
const where = searchCondition ? and(baseWhere, searchCondition) : baseWhere
const sortableColumns: Record<string, Column> = {
batch_number: repairBatches.batchNumber,
@@ -340,19 +320,19 @@ export const RepairBatchService = {
created_at: repairBatches.createdAt,
}
let query = db.select().from(repairBatches).where(where).$dynamic()
let query = db.select().from(repairBatches).where(searchCondition).$dynamic()
query = withSort(query, params.sort, params.order, sortableColumns, repairBatches.createdAt)
query = withPagination(query, params.page, params.limit)
const [data, [{ total }]] = await Promise.all([
query,
db.select({ total: count() }).from(repairBatches).where(where),
db.select({ total: count() }).from(repairBatches).where(searchCondition),
])
return paginatedResponse(data, total, params.page, params.limit)
},
async update(db: PostgresJsDatabase<any>, companyId: string, id: string, input: RepairBatchUpdateInput) {
async update(db: PostgresJsDatabase<any>, id: string, input: RepairBatchUpdateInput) {
const values: Record<string, unknown> = { ...input, updatedAt: new Date() }
if (input.pickupDate !== undefined) values.pickupDate = input.pickupDate ? new Date(input.pickupDate) : null
if (input.dueDate !== undefined) values.dueDate = input.dueDate ? new Date(input.dueDate) : null
@@ -360,12 +340,12 @@ export const RepairBatchService = {
const [batch] = await db
.update(repairBatches)
.set(values)
.where(and(eq(repairBatches.id, id), eq(repairBatches.companyId, companyId)))
.where(eq(repairBatches.id, id))
.returning()
return batch ?? null
},
async updateStatus(db: PostgresJsDatabase<any>, companyId: string, id: string, status: string) {
async updateStatus(db: PostgresJsDatabase<any>, id: string, status: string) {
const updates: Record<string, unknown> = { status, updatedAt: new Date() }
if (status === 'completed') updates.completedDate = new Date()
if (status === 'delivered') updates.deliveredDate = new Date()
@@ -373,12 +353,12 @@ export const RepairBatchService = {
const [batch] = await db
.update(repairBatches)
.set(updates)
.where(and(eq(repairBatches.id, id), eq(repairBatches.companyId, companyId)))
.where(eq(repairBatches.id, id))
.returning()
return batch ?? null
},
async approve(db: PostgresJsDatabase<any>, companyId: string, id: string, approvedBy: string) {
async approve(db: PostgresJsDatabase<any>, id: string, approvedBy: string) {
const [batch] = await db
.update(repairBatches)
.set({
@@ -387,30 +367,29 @@ export const RepairBatchService = {
approvedAt: new Date(),
updatedAt: new Date(),
})
.where(and(eq(repairBatches.id, id), eq(repairBatches.companyId, companyId)))
.where(eq(repairBatches.id, id))
.returning()
return batch ?? null
},
async reject(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async reject(db: PostgresJsDatabase<any>, id: string) {
const [batch] = await db
.update(repairBatches)
.set({
approvalStatus: 'rejected',
updatedAt: new Date(),
})
.where(and(eq(repairBatches.id, id), eq(repairBatches.companyId, companyId)))
.where(eq(repairBatches.id, id))
.returning()
return batch ?? null
},
}
export const RepairServiceTemplateService = {
async create(db: PostgresJsDatabase<any>, companyId: string, input: RepairServiceTemplateCreateInput) {
async create(db: PostgresJsDatabase<any>, input: RepairServiceTemplateCreateInput) {
const [template] = await db
.insert(repairServiceTemplates)
.values({
companyId,
name: input.name,
instrumentType: input.instrumentType,
size: input.size,
@@ -424,8 +403,8 @@ export const RepairServiceTemplateService = {
return template
},
async list(db: PostgresJsDatabase<any>, companyId: string, params: PaginationInput) {
const baseWhere = and(eq(repairServiceTemplates.companyId, companyId), eq(repairServiceTemplates.isActive, true))
async list(db: PostgresJsDatabase<any>, params: PaginationInput) {
const baseWhere = eq(repairServiceTemplates.isActive, true)
const searchCondition = params.q
? buildSearchCondition(params.q, [repairServiceTemplates.name, repairServiceTemplates.instrumentType, repairServiceTemplates.size, repairServiceTemplates.description])
: undefined
@@ -451,7 +430,7 @@ export const RepairServiceTemplateService = {
return paginatedResponse(data, total, params.page, params.limit)
},
async update(db: PostgresJsDatabase<any>, companyId: string, id: string, input: RepairServiceTemplateUpdateInput) {
async update(db: PostgresJsDatabase<any>, id: string, input: RepairServiceTemplateUpdateInput) {
const values: Record<string, unknown> = { ...input, updatedAt: new Date() }
if (input.defaultPrice !== undefined) values.defaultPrice = input.defaultPrice.toString()
if (input.defaultCost !== undefined) values.defaultCost = input.defaultCost.toString()
@@ -459,16 +438,16 @@ export const RepairServiceTemplateService = {
const [template] = await db
.update(repairServiceTemplates)
.set(values)
.where(and(eq(repairServiceTemplates.id, id), eq(repairServiceTemplates.companyId, companyId)))
.where(eq(repairServiceTemplates.id, id))
.returning()
return template ?? null
},
async delete(db: PostgresJsDatabase<any>, companyId: string, id: string) {
async delete(db: PostgresJsDatabase<any>, id: string) {
const [template] = await db
.update(repairServiceTemplates)
.set({ isActive: false, updatedAt: new Date() })
.where(and(eq(repairServiceTemplates.id, id), eq(repairServiceTemplates.companyId, companyId)))
.where(eq(repairServiceTemplates.id, id))
.returning()
return template ?? null
},
@@ -514,16 +493,7 @@ export const RepairNoteService = {
return paginatedResponse(data, total, params.page, params.limit)
},
async delete(db: PostgresJsDatabase<any>, companyId: string, id: string) {
// Verify note belongs to a ticket owned by this company
const [owned] = await db
.select({ id: repairNotes.id })
.from(repairNotes)
.innerJoin(repairTickets, eq(repairNotes.repairTicketId, repairTickets.id))
.where(and(eq(repairNotes.id, id), eq(repairTickets.companyId, companyId)))
.limit(1)
if (!owned) return null
async delete(db: PostgresJsDatabase<any>, id: string) {
const [note] = await db
.delete(repairNotes)
.where(eq(repairNotes.id, id))