Add company profile schema (bank accounts, cards, addresses)
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) <noreply@anthropic.com>
This commit is contained in:
+106
-2
@@ -10,7 +10,9 @@ import {
|
|||||||
numeric,
|
numeric,
|
||||||
date,
|
date,
|
||||||
index,
|
index,
|
||||||
primaryKey
|
primaryKey,
|
||||||
|
integer,
|
||||||
|
varchar
|
||||||
} from 'drizzle-orm/pg-core';
|
} from 'drizzle-orm/pg-core';
|
||||||
|
|
||||||
// ── Enums ──────────────────────────────────────────────
|
// ── Enums ──────────────────────────────────────────────
|
||||||
@@ -671,6 +673,100 @@ export const featureRequestVotes = pgTable(
|
|||||||
(table) => [uniqueIndex('feature_request_votes_request_user_idx').on(table.requestId, table.userId)]
|
(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) ──────────────────────────
|
// ── Company Log (Audit Trail) ──────────────────────────
|
||||||
|
|
||||||
export const companyLogEventEnum = pgEnum('company_log_event', [
|
export const companyLogEventEnum = pgEnum('company_log_event', [
|
||||||
@@ -715,7 +811,15 @@ export const companyLogEventEnum = pgEnum('company_log_event', [
|
|||||||
'package_status_refreshed',
|
'package_status_refreshed',
|
||||||
'shipping_account_added',
|
'shipping_account_added',
|
||||||
'shipping_account_removed',
|
'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(
|
export const companyLog = pgTable(
|
||||||
|
|||||||
Reference in New Issue
Block a user