Add lookup tables, payment methods, tax exemptions, and processor link APIs
Replace unit_status and item_condition pgEnums with company-scoped lookup tables that support custom values. Add account_payment_method table, tax_exemption table with approve/revoke workflow, and CRUD routes for processor links. Validate inventory unit status/condition against lookup tables at service layer.
This commit is contained in:
@@ -1,7 +1,23 @@
|
||||
import { eq, and, sql, count } from 'drizzle-orm'
|
||||
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
|
||||
import { accounts, members } from '../db/schema/accounts.js'
|
||||
import type { AccountCreateInput, AccountUpdateInput, PaginationInput } from '@forte/shared/schemas'
|
||||
import {
|
||||
accounts,
|
||||
members,
|
||||
accountProcessorLinks,
|
||||
accountPaymentMethods,
|
||||
taxExemptions,
|
||||
} from '../db/schema/accounts.js'
|
||||
import type {
|
||||
AccountCreateInput,
|
||||
AccountUpdateInput,
|
||||
ProcessorLinkCreateInput,
|
||||
ProcessorLinkUpdateInput,
|
||||
PaymentMethodCreateInput,
|
||||
PaymentMethodUpdateInput,
|
||||
TaxExemptionCreateInput,
|
||||
TaxExemptionUpdateInput,
|
||||
PaginationInput,
|
||||
} from '@forte/shared/schemas'
|
||||
import { isMinor } from '@forte/shared/utils'
|
||||
import {
|
||||
withPagination,
|
||||
@@ -193,3 +209,223 @@ export const MemberService = {
|
||||
return member ?? null
|
||||
},
|
||||
}
|
||||
|
||||
export const ProcessorLinkService = {
|
||||
async create(db: PostgresJsDatabase, companyId: string, input: ProcessorLinkCreateInput) {
|
||||
const [link] = await db
|
||||
.insert(accountProcessorLinks)
|
||||
.values({
|
||||
companyId,
|
||||
accountId: input.accountId,
|
||||
processor: input.processor,
|
||||
processorCustomerId: input.processorCustomerId,
|
||||
})
|
||||
.returning()
|
||||
return link
|
||||
},
|
||||
|
||||
async getById(db: PostgresJsDatabase, companyId: string, id: string) {
|
||||
const [link] = await db
|
||||
.select()
|
||||
.from(accountProcessorLinks)
|
||||
.where(and(eq(accountProcessorLinks.id, id), eq(accountProcessorLinks.companyId, companyId)))
|
||||
.limit(1)
|
||||
return link ?? null
|
||||
},
|
||||
|
||||
async listByAccount(db: PostgresJsDatabase, companyId: string, accountId: string) {
|
||||
return db
|
||||
.select()
|
||||
.from(accountProcessorLinks)
|
||||
.where(
|
||||
and(
|
||||
eq(accountProcessorLinks.companyId, companyId),
|
||||
eq(accountProcessorLinks.accountId, accountId),
|
||||
),
|
||||
)
|
||||
},
|
||||
|
||||
async update(db: PostgresJsDatabase, companyId: string, id: string, input: ProcessorLinkUpdateInput) {
|
||||
const [link] = await db
|
||||
.update(accountProcessorLinks)
|
||||
.set(input)
|
||||
.where(and(eq(accountProcessorLinks.id, id), eq(accountProcessorLinks.companyId, companyId)))
|
||||
.returning()
|
||||
return link ?? null
|
||||
},
|
||||
|
||||
async delete(db: PostgresJsDatabase, companyId: string, id: string) {
|
||||
const [link] = await db
|
||||
.delete(accountProcessorLinks)
|
||||
.where(and(eq(accountProcessorLinks.id, id), eq(accountProcessorLinks.companyId, companyId)))
|
||||
.returning()
|
||||
return link ?? null
|
||||
},
|
||||
}
|
||||
|
||||
export const PaymentMethodService = {
|
||||
async create(db: PostgresJsDatabase, companyId: string, input: PaymentMethodCreateInput) {
|
||||
// If this is the default, unset any existing default for this account
|
||||
if (input.isDefault) {
|
||||
await db
|
||||
.update(accountPaymentMethods)
|
||||
.set({ isDefault: false })
|
||||
.where(
|
||||
and(
|
||||
eq(accountPaymentMethods.companyId, companyId),
|
||||
eq(accountPaymentMethods.accountId, input.accountId),
|
||||
eq(accountPaymentMethods.isDefault, true),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
const [method] = await db
|
||||
.insert(accountPaymentMethods)
|
||||
.values({
|
||||
companyId,
|
||||
accountId: input.accountId,
|
||||
processor: input.processor,
|
||||
processorPaymentMethodId: input.processorPaymentMethodId,
|
||||
cardBrand: input.cardBrand,
|
||||
lastFour: input.lastFour,
|
||||
expMonth: input.expMonth,
|
||||
expYear: input.expYear,
|
||||
isDefault: input.isDefault,
|
||||
})
|
||||
.returning()
|
||||
return method
|
||||
},
|
||||
|
||||
async getById(db: PostgresJsDatabase, companyId: string, id: string) {
|
||||
const [method] = await db
|
||||
.select()
|
||||
.from(accountPaymentMethods)
|
||||
.where(and(eq(accountPaymentMethods.id, id), eq(accountPaymentMethods.companyId, companyId)))
|
||||
.limit(1)
|
||||
return method ?? null
|
||||
},
|
||||
|
||||
async listByAccount(db: PostgresJsDatabase, companyId: string, accountId: string) {
|
||||
return db
|
||||
.select()
|
||||
.from(accountPaymentMethods)
|
||||
.where(
|
||||
and(
|
||||
eq(accountPaymentMethods.companyId, companyId),
|
||||
eq(accountPaymentMethods.accountId, accountId),
|
||||
),
|
||||
)
|
||||
},
|
||||
|
||||
async update(db: PostgresJsDatabase, companyId: string, id: string, input: PaymentMethodUpdateInput) {
|
||||
// If setting as default, unset existing default
|
||||
if (input.isDefault) {
|
||||
const existing = await this.getById(db, companyId, 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),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const [method] = await db
|
||||
.update(accountPaymentMethods)
|
||||
.set(input)
|
||||
.where(and(eq(accountPaymentMethods.id, id), eq(accountPaymentMethods.companyId, companyId)))
|
||||
.returning()
|
||||
return method ?? null
|
||||
},
|
||||
|
||||
async delete(db: PostgresJsDatabase, companyId: string, id: string) {
|
||||
const [method] = await db
|
||||
.delete(accountPaymentMethods)
|
||||
.where(and(eq(accountPaymentMethods.id, id), eq(accountPaymentMethods.companyId, companyId)))
|
||||
.returning()
|
||||
return method ?? null
|
||||
},
|
||||
}
|
||||
|
||||
export const TaxExemptionService = {
|
||||
async create(db: PostgresJsDatabase, companyId: string, input: TaxExemptionCreateInput) {
|
||||
const [exemption] = await db
|
||||
.insert(taxExemptions)
|
||||
.values({
|
||||
companyId,
|
||||
accountId: input.accountId,
|
||||
certificateNumber: input.certificateNumber,
|
||||
certificateType: input.certificateType,
|
||||
issuingState: input.issuingState,
|
||||
expiresAt: input.expiresAt,
|
||||
notes: input.notes,
|
||||
status: 'pending',
|
||||
})
|
||||
.returning()
|
||||
return exemption
|
||||
},
|
||||
|
||||
async getById(db: PostgresJsDatabase, companyId: string, id: string) {
|
||||
const [exemption] = await db
|
||||
.select()
|
||||
.from(taxExemptions)
|
||||
.where(and(eq(taxExemptions.id, id), eq(taxExemptions.companyId, companyId)))
|
||||
.limit(1)
|
||||
return exemption ?? null
|
||||
},
|
||||
|
||||
async listByAccount(db: PostgresJsDatabase, companyId: string, accountId: string) {
|
||||
return db
|
||||
.select()
|
||||
.from(taxExemptions)
|
||||
.where(
|
||||
and(
|
||||
eq(taxExemptions.companyId, companyId),
|
||||
eq(taxExemptions.accountId, accountId),
|
||||
),
|
||||
)
|
||||
},
|
||||
|
||||
async update(db: PostgresJsDatabase, companyId: string, 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)))
|
||||
.returning()
|
||||
return exemption ?? null
|
||||
},
|
||||
|
||||
async approve(db: PostgresJsDatabase, companyId: string, id: string, approvedBy: string) {
|
||||
const [exemption] = await db
|
||||
.update(taxExemptions)
|
||||
.set({
|
||||
status: 'approved',
|
||||
approvedBy,
|
||||
approvedAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(and(eq(taxExemptions.id, id), eq(taxExemptions.companyId, companyId)))
|
||||
.returning()
|
||||
return exemption ?? null
|
||||
},
|
||||
|
||||
async revoke(db: PostgresJsDatabase, companyId: string, id: string, revokedBy: string, reason: string) {
|
||||
const [exemption] = await db
|
||||
.update(taxExemptions)
|
||||
.set({
|
||||
status: 'none',
|
||||
revokedBy,
|
||||
revokedAt: new Date(),
|
||||
revokedReason: reason,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(and(eq(taxExemptions.id, id), eq(taxExemptions.companyId, companyId)))
|
||||
.returning()
|
||||
return exemption ?? null
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user