From e77d55ac50827213c5beb6d4eec277b6ce366d2f Mon Sep 17 00:00:00 2001 From: Matt Ciaccio Date: Tue, 28 Apr 2026 14:43:01 +0200 Subject: [PATCH] feat(insights): Phase B schema + service skeletons PR1 of Phase B per docs/superpowers/specs/2026-04-28-phase-b-insights-alerts-design.md. Lays the foundation that PRs 2-10 will fill in with behaviour. Schema (migration 0014): - alerts table with rule-engine fields (rule_id, severity, link, entity_type/id, fingerprint, fired/dismissed/acknowledged/resolved timestamps, jsonb metadata). Partial-unique fingerprint index keeps one open row per (port, rule, entity); separate indexes power severity-filtered and time-ordered queries. - analytics_snapshots (port_id, metric_id) -> jsonb cache + computedAt for the 15-min recurring refresh. - expenses: duplicate_of self-FK, dedup_scanned_at, ocr_status/raw/ confidence; partial index on (port, vendor, amount, date) where duplicate_of IS NULL drives the dedup heuristic. - audit_logs.search_text: GENERATED ALWAYS tsvector over action+entity_type+entity_id+user_id, GIN-indexed (drizzle can't model GENERATED ALWAYS in TS yet, so the migration appends manual ALTER + the GIN index). Service skeletons in src/lib/services/: - alerts.service.ts: fingerprintFor, reconcileAlertsForPort (upsert + auto-resolve), dismiss, acknowledge, listAlertsForPort. - alert-rules.ts: RULE_REGISTRY of 10 rule evaluators (currently no-op); PR2 fills in the bodies. - analytics.service.ts: readSnapshot/writeSnapshot with 15-min TTL + no-op compute* stubs for the four chart series; PR3 fills behavior. - expense-dedup.service.ts: scanForDuplicates + markBestDuplicate using the partial dedup index. PR8 wires the BullMQ trigger. - expense-ocr.service.ts: OcrResult/OcrLineItem types + ocrReceipt stub. PR9 wires Claude Vision (Haiku 4.5 + ephemeral system-prompt cache). - audit-search.service.ts: tsvector @@ plainto_tsquery + cursor pagination on (createdAt, id). PR10 wires the admin UI. tsc clean, lint clean, vitest 675/675 (one unrelated AES random-output flake passes solo). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib/db/migrations/0014_black_banshee.sql | 56 + src/lib/db/migrations/meta/0014_snapshot.json | 9780 +++++++++++++++++ src/lib/db/migrations/meta/_journal.json | 7 + src/lib/db/schema/financial.ts | 20 + src/lib/db/schema/index.ts | 3 + src/lib/db/schema/insights.ts | 101 + src/lib/db/schema/system.ts | 52 +- src/lib/services/alert-rules.ts | 31 + src/lib/services/alerts.service.ts | 115 + src/lib/services/analytics.service.ts | 106 + src/lib/services/audit-search.service.ts | 72 + src/lib/services/expense-dedup.service.ts | 71 + src/lib/services/expense-ocr.service.ts | 47 + 13 files changed, 10451 insertions(+), 10 deletions(-) create mode 100644 src/lib/db/migrations/0014_black_banshee.sql create mode 100644 src/lib/db/migrations/meta/0014_snapshot.json create mode 100644 src/lib/db/schema/insights.ts create mode 100644 src/lib/services/alert-rules.ts create mode 100644 src/lib/services/alerts.service.ts create mode 100644 src/lib/services/analytics.service.ts create mode 100644 src/lib/services/audit-search.service.ts create mode 100644 src/lib/services/expense-dedup.service.ts create mode 100644 src/lib/services/expense-ocr.service.ts diff --git a/src/lib/db/migrations/0014_black_banshee.sql b/src/lib/db/migrations/0014_black_banshee.sql new file mode 100644 index 0000000..b6dcf8c --- /dev/null +++ b/src/lib/db/migrations/0014_black_banshee.sql @@ -0,0 +1,56 @@ +CREATE TABLE "alerts" ( + "id" text PRIMARY KEY NOT NULL, + "port_id" text NOT NULL, + "rule_id" text NOT NULL, + "severity" text NOT NULL, + "title" text NOT NULL, + "body" text, + "link" text NOT NULL, + "entity_type" text, + "entity_id" text, + "fingerprint" text NOT NULL, + "fired_at" timestamp with time zone DEFAULT now() NOT NULL, + "dismissed_at" timestamp with time zone, + "dismissed_by" text, + "acknowledged_at" timestamp with time zone, + "acknowledged_by" text, + "resolved_at" timestamp with time zone, + "metadata" jsonb DEFAULT '{}'::jsonb +); +--> statement-breakpoint +CREATE TABLE "analytics_snapshots" ( + "port_id" text NOT NULL, + "metric_id" text NOT NULL, + "computed_at" timestamp with time zone DEFAULT now() NOT NULL, + "data" jsonb NOT NULL +); +--> statement-breakpoint +ALTER TABLE "expenses" ADD COLUMN "duplicate_of" text;--> statement-breakpoint +ALTER TABLE "expenses" ADD COLUMN "dedup_scanned_at" timestamp with time zone;--> statement-breakpoint +ALTER TABLE "expenses" ADD COLUMN "ocr_status" text DEFAULT 'pending';--> statement-breakpoint +ALTER TABLE "expenses" ADD COLUMN "ocr_raw" jsonb;--> statement-breakpoint +ALTER TABLE "expenses" ADD COLUMN "ocr_confidence" numeric;--> statement-breakpoint +ALTER TABLE "audit_logs" ADD COLUMN "search_text" "tsvector";--> statement-breakpoint +ALTER TABLE "alerts" ADD CONSTRAINT "alerts_port_id_ports_id_fk" FOREIGN KEY ("port_id") REFERENCES "public"."ports"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "alerts" ADD CONSTRAINT "alerts_dismissed_by_user_id_fk" FOREIGN KEY ("dismissed_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "alerts" ADD CONSTRAINT "alerts_acknowledged_by_user_id_fk" FOREIGN KEY ("acknowledged_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "analytics_snapshots" ADD CONSTRAINT "analytics_snapshots_port_id_ports_id_fk" FOREIGN KEY ("port_id") REFERENCES "public"."ports"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "idx_alerts_fingerprint_open" ON "alerts" USING btree ("port_id","fingerprint") WHERE resolved_at IS NULL;--> statement-breakpoint +CREATE INDEX "idx_alerts_port_fired" ON "alerts" USING btree ("port_id","fired_at");--> statement-breakpoint +CREATE INDEX "idx_alerts_port_severity_open" ON "alerts" USING btree ("port_id","severity") WHERE resolved_at IS NULL AND dismissed_at IS NULL;--> statement-breakpoint +CREATE UNIQUE INDEX "idx_analytics_pk" ON "analytics_snapshots" USING btree ("port_id","metric_id");--> statement-breakpoint +ALTER TABLE "expenses" ADD CONSTRAINT "expenses_duplicate_of_expenses_id_fk" FOREIGN KEY ("duplicate_of") REFERENCES "public"."expenses"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "idx_expenses_dedup" ON "expenses" USING btree ("port_id","establishment_name","amount","expense_date") WHERE duplicate_of IS NULL;--> statement-breakpoint +-- audit_logs.search_text needs to be GENERATED ALWAYS (drizzle can't model that +-- in TS yet); drop the empty column and re-add it as the generated form. +ALTER TABLE "audit_logs" DROP COLUMN "search_text";--> statement-breakpoint +ALTER TABLE "audit_logs" ADD COLUMN "search_text" tsvector + GENERATED ALWAYS AS ( + to_tsvector('simple', + coalesce("action", '') || ' ' || + coalesce("entity_type", '') || ' ' || + coalesce("entity_id", '') || ' ' || + coalesce("user_id", '') + ) + ) STORED;--> statement-breakpoint +CREATE INDEX "idx_audit_search" ON "audit_logs" USING gin("search_text"); diff --git a/src/lib/db/migrations/meta/0014_snapshot.json b/src/lib/db/migrations/meta/0014_snapshot.json new file mode 100644 index 0000000..63cc9e4 --- /dev/null +++ b/src/lib/db/migrations/meta/0014_snapshot.json @@ -0,0 +1,9780 @@ +{ + "id": "478bb330-ef5d-419a-ae6c-a54c3d001085", + "prevId": "5a921a0d-f769-4283-a561-a8b659697c01", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.berth_maintenance_log": { + "name": "berth_maintenance_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cost": { + "name": "cost", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "cost_currency": { + "name": "cost_currency", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'USD'" + }, + "responsible_party": { + "name": "responsible_party", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "performed_date": { + "name": "performed_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "photo_file_ids": { + "name": "photo_file_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_bml_berth": { + "name": "idx_bml_berth", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_bml_port": { + "name": "idx_bml_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "berth_maintenance_log_berth_id_berths_id_fk": { + "name": "berth_maintenance_log_berth_id_berths_id_fk", + "tableFrom": "berth_maintenance_log", + "tableTo": "berths", + "columnsFrom": ["berth_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "berth_maintenance_log_port_id_ports_id_fk": { + "name": "berth_maintenance_log_port_id_ports_id_fk", + "tableFrom": "berth_maintenance_log", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.berth_map_data": { + "name": "berth_map_data", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "svg_path": { + "name": "svg_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "x": { + "name": "x", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "y": { + "name": "y", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "transform": { + "name": "transform", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "font_size": { + "name": "font_size", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "extra_data": { + "name": "extra_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "berth_map_data_berth_id_idx": { + "name": "berth_map_data_berth_id_idx", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "berth_map_data_berth_id_berths_id_fk": { + "name": "berth_map_data_berth_id_berths_id_fk", + "tableFrom": "berth_map_data", + "tableTo": "berths", + "columnsFrom": ["berth_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "berth_map_data_berth_id_unique": { + "name": "berth_map_data_berth_id_unique", + "nullsNotDistinct": false, + "columns": ["berth_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.berth_recommendations": { + "name": "berth_recommendations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_score": { + "name": "match_score", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "match_reasons": { + "name": "match_reasons", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ai'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "berth_rec_interest_berth_idx": { + "name": "berth_rec_interest_berth_idx", + "columns": [ + { + "expression": "interest_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_br_interest": { + "name": "idx_br_interest", + "columns": [ + { + "expression": "interest_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "berth_recommendations_berth_id_berths_id_fk": { + "name": "berth_recommendations_berth_id_berths_id_fk", + "tableFrom": "berth_recommendations", + "tableTo": "berths", + "columnsFrom": ["berth_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.berth_tags": { + "name": "berth_tags", + "schema": "", + "columns": { + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "berth_tags_berth_id_berths_id_fk": { + "name": "berth_tags_berth_id_berths_id_fk", + "tableFrom": "berth_tags", + "tableTo": "berths", + "columnsFrom": ["berth_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "berth_tags_berth_id_tag_id_pk": { + "name": "berth_tags_berth_id_tag_id_pk", + "columns": ["berth_id", "tag_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.berth_waiting_list": { + "name": "berth_waiting_list", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'normal'" + }, + "notify_pref": { + "name": "notify_pref", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'email'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "berth_waiting_list_berth_client_idx": { + "name": "berth_waiting_list_berth_client_idx", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_bwl_berth": { + "name": "idx_bwl_berth", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "berth_waiting_list_berth_id_berths_id_fk": { + "name": "berth_waiting_list_berth_id_berths_id_fk", + "tableFrom": "berth_waiting_list", + "tableTo": "berths", + "columnsFrom": ["berth_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "berth_waiting_list_client_id_clients_id_fk": { + "name": "berth_waiting_list_client_id_clients_id_fk", + "tableFrom": "berth_waiting_list", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.berths": { + "name": "berths", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mooring_number": { + "name": "mooring_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "area": { + "name": "area", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "length_ft": { + "name": "length_ft", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "width_ft": { + "name": "width_ft", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "draft_ft": { + "name": "draft_ft", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "length_m": { + "name": "length_m", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "width_m": { + "name": "width_m", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "draft_m": { + "name": "draft_m", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "width_is_minimum": { + "name": "width_is_minimum", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "nominal_boat_size": { + "name": "nominal_boat_size", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "nominal_boat_size_m": { + "name": "nominal_boat_size_m", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "water_depth": { + "name": "water_depth", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "water_depth_m": { + "name": "water_depth_m", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "water_depth_is_minimum": { + "name": "water_depth_is_minimum", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "side_pontoon": { + "name": "side_pontoon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "power_capacity": { + "name": "power_capacity", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "voltage": { + "name": "voltage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mooring_type": { + "name": "mooring_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleat_type": { + "name": "cleat_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleat_capacity": { + "name": "cleat_capacity", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bollard_type": { + "name": "bollard_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bollard_capacity": { + "name": "bollard_capacity", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access": { + "name": "access", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "price": { + "name": "price", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "price_currency": { + "name": "price_currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "bow_facing": { + "name": "bow_facing", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "berth_approved": { + "name": "berth_approved", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "tenure_type": { + "name": "tenure_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'permanent'" + }, + "tenure_years": { + "name": "tenure_years", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tenure_start_date": { + "name": "tenure_start_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "tenure_end_date": { + "name": "tenure_end_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "status_last_changed_by": { + "name": "status_last_changed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_last_changed_reason": { + "name": "status_last_changed_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_last_modified": { + "name": "status_last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_berths_port": { + "name": "idx_berths_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_berths_status": { + "name": "idx_berths_status", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_berths_area": { + "name": "idx_berths_area", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "area", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_berths_mooring": { + "name": "idx_berths_mooring", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mooring_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "berths_port_id_ports_id_fk": { + "name": "berths_port_id_ports_id_fk", + "tableFrom": "berths", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_addresses": { + "name": "client_addresses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Primary'" + }, + "street_address": { + "name": "street_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_province": { + "name": "state_province", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postal_code": { + "name": "postal_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ca_client": { + "name": "idx_ca_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ca_port": { + "name": "idx_ca_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ca_primary": { + "name": "idx_ca_primary", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"client_addresses\".\"is_primary\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "client_addresses_client_id_clients_id_fk": { + "name": "client_addresses_client_id_clients_id_fk", + "tableFrom": "client_addresses", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "client_addresses_port_id_ports_id_fk": { + "name": "client_addresses_port_id_ports_id_fk", + "tableFrom": "client_addresses", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_contacts": { + "name": "client_contacts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_cc_client": { + "name": "idx_cc_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cc_email": { + "name": "idx_cc_email", + "columns": [ + { + "expression": "channel", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "value", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"client_contacts\".\"channel\" = 'email'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cc_phone": { + "name": "idx_cc_phone", + "columns": [ + { + "expression": "channel", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "value", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"client_contacts\".\"channel\" = 'phone'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "client_contacts_client_id_clients_id_fk": { + "name": "client_contacts_client_id_clients_id_fk", + "tableFrom": "client_contacts", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_merge_log": { + "name": "client_merge_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "surviving_client_id": { + "name": "surviving_client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "merged_client_id": { + "name": "merged_client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "merged_by": { + "name": "merged_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "merge_details": { + "name": "merge_details", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_cml_port": { + "name": "idx_cml_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "client_merge_log_port_id_ports_id_fk": { + "name": "client_merge_log_port_id_ports_id_fk", + "tableFrom": "client_merge_log", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "client_merge_log_surviving_client_id_clients_id_fk": { + "name": "client_merge_log_surviving_client_id_clients_id_fk", + "tableFrom": "client_merge_log", + "tableTo": "clients", + "columnsFrom": ["surviving_client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_notes": { + "name": "client_notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mentions": { + "name": "mentions", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "is_locked": { + "name": "is_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_cn_client": { + "name": "idx_cn_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "client_notes_client_id_clients_id_fk": { + "name": "client_notes_client_id_clients_id_fk", + "tableFrom": "client_notes", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_relationships": { + "name": "client_relationships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_a_id": { + "name": "client_a_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_b_id": { + "name": "client_b_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "relationship_type": { + "name": "relationship_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_cr_port": { + "name": "idx_cr_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "client_relationships_port_id_ports_id_fk": { + "name": "client_relationships_port_id_ports_id_fk", + "tableFrom": "client_relationships", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "client_relationships_client_a_id_clients_id_fk": { + "name": "client_relationships_client_a_id_clients_id_fk", + "tableFrom": "client_relationships", + "tableTo": "clients", + "columnsFrom": ["client_a_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "client_relationships_client_b_id_clients_id_fk": { + "name": "client_relationships_client_b_id_clients_id_fk", + "tableFrom": "client_relationships", + "tableTo": "clients", + "columnsFrom": ["client_b_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_tags": { + "name": "client_tags", + "schema": "", + "columns": { + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "client_tags_client_id_clients_id_fk": { + "name": "client_tags_client_id_clients_id_fk", + "tableFrom": "client_tags", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "client_tags_client_id_tag_id_pk": { + "name": "client_tags_client_id_tag_id_pk", + "columns": ["client_id", "tag_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.clients": { + "name": "clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nationality": { + "name": "nationality", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preferred_contact_method": { + "name": "preferred_contact_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preferred_language": { + "name": "preferred_language", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_details": { + "name": "source_details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_clients_port": { + "name": "idx_clients_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_clients_name": { + "name": "idx_clients_name", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_clients_archived": { + "name": "idx_clients_archived", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "clients_port_id_ports_id_fk": { + "name": "clients_port_id_ports_id_fk", + "tableFrom": "clients", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "legal_name": { + "name": "legal_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tax_id": { + "name": "tax_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_number": { + "name": "registration_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "incorporation_country": { + "name": "incorporation_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "incorporation_date": { + "name": "incorporation_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_companies_port": { + "name": "idx_companies_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_companies_name_unique": { + "name": "idx_companies_name_unique", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_companies_taxid": { + "name": "idx_companies_taxid", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tax_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"companies\".\"tax_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "companies_port_id_ports_id_fk": { + "name": "companies_port_id_ports_id_fk", + "tableFrom": "companies", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_addresses": { + "name": "company_addresses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Primary'" + }, + "street_address": { + "name": "street_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_province": { + "name": "state_province", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postal_code": { + "name": "postal_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_compa_company": { + "name": "idx_compa_company", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_compa_port": { + "name": "idx_compa_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_compa_primary": { + "name": "idx_compa_primary", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_addresses\".\"is_primary\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_addresses_company_id_companies_id_fk": { + "name": "company_addresses_company_id_companies_id_fk", + "tableFrom": "company_addresses", + "tableTo": "companies", + "columnsFrom": ["company_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_addresses_port_id_ports_id_fk": { + "name": "company_addresses_port_id_ports_id_fk", + "tableFrom": "company_addresses", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_detail": { + "name": "role_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start_date": { + "name": "start_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "end_date": { + "name": "end_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_cm_company": { + "name": "idx_cm_company", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cm_client": { + "name": "idx_cm_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cm_active": { + "name": "idx_cm_active", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"company_memberships\".\"end_date\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_cm_exact": { + "name": "unique_cm_exact", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "start_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": ["company_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_memberships_client_id_clients_id_fk": { + "name": "company_memberships_client_id_clients_id_fk", + "tableFrom": "company_memberships", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_notes": { + "name": "company_notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mentions": { + "name": "mentions", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "is_locked": { + "name": "is_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_compn_company": { + "name": "idx_compn_company", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_notes_company_id_companies_id_fk": { + "name": "company_notes_company_id_companies_id_fk", + "tableFrom": "company_notes", + "tableTo": "companies", + "columnsFrom": ["company_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_tags": { + "name": "company_tags", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "company_tags_company_id_companies_id_fk": { + "name": "company_tags_company_id_companies_id_fk", + "tableFrom": "company_tags", + "tableTo": "companies", + "columnsFrom": ["company_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "company_tags_company_id_tag_id_pk": { + "name": "company_tags_company_id_tag_id_pk", + "columns": ["company_id", "tag_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.crm_user_invites": { + "name": "crm_user_invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_super_admin": { + "name": "is_super_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_crm_invites_token_hash": { + "name": "idx_crm_invites_token_hash", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_crm_invites_email": { + "name": "idx_crm_invites_email", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_events": { + "name": "document_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signer_id": { + "name": "signer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_data": { + "name": "event_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "signature_hash": { + "name": "signature_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_de_doc": { + "name": "idx_de_doc", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_de_dedup": { + "name": "idx_de_dedup", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "signature_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"document_events\".\"signature_hash\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_events_document_id_documents_id_fk": { + "name": "document_events_document_id_documents_id_fk", + "tableFrom": "document_events", + "tableTo": "documents", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_events_signer_id_document_signers_id_fk": { + "name": "document_events_signer_id_document_signers_id_fk", + "tableFrom": "document_events", + "tableTo": "document_signers", + "columnsFrom": ["signer_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_signers": { + "name": "document_signers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signer_name": { + "name": "signer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signer_email": { + "name": "signer_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signer_role": { + "name": "signer_role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signing_order": { + "name": "signing_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "signed_at": { + "name": "signed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "signing_url": { + "name": "signing_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "embedded_url": { + "name": "embedded_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ds_doc": { + "name": "idx_ds_doc", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_signers_document_id_documents_id_fk": { + "name": "document_signers_document_id_documents_id_fk", + "tableFrom": "document_signers", + "tableTo": "documents", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_templates": { + "name": "document_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_type": { + "name": "template_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "merge_fields": { + "name": "merge_fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "template_format": { + "name": "template_format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'html'" + }, + "source_file_id": { + "name": "source_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "documenso_template_id": { + "name": "documenso_template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "field_mapping": { + "name": "field_mapping", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "overlay_positions": { + "name": "overlay_positions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "reminder_cadence_days": { + "name": "reminder_cadence_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_dt_port": { + "name": "idx_dt_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_dt_type": { + "name": "idx_dt_type", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_templates_port_id_ports_id_fk": { + "name": "document_templates_port_id_ports_id_fk", + "tableFrom": "document_templates", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_templates_source_file_id_files_id_fk": { + "name": "document_templates_source_file_id_files_id_fk", + "tableFrom": "document_templates", + "tableTo": "files", + "columnsFrom": ["source_file_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_watchers": { + "name": "document_watchers", + "schema": "", + "columns": { + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_doc_watchers_doc": { + "name": "idx_doc_watchers_doc", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_doc_watchers_user": { + "name": "idx_doc_watchers_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_watchers_document_id_documents_id_fk": { + "name": "document_watchers_document_id_documents_id_fk", + "tableFrom": "document_watchers", + "tableTo": "documents", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "document_watchers_document_id_user_id_pk": { + "name": "document_watchers_document_id_user_id_pk", + "columns": ["document_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "company_id": { + "name": "company_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reservation_id": { + "name": "reservation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "document_type": { + "name": "document_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "documenso_id": { + "name": "documenso_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signed_file_id": { + "name": "signed_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_manual_upload": { + "name": "is_manual_upload", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reminders_disabled": { + "name": "reminders_disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "reminder_cadence_override": { + "name": "reminder_cadence_override", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_docs_port": { + "name": "idx_docs_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_docs_interest": { + "name": "idx_docs_interest", + "columns": [ + { + "expression": "interest_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_docs_client": { + "name": "idx_docs_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_documents_yacht": { + "name": "idx_documents_yacht", + "columns": [ + { + "expression": "yacht_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_documents_company": { + "name": "idx_documents_company", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_docs_reservation": { + "name": "idx_docs_reservation", + "columns": [ + { + "expression": "reservation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_docs_type": { + "name": "idx_docs_type", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_docs_status_port": { + "name": "idx_docs_status_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "documents_port_id_ports_id_fk": { + "name": "documents_port_id_ports_id_fk", + "tableFrom": "documents", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_client_id_clients_id_fk": { + "name": "documents_client_id_clients_id_fk", + "tableFrom": "documents", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_file_id_files_id_fk": { + "name": "documents_file_id_files_id_fk", + "tableFrom": "documents", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_signed_file_id_files_id_fk": { + "name": "documents_signed_file_id_files_id_fk", + "tableFrom": "documents", + "tableTo": "files", + "columnsFrom": ["signed_file_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.files": { + "name": "files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "company_id": { + "name": "company_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_bucket": { + "name": "storage_bucket", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'crm-files'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_files_port": { + "name": "idx_files_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_files_client": { + "name": "idx_files_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_files_yacht": { + "name": "idx_files_yacht", + "columns": [ + { + "expression": "yacht_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_files_company": { + "name": "idx_files_company", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "files_port_id_ports_id_fk": { + "name": "files_port_id_ports_id_fk", + "tableFrom": "files", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "files_client_id_clients_id_fk": { + "name": "files_client_id_clients_id_fk", + "tableFrom": "files", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.form_submissions": { + "name": "form_submissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "form_template_id": { + "name": "form_template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefilled_data": { + "name": "prefilled_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "submitted_data": { + "name": "submitted_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "submitted_at": { + "name": "submitted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_fs_token": { + "name": "idx_fs_token", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "form_submissions_form_template_id_form_templates_id_fk": { + "name": "form_submissions_form_template_id_form_templates_id_fk", + "tableFrom": "form_submissions", + "tableTo": "form_templates", + "columnsFrom": ["form_template_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "form_submissions_client_id_clients_id_fk": { + "name": "form_submissions_client_id_clients_id_fk", + "tableFrom": "form_submissions", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "form_submissions_token_unique": { + "name": "form_submissions_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.form_templates": { + "name": "form_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "branding": { + "name": "branding", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ft_port": { + "name": "idx_ft_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "form_templates_port_id_ports_id_fk": { + "name": "form_templates_port_id_ports_id_fk", + "tableFrom": "form_templates", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_accounts": { + "name": "email_accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_address": { + "name": "email_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "smtp_host": { + "name": "smtp_host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "smtp_port": { + "name": "smtp_port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "imap_host": { + "name": "imap_host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imap_port": { + "name": "imap_port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credentials_enc": { + "name": "credentials_enc", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ea_user": { + "name": "idx_ea_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ea_port": { + "name": "idx_ea_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_accounts_port_id_ports_id_fk": { + "name": "email_accounts_port_id_ports_id_fk", + "tableFrom": "email_accounts", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_messages": { + "name": "email_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id_header": { + "name": "message_id_header", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "from_address": { + "name": "from_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_addresses": { + "name": "to_addresses", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "cc_addresses": { + "name": "cc_addresses", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attachment_file_ids": { + "name": "attachment_file_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "raw_file_id": { + "name": "raw_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_em_thread": { + "name": "idx_em_thread", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_em_message_id": { + "name": "idx_em_message_id", + "columns": [ + { + "expression": "message_id_header", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"email_messages\".\"message_id_header\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_messages_thread_id_email_threads_id_fk": { + "name": "email_messages_thread_id_email_threads_id_fk", + "tableFrom": "email_messages", + "tableTo": "email_threads", + "columnsFrom": ["thread_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_messages_raw_file_id_files_id_fk": { + "name": "email_messages_raw_file_id_files_id_fk", + "tableFrom": "email_messages", + "tableTo": "files", + "columnsFrom": ["raw_file_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_threads": { + "name": "email_threads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_message_at": { + "name": "last_message_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "message_count": { + "name": "message_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_et_client": { + "name": "idx_et_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_et_port": { + "name": "idx_et_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_threads_port_id_ports_id_fk": { + "name": "email_threads_port_id_ports_id_fk", + "tableFrom": "email_threads", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "email_threads_client_id_clients_id_fk": { + "name": "email_threads_client_id_clients_id_fk", + "tableFrom": "email_threads", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.expenses": { + "name": "expenses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "establishment_name": { + "name": "establishment_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount": { + "name": "amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "amount_usd": { + "name": "amount_usd", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "exchange_rate": { + "name": "exchange_rate", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "payment_method": { + "name": "payment_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payer": { + "name": "payer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expense_date": { + "name": "expense_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "receipt_file_ids": { + "name": "receipt_file_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "payment_status": { + "name": "payment_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'unpaid'" + }, + "payment_date": { + "name": "payment_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "payment_reference": { + "name": "payment_reference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payment_notes": { + "name": "payment_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duplicate_of": { + "name": "duplicate_of", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dedup_scanned_at": { + "name": "dedup_scanned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ocr_status": { + "name": "ocr_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'pending'" + }, + "ocr_raw": { + "name": "ocr_raw", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ocr_confidence": { + "name": "ocr_confidence", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_expenses_port": { + "name": "idx_expenses_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_expenses_date": { + "name": "idx_expenses_date", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expense_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_expenses_category": { + "name": "idx_expenses_category", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_expenses_dedup": { + "name": "idx_expenses_dedup", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "establishment_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "amount", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expense_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "duplicate_of IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "expenses_port_id_ports_id_fk": { + "name": "expenses_port_id_ports_id_fk", + "tableFrom": "expenses", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "expenses_duplicate_of_expenses_id_fk": { + "name": "expenses_duplicate_of_expenses_id_fk", + "tableFrom": "expenses", + "tableTo": "expenses", + "columnsFrom": ["duplicate_of"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invoice_expenses": { + "name": "invoice_expenses", + "schema": "", + "columns": { + "invoice_id": { + "name": "invoice_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expense_id": { + "name": "expense_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "invoice_expenses_invoice_id_invoices_id_fk": { + "name": "invoice_expenses_invoice_id_invoices_id_fk", + "tableFrom": "invoice_expenses", + "tableTo": "invoices", + "columnsFrom": ["invoice_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invoice_expenses_expense_id_expenses_id_fk": { + "name": "invoice_expenses_expense_id_expenses_id_fk", + "tableFrom": "invoice_expenses", + "tableTo": "expenses", + "columnsFrom": ["expense_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "invoice_expenses_invoice_id_expense_id_pk": { + "name": "invoice_expenses_invoice_id_expense_id_pk", + "columns": ["invoice_id", "expense_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invoice_line_items": { + "name": "invoice_line_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "invoice_id": { + "name": "invoice_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'1'" + }, + "unit_price": { + "name": "unit_price", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "total": { + "name": "total", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ili_invoice": { + "name": "idx_ili_invoice", + "columns": [ + { + "expression": "invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invoice_line_items_invoice_id_invoices_id_fk": { + "name": "invoice_line_items_invoice_id_invoices_id_fk", + "tableFrom": "invoice_line_items", + "tableTo": "invoices", + "columnsFrom": ["invoice_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invoices": { + "name": "invoices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invoice_number": { + "name": "invoice_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "billing_entity_type": { + "name": "billing_entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'client'" + }, + "billing_entity_id": { + "name": "billing_entity_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_address": { + "name": "billing_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "due_date": { + "name": "due_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "payment_terms": { + "name": "payment_terms", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'net30'" + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "subtotal": { + "name": "subtotal", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "discount_pct": { + "name": "discount_pct", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "discount_amount": { + "name": "discount_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "fee_pct": { + "name": "fee_pct", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "fee_amount": { + "name": "fee_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "total": { + "name": "total", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "payment_status": { + "name": "payment_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'unpaid'" + }, + "payment_date": { + "name": "payment_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "payment_method": { + "name": "payment_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payment_reference": { + "name": "payment_reference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pdf_file_id": { + "name": "pdf_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_invoices_number": { + "name": "idx_invoices_number", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invoice_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_invoices_port": { + "name": "idx_invoices_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_invoices_status": { + "name": "idx_invoices_status", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_invoices_billing_entity": { + "name": "idx_invoices_billing_entity", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invoices_port_id_ports_id_fk": { + "name": "invoices_port_id_ports_id_fk", + "tableFrom": "invoices", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_pdf_file_id_files_id_fk": { + "name": "invoices_pdf_file_id_files_id_fk", + "tableFrom": "invoices", + "tableTo": "files", + "columnsFrom": ["pdf_file_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ports": { + "name": "ports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "primary_color": { + "name": "primary_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_currency": { + "name": "default_currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'America/Anguilla'" + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ports_slug_idx": { + "name": "ports_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.port_role_overrides": { + "name": "port_role_overrides", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_overrides": { + "name": "permission_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "port_role_overrides_port_role_idx": { + "name": "port_role_overrides_port_role_idx", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "port_role_overrides_port_idx": { + "name": "port_role_overrides_port_idx", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "port_role_overrides_port_id_ports_id_fk": { + "name": "port_role_overrides_port_id_ports_id_fk", + "tableFrom": "port_role_overrides", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "port_role_overrides_role_id_roles_id_fk": { + "name": "port_role_overrides_role_id_roles_id_fk", + "tableFrom": "port_role_overrides", + "tableTo": "roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "is_global": { + "name": "is_global", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sessions_token_idx": { + "name": "sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_port_roles": { + "name": "user_port_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "residential_access": { + "name": "residential_access", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_port_roles_user_port_role_idx": { + "name": "user_port_roles_user_port_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_upr_user": { + "name": "idx_upr_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_upr_port": { + "name": "idx_upr_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_port_roles_port_id_ports_id_fk": { + "name": "user_port_roles_port_id_ports_id_fk", + "tableFrom": "user_port_roles", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_port_roles_role_id_roles_id_fk": { + "name": "user_port_roles_role_id_roles_id_fk", + "tableFrom": "user_port_roles", + "tableTo": "roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_profiles": { + "name": "user_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_super_admin": { + "name": "is_super_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_login_at": { + "name": "last_login_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "preferences": { + "name": "preferences", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_profiles_user_id_idx": { + "name": "user_profiles_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_profiles_user_id_unique": { + "name": "user_profiles_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.yacht_notes": { + "name": "yacht_notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mentions": { + "name": "mentions", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "is_locked": { + "name": "is_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_yn_yacht": { + "name": "idx_yn_yacht", + "columns": [ + { + "expression": "yacht_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "yacht_notes_yacht_id_yachts_id_fk": { + "name": "yacht_notes_yacht_id_yachts_id_fk", + "tableFrom": "yacht_notes", + "tableTo": "yachts", + "columnsFrom": ["yacht_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.yacht_ownership_history": { + "name": "yacht_ownership_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_date": { + "name": "start_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "end_date": { + "name": "end_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "transfer_reason": { + "name": "transfer_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transfer_notes": { + "name": "transfer_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_yoh_yacht": { + "name": "idx_yoh_yacht", + "columns": [ + { + "expression": "yacht_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_yoh_active": { + "name": "idx_yoh_active", + "columns": [ + { + "expression": "yacht_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"yacht_ownership_history\".\"end_date\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "yacht_ownership_history_yacht_id_yachts_id_fk": { + "name": "yacht_ownership_history_yacht_id_yachts_id_fk", + "tableFrom": "yacht_ownership_history", + "tableTo": "yachts", + "columnsFrom": ["yacht_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.yacht_tags": { + "name": "yacht_tags", + "schema": "", + "columns": { + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "yacht_tags_yacht_id_yachts_id_fk": { + "name": "yacht_tags_yacht_id_yachts_id_fk", + "tableFrom": "yacht_tags", + "tableTo": "yachts", + "columnsFrom": ["yacht_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "yacht_tags_yacht_id_tag_id_pk": { + "name": "yacht_tags_yacht_id_tag_id_pk", + "columns": ["yacht_id", "tag_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.yachts": { + "name": "yachts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hull_number": { + "name": "hull_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration": { + "name": "registration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "flag": { + "name": "flag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "year_built": { + "name": "year_built", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "builder": { + "name": "builder", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hull_material": { + "name": "hull_material", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "length_ft": { + "name": "length_ft", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "width_ft": { + "name": "width_ft", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "draft_ft": { + "name": "draft_ft", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "length_m": { + "name": "length_m", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "width_m": { + "name": "width_m", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "draft_m": { + "name": "draft_m", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "current_owner_type": { + "name": "current_owner_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "current_owner_id": { + "name": "current_owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_yachts_port": { + "name": "idx_yachts_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_yachts_current_owner": { + "name": "idx_yachts_current_owner", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_owner_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_yachts_name": { + "name": "idx_yachts_name", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_yachts_archived": { + "name": "idx_yachts_archived", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "yachts_port_id_ports_id_fk": { + "name": "yachts_port_id_ports_id_fk", + "tableFrom": "yachts", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interest_notes": { + "name": "interest_notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mentions": { + "name": "mentions", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "is_locked": { + "name": "is_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_in_interest": { + "name": "idx_in_interest", + "columns": [ + { + "expression": "interest_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "interest_notes_interest_id_interests_id_fk": { + "name": "interest_notes_interest_id_interests_id_fk", + "tableFrom": "interest_notes", + "tableTo": "interests", + "columnsFrom": ["interest_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interest_tags": { + "name": "interest_tags", + "schema": "", + "columns": { + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "interest_tags_interest_id_interests_id_fk": { + "name": "interest_tags_interest_id_interests_id_fk", + "tableFrom": "interest_tags", + "tableTo": "interests", + "columnsFrom": ["interest_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "interest_tags_interest_id_tag_id_pk": { + "name": "interest_tags_interest_id_tag_id_pk", + "columns": ["interest_id", "tag_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interests": { + "name": "interests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pipeline_stage": { + "name": "pipeline_stage", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "lead_category": { + "name": "lead_category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "eoi_status": { + "name": "eoi_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "documenso_id": { + "name": "documenso_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contract_status": { + "name": "contract_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deposit_status": { + "name": "deposit_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reservation_status": { + "name": "reservation_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date_first_contact": { + "name": "date_first_contact", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "date_last_contact": { + "name": "date_last_contact", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "date_eoi_sent": { + "name": "date_eoi_sent", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "date_eoi_signed": { + "name": "date_eoi_signed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "date_contract_sent": { + "name": "date_contract_sent", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "date_contract_signed": { + "name": "date_contract_signed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "date_deposit_received": { + "name": "date_deposit_received", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reminder_enabled": { + "name": "reminder_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "reminder_days": { + "name": "reminder_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "reminder_last_fired": { + "name": "reminder_last_fired", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_interests_port": { + "name": "idx_interests_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_interests_client": { + "name": "idx_interests_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_interests_berth": { + "name": "idx_interests_berth", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_interests_yacht": { + "name": "idx_interests_yacht", + "columns": [ + { + "expression": "yacht_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_interests_stage": { + "name": "idx_interests_stage", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_stage", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_interests_archived": { + "name": "idx_interests_archived", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "interests_port_id_ports_id_fk": { + "name": "interests_port_id_ports_id_fk", + "tableFrom": "interests", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "interests_client_id_clients_id_fk": { + "name": "interests_client_id_clients_id_fk", + "tableFrom": "interests", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.berth_reservations": { + "name": "berth_reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_date": { + "name": "start_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "end_date": { + "name": "end_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "tenure_type": { + "name": "tenure_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'permanent'" + }, + "contract_file_id": { + "name": "contract_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_br_berth": { + "name": "idx_br_berth", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_br_client": { + "name": "idx_br_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_br_yacht": { + "name": "idx_br_yacht", + "columns": [ + { + "expression": "yacht_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_br_port": { + "name": "idx_br_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_br_active": { + "name": "idx_br_active", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"berth_reservations\".\"status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "berth_reservations_berth_id_berths_id_fk": { + "name": "berth_reservations_berth_id_berths_id_fk", + "tableFrom": "berth_reservations", + "tableTo": "berths", + "columnsFrom": ["berth_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "berth_reservations_port_id_ports_id_fk": { + "name": "berth_reservations_port_id_ports_id_fk", + "tableFrom": "berth_reservations", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "berth_reservations_client_id_clients_id_fk": { + "name": "berth_reservations_client_id_clients_id_fk", + "tableFrom": "berth_reservations", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "berth_reservations_yacht_id_yachts_id_fk": { + "name": "berth_reservations_yacht_id_yachts_id_fk", + "tableFrom": "berth_reservations", + "tableTo": "yachts", + "columnsFrom": ["yacht_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "berth_reservations_interest_id_interests_id_fk": { + "name": "berth_reservations_interest_id_interests_id_fk", + "tableFrom": "berth_reservations", + "tableTo": "interests", + "columnsFrom": ["interest_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "berth_reservations_contract_file_id_files_id_fk": { + "name": "berth_reservations_contract_file_id_files_id_fk", + "tableFrom": "berth_reservations", + "tableTo": "files", + "columnsFrom": ["contract_file_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.portal_auth_tokens": { + "name": "portal_auth_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "portal_user_id": { + "name": "portal_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_portal_tokens_hash_unique": { + "name": "idx_portal_tokens_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_portal_tokens_user": { + "name": "idx_portal_tokens_user", + "columns": [ + { + "expression": "portal_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "portal_auth_tokens_portal_user_id_portal_users_id_fk": { + "name": "portal_auth_tokens_portal_user_id_portal_users_id_fk", + "tableFrom": "portal_auth_tokens", + "tableTo": "portal_users", + "columnsFrom": ["portal_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.portal_users": { + "name": "portal_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_login_at": { + "name": "last_login_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_portal_users_email_unique": { + "name": "idx_portal_users_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_portal_users_client": { + "name": "idx_portal_users_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_portal_users_port": { + "name": "idx_portal_users_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "portal_users_port_id_ports_id_fk": { + "name": "portal_users_port_id_ports_id_fk", + "tableFrom": "portal_users", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "portal_users_client_id_clients_id_fk": { + "name": "portal_users_client_id_clients_id_fk", + "tableFrom": "portal_users", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.residential_clients": { + "name": "residential_clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "place_of_residence": { + "name": "place_of_residence", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preferred_contact_method": { + "name": "preferred_contact_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'prospect'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_residential_clients_port": { + "name": "idx_residential_clients_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_residential_clients_email": { + "name": "idx_residential_clients_email", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_residential_clients_archived": { + "name": "idx_residential_clients_archived", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "residential_clients_port_id_ports_id_fk": { + "name": "residential_clients_port_id_ports_id_fk", + "tableFrom": "residential_clients", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.residential_interests": { + "name": "residential_interests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "residential_client_id": { + "name": "residential_client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pipeline_stage": { + "name": "pipeline_stage", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'new'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preferences": { + "name": "preferences", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_to": { + "name": "assigned_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date_first_contact": { + "name": "date_first_contact", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "date_last_contact": { + "name": "date_last_contact", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_residential_interests_port": { + "name": "idx_residential_interests_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_residential_interests_client": { + "name": "idx_residential_interests_client", + "columns": [ + { + "expression": "residential_client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_residential_interests_stage": { + "name": "idx_residential_interests_stage", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_stage", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_residential_interests_assigned": { + "name": "idx_residential_interests_assigned", + "columns": [ + { + "expression": "assigned_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_residential_interests_archived": { + "name": "idx_residential_interests_archived", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "residential_interests_port_id_ports_id_fk": { + "name": "residential_interests_port_id_ports_id_fk", + "tableFrom": "residential_interests", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "residential_interests_residential_client_id_residential_clients_id_fk": { + "name": "residential_interests_residential_client_id_residential_clients_id_fk", + "tableFrom": "residential_interests", + "tableTo": "residential_clients", + "columnsFrom": ["residential_client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.generated_reports": { + "name": "generated_reports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scheduled_report_id": { + "name": "scheduled_report_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "report_type": { + "name": "report_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "parameters": { + "name": "parameters", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_gr_port_created": { + "name": "idx_gr_port_created", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_gr_port_status": { + "name": "idx_gr_port_status", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_gr_scheduled": { + "name": "idx_gr_scheduled", + "columns": [ + { + "expression": "scheduled_report_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"generated_reports\".\"scheduled_report_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "generated_reports_port_id_ports_id_fk": { + "name": "generated_reports_port_id_ports_id_fk", + "tableFrom": "generated_reports", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "generated_reports_scheduled_report_id_scheduled_reports_id_fk": { + "name": "generated_reports_scheduled_report_id_scheduled_reports_id_fk", + "tableFrom": "generated_reports", + "tableTo": "scheduled_reports", + "columnsFrom": ["scheduled_report_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "generated_reports_file_id_files_id_fk": { + "name": "generated_reports_file_id_files_id_fk", + "tableFrom": "generated_reports", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.google_calendar_cache": { + "name": "google_calendar_cache", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_at": { + "name": "start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "end_at": { + "name": "end_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_crm_pushed": { + "name": "is_crm_pushed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "reminder_id": { + "name": "reminder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "gcal_cache_user_event_idx": { + "name": "gcal_cache_user_event_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_gcal_cache_user": { + "name": "idx_gcal_cache_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "google_calendar_cache_reminder_id_reminders_id_fk": { + "name": "google_calendar_cache_reminder_id_reminders_id_fk", + "tableFrom": "google_calendar_cache", + "tableTo": "reminders", + "columnsFrom": ["reminder_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.google_calendar_tokens": { + "name": "google_calendar_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_expiry": { + "name": "token_expiry", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "calendar_id": { + "name": "calendar_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'primary'" + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sync_enabled": { + "name": "sync_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "gcal_tokens_user_id_idx": { + "name": "gcal_tokens_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "google_calendar_tokens_user_id_unique": { + "name": "google_calendar_tokens_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_read": { + "name": "is_read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email_sent": { + "name": "email_sent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_notif_user": { + "name": "idx_notif_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_read", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notif_port": { + "name": "idx_notif_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notifications_user_type": { + "name": "idx_notifications_user_type", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notifications_port_id_ports_id_fk": { + "name": "notifications_port_id_ports_id_fk", + "tableFrom": "notifications", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reminders": { + "name": "reminders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "due_at": { + "name": "due_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "assigned_to": { + "name": "assigned_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auto_generated": { + "name": "auto_generated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "google_calendar_event_id": { + "name": "google_calendar_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "google_calendar_synced": { + "name": "google_calendar_synced", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_reminders_port": { + "name": "idx_reminders_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_reminders_assigned": { + "name": "idx_reminders_assigned", + "columns": [ + { + "expression": "assigned_to", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_reminders_due": { + "name": "idx_reminders_due", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "due_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"reminders\".\"status\" IN ('pending', 'snoozed')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reminders_port_id_ports_id_fk": { + "name": "reminders_port_id_ports_id_fk", + "tableFrom": "reminders", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "reminders_client_id_clients_id_fk": { + "name": "reminders_client_id_clients_id_fk", + "tableFrom": "reminders", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.report_recipients": { + "name": "report_recipients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "report_id": { + "name": "report_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "report_recipients_report_email_idx": { + "name": "report_recipients_report_email_idx", + "columns": [ + { + "expression": "report_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_rr_report": { + "name": "idx_rr_report", + "columns": [ + { + "expression": "report_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "report_recipients_report_id_scheduled_reports_id_fk": { + "name": "report_recipients_report_id_scheduled_reports_id_fk", + "tableFrom": "report_recipients", + "tableTo": "scheduled_reports", + "columnsFrom": ["report_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scheduled_reports": { + "name": "scheduled_reports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "report_type": { + "name": "report_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_sr_port": { + "name": "idx_sr_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scheduled_reports_port_id_ports_id_fk": { + "name": "scheduled_reports_port_id_ports_id_fk", + "tableFrom": "scheduled_reports", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_logs": { + "name": "audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "field_changed": { + "name": "field_changed", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "old_value": { + "name": "old_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "new_value": { + "name": "new_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reverted_by": { + "name": "reverted_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reverted_at": { + "name": "reverted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revert_of": { + "name": "revert_of", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "search_text": { + "name": "search_text", + "type": "tsvector", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_al_port": { + "name": "idx_al_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_al_entity": { + "name": "idx_al_entity", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_al_user": { + "name": "idx_al_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_al_created": { + "name": "idx_al_created", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_logs_port_id_ports_id_fk": { + "name": "audit_logs_port_id_ports_id_fk", + "tableFrom": "audit_logs", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "audit_logs_revert_of_audit_logs_id_fk": { + "name": "audit_logs_revert_of_audit_logs_id_fk", + "tableFrom": "audit_logs", + "tableTo": "audit_logs", + "columnsFrom": ["revert_of"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.currency_rates": { + "name": "currency_rates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "base_currency": { + "name": "base_currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_currency": { + "name": "target_currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rate": { + "name": "rate", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'frankfurter'" + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "currency_rates_base_target_idx": { + "name": "currency_rates_base_target_idx", + "columns": [ + { + "expression": "base_currency", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_currency", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_field_definitions": { + "name": "custom_field_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_name": { + "name": "field_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_label": { + "name": "field_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "select_options": { + "name": "select_options", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_required": { + "name": "is_required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cfd_port_entity_name_idx": { + "name": "cfd_port_entity_name_idx", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "field_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cfd_port": { + "name": "idx_cfd_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_field_definitions_port_id_ports_id_fk": { + "name": "custom_field_definitions_port_id_ports_id_fk", + "tableFrom": "custom_field_definitions", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_field_values": { + "name": "custom_field_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "field_id": { + "name": "field_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cfv_field_entity_idx": { + "name": "cfv_field_entity_idx", + "columns": [ + { + "expression": "field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cfv_entity": { + "name": "idx_cfv_entity", + "columns": [ + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_field_values_field_id_custom_field_definitions_id_fk": { + "name": "custom_field_values_field_id_custom_field_definitions_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "custom_field_definitions", + "columnsFrom": ["field_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.saved_views": { + "name": "saved_views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filters": { + "name": "filters", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "sort_config": { + "name": "sort_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "column_config": { + "name": "column_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_shared": { + "name": "is_shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_sv_user": { + "name": "idx_sv_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "saved_views_port_id_ports_id_fk": { + "name": "saved_views_port_id_ports_id_fk", + "tableFrom": "saved_views", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scratchpad_notes": { + "name": "scratchpad_notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "linked_client_id": { + "name": "linked_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linked_at": { + "name": "linked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_sp_user": { + "name": "idx_sp_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scratchpad_notes_linked_client_id_clients_id_fk": { + "name": "scratchpad_notes_linked_client_id_clients_id_fk", + "tableFrom": "scratchpad_notes", + "tableTo": "clients", + "columnsFrom": ["linked_client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "system_settings_key_port_idx": { + "name": "system_settings_key_port_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "system_settings_port_id_ports_id_fk": { + "name": "system_settings_port_id_ports_id_fk", + "tableFrom": "system_settings", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tags": { + "name": "tags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#6B7280'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tags_port_name_idx": { + "name": "tags_port_name_idx", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_tags_port": { + "name": "idx_tags_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tags_port_id_ports_id_fk": { + "name": "tags_port_id_ports_id_fk", + "tableFrom": "tags", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_notification_preferences": { + "name": "user_notification_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "in_app": { + "name": "in_app", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "email": { + "name": "email", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "unp_user_port_type_idx": { + "name": "unp_user_port_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_notification_preferences_port_id_ports_id_fk": { + "name": "user_notification_preferences_port_id_ports_id_fk", + "tableFrom": "user_notification_preferences", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_deliveries": { + "name": "webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "response_status": { + "name": "response_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "response_body": { + "name": "response_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_wd_webhook": { + "name": "idx_wd_webhook", + "columns": [ + { + "expression": "webhook_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_deliveries_webhook_id_webhooks_id_fk": { + "name": "webhook_deliveries_webhook_id_webhooks_id_fk", + "tableFrom": "webhook_deliveries", + "tableTo": "webhooks", + "columnsFrom": ["webhook_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhooks": { + "name": "webhooks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "events": { + "name": "events", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_webhooks_port": { + "name": "idx_webhooks_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhooks_port_id_ports_id_fk": { + "name": "webhooks_port_id_ports_id_fk", + "tableFrom": "webhooks", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.alerts": { + "name": "alerts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rule_id": { + "name": "rule_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fired_at": { + "name": "fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "dismissed_by": { + "name": "dismissed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "acknowledged_by": { + "name": "acknowledged_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "idx_alerts_fingerprint_open": { + "name": "idx_alerts_fingerprint_open", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "resolved_at IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_alerts_port_fired": { + "name": "idx_alerts_port_fired", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fired_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_alerts_port_severity_open": { + "name": "idx_alerts_port_severity_open", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "severity", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "resolved_at IS NULL AND dismissed_at IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "alerts_port_id_ports_id_fk": { + "name": "alerts_port_id_ports_id_fk", + "tableFrom": "alerts", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "alerts_dismissed_by_user_id_fk": { + "name": "alerts_dismissed_by_user_id_fk", + "tableFrom": "alerts", + "tableTo": "user", + "columnsFrom": ["dismissed_by"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "alerts_acknowledged_by_user_id_fk": { + "name": "alerts_acknowledged_by_user_id_fk", + "tableFrom": "alerts", + "tableTo": "user", + "columnsFrom": ["acknowledged_by"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.analytics_snapshots": { + "name": "analytics_snapshots", + "schema": "", + "columns": { + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metric_id": { + "name": "metric_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "computed_at": { + "name": "computed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_analytics_pk": { + "name": "idx_analytics_pk", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "analytics_snapshots_port_id_ports_id_fk": { + "name": "analytics_snapshots_port_id_ports_id_fk", + "tableFrom": "analytics_snapshots", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/src/lib/db/migrations/meta/_journal.json b/src/lib/db/migrations/meta/_journal.json index 27f7911..73d9820 100644 --- a/src/lib/db/migrations/meta/_journal.json +++ b/src/lib/db/migrations/meta/_journal.json @@ -99,6 +99,13 @@ "when": 1777334766194, "tag": "0013_abnormal_thundra", "breakpoints": true + }, + { + "idx": 14, + "version": "7", + "when": 1777379952283, + "tag": "0014_black_banshee", + "breakpoints": true } ] } diff --git a/src/lib/db/schema/financial.ts b/src/lib/db/schema/financial.ts index ffae5a7..cab7533 100644 --- a/src/lib/db/schema/financial.ts +++ b/src/lib/db/schema/financial.ts @@ -8,7 +8,10 @@ import { index, uniqueIndex, primaryKey, + jsonb, + AnyPgColumn, } from 'drizzle-orm/pg-core'; +import { sql } from 'drizzle-orm'; import { ports } from './ports'; import { files } from './documents'; @@ -36,6 +39,19 @@ export const expenses = pgTable( paymentDate: date('payment_date'), paymentReference: text('payment_reference'), paymentNotes: text('payment_notes'), + /** When set, this expense is flagged as a duplicate of another in the + * same port. Self-referencing FK; the dedup service writes this. */ + duplicateOf: text('duplicate_of').references((): AnyPgColumn => expenses.id, { + onDelete: 'set null', + }), + /** Last time the dedup heuristic ran against this row. */ + dedupScannedAt: timestamp('dedup_scanned_at', { withTimezone: true }), + /** OCR pipeline state: 'pending'|'ok'|'failed'|'low_confidence'. */ + ocrStatus: text('ocr_status').default('pending'), + /** Full Claude Vision response payload for audit/debug. */ + ocrRaw: jsonb('ocr_raw'), + /** 0..1; values < 0.6 force the verify-mode UI. */ + ocrConfidence: numeric('ocr_confidence'), createdBy: text('created_by').notNull(), archivedAt: timestamp('archived_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), @@ -45,6 +61,10 @@ export const expenses = pgTable( index('idx_expenses_port').on(table.portId), index('idx_expenses_date').on(table.portId, table.expenseDate), index('idx_expenses_category').on(table.portId, table.category), + // Powers the dedup heuristic lookup (port + vendor + amount + date window). + index('idx_expenses_dedup') + .on(table.portId, table.establishmentName, table.amount, table.expenseDate) + .where(sql`duplicate_of IS NULL`), ], ); diff --git a/src/lib/db/schema/index.ts b/src/lib/db/schema/index.ts index bc73800..745e005 100644 --- a/src/lib/db/schema/index.ts +++ b/src/lib/db/schema/index.ts @@ -47,5 +47,8 @@ export * from './operations'; // System export * from './system'; +// Insights (Phase B): alerts, analytics_snapshots +export * from './insights'; + // Relations (must come last — references all tables) export * from './relations'; diff --git a/src/lib/db/schema/insights.ts b/src/lib/db/schema/insights.ts new file mode 100644 index 0000000..5fb6ee7 --- /dev/null +++ b/src/lib/db/schema/insights.ts @@ -0,0 +1,101 @@ +/** + * Phase B — operational insight surfaces. + * + * - `alerts`: rule-engine-fired actionable cards. The fingerprint column + * dedupes re-evaluations of the same condition; the partial unique + * index keeps a single open row per `(port, fingerprint)` while + * resolved/dismissed history accumulates. + * - `analytics_snapshots`: cached aggregate JSON keyed by metric+range, + * refreshed by a recurring job so dashboard hits don't recompute. + */ + +import { pgTable, text, timestamp, jsonb, index, uniqueIndex } from 'drizzle-orm/pg-core'; +import { sql } from 'drizzle-orm'; + +import { ports } from './ports'; +import { user } from './users'; + +export const alerts = pgTable( + 'alerts', + { + id: text('id') + .primaryKey() + .$defaultFn(() => crypto.randomUUID()), + portId: text('port_id') + .notNull() + .references(() => ports.id, { onDelete: 'cascade' }), + /** Stable rule identifier: 'reservation.no_agreement', 'interest.stale', ... */ + ruleId: text('rule_id').notNull(), + /** 'info' | 'warning' | 'critical' */ + severity: text('severity').notNull(), + title: text('title').notNull(), + body: text('body'), + /** Relative path the card deep-links to. */ + link: text('link').notNull(), + /** Optional FK target: 'interest', 'reservation', 'document', 'expense', ... */ + entityType: text('entity_type'), + entityId: text('entity_id'), + /** Hash of (rule_id + entity_type + entity_id) — dedupes re-evaluations. */ + fingerprint: text('fingerprint').notNull(), + firedAt: timestamp('fired_at', { withTimezone: true }).notNull().defaultNow(), + dismissedAt: timestamp('dismissed_at', { withTimezone: true }), + dismissedBy: text('dismissed_by').references(() => user.id), + /** "Someone is on it" — alert stays visible but stops nagging. */ + acknowledgedAt: timestamp('acknowledged_at', { withTimezone: true }), + acknowledgedBy: text('acknowledged_by').references(() => user.id), + /** Set by the engine when the underlying condition no longer fires. */ + resolvedAt: timestamp('resolved_at', { withTimezone: true }), + /** Per-rule extras: days_stale, amount_at_risk, etc. */ + metadata: jsonb('metadata').default({}), + }, + (table) => [ + // Only one open alert per (port, fingerprint) — re-evaluation upserts. + uniqueIndex('idx_alerts_fingerprint_open') + .on(table.portId, table.fingerprint) + .where(sql`resolved_at IS NULL`), + index('idx_alerts_port_fired').on(table.portId, table.firedAt), + index('idx_alerts_port_severity_open') + .on(table.portId, table.severity) + .where(sql`resolved_at IS NULL AND dismissed_at IS NULL`), + ], +); + +export type Alert = typeof alerts.$inferSelect; +export type NewAlert = typeof alerts.$inferInsert; + +export const analyticsSnapshots = pgTable( + 'analytics_snapshots', + { + portId: text('port_id') + .notNull() + .references(() => ports.id, { onDelete: 'cascade' }), + /** Composite key: e.g. 'pipeline_funnel.30d', 'occupancy_timeline.90d'. */ + metricId: text('metric_id').notNull(), + computedAt: timestamp('computed_at', { withTimezone: true }).notNull().defaultNow(), + /** Pre-shaped chart data. */ + data: jsonb('data').notNull(), + }, + (table) => [uniqueIndex('idx_analytics_pk').on(table.portId, table.metricId)], +); + +export type AnalyticsSnapshot = typeof analyticsSnapshots.$inferSelect; +export type NewAnalyticsSnapshot = typeof analyticsSnapshots.$inferInsert; + +/** Severity literal type for callers that want a typed enum. */ +export type AlertSeverity = 'info' | 'warning' | 'critical'; + +/** Rule IDs in the v1 catalog — keep in sync with `alert-rules.ts`. */ +export const ALERT_RULES = [ + 'reservation.no_agreement', + 'interest.stale', + 'document.expiring_soon', + 'document.signer_overdue', + 'berth.under_offer_stalled', + 'expense.duplicate', + 'expense.unscanned', + 'interest.high_value_silent', + 'eoi.unsigned_long', + 'audit.suspicious_login', +] as const; + +export type AlertRuleId = (typeof ALERT_RULES)[number]; diff --git a/src/lib/db/schema/system.ts b/src/lib/db/schema/system.ts index 19d1d4b..ec2fedc 100644 --- a/src/lib/db/schema/system.ts +++ b/src/lib/db/schema/system.ts @@ -8,14 +8,24 @@ import { jsonb, index, uniqueIndex, + customType, } from 'drizzle-orm/pg-core'; import { ports } from './ports'; import { clients } from './clients'; +// Drizzle doesn't ship a first-class tsvector type; declare a thin custom one. +const tsvector = customType<{ data: string; driverData: string }>({ + dataType() { + return 'tsvector'; + }, +}); + export const auditLogs = pgTable( 'audit_logs', { - id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), + id: text('id') + .primaryKey() + .$defaultFn(() => crypto.randomUUID()), portId: text('port_id').references(() => ports.id), // null for system-level events userId: text('user_id'), // null for system-generated events action: text('action').notNull(), // create, update, delete, archive, restore, merge, login, logout, revert @@ -31,6 +41,10 @@ export const auditLogs = pgTable( // eslint-disable-next-line @typescript-eslint/no-explicit-any revertOf: text('revert_of').references((): any => auditLogs.id), metadata: jsonb('metadata').default({}), + /** Full-text search column. Stored generated; updated by the migration's + * GENERATED ALWAYS expression covering action + entityType + entityId + * + actor email lookup. */ + searchText: tsvector('search_text'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [ @@ -44,7 +58,9 @@ export const auditLogs = pgTable( export const tags = pgTable( 'tags', { - id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), + id: text('id') + .primaryKey() + .$defaultFn(() => crypto.randomUUID()), portId: text('port_id') .notNull() .references(() => ports.id), @@ -61,7 +77,9 @@ export const tags = pgTable( export const webhooks = pgTable( 'webhooks', { - id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), + id: text('id') + .primaryKey() + .$defaultFn(() => crypto.randomUUID()), portId: text('port_id') .notNull() .references(() => ports.id), @@ -80,7 +98,9 @@ export const webhooks = pgTable( export const webhookDeliveries = pgTable( 'webhook_deliveries', { - id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), + id: text('id') + .primaryKey() + .$defaultFn(() => crypto.randomUUID()), webhookId: text('webhook_id') .notNull() .references(() => webhooks.id, { onDelete: 'cascade' }), @@ -115,7 +135,9 @@ export const systemSettings = pgTable( export const savedViews = pgTable( 'saved_views', { - id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), + id: text('id') + .primaryKey() + .$defaultFn(() => crypto.randomUUID()), portId: text('port_id') .notNull() .references(() => ports.id), @@ -136,7 +158,9 @@ export const savedViews = pgTable( export const scratchpadNotes = pgTable( 'scratchpad_notes', { - id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), + id: text('id') + .primaryKey() + .$defaultFn(() => crypto.randomUUID()), userId: text('user_id').notNull(), content: text('content').notNull(), linkedClientId: text('linked_client_id').references(() => clients.id), @@ -150,7 +174,9 @@ export const scratchpadNotes = pgTable( export const userNotificationPreferences = pgTable( 'user_notification_preferences', { - id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), + id: text('id') + .primaryKey() + .$defaultFn(() => crypto.randomUUID()), userId: text('user_id').notNull(), portId: text('port_id') .notNull() @@ -167,7 +193,9 @@ export const userNotificationPreferences = pgTable( export const currencyRates = pgTable( 'currency_rates', { - id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), + id: text('id') + .primaryKey() + .$defaultFn(() => crypto.randomUUID()), baseCurrency: text('base_currency').notNull(), targetCurrency: text('target_currency').notNull(), rate: numeric('rate').notNull(), @@ -182,7 +210,9 @@ export const currencyRates = pgTable( export const customFieldDefinitions = pgTable( 'custom_field_definitions', { - id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), + id: text('id') + .primaryKey() + .$defaultFn(() => crypto.randomUUID()), portId: text('port_id') .notNull() .references(() => ports.id), @@ -204,7 +234,9 @@ export const customFieldDefinitions = pgTable( export const customFieldValues = pgTable( 'custom_field_values', { - id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), + id: text('id') + .primaryKey() + .$defaultFn(() => crypto.randomUUID()), fieldId: text('field_id') .notNull() .references(() => customFieldDefinitions.id, { onDelete: 'cascade' }), diff --git a/src/lib/services/alert-rules.ts b/src/lib/services/alert-rules.ts new file mode 100644 index 0000000..c98db08 --- /dev/null +++ b/src/lib/services/alert-rules.ts @@ -0,0 +1,31 @@ +/** + * Alert rule catalog. Each entry is a pure async function that takes a + * `portId` and returns an array of `AlertCandidate` rows the engine should + * upsert into `alerts`. Skeleton: signatures only — implementations land + * in PR2. + */ + +import type { AlertCandidate } from './alerts.service'; +import { ALERT_RULES, type AlertRuleId } from '@/lib/db/schema/insights'; + +type RuleEvaluator = (portId: string) => Promise; + +/** Empty implementations — every evaluator returns no candidates. PR2 + * fills these in; the cron dispatcher in PR2 walks `RULE_REGISTRY`. */ +export const RULE_REGISTRY: Record = { + 'reservation.no_agreement': async () => [], + 'interest.stale': async () => [], + 'document.expiring_soon': async () => [], + 'document.signer_overdue': async () => [], + 'berth.under_offer_stalled': async () => [], + 'expense.duplicate': async () => [], + 'expense.unscanned': async () => [], + 'interest.high_value_silent': async () => [], + 'eoi.unsigned_long': async () => [], + 'audit.suspicious_login': async () => [], +}; + +/** Sanity check: catalog matches the ALERT_RULES literal type. */ +export function listRuleIds(): readonly AlertRuleId[] { + return ALERT_RULES; +} diff --git a/src/lib/services/alerts.service.ts b/src/lib/services/alerts.service.ts new file mode 100644 index 0000000..b294bef --- /dev/null +++ b/src/lib/services/alerts.service.ts @@ -0,0 +1,115 @@ +/** + * Phase B alert framework — service layer. + * + * This is the skeleton: types, function shapes, and behaviour stubs. The + * actual rule evaluators live in `alert-rules.ts` (PR2). The cron + * dispatcher will compose this service with that catalogue. + */ + +import { and, eq, isNull, sql } from 'drizzle-orm'; +import { createHash } from 'crypto'; + +import { db } from '@/lib/db'; +import { alerts, type Alert, type AlertSeverity, type AlertRuleId } from '@/lib/db/schema/insights'; + +export interface AlertCandidate { + ruleId: AlertRuleId; + severity: AlertSeverity; + title: string; + body?: string; + link: string; + entityType?: string; + entityId?: string; + metadata?: Record; +} + +/** + * Stable identity hash so re-evaluations of the same condition upsert + * onto the same row (via `idx_alerts_fingerprint_open`). + */ +export function fingerprintFor( + c: Pick, +): string { + return createHash('sha1') + .update(`${c.ruleId}|${c.entityType ?? ''}|${c.entityId ?? ''}`) + .digest('hex'); +} + +/** + * Apply a batch of rule outputs against the open-alert table: + * - upsert open alerts (rule still firing) + * - resolve any open alert in scope whose fingerprint isn't in this batch + */ +export async function reconcileAlertsForPort( + portId: string, + ruleId: AlertRuleId, + candidates: AlertCandidate[], +): Promise { + // Insert new / leave existing — only one open row per fingerprint + // thanks to the partial unique index. + for (const c of candidates) { + const fingerprint = fingerprintFor(c); + await db + .insert(alerts) + .values({ + portId, + ruleId: c.ruleId, + severity: c.severity, + title: c.title, + body: c.body, + link: c.link, + entityType: c.entityType, + entityId: c.entityId, + fingerprint, + metadata: c.metadata ?? {}, + }) + .onConflictDoNothing(); + } + + // Auto-resolve open alerts for this rule whose fingerprint disappeared. + const liveFingerprints = new Set(candidates.map((c) => fingerprintFor(c))); + const open = await db.query.alerts.findMany({ + where: and(eq(alerts.portId, portId), eq(alerts.ruleId, ruleId), isNull(alerts.resolvedAt)), + }); + const stale = open.filter((a) => !liveFingerprints.has(a.fingerprint)); + for (const a of stale) { + await db + .update(alerts) + .set({ resolvedAt: sql`now()` }) + .where(eq(alerts.id, a.id)); + } +} + +export async function dismissAlert(alertId: string, userId: string): Promise { + await db + .update(alerts) + .set({ dismissedAt: sql`now()`, dismissedBy: userId }) + .where(eq(alerts.id, alertId)); +} + +export async function acknowledgeAlert(alertId: string, userId: string): Promise { + await db + .update(alerts) + .set({ acknowledgedAt: sql`now()`, acknowledgedBy: userId }) + .where(eq(alerts.id, alertId)); +} + +export interface ListAlertsOptions { + severity?: AlertSeverity[]; + includeDismissed?: boolean; + includeResolved?: boolean; +} + +export async function listAlertsForPort( + portId: string, + options: ListAlertsOptions = {}, +): Promise { + const conditions = [eq(alerts.portId, portId)]; + if (!options.includeResolved) conditions.push(isNull(alerts.resolvedAt)); + if (!options.includeDismissed) conditions.push(isNull(alerts.dismissedAt)); + return db.query.alerts.findMany({ + where: and(...conditions), + orderBy: (a, { desc }) => [desc(a.firedAt)], + limit: 100, + }); +} diff --git a/src/lib/services/analytics.service.ts b/src/lib/services/analytics.service.ts new file mode 100644 index 0000000..b1852dd --- /dev/null +++ b/src/lib/services/analytics.service.ts @@ -0,0 +1,106 @@ +/** + * Phase B analytics service. Reads pre-computed snapshots from + * `analytics_snapshots` keyed by `metric_id` and recomputes on demand if + * the cached row is older than `SNAPSHOT_TTL_MS`. The recomputation jobs + * land in `analytics-snapshot-job.ts` (PR3). + */ + +import { and, eq } from 'drizzle-orm'; + +import { db } from '@/lib/db'; +import { analyticsSnapshots } from '@/lib/db/schema/insights'; + +export type DateRange = '7d' | '30d' | '90d' | 'today'; + +export type MetricId = + | `pipeline_funnel.${DateRange}` + | `occupancy_timeline.${DateRange}` + | `revenue_breakdown.${DateRange}` + | `lead_source_attribution.${DateRange}`; + +export const SNAPSHOT_TTL_MS = 15 * 60 * 1000; // 15 minutes + +export interface PipelineFunnelData { + stages: Array<{ stage: string; count: number; conversionPct: number }>; +} + +export interface OccupancyTimelineData { + points: Array<{ date: string; available: number; underOffer: number; sold: number }>; +} + +export interface RevenueBreakdownData { + bars: Array<{ category: string; amount: number; currency: string }>; +} + +export interface LeadSourceAttributionData { + slices: Array<{ source: string; count: number }>; +} + +export type SnapshotData = + | PipelineFunnelData + | OccupancyTimelineData + | RevenueBreakdownData + | LeadSourceAttributionData; + +/** + * Read a snapshot by `(portId, metricId)`. Returns null when missing or + * stale; the caller should request a recompute (or the recurring job + * eventually fills it). + */ +export async function readSnapshot( + portId: string, + metricId: MetricId, +): Promise { + const row = await db.query.analyticsSnapshots.findFirst({ + where: and(eq(analyticsSnapshots.portId, portId), eq(analyticsSnapshots.metricId, metricId)), + }); + if (!row) return null; + const age = Date.now() - row.computedAt.getTime(); + if (age > SNAPSHOT_TTL_MS) return null; + return row.data as T; +} + +export async function writeSnapshot( + portId: string, + metricId: MetricId, + data: SnapshotData, +): Promise { + await db + .insert(analyticsSnapshots) + .values({ portId, metricId, data }) + .onConflictDoUpdate({ + target: [analyticsSnapshots.portId, analyticsSnapshots.metricId], + set: { data, computedAt: new Date() }, + }); +} + +// Computation entrypoints — bodies land in PR3 along with the recurring +// snapshot job. Exported as no-op stubs so PR1's tsc/lint stay green. + +export async function computePipelineFunnel( + _portId: string, + _range: DateRange, +): Promise { + return { stages: [] }; +} + +export async function computeOccupancyTimeline( + _portId: string, + _range: DateRange, +): Promise { + return { points: [] }; +} + +export async function computeRevenueBreakdown( + _portId: string, + _range: DateRange, +): Promise { + return { bars: [] }; +} + +export async function computeLeadSourceAttribution( + _portId: string, + _range: DateRange, +): Promise { + return { slices: [] }; +} diff --git a/src/lib/services/audit-search.service.ts b/src/lib/services/audit-search.service.ts new file mode 100644 index 0000000..c8aea97 --- /dev/null +++ b/src/lib/services/audit-search.service.ts @@ -0,0 +1,72 @@ +/** + * Audit log search — PR1 skeleton. PR10 fills in the cursor pagination + * and per-port + super-admin scoping; v1 already has the GIN index on + * `audit_logs.search_text`. + */ + +import { and, desc, eq, gte, lte, sql, type SQL } from 'drizzle-orm'; + +import { db } from '@/lib/db'; +import { auditLogs, type AuditLog } from '@/lib/db/schema/system'; + +export interface AuditSearchOptions { + /** Limit results to a single port. Omit for super-admin all-ports view. */ + portId?: string; + /** Free-text query — runs against the GIN-indexed search_text column. */ + q?: string; + /** Filter by actor (user id). */ + userId?: string; + /** Filter by action verb: 'create' | 'update' | 'delete' | ... */ + action?: string; + /** Filter by entity type: 'client' | 'interest' | 'document' | ... */ + entityType?: string; + /** Filter by exact entity id (e.g. paste a uuid into search). */ + entityId?: string; + /** Inclusive date range. */ + from?: Date; + to?: Date; + /** Pagination — cursor on (createdAt, id). */ + cursor?: { createdAt: Date; id: string }; + limit?: number; +} + +export interface AuditSearchPage { + rows: AuditLog[]; + nextCursor: { createdAt: Date; id: string } | null; +} + +export async function searchAuditLogs(options: AuditSearchOptions = {}): Promise { + const conds: SQL[] = []; + if (options.portId) conds.push(eq(auditLogs.portId, options.portId)); + if (options.userId) conds.push(eq(auditLogs.userId, options.userId)); + if (options.action) conds.push(eq(auditLogs.action, options.action)); + if (options.entityType) conds.push(eq(auditLogs.entityType, options.entityType)); + if (options.entityId) conds.push(eq(auditLogs.entityId, options.entityId)); + if (options.from) conds.push(gte(auditLogs.createdAt, options.from)); + if (options.to) conds.push(lte(auditLogs.createdAt, options.to)); + if (options.q) { + // tsquery match against the GENERATED tsvector column. + conds.push(sql`${auditLogs.searchText} @@ plainto_tsquery('simple', ${options.q})`); + } + if (options.cursor) { + // Strict less-than on (createdAt, id) for stable cursor pagination. + conds.push( + sql`(${auditLogs.createdAt}, ${auditLogs.id}) < (${options.cursor.createdAt}, ${options.cursor.id})`, + ); + } + + const limit = Math.min(options.limit ?? 50, 200); + const rows = await db.query.auditLogs.findMany({ + where: conds.length > 0 ? and(...conds) : undefined, + orderBy: [desc(auditLogs.createdAt), desc(auditLogs.id)], + limit: limit + 1, + }); + + const hasMore = rows.length > limit; + const truncated = hasMore ? rows.slice(0, limit) : rows; + const last = truncated[truncated.length - 1]; + return { + rows: truncated, + nextCursor: hasMore && last ? { createdAt: last.createdAt, id: last.id } : null, + }; +} diff --git a/src/lib/services/expense-dedup.service.ts b/src/lib/services/expense-dedup.service.ts new file mode 100644 index 0000000..e89f729 --- /dev/null +++ b/src/lib/services/expense-dedup.service.ts @@ -0,0 +1,71 @@ +/** + * Expense duplicate detection — heuristic match on + * (port + vendor + amount + date ± 3d). PR1 ships the function shape; + * PR8 wires the BullMQ trigger and the merge service. + */ + +import { and, between, eq, ne, sql } from 'drizzle-orm'; + +import { db } from '@/lib/db'; +import { expenses } from '@/lib/db/schema/financial'; + +const DEDUP_WINDOW_DAYS = 3; + +export interface DedupCandidate { + /** Existing expense that the new one likely duplicates. */ + candidateId: string; + /** 0..1 confidence; 1.0 = exact vendor + amount + same day. */ + confidence: number; +} + +export async function scanForDuplicates(expenseId: string): Promise { + const target = await db.query.expenses.findFirst({ where: eq(expenses.id, expenseId) }); + if (!target) return []; + + const { portId, establishmentName, amount, expenseDate } = target; + if (!establishmentName || !amount || !expenseDate) return []; + + const lo = new Date(expenseDate); + lo.setDate(lo.getDate() - DEDUP_WINDOW_DAYS); + const hi = new Date(expenseDate); + hi.setDate(hi.getDate() + DEDUP_WINDOW_DAYS); + + const matches = await db.query.expenses.findMany({ + where: and( + eq(expenses.portId, portId), + sql`lower(${expenses.establishmentName}) = lower(${establishmentName})`, + eq(expenses.amount, amount), + between(expenses.expenseDate, lo, hi), + ne(expenses.id, expenseId), + ), + limit: 5, + }); + + return matches.map((m) => ({ + candidateId: m.id, + confidence: dayDiff(m.expenseDate, expenseDate) === 0 ? 1.0 : 0.85, + })); +} + +function dayDiff(a: Date, b: Date): number { + const ms = Math.abs(a.getTime() - b.getTime()); + return Math.round(ms / 86_400_000); +} + +/** Mark an expense as a duplicate of the candidate with the highest score. */ +export async function markBestDuplicate(expenseId: string): Promise { + const candidates = await scanForDuplicates(expenseId); + if (candidates.length === 0) { + await db + .update(expenses) + .set({ dedupScannedAt: sql`now()` }) + .where(eq(expenses.id, expenseId)); + return null; + } + const best = candidates.reduce((a, b) => (a.confidence >= b.confidence ? a : b)); + await db + .update(expenses) + .set({ duplicateOf: best.candidateId, dedupScannedAt: sql`now()` }) + .where(eq(expenses.id, expenseId)); + return best.candidateId; +} diff --git a/src/lib/services/expense-ocr.service.ts b/src/lib/services/expense-ocr.service.ts new file mode 100644 index 0000000..587afb0 --- /dev/null +++ b/src/lib/services/expense-ocr.service.ts @@ -0,0 +1,47 @@ +/** + * Claude Vision-driven OCR for expense receipts. PR1 stub: types and the + * service contract. The actual API call wires up in PR9 with prompt + * caching on the system text and Haiku 4.5 by default. + */ + +export interface OcrLineItem { + description: string; + quantity?: number; + unitPrice?: number; + amount: number; +} + +export interface OcrResult { + vendor: string | null; + amount: number | null; + currency: string | null; + /** ISO date YYYY-MM-DD. */ + date: string | null; + lineItems: OcrLineItem[]; + /** 0..1; below 0.6 surfaces "verify mode" UI. */ + confidence: number; +} + +export interface OcrContext { + fileId: string; + fileUrl: string; + /** Optional MIME hint; the service still detects from bytes. */ + mimeType?: string; +} + +/** Cost ceiling per call (Haiku 4.5 + cached system prompt). PR9 enforces. */ +export const OCR_MAX_TOKENS = 1024; +export const OCR_LOW_CONFIDENCE_THRESHOLD = 0.6; + +/** Stub — returns "pending" shape so callers can wire UI in PR1 without + * Anthropic credentials. */ +export async function ocrReceipt(_ctx: OcrContext): Promise { + return { + vendor: null, + amount: null, + currency: null, + date: null, + lineItems: [], + confidence: 0, + }; +}