Major expansion: HR module, CRM, integrations, packages, validation pipeline
Validate / validate (push) Successful in 34s
Validate / validate (push) Successful in 34s
HR module: - Multi-role per company (admin/manager/user/viewer/hr orthogonal) - Employees with salary history, terminate/reactivate - Per-company public holidays (seeded from ppraserts/thailand-open-data with manual fallback for unsupported years) - Leave types (editable defaults), leave requests with approve/reject - Per-employee leave balances (auto-seeded), remaining-days hint on request form, HR balance summary on requests page - Thai-compliant payroll: SSO 5% capped, PND1 brackets, monthly WHT - Payslip generation with editable line items, finalize/mark-paid, pdf-lib PDF download - CSV export of leave per employee or company-wide CRM & invoicing: - Customer/supplier party database with archive - Invoice line items, VAT 7%, status transitions, PDF generation - Outgoing/incoming direction; incoming auto-creates linked expense Package tracking: - packages + package_events + shipping_accounts tables - 8 carrier stubs (UPS/FedEx/DHL/USPS/Flash Express/Kerry/J&T/TH Post) with API doc references for future implementation - Manual status updates with timeline - Customs duty invoice flow on delivery - Per-company carrier credentials (admin only) Integrations scaffold: - external_accounts + external_transactions (Kasikorn K-Biz, Ether.fi) - Manual transaction matching to expenses Infrastructure: - APP_NAME env var for branding - Soft-delete for companies and parties - Light/dark mode toggle, dark-mode classes throughout - pre-push hook (husky) + Gitea/GitHub Actions running svelte-check with --threshold warning + vite build - npm run validate combines both checks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+445
-3
@@ -15,7 +15,7 @@ import {
|
||||
|
||||
// ── Enums ──────────────────────────────────────────────
|
||||
|
||||
export const companyRoleEnum = pgEnum('company_role', ['admin', 'manager', 'user', 'viewer']);
|
||||
export const companyRoleEnum = pgEnum('company_role', ['admin', 'manager', 'user', 'viewer', 'hr']);
|
||||
export const expenseStatusEnum = pgEnum('expense_status', ['pending', 'approved', 'rejected']);
|
||||
|
||||
// ── Users ──────────────────────────────────────────────
|
||||
@@ -77,7 +77,7 @@ export const companyMembers = pgTable(
|
||||
companyId: uuid('company_id')
|
||||
.notNull()
|
||||
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||
role: companyRoleEnum('role').notNull(),
|
||||
roles: companyRoleEnum('roles').array().notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow()
|
||||
},
|
||||
(table) => [uniqueIndex('company_members_user_company_idx').on(table.userId, table.companyId)]
|
||||
@@ -124,6 +124,7 @@ export const expenses = pgTable(
|
||||
.notNull()
|
||||
.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' }),
|
||||
submittedBy: text('submitted_by')
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
@@ -192,6 +193,422 @@ export const budgetAllocations = pgTable('budget_allocations', {
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow()
|
||||
});
|
||||
|
||||
// ── Employees ──────────────────────────────────────────
|
||||
|
||||
export const employees = pgTable('employees', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
companyId: uuid('company_id')
|
||||
.notNull()
|
||||
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||
userId: text('user_id').references(() => users.id, { onDelete: 'set null' }),
|
||||
firstName: text('first_name').notNull(),
|
||||
lastName: text('last_name').notNull(),
|
||||
displayName: text('display_name'),
|
||||
email: text('email'),
|
||||
phone: text('phone'),
|
||||
employeeCode: text('employee_code'),
|
||||
position: text('position'),
|
||||
department: text('department'),
|
||||
hireDate: date('hire_date').notNull(),
|
||||
terminationDate: date('termination_date'),
|
||||
nationalId: text('national_id'),
|
||||
taxId: text('tax_id'),
|
||||
bankName: text('bank_name'),
|
||||
bankAccount: text('bank_account'),
|
||||
isActive: boolean('is_active').notNull().default(true),
|
||||
deletedAt: timestamp('deleted_at', { withTimezone: true }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
|
||||
});
|
||||
|
||||
export const salaryHistory = pgTable('salary_history', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
employeeId: uuid('employee_id')
|
||||
.notNull()
|
||||
.references(() => employees.id, { onDelete: 'cascade' }),
|
||||
effectiveFrom: date('effective_from').notNull(),
|
||||
grossSalary: numeric('gross_salary', { precision: 15, scale: 2 }).notNull(),
|
||||
currency: text('currency').notNull().default('THB'),
|
||||
note: text('note'),
|
||||
setBy: text('set_by')
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow()
|
||||
});
|
||||
|
||||
// ── Public Holidays ────────────────────────────────────
|
||||
|
||||
export const publicHolidays = pgTable(
|
||||
'public_holidays',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
companyId: uuid('company_id')
|
||||
.notNull()
|
||||
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||
date: date('date').notNull(),
|
||||
name: text('name').notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow()
|
||||
},
|
||||
(table) => [uniqueIndex('public_holidays_company_date_idx').on(table.companyId, table.date)]
|
||||
);
|
||||
|
||||
// ── Leave ──────────────────────────────────────────────
|
||||
|
||||
export const leaveStatusEnum = pgEnum('leave_status', ['pending', 'approved', 'rejected']);
|
||||
|
||||
export const leaveTypes = pgTable(
|
||||
'leave_types',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
companyId: uuid('company_id')
|
||||
.notNull()
|
||||
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull(),
|
||||
defaultDaysPerYear: numeric('default_days_per_year', { precision: 5, scale: 2 }),
|
||||
isPaid: boolean('is_paid').notNull().default(true),
|
||||
color: text('color'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow()
|
||||
},
|
||||
(table) => [uniqueIndex('leave_types_company_name_idx').on(table.companyId, table.name)]
|
||||
);
|
||||
|
||||
export const leaveBalances = pgTable(
|
||||
'leave_balances',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
employeeId: uuid('employee_id')
|
||||
.notNull()
|
||||
.references(() => employees.id, { onDelete: 'cascade' }),
|
||||
leaveTypeId: uuid('leave_type_id')
|
||||
.notNull()
|
||||
.references(() => leaveTypes.id, { onDelete: 'cascade' }),
|
||||
year: numeric('year', { precision: 4, scale: 0 }).notNull(),
|
||||
allocated: numeric('allocated', { precision: 5, scale: 2 }).notNull().default('0'),
|
||||
used: numeric('used', { precision: 5, scale: 2 }).notNull().default('0'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
|
||||
},
|
||||
(table) => [
|
||||
uniqueIndex('leave_balances_emp_type_year_idx').on(
|
||||
table.employeeId,
|
||||
table.leaveTypeId,
|
||||
table.year
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
export const leaveRequests = pgTable('leave_requests', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
companyId: uuid('company_id')
|
||||
.notNull()
|
||||
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||
employeeId: uuid('employee_id')
|
||||
.notNull()
|
||||
.references(() => employees.id, { onDelete: 'cascade' }),
|
||||
leaveTypeId: uuid('leave_type_id')
|
||||
.notNull()
|
||||
.references(() => leaveTypes.id, { onDelete: 'cascade' }),
|
||||
startDate: date('start_date').notNull(),
|
||||
endDate: date('end_date').notNull(),
|
||||
days: numeric('days', { precision: 5, scale: 2 }).notNull(),
|
||||
reason: text('reason'),
|
||||
status: leaveStatusEnum('status').notNull().default('pending'),
|
||||
reviewedBy: text('reviewed_by').references(() => users.id),
|
||||
reviewedAt: timestamp('reviewed_at', { withTimezone: true }),
|
||||
rejectionReason: text('rejection_reason'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
|
||||
});
|
||||
|
||||
// ── Payslips ───────────────────────────────────────────
|
||||
|
||||
export const payslipStatusEnum = pgEnum('payslip_status', ['draft', 'finalized', 'paid']);
|
||||
export const payslipLineTypeEnum = pgEnum('payslip_line_type', ['earning', 'deduction']);
|
||||
|
||||
export const payslips = pgTable(
|
||||
'payslips',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
companyId: uuid('company_id')
|
||||
.notNull()
|
||||
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||
employeeId: uuid('employee_id')
|
||||
.notNull()
|
||||
.references(() => employees.id, { onDelete: 'cascade' }),
|
||||
periodYear: numeric('period_year', { precision: 4, scale: 0 }).notNull(),
|
||||
periodMonth: numeric('period_month', { precision: 2, scale: 0 }).notNull(),
|
||||
grossSalary: numeric('gross_salary', { precision: 15, scale: 2 }).notNull(),
|
||||
overtime: numeric('overtime', { precision: 15, scale: 2 }).notNull().default('0'),
|
||||
bonus: numeric('bonus', { precision: 15, scale: 2 }).notNull().default('0'),
|
||||
otherEarnings: numeric('other_earnings', { precision: 15, scale: 2 }).notNull().default('0'),
|
||||
ssoEmployee: numeric('sso_employee', { precision: 15, scale: 2 }).notNull().default('0'),
|
||||
ssoEmployer: numeric('sso_employer', { precision: 15, scale: 2 }).notNull().default('0'),
|
||||
incomeTax: numeric('income_tax', { precision: 15, scale: 2 }).notNull().default('0'),
|
||||
otherDeductions: numeric('other_deductions', { precision: 15, scale: 2 }).notNull().default('0'),
|
||||
netPay: numeric('net_pay', { precision: 15, scale: 2 }).notNull(),
|
||||
currency: text('currency').notNull().default('THB'),
|
||||
status: payslipStatusEnum('status').notNull().default('draft'),
|
||||
finalizedAt: timestamp('finalized_at', { withTimezone: true }),
|
||||
paidAt: timestamp('paid_at', { withTimezone: true }),
|
||||
generatedBy: text('generated_by')
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
pdfPath: text('pdf_path'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
|
||||
},
|
||||
(table) => [
|
||||
uniqueIndex('payslips_emp_period_idx').on(table.employeeId, table.periodYear, table.periodMonth)
|
||||
]
|
||||
);
|
||||
|
||||
export const payslipLineItems = pgTable('payslip_line_items', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
payslipId: uuid('payslip_id')
|
||||
.notNull()
|
||||
.references(() => payslips.id, { onDelete: 'cascade' }),
|
||||
type: payslipLineTypeEnum('type').notNull(),
|
||||
label: text('label').notNull(),
|
||||
amount: numeric('amount', { precision: 15, scale: 2 }).notNull(),
|
||||
isStatutory: boolean('is_statutory').notNull().default(false)
|
||||
});
|
||||
|
||||
// ── Parties (Customers / Suppliers) ────────────────────
|
||||
|
||||
export const partyTypeEnum = pgEnum('party_type', ['customer', 'supplier', 'both']);
|
||||
|
||||
export const parties = pgTable('parties', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
companyId: uuid('company_id')
|
||||
.notNull()
|
||||
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||
type: partyTypeEnum('type').notNull().default('customer'),
|
||||
name: text('name').notNull(),
|
||||
contactPerson: text('contact_person'),
|
||||
email: text('email'),
|
||||
phone: text('phone'),
|
||||
website: text('website'),
|
||||
taxId: text('tax_id'),
|
||||
addressLine1: text('address_line_1'),
|
||||
addressLine2: text('address_line_2'),
|
||||
city: text('city'),
|
||||
postalCode: text('postal_code'),
|
||||
country: text('country'),
|
||||
paymentTerms: text('payment_terms'),
|
||||
notes: text('notes'),
|
||||
isActive: boolean('is_active').notNull().default(true),
|
||||
deletedAt: timestamp('deleted_at', { withTimezone: true }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
|
||||
});
|
||||
|
||||
// ── Invoices ───────────────────────────────────────────
|
||||
|
||||
export const invoiceDirectionEnum = pgEnum('invoice_direction', ['incoming', 'outgoing']);
|
||||
export const invoiceStatusEnum = pgEnum('invoice_status', [
|
||||
'draft',
|
||||
'sent',
|
||||
'paid',
|
||||
'overdue',
|
||||
'cancelled'
|
||||
]);
|
||||
|
||||
export const invoices = pgTable(
|
||||
'invoices',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
companyId: uuid('company_id')
|
||||
.notNull()
|
||||
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||
partyId: uuid('party_id')
|
||||
.notNull()
|
||||
.references(() => parties.id, { onDelete: 'restrict' }),
|
||||
direction: invoiceDirectionEnum('direction').notNull(),
|
||||
invoiceNumber: text('invoice_number').notNull(),
|
||||
issueDate: date('issue_date').notNull(),
|
||||
dueDate: date('due_date'),
|
||||
subtotal: numeric('subtotal', { precision: 15, scale: 2 }).notNull(),
|
||||
vat: numeric('vat', { precision: 15, scale: 2 }).notNull().default('0'),
|
||||
total: numeric('total', { precision: 15, scale: 2 }).notNull(),
|
||||
currency: text('currency').notNull().default('THB'),
|
||||
status: invoiceStatusEnum('status').notNull().default('draft'),
|
||||
expenseId: uuid('expense_id').references(() => expenses.id, { onDelete: 'set null' }),
|
||||
notes: text('notes'),
|
||||
pdfPath: text('pdf_path'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
|
||||
},
|
||||
(table) => [
|
||||
uniqueIndex('invoices_company_direction_number_idx').on(
|
||||
table.companyId,
|
||||
table.direction,
|
||||
table.invoiceNumber
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
export const invoiceLineItems = pgTable('invoice_line_items', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
invoiceId: uuid('invoice_id')
|
||||
.notNull()
|
||||
.references(() => invoices.id, { onDelete: 'cascade' }),
|
||||
description: text('description').notNull(),
|
||||
quantity: numeric('quantity', { precision: 10, scale: 2 }).notNull().default('1'),
|
||||
unitPrice: numeric('unit_price', { precision: 15, scale: 2 }).notNull(),
|
||||
total: numeric('total', { precision: 15, scale: 2 }).notNull()
|
||||
});
|
||||
|
||||
// ── External Integrations ──────────────────────────────
|
||||
|
||||
export const integrationProviderEnum = pgEnum('integration_provider', [
|
||||
'kasikorn_kbiz',
|
||||
'etherfi',
|
||||
'manual'
|
||||
]);
|
||||
export const txDirectionEnum = pgEnum('tx_direction', ['credit', 'debit']);
|
||||
|
||||
export const externalAccounts = pgTable('external_accounts', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
companyId: uuid('company_id')
|
||||
.notNull()
|
||||
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||
provider: integrationProviderEnum('provider').notNull(),
|
||||
displayName: text('display_name').notNull(),
|
||||
accountIdentifier: text('account_identifier').notNull(),
|
||||
credentialsEncrypted: text('credentials_encrypted'),
|
||||
isActive: boolean('is_active').notNull().default(true),
|
||||
lastSyncedAt: timestamp('last_synced_at', { withTimezone: true }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
|
||||
});
|
||||
|
||||
export const externalTransactions = pgTable(
|
||||
'external_transactions',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
accountId: uuid('account_id')
|
||||
.notNull()
|
||||
.references(() => externalAccounts.id, { onDelete: 'cascade' }),
|
||||
companyId: uuid('company_id')
|
||||
.notNull()
|
||||
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||
externalId: text('external_id').notNull(),
|
||||
occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull(),
|
||||
amount: numeric('amount', { precision: 15, scale: 2 }).notNull(),
|
||||
currency: text('currency').notNull(),
|
||||
direction: txDirectionEnum('direction').notNull(),
|
||||
description: text('description'),
|
||||
counterparty: text('counterparty'),
|
||||
matchedExpenseId: uuid('matched_expense_id').references(() => expenses.id, {
|
||||
onDelete: 'set null'
|
||||
}),
|
||||
rawPayload: text('raw_payload'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow()
|
||||
},
|
||||
(table) => [uniqueIndex('external_tx_account_extid_idx').on(table.accountId, table.externalId)]
|
||||
);
|
||||
|
||||
// ── Package Tracking ───────────────────────────────────
|
||||
|
||||
export const packageDirectionEnum = pgEnum('package_direction', ['incoming', 'outgoing']);
|
||||
export const packageStatusEnum = pgEnum('package_status', [
|
||||
'pending',
|
||||
'in_transit',
|
||||
'out_for_delivery',
|
||||
'delivered',
|
||||
'exception',
|
||||
'returned',
|
||||
'cancelled'
|
||||
]);
|
||||
export const carrierEnum = pgEnum('carrier', [
|
||||
'ups',
|
||||
'fedex',
|
||||
'dhl',
|
||||
'usps',
|
||||
'flash_express',
|
||||
'kerry_th',
|
||||
'jnt_express',
|
||||
'thailand_post',
|
||||
'other'
|
||||
]);
|
||||
|
||||
export const packages = pgTable(
|
||||
'packages',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
companyId: uuid('company_id')
|
||||
.notNull()
|
||||
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||
direction: packageDirectionEnum('direction').notNull(),
|
||||
carrier: carrierEnum('carrier').notNull(),
|
||||
trackingNumber: text('tracking_number').notNull(),
|
||||
status: packageStatusEnum('status').notNull().default('pending'),
|
||||
currentLocation: text('current_location'),
|
||||
description: text('description'),
|
||||
recipientName: text('recipient_name'),
|
||||
estimatedDelivery: date('estimated_delivery'),
|
||||
shippedAt: timestamp('shipped_at', { withTimezone: true }),
|
||||
deliveredAt: timestamp('delivered_at', { withTimezone: true }),
|
||||
weightKg: numeric('weight_kg', { precision: 10, scale: 3 }),
|
||||
shippingCost: numeric('shipping_cost', { precision: 15, scale: 2 }),
|
||||
currency: text('currency').notNull().default('THB'),
|
||||
invoiceId: uuid('invoice_id').references(() => invoices.id, { onDelete: 'set null' }),
|
||||
customsInvoiceId: uuid('customs_invoice_id').references(() => invoices.id, { onDelete: 'set null' }),
|
||||
expenseId: uuid('expense_id').references(() => expenses.id, { onDelete: 'set null' }),
|
||||
partyId: uuid('party_id').references(() => parties.id, { onDelete: 'set null' }),
|
||||
notes: text('notes'),
|
||||
lastRefreshedAt: timestamp('last_refreshed_at', { withTimezone: true }),
|
||||
createdBy: text('created_by')
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
|
||||
},
|
||||
(table) => [
|
||||
uniqueIndex('packages_company_carrier_tracking_idx').on(
|
||||
table.companyId,
|
||||
table.carrier,
|
||||
table.trackingNumber
|
||||
),
|
||||
index('packages_company_status_idx').on(table.companyId, table.status)
|
||||
]
|
||||
);
|
||||
|
||||
export const packageEvents = pgTable(
|
||||
'package_events',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
packageId: uuid('package_id')
|
||||
.notNull()
|
||||
.references(() => packages.id, { onDelete: 'cascade' }),
|
||||
occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull(),
|
||||
status: packageStatusEnum('status'),
|
||||
location: text('location'),
|
||||
description: text('description'),
|
||||
source: text('source').notNull().default('manual'),
|
||||
rawPayload: text('raw_payload'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow()
|
||||
},
|
||||
(table) => [index('package_events_package_idx').on(table.packageId, table.occurredAt)]
|
||||
);
|
||||
|
||||
export const shippingAccounts = pgTable(
|
||||
'shipping_accounts',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
companyId: uuid('company_id')
|
||||
.notNull()
|
||||
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||
carrier: carrierEnum('carrier').notNull(),
|
||||
displayName: text('display_name'),
|
||||
credentialsEncrypted: text('credentials_encrypted'),
|
||||
isActive: boolean('is_active').notNull().default(true),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
|
||||
},
|
||||
(table) => [uniqueIndex('shipping_accounts_company_carrier_idx').on(table.companyId, table.carrier)]
|
||||
);
|
||||
|
||||
// ── Company Log (Audit Trail) ──────────────────────────
|
||||
|
||||
export const companyLogEventEnum = pgEnum('company_log_event', [
|
||||
@@ -210,7 +627,32 @@ export const companyLogEventEnum = pgEnum('company_log_event', [
|
||||
'expense_approved',
|
||||
'expense_rejected',
|
||||
'category_created',
|
||||
'import_completed'
|
||||
'import_completed',
|
||||
'employee_created',
|
||||
'employee_updated',
|
||||
'employee_terminated',
|
||||
'salary_changed',
|
||||
'holiday_added',
|
||||
'leave_type_created',
|
||||
'leave_submitted',
|
||||
'leave_approved',
|
||||
'leave_rejected',
|
||||
'payslip_generated',
|
||||
'payslip_finalized',
|
||||
'payslip_paid',
|
||||
'party_created',
|
||||
'invoice_created',
|
||||
'invoice_sent',
|
||||
'invoice_paid',
|
||||
'integration_connected',
|
||||
'integration_disconnected',
|
||||
'transaction_matched',
|
||||
'package_created',
|
||||
'package_updated',
|
||||
'package_delivered',
|
||||
'package_status_refreshed',
|
||||
'shipping_account_added',
|
||||
'shipping_account_removed'
|
||||
]);
|
||||
|
||||
export const companyLog = pgTable(
|
||||
|
||||
Reference in New Issue
Block a user