member_identifier table with type, value, issuing authority, expiry, front/back image storage (base64 in Postgres), primary flag. CRUD endpoints under /members/:memberId/identifiers. Zod schemas with constrained type enum.
161 lines
6.3 KiB
TypeScript
161 lines
6.3 KiB
TypeScript
import {
|
|
pgTable,
|
|
uuid,
|
|
varchar,
|
|
text,
|
|
jsonb,
|
|
timestamp,
|
|
boolean,
|
|
integer,
|
|
date,
|
|
pgEnum,
|
|
} from 'drizzle-orm/pg-core'
|
|
import { companies } from './stores.js'
|
|
|
|
export const billingModeEnum = pgEnum('billing_mode', ['consolidated', 'split'])
|
|
export const taxExemptStatusEnum = pgEnum('tax_exempt_status', ['none', 'pending', 'approved'])
|
|
|
|
export const accounts = pgTable('account', {
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
companyId: uuid('company_id')
|
|
.notNull()
|
|
.references(() => companies.id),
|
|
accountNumber: varchar('account_number', { length: 50 }),
|
|
name: varchar('name', { length: 255 }).notNull(),
|
|
email: varchar('email', { length: 255 }),
|
|
phone: varchar('phone', { length: 50 }),
|
|
address: jsonb('address').$type<{
|
|
street?: string
|
|
city?: string
|
|
state?: string
|
|
zip?: string
|
|
}>(),
|
|
billingMode: billingModeEnum('billing_mode').notNull().default('consolidated'),
|
|
primaryMemberId: uuid('primary_member_id'),
|
|
notes: text('notes'),
|
|
isActive: boolean('is_active').notNull().default(true),
|
|
legacyId: varchar('legacy_id', { length: 255 }),
|
|
legacySource: varchar('legacy_source', { length: 50 }),
|
|
migratedAt: timestamp('migrated_at', { withTimezone: true }),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
})
|
|
|
|
export const members = pgTable('member', {
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
accountId: uuid('account_id')
|
|
.notNull()
|
|
.references(() => accounts.id),
|
|
companyId: uuid('company_id')
|
|
.notNull()
|
|
.references(() => companies.id),
|
|
memberNumber: varchar('member_number', { length: 50 }),
|
|
firstName: varchar('first_name', { length: 100 }).notNull(),
|
|
lastName: varchar('last_name', { length: 100 }).notNull(),
|
|
dateOfBirth: date('date_of_birth'),
|
|
isMinor: boolean('is_minor').notNull().default(false),
|
|
email: varchar('email', { length: 255 }),
|
|
phone: varchar('phone', { length: 50 }),
|
|
notes: text('notes'),
|
|
legacyId: varchar('legacy_id', { length: 255 }),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
})
|
|
|
|
export const memberIdentifiers = pgTable('member_identifier', {
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
memberId: uuid('member_id')
|
|
.notNull()
|
|
.references(() => members.id),
|
|
companyId: uuid('company_id')
|
|
.notNull()
|
|
.references(() => companies.id),
|
|
type: varchar('type', { length: 50 }).notNull(),
|
|
label: varchar('label', { length: 100 }),
|
|
value: varchar('value', { length: 255 }).notNull(),
|
|
issuingAuthority: varchar('issuing_authority', { length: 255 }),
|
|
issuedDate: date('issued_date'),
|
|
expiresAt: date('expires_at'),
|
|
imageFront: text('image_front'),
|
|
imageBack: text('image_back'),
|
|
notes: text('notes'),
|
|
isPrimary: boolean('is_primary').notNull().default(false),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
})
|
|
|
|
export type MemberIdentifier = typeof memberIdentifiers.$inferSelect
|
|
export type MemberIdentifierInsert = typeof memberIdentifiers.$inferInsert
|
|
|
|
export const processorEnum = pgEnum('payment_processor', ['stripe', 'global_payments'])
|
|
|
|
export const accountProcessorLinks = pgTable('account_processor_link', {
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
accountId: uuid('account_id')
|
|
.notNull()
|
|
.references(() => accounts.id),
|
|
companyId: uuid('company_id')
|
|
.notNull()
|
|
.references(() => companies.id),
|
|
processor: processorEnum('processor').notNull(),
|
|
processorCustomerId: varchar('processor_customer_id', { length: 255 }).notNull(),
|
|
isActive: boolean('is_active').notNull().default(true),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
})
|
|
|
|
export type AccountProcessorLink = typeof accountProcessorLinks.$inferSelect
|
|
export type AccountProcessorLinkInsert = typeof accountProcessorLinks.$inferInsert
|
|
|
|
export const accountPaymentMethods = pgTable('account_payment_method', {
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
accountId: uuid('account_id')
|
|
.notNull()
|
|
.references(() => accounts.id),
|
|
companyId: uuid('company_id')
|
|
.notNull()
|
|
.references(() => companies.id),
|
|
processor: processorEnum('processor').notNull(),
|
|
processorPaymentMethodId: varchar('processor_payment_method_id', { length: 255 }).notNull(),
|
|
cardBrand: varchar('card_brand', { length: 50 }),
|
|
lastFour: varchar('last_four', { length: 4 }),
|
|
expMonth: integer('exp_month'),
|
|
expYear: integer('exp_year'),
|
|
isDefault: boolean('is_default').notNull().default(false),
|
|
requiresUpdate: boolean('requires_update').notNull().default(false),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
})
|
|
|
|
export type AccountPaymentMethod = typeof accountPaymentMethods.$inferSelect
|
|
export type AccountPaymentMethodInsert = typeof accountPaymentMethods.$inferInsert
|
|
|
|
export const taxExemptions = pgTable('tax_exemption', {
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
accountId: uuid('account_id')
|
|
.notNull()
|
|
.references(() => accounts.id),
|
|
companyId: uuid('company_id')
|
|
.notNull()
|
|
.references(() => companies.id),
|
|
status: taxExemptStatusEnum('status').notNull().default('pending'),
|
|
certificateNumber: varchar('certificate_number', { length: 255 }).notNull(),
|
|
certificateType: varchar('certificate_type', { length: 100 }),
|
|
issuingState: varchar('issuing_state', { length: 2 }),
|
|
expiresAt: date('expires_at'),
|
|
approvedBy: uuid('approved_by'),
|
|
approvedAt: timestamp('approved_at', { withTimezone: true }),
|
|
revokedBy: uuid('revoked_by'),
|
|
revokedAt: timestamp('revoked_at', { withTimezone: true }),
|
|
revokedReason: text('revoked_reason'),
|
|
notes: text('notes'),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
})
|
|
|
|
export type TaxExemption = typeof taxExemptions.$inferSelect
|
|
export type TaxExemptionInsert = typeof taxExemptions.$inferInsert
|
|
|
|
export type Account = typeof accounts.$inferSelect
|
|
export type AccountInsert = typeof accounts.$inferInsert
|
|
export type Member = typeof members.$inferSelect
|
|
export type MemberInsert = typeof members.$inferInsert
|