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:
Ryan Moon
2026-03-27 20:53:30 -05:00
parent e7853f59f2
commit 0a2d6e23af
17 changed files with 1431 additions and 28 deletions

View File

@@ -3,6 +3,12 @@ import { z } from 'zod'
export const BillingMode = z.enum(['consolidated', 'split'])
export type BillingMode = z.infer<typeof BillingMode>
export const PaymentProcessor = z.enum(['stripe', 'global_payments'])
export type PaymentProcessor = z.infer<typeof PaymentProcessor>
export const TaxExemptStatus = z.enum(['none', 'pending', 'approved'])
export type TaxExemptStatus = z.infer<typeof TaxExemptStatus>
export const AccountCreateSchema = z.object({
name: z.string().min(1).max(255),
email: z.string().email().optional(),
@@ -40,3 +46,76 @@ export type MemberUpdateInput = z.infer<typeof MemberUpdateSchema>
export const AccountSearchSchema = z.object({
q: z.string().min(1).max(255),
})
// --- Account Processor Link ---
export const ProcessorLinkCreateSchema = z.object({
accountId: z.string().uuid(),
processor: PaymentProcessor,
processorCustomerId: z.string().min(1).max(255),
})
export type ProcessorLinkCreateInput = z.infer<typeof ProcessorLinkCreateSchema>
export const ProcessorLinkUpdateSchema = z.object({
isActive: z.boolean().optional(),
})
export type ProcessorLinkUpdateInput = z.infer<typeof ProcessorLinkUpdateSchema>
// --- Account Payment Method ---
export const PaymentMethodCreateSchema = z.object({
accountId: z.string().uuid(),
processor: PaymentProcessor,
processorPaymentMethodId: z.string().min(1).max(255),
cardBrand: z.string().max(50).optional(),
lastFour: z.string().length(4).optional(),
expMonth: z.number().int().min(1).max(12).optional(),
expYear: z.number().int().min(2000).max(2100).optional(),
isDefault: z.boolean().default(false),
})
export type PaymentMethodCreateInput = z.infer<typeof PaymentMethodCreateSchema>
export const PaymentMethodUpdateSchema = z.object({
isDefault: z.boolean().optional(),
requiresUpdate: z.boolean().optional(),
})
export type PaymentMethodUpdateInput = z.infer<typeof PaymentMethodUpdateSchema>
// --- Tax Exemption ---
export const TaxExemptionCreateSchema = z.object({
accountId: z.string().uuid(),
certificateNumber: z.string().min(1).max(255),
certificateType: z.string().max(100).optional(),
issuingState: z.string().length(2).optional(),
expiresAt: z.string().date().optional(),
notes: z.string().optional(),
})
export type TaxExemptionCreateInput = z.infer<typeof TaxExemptionCreateSchema>
export const TaxExemptionUpdateSchema = z.object({
certificateNumber: z.string().min(1).max(255).optional(),
certificateType: z.string().max(100).optional(),
issuingState: z.string().length(2).optional(),
expiresAt: z.string().date().optional(),
notes: z.string().optional(),
})
export type TaxExemptionUpdateInput = z.infer<typeof TaxExemptionUpdateSchema>
// --- Lookup Tables ---
export const LookupCreateSchema = z.object({
name: z.string().min(1).max(100),
slug: z.string().min(1).max(100).regex(/^[a-z0-9_]+$/, 'Slug must be lowercase alphanumeric with underscores'),
description: z.string().optional(),
sortOrder: z.number().int().default(0),
})
export type LookupCreateInput = z.infer<typeof LookupCreateSchema>
export const LookupUpdateSchema = z.object({
name: z.string().min(1).max(100).optional(),
description: z.string().optional(),
sortOrder: z.number().int().optional(),
isActive: z.boolean().optional(),
})
export type LookupUpdateInput = z.infer<typeof LookupUpdateSchema>

View File

@@ -6,17 +6,35 @@ export type { RegisterInput, LoginInput } from './auth.schema.js'
export {
BillingMode,
PaymentProcessor,
TaxExemptStatus,
AccountCreateSchema,
AccountUpdateSchema,
MemberCreateSchema,
MemberUpdateSchema,
AccountSearchSchema,
ProcessorLinkCreateSchema,
ProcessorLinkUpdateSchema,
PaymentMethodCreateSchema,
PaymentMethodUpdateSchema,
TaxExemptionCreateSchema,
TaxExemptionUpdateSchema,
LookupCreateSchema,
LookupUpdateSchema,
} from './account.schema.js'
export type {
AccountCreateInput,
AccountUpdateInput,
MemberCreateInput,
MemberUpdateInput,
ProcessorLinkCreateInput,
ProcessorLinkUpdateInput,
PaymentMethodCreateInput,
PaymentMethodUpdateInput,
TaxExemptionCreateInput,
TaxExemptionUpdateInput,
LookupCreateInput,
LookupUpdateInput,
} from './account.schema.js'
export {
@@ -26,6 +44,8 @@ export {
SupplierUpdateSchema,
ItemCondition,
UnitStatus,
SystemItemCondition,
SystemUnitStatus,
ProductCreateSchema,
ProductUpdateSchema,
ProductSearchSchema,

View File

@@ -26,8 +26,23 @@ export type SupplierCreateInput = z.infer<typeof SupplierCreateSchema>
export const SupplierUpdateSchema = SupplierCreateSchema.partial()
export type SupplierUpdateInput = z.infer<typeof SupplierUpdateSchema>
export const ItemCondition = z.enum(['new', 'excellent', 'good', 'fair', 'poor'])
export const UnitStatus = z.enum(['available', 'sold', 'rented', 'in_repair', 'retired'])
// System slugs — used for code-level business logic references.
// Actual valid values are stored in lookup tables and are company-configurable.
export const SystemItemCondition = z.enum(['new', 'excellent', 'good', 'fair', 'poor'])
export const SystemUnitStatus = z.enum([
'available',
'sold',
'rented',
'on_trial',
'in_repair',
'layaway',
'lost',
'retired',
])
// API validation accepts any string slug (validated against lookup table at service layer)
export const ItemCondition = z.string().min(1).max(100)
export const UnitStatus = z.string().min(1).max(100)
export const ProductCreateSchema = z.object({
sku: z.string().max(100).optional(),