mirror of
https://github.com/Sea-Haven-Industries/seahaven-ap.git
synced 2026-10-02 09:43:24 +00:00
* feat(api): stand up Hono Drizzle foundation with auth and Redocly * fix(api): bump drizzle-orm and hono node-server past audit highs * fix(api): harden auth upsert and Cognito token verification
198 lines
7.7 KiB
TypeScript
198 lines
7.7 KiB
TypeScript
import {
|
|
boolean,
|
|
integer,
|
|
numeric,
|
|
pgEnum,
|
|
pgTable,
|
|
text,
|
|
timestamp,
|
|
uniqueIndex,
|
|
uuid,
|
|
} from "drizzle-orm/pg-core";
|
|
import { sql } from "drizzle-orm";
|
|
|
|
/** Keep aligned with `@seahaven-ap/shared` and `USER_ROLES` in env.ts. */
|
|
export const userRoleEnum = pgEnum("user_role", ["admin", "ap_processor", "approver", "viewer"]);
|
|
|
|
export const invoiceStatusEnum = pgEnum("invoice_status", [
|
|
"pending_approval",
|
|
"approved",
|
|
"scheduled",
|
|
"paid",
|
|
"rejected",
|
|
"void",
|
|
]);
|
|
|
|
export const paymentStatusEnum = pgEnum("payment_status", [
|
|
"scheduled",
|
|
"payment_submitted",
|
|
"issued",
|
|
"outstanding",
|
|
"cleared",
|
|
]);
|
|
|
|
export const paymentMethodEnum = pgEnum("payment_method", ["check", "ach"]);
|
|
|
|
export const approvalStatusEnum = pgEnum("approval_status", [
|
|
"pending",
|
|
"approved",
|
|
"rejected",
|
|
"skipped",
|
|
]);
|
|
|
|
export const users = pgTable(
|
|
"users",
|
|
{
|
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
cognitoSub: text("cognito_sub").notNull(),
|
|
email: text("email").notNull(),
|
|
name: text("name").notNull(),
|
|
role: userRoleEnum("role").notNull().default("viewer"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
uniqueIndex("users_cognito_sub_uidx").on(table.cognitoSub),
|
|
uniqueIndex("users_email_uidx").on(table.email),
|
|
],
|
|
);
|
|
|
|
export const vendors = pgTable("vendors", {
|
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
name: text("name").notNull(),
|
|
email: text("email"),
|
|
defaultPaymentMethod: paymentMethodEnum("default_payment_method").notNull().default("check"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const glAccounts = pgTable("gl_accounts", {
|
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
code: text("code").notNull(),
|
|
name: text("name").notNull(),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const departments = pgTable("departments", {
|
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
code: text("code").notNull(),
|
|
name: text("name").notNull(),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const invoices = pgTable(
|
|
"invoices",
|
|
{
|
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
vendorId: uuid("vendor_id")
|
|
.notNull()
|
|
.references(() => vendors.id),
|
|
invoiceNumber: text("invoice_number").notNull(),
|
|
amount: numeric("amount", { precision: 14, scale: 2 }).notNull(),
|
|
amountDue: numeric("amount_due", { precision: 14, scale: 2 }).notNull(),
|
|
dueDate: text("due_date").notNull(),
|
|
payDate: text("pay_date"),
|
|
sendPaymentOn: text("send_payment_on"),
|
|
status: invoiceStatusEnum("status").notNull().default("pending_approval"),
|
|
paymentMethod: paymentMethodEnum("payment_method").notNull().default("check"),
|
|
memo: text("memo").notNull().default(""),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
uniqueIndex("invoices_vendor_number_active_uidx")
|
|
.on(table.vendorId, table.invoiceNumber)
|
|
.where(sql`${table.status} <> 'void'`),
|
|
],
|
|
);
|
|
|
|
export const invoiceLines = pgTable("invoice_lines", {
|
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
invoiceId: uuid("invoice_id")
|
|
.notNull()
|
|
.references(() => invoices.id, { onDelete: "cascade" }),
|
|
description: text("description").notNull(),
|
|
amount: numeric("amount", { precision: 14, scale: 2 }).notNull(),
|
|
glAccountId: uuid("gl_account_id").references(() => glAccounts.id),
|
|
departmentId: uuid("department_id").references(() => departments.id),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const invoiceComments = pgTable("invoice_comments", {
|
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
invoiceId: uuid("invoice_id")
|
|
.notNull()
|
|
.references(() => invoices.id, { onDelete: "cascade" }),
|
|
authorUserId: uuid("author_user_id").references(() => users.id),
|
|
body: text("body").notNull(),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const activityLog = pgTable("activity_log", {
|
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
invoiceId: uuid("invoice_id")
|
|
.notNull()
|
|
.references(() => invoices.id, { onDelete: "cascade" }),
|
|
actorUserId: uuid("actor_user_id").references(() => users.id),
|
|
message: text("message").notNull(),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const approvalPolicies = pgTable("approval_policies", {
|
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
name: text("name").notNull(),
|
|
priority: integer("priority").notNull().default(100),
|
|
amountThreshold: numeric("amount_threshold", { precision: 14, scale: 2 }),
|
|
skipBelowAmount: numeric("skip_below_amount", { precision: 14, scale: 2 }),
|
|
active: boolean("active").notNull().default(true),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const approvalSteps = pgTable("approval_steps", {
|
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
invoiceId: uuid("invoice_id")
|
|
.notNull()
|
|
.references(() => invoices.id, { onDelete: "cascade" }),
|
|
policyId: uuid("policy_id").references(() => approvalPolicies.id),
|
|
stepOrder: integer("step_order").notNull().default(1),
|
|
approverRole: userRoleEnum("approver_role").notNull().default("approver"),
|
|
assigneeUserId: uuid("assignee_user_id").references(() => users.id),
|
|
status: approvalStatusEnum("status").notNull().default("pending"),
|
|
actedByUserId: uuid("acted_by_user_id").references(() => users.id),
|
|
actedAt: timestamp("acted_at", { withTimezone: true }),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const documents = pgTable("documents", {
|
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
invoiceId: uuid("invoice_id").references(() => invoices.id, { onDelete: "cascade" }),
|
|
objectKey: text("object_key").notNull(),
|
|
contentType: text("content_type").notNull().default("application/pdf"),
|
|
fileName: text("file_name").notNull(),
|
|
uploadedByUserId: uuid("uploaded_by_user_id").references(() => users.id),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const payRuns = pgTable("pay_runs", {
|
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
createdByUserId: uuid("created_by_user_id").references(() => users.id),
|
|
status: text("status").notNull().default("draft"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const payments = pgTable("payments", {
|
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
payRunId: uuid("pay_run_id").references(() => payRuns.id),
|
|
invoiceId: uuid("invoice_id").references(() => invoices.id),
|
|
vendorId: uuid("vendor_id").references(() => vendors.id),
|
|
amount: numeric("amount", { precision: 14, scale: 2 }).notNull(),
|
|
paymentMethod: paymentMethodEnum("payment_method").notNull().default("check"),
|
|
checkNumber: integer("check_number"),
|
|
status: paymentStatusEnum("status").notNull().default("scheduled"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|