From 92a07685b0683b63845d667294f1c285629f5ac2 Mon Sep 17 00:00:00 2001 From: grabowski Date: Wed, 15 Apr 2026 10:15:56 +0700 Subject: [PATCH] Add company profile schema (bank accounts, cards, addresses) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three new per-company tables backing the upcoming Profile page: - company_bank_accounts: bank/account name, account number, type, branch, SWIFT/BIC, IBAN, currency (default THB), isPrimary, isActive, notes - company_cards: brand (visa/mastercard/amex/jcb/unionpay/discover/ other), last4 (varchar(4)), cardholder, expiry month/year, nickname, optional FK to a bank account. Stores ONLY last 4 digits — never the full PAN, to avoid PCI-DSS scope. - company_addresses: type enum (legal/shipping/billing/other), label, recipient, full Thai address fields (subdistrict/district/ province/postal code), country defaulting to Thailand, contact person + phone, isDefault, notes Eight new audit events in companyLogEventEnum cover add/update/ remove operations on each. Page UI and export integration follow. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/lib/server/db/schema.ts | 108 +++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index 68f9680..8234968 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -10,7 +10,9 @@ import { numeric, date, index, - primaryKey + primaryKey, + integer, + varchar } from 'drizzle-orm/pg-core'; // ── Enums ────────────────────────────────────────────── @@ -671,6 +673,100 @@ export const featureRequestVotes = pgTable( (table) => [uniqueIndex('feature_request_votes_request_user_idx').on(table.requestId, table.userId)] ); +// ── Company Profile (bank accounts, cards, addresses) ── + +export const companyAddressTypeEnum = pgEnum('company_address_type', [ + 'legal', + 'shipping', + 'billing', + 'other' +]); + +export const cardBrandEnum = pgEnum('card_brand', [ + 'visa', + 'mastercard', + 'amex', + 'jcb', + 'unionpay', + 'discover', + 'other' +]); + +export const companyBankAccounts = pgTable( + 'company_bank_accounts', + { + id: uuid('id').primaryKey().defaultRandom(), + companyId: uuid('company_id') + .notNull() + .references(() => companies.id, { onDelete: 'cascade' }), + bankName: text('bank_name').notNull(), + accountName: text('account_name').notNull(), + accountNumber: text('account_number').notNull(), + accountType: text('account_type'), + branch: text('branch'), + swiftBic: text('swift_bic'), + iban: text('iban'), + currency: text('currency').notNull().default('THB'), + isPrimary: boolean('is_primary').notNull().default(false), + isActive: boolean('is_active').notNull().default(true), + notes: text('notes'), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow() + }, + (table) => [index('company_bank_accounts_company_idx').on(table.companyId)] +); + +export const companyCards = pgTable( + 'company_cards', + { + id: uuid('id').primaryKey().defaultRandom(), + companyId: uuid('company_id') + .notNull() + .references(() => companies.id, { onDelete: 'cascade' }), + brand: cardBrandEnum('brand').notNull(), + last4: varchar('last4', { length: 4 }).notNull(), + cardholderName: text('cardholder_name').notNull(), + expiryMonth: integer('expiry_month'), + expiryYear: integer('expiry_year'), + nickname: text('nickname'), + bankAccountId: uuid('bank_account_id').references(() => companyBankAccounts.id, { + onDelete: 'set null' + }), + isActive: boolean('is_active').notNull().default(true), + notes: text('notes'), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow() + }, + (table) => [index('company_cards_company_idx').on(table.companyId)] +); + +export const companyAddresses = pgTable( + 'company_addresses', + { + id: uuid('id').primaryKey().defaultRandom(), + companyId: uuid('company_id') + .notNull() + .references(() => companies.id, { onDelete: 'cascade' }), + type: companyAddressTypeEnum('type').notNull(), + label: text('label'), + recipient: text('recipient'), + addressLine1: text('address_line_1'), + addressLine2: text('address_line_2'), + subdistrict: text('subdistrict'), + district: text('district'), + province: text('province'), + postalCode: text('postal_code'), + country: text('country').notNull().default('Thailand'), + contactPerson: text('contact_person'), + contactPhone: text('contact_phone'), + isDefault: boolean('is_default').notNull().default(false), + notes: text('notes'), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow() + }, + (table) => [index('company_addresses_company_type_idx').on(table.companyId, table.type)] +); + // ── Company Log (Audit Trail) ────────────────────────── export const companyLogEventEnum = pgEnum('company_log_event', [ @@ -715,7 +811,15 @@ export const companyLogEventEnum = pgEnum('company_log_event', [ 'package_status_refreshed', 'shipping_account_added', 'shipping_account_removed', - 'financial_exported' + 'financial_exported', + 'bank_account_added', + 'bank_account_updated', + 'bank_account_removed', + 'card_added', + 'card_removed', + 'address_added', + 'address_updated', + 'address_removed' ]); export const companyLog = pgTable(