feat(companies): add companies, memberships, addresses, notes, tags schema
This commit is contained in:
80
src/lib/db/migrations/0003_opposite_lucky_pierre.sql
Normal file
80
src/lib/db/migrations/0003_opposite_lucky_pierre.sql
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
CREATE TABLE "companies" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"port_id" text NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"legal_name" text,
|
||||||
|
"tax_id" text,
|
||||||
|
"registration_number" text,
|
||||||
|
"incorporation_country" text,
|
||||||
|
"incorporation_date" timestamp with time zone,
|
||||||
|
"status" text DEFAULT 'active' NOT NULL,
|
||||||
|
"billing_email" text,
|
||||||
|
"notes" text,
|
||||||
|
"archived_at" timestamp with time zone,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "company_addresses" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"company_id" text NOT NULL,
|
||||||
|
"port_id" text NOT NULL,
|
||||||
|
"label" text DEFAULT 'Primary' NOT NULL,
|
||||||
|
"street_address" text,
|
||||||
|
"city" text,
|
||||||
|
"state_province" text,
|
||||||
|
"postal_code" text,
|
||||||
|
"country" text,
|
||||||
|
"is_primary" boolean DEFAULT true NOT NULL,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "company_memberships" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"company_id" text NOT NULL,
|
||||||
|
"client_id" text NOT NULL,
|
||||||
|
"role" text NOT NULL,
|
||||||
|
"role_detail" text,
|
||||||
|
"start_date" timestamp with time zone NOT NULL,
|
||||||
|
"end_date" timestamp with time zone,
|
||||||
|
"is_primary" boolean DEFAULT false NOT NULL,
|
||||||
|
"notes" text,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "company_notes" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"company_id" text NOT NULL,
|
||||||
|
"author_id" text NOT NULL,
|
||||||
|
"content" text NOT NULL,
|
||||||
|
"mentions" text[],
|
||||||
|
"is_locked" boolean DEFAULT false NOT NULL,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "company_tags" (
|
||||||
|
"company_id" text NOT NULL,
|
||||||
|
"tag_id" text NOT NULL,
|
||||||
|
CONSTRAINT "company_tags_company_id_tag_id_pk" PRIMARY KEY("company_id","tag_id")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "companies" ADD CONSTRAINT "companies_port_id_ports_id_fk" FOREIGN KEY ("port_id") REFERENCES "public"."ports"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "company_addresses" ADD CONSTRAINT "company_addresses_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "company_addresses" ADD CONSTRAINT "company_addresses_port_id_ports_id_fk" FOREIGN KEY ("port_id") REFERENCES "public"."ports"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "company_memberships" ADD CONSTRAINT "company_memberships_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "company_memberships" ADD CONSTRAINT "company_memberships_client_id_clients_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."clients"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "company_notes" ADD CONSTRAINT "company_notes_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "company_tags" ADD CONSTRAINT "company_tags_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
CREATE INDEX "idx_companies_port" ON "companies" USING btree ("port_id");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "idx_companies_name_unique" ON "companies" USING btree ("port_id",lower("name"));--> statement-breakpoint
|
||||||
|
CREATE INDEX "idx_companies_taxid" ON "companies" USING btree ("port_id","tax_id") WHERE "companies"."tax_id" IS NOT NULL;--> statement-breakpoint
|
||||||
|
CREATE INDEX "idx_compa_company" ON "company_addresses" USING btree ("company_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "idx_compa_port" ON "company_addresses" USING btree ("port_id");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "idx_compa_primary" ON "company_addresses" USING btree ("company_id") WHERE "company_addresses"."is_primary" = true;--> statement-breakpoint
|
||||||
|
CREATE INDEX "idx_cm_company" ON "company_memberships" USING btree ("company_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "idx_cm_client" ON "company_memberships" USING btree ("client_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "idx_cm_active" ON "company_memberships" USING btree ("company_id","client_id") WHERE "company_memberships"."end_date" IS NULL;--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "unique_cm_exact" ON "company_memberships" USING btree ("company_id","client_id","role","start_date");--> statement-breakpoint
|
||||||
|
CREATE INDEX "idx_compn_company" ON "company_notes" USING btree ("company_id");
|
||||||
8219
src/lib/db/migrations/meta/0003_snapshot.json
Normal file
8219
src/lib/db/migrations/meta/0003_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,13 @@
|
|||||||
"when": 1776958500747,
|
"when": 1776958500747,
|
||||||
"tag": "0002_groovy_excalibur",
|
"tag": "0002_groovy_excalibur",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1776959610819,
|
||||||
|
"tag": "0003_opposite_lucky_pierre",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
143
src/lib/db/schema/companies.ts
Normal file
143
src/lib/db/schema/companies.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import {
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
boolean,
|
||||||
|
index,
|
||||||
|
uniqueIndex,
|
||||||
|
primaryKey,
|
||||||
|
} from 'drizzle-orm/pg-core';
|
||||||
|
import { sql } from 'drizzle-orm';
|
||||||
|
import { ports } from './ports';
|
||||||
|
import { clients } from './clients';
|
||||||
|
|
||||||
|
export const companies = pgTable(
|
||||||
|
'companies',
|
||||||
|
{
|
||||||
|
id: text('id')
|
||||||
|
.primaryKey()
|
||||||
|
.$defaultFn(() => crypto.randomUUID()),
|
||||||
|
portId: text('port_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => ports.id),
|
||||||
|
name: text('name').notNull(),
|
||||||
|
legalName: text('legal_name'),
|
||||||
|
taxId: text('tax_id'),
|
||||||
|
registrationNumber: text('registration_number'),
|
||||||
|
incorporationCountry: text('incorporation_country'),
|
||||||
|
incorporationDate: timestamp('incorporation_date', { withTimezone: true, mode: 'date' }),
|
||||||
|
status: text('status').notNull().default('active'), // 'active' | 'dissolved'
|
||||||
|
billingEmail: text('billing_email'),
|
||||||
|
notes: text('notes'),
|
||||||
|
archivedAt: timestamp('archived_at', { withTimezone: true }),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
index('idx_companies_port').on(table.portId),
|
||||||
|
uniqueIndex('idx_companies_name_unique').on(table.portId, sql`lower(${table.name})`),
|
||||||
|
index('idx_companies_taxid')
|
||||||
|
.on(table.portId, table.taxId)
|
||||||
|
.where(sql`${table.taxId} IS NOT NULL`),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const companyMemberships = pgTable(
|
||||||
|
'company_memberships',
|
||||||
|
{
|
||||||
|
id: text('id')
|
||||||
|
.primaryKey()
|
||||||
|
.$defaultFn(() => crypto.randomUUID()),
|
||||||
|
companyId: text('company_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||||
|
clientId: text('client_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => clients.id, { onDelete: 'cascade' }),
|
||||||
|
role: text('role').notNull(), // director | officer | broker | representative | legal_counsel | employee | shareholder | other
|
||||||
|
roleDetail: text('role_detail'),
|
||||||
|
startDate: timestamp('start_date', { withTimezone: true, mode: 'date' }).notNull(),
|
||||||
|
endDate: timestamp('end_date', { withTimezone: true, mode: 'date' }),
|
||||||
|
isPrimary: boolean('is_primary').notNull().default(false),
|
||||||
|
notes: text('notes'),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
index('idx_cm_company').on(table.companyId),
|
||||||
|
index('idx_cm_client').on(table.clientId),
|
||||||
|
index('idx_cm_active')
|
||||||
|
.on(table.companyId, table.clientId)
|
||||||
|
.where(sql`${table.endDate} IS NULL`),
|
||||||
|
uniqueIndex('unique_cm_exact').on(table.companyId, table.clientId, table.role, table.startDate),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const companyAddresses = pgTable(
|
||||||
|
'company_addresses',
|
||||||
|
{
|
||||||
|
id: text('id')
|
||||||
|
.primaryKey()
|
||||||
|
.$defaultFn(() => crypto.randomUUID()),
|
||||||
|
companyId: text('company_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||||
|
portId: text('port_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => ports.id, { onDelete: 'cascade' }),
|
||||||
|
label: text('label').notNull().default('Primary'),
|
||||||
|
streetAddress: text('street_address'),
|
||||||
|
city: text('city'),
|
||||||
|
stateProvince: text('state_province'),
|
||||||
|
postalCode: text('postal_code'),
|
||||||
|
country: text('country'),
|
||||||
|
isPrimary: boolean('is_primary').notNull().default(true),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
index('idx_compa_company').on(table.companyId),
|
||||||
|
index('idx_compa_port').on(table.portId),
|
||||||
|
uniqueIndex('idx_compa_primary')
|
||||||
|
.on(table.companyId)
|
||||||
|
.where(sql`${table.isPrimary} = true`),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const companyNotes = pgTable(
|
||||||
|
'company_notes',
|
||||||
|
{
|
||||||
|
id: text('id')
|
||||||
|
.primaryKey()
|
||||||
|
.$defaultFn(() => crypto.randomUUID()),
|
||||||
|
companyId: text('company_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||||
|
authorId: text('author_id').notNull(),
|
||||||
|
content: text('content').notNull(),
|
||||||
|
mentions: text('mentions').array(),
|
||||||
|
isLocked: boolean('is_locked').notNull().default(false),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
},
|
||||||
|
(table) => [index('idx_compn_company').on(table.companyId)],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const companyTags = pgTable(
|
||||||
|
'company_tags',
|
||||||
|
{
|
||||||
|
companyId: text('company_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => companies.id, { onDelete: 'cascade' }),
|
||||||
|
tagId: text('tag_id').notNull(),
|
||||||
|
},
|
||||||
|
(table) => [primaryKey({ columns: [table.companyId, table.tagId] })],
|
||||||
|
);
|
||||||
|
|
||||||
|
export type Company = typeof companies.$inferSelect;
|
||||||
|
export type NewCompany = typeof companies.$inferInsert;
|
||||||
|
export type CompanyMembership = typeof companyMemberships.$inferSelect;
|
||||||
|
export type NewCompanyMembership = typeof companyMemberships.$inferInsert;
|
||||||
|
export type CompanyAddress = typeof companyAddresses.$inferSelect;
|
||||||
|
export type NewCompanyAddress = typeof companyAddresses.$inferInsert;
|
||||||
|
export type CompanyNote = typeof companyNotes.$inferSelect;
|
||||||
|
export type NewCompanyNote = typeof companyNotes.$inferInsert;
|
||||||
@@ -7,6 +7,9 @@ export * from './users';
|
|||||||
// Clients
|
// Clients
|
||||||
export * from './clients';
|
export * from './clients';
|
||||||
|
|
||||||
|
// Companies
|
||||||
|
export * from './companies';
|
||||||
|
|
||||||
// Yachts
|
// Yachts
|
||||||
export * from './yachts';
|
export * from './yachts';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user