Add companyAccounts schema, ledger helper, legacy migration script
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+126
-1
@@ -127,6 +127,9 @@ export const expenses = pgTable(
|
||||
.references(() => projects.id, { onDelete: 'cascade' }),
|
||||
categoryId: uuid('category_id').references(() => categories.id, { onDelete: 'set null' }),
|
||||
partyId: uuid('party_id').references((): any => parties.id, { onDelete: 'set null' }),
|
||||
accountId: uuid('account_id').references((): any => companyAccounts.id, {
|
||||
onDelete: 'set null'
|
||||
}),
|
||||
submittedBy: text('submitted_by')
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
@@ -452,6 +455,9 @@ export const invoices = pgTable(
|
||||
currency: text('currency').notNull().default('THB'),
|
||||
status: invoiceStatusEnum('status').notNull().default('draft'),
|
||||
expenseId: uuid('expense_id').references(() => expenses.id, { onDelete: 'set null' }),
|
||||
paymentAccountId: uuid('payment_account_id').references((): any => companyAccounts.id, {
|
||||
onDelete: 'set null'
|
||||
}),
|
||||
notes: text('notes'),
|
||||
pdfPath: text('pdf_path'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
@@ -826,6 +832,118 @@ export const cardBrandEnum = pgEnum('card_brand', [
|
||||
'other'
|
||||
]);
|
||||
|
||||
// ── Company Accounts (unified ledger) ──────────────────
|
||||
|
||||
export const companyAccountTypeEnum = pgEnum('company_account_type', [
|
||||
'bank',
|
||||
'credit_card',
|
||||
'cash',
|
||||
'mobile_money',
|
||||
'petty_cash',
|
||||
'loan',
|
||||
'other'
|
||||
]);
|
||||
|
||||
export const companyAccountTxnTypeEnum = pgEnum('company_account_txn_type', [
|
||||
'opening_balance',
|
||||
'expense',
|
||||
'invoice_payment',
|
||||
'transfer_in',
|
||||
'transfer_out',
|
||||
'deposit',
|
||||
'adjustment',
|
||||
'reconciliation'
|
||||
]);
|
||||
|
||||
export const companyAccounts = pgTable(
|
||||
'company_accounts',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
companyId: uuid('company_id')
|
||||
.notNull()
|
||||
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||
accountType: companyAccountTypeEnum('account_type').notNull(),
|
||||
name: text('name').notNull(),
|
||||
currency: text('currency').notNull().default('THB'),
|
||||
isActive: boolean('is_active').notNull().default(true),
|
||||
isArchived: boolean('is_archived').notNull().default(false),
|
||||
notes: text('notes'),
|
||||
sortOrder: integer('sort_order').notNull().default(0),
|
||||
createdBy: text('created_by').references(() => users.id, { onDelete: 'set null' }),
|
||||
deletedAt: timestamp('deleted_at', { withTimezone: true }),
|
||||
// Bank-specific
|
||||
bankName: text('bank_name'),
|
||||
accountNumber: text('account_number'),
|
||||
branch: text('branch'),
|
||||
swiftBic: text('swift_bic'),
|
||||
iban: text('iban'),
|
||||
accountHolderName: text('account_holder_name'),
|
||||
// Card-specific
|
||||
cardBrand: cardBrandEnum('card_brand'),
|
||||
last4: varchar('last4', { length: 4 }),
|
||||
cardholderName: text('cardholder_name'),
|
||||
expiryMonth: integer('expiry_month'),
|
||||
expiryYear: integer('expiry_year'),
|
||||
creditLimit: numeric('credit_limit', { precision: 15, scale: 2 }),
|
||||
statementCloseDay: integer('statement_close_day'),
|
||||
paymentDueDay: integer('payment_due_day'),
|
||||
// Banking integration link
|
||||
externalAccountId: uuid('external_account_id').references(() => externalAccounts.id, {
|
||||
onDelete: 'set null'
|
||||
}),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
|
||||
},
|
||||
(table) => [
|
||||
index('company_accounts_company_type_idx').on(table.companyId, table.accountType),
|
||||
index('company_accounts_company_archived_idx').on(table.companyId, table.isArchived)
|
||||
]
|
||||
);
|
||||
|
||||
export const companyAccountTransactions = pgTable(
|
||||
'company_account_transactions',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
accountId: uuid('account_id')
|
||||
.notNull()
|
||||
.references(() => companyAccounts.id, { onDelete: 'cascade' }),
|
||||
companyId: uuid('company_id')
|
||||
.notNull()
|
||||
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||
type: companyAccountTxnTypeEnum('type').notNull(),
|
||||
amount: numeric('amount', { precision: 15, scale: 2 }).notNull(),
|
||||
currency: text('currency').notNull(),
|
||||
occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull(),
|
||||
description: text('description'),
|
||||
reference: text('reference'),
|
||||
counterpartyAccountId: uuid('counterparty_account_id').references(
|
||||
(): any => companyAccounts.id,
|
||||
{ onDelete: 'set null' }
|
||||
),
|
||||
sourceExpenseId: uuid('source_expense_id').references(() => expenses.id, {
|
||||
onDelete: 'set null'
|
||||
}),
|
||||
sourceInvoiceId: uuid('source_invoice_id').references(() => invoices.id, {
|
||||
onDelete: 'set null'
|
||||
}),
|
||||
sourceExternalTransactionId: uuid('source_external_transaction_id').references(
|
||||
() => externalTransactions.id,
|
||||
{ onDelete: 'set null' }
|
||||
),
|
||||
fxRate: numeric('fx_rate', { precision: 18, scale: 8 }),
|
||||
fxAmount: numeric('fx_amount', { precision: 15, scale: 2 }),
|
||||
createdBy: text('created_by').references(() => users.id, { onDelete: 'set null' }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
|
||||
},
|
||||
(table) => [
|
||||
index('company_account_txns_account_occurred_idx').on(table.accountId, table.occurredAt),
|
||||
index('company_account_txns_company_occurred_idx').on(table.companyId, table.occurredAt),
|
||||
index('company_account_txns_expense_idx').on(table.sourceExpenseId),
|
||||
index('company_account_txns_invoice_idx').on(table.sourceInvoiceId)
|
||||
]
|
||||
);
|
||||
|
||||
export const companyBankAccounts = pgTable(
|
||||
'company_bank_accounts',
|
||||
{
|
||||
@@ -960,7 +1078,14 @@ export const companyLogEventEnum = pgEnum('company_log_event', [
|
||||
'document_deleted',
|
||||
'link_added',
|
||||
'link_updated',
|
||||
'link_deleted'
|
||||
'link_deleted',
|
||||
'account_created',
|
||||
'account_updated',
|
||||
'account_archived',
|
||||
'account_deleted',
|
||||
'account_transaction_added',
|
||||
'account_transfer_posted',
|
||||
'account_reconciled'
|
||||
]);
|
||||
|
||||
export const companyLog = pgTable(
|
||||
|
||||
Reference in New Issue
Block a user