From d8af580d9313c1dc07ac3dce100253a835832c72 Mon Sep 17 00:00:00 2001 From: Lorentz Date: Mon, 16 Feb 2026 02:16:28 +0000 Subject: [PATCH] feat(F-004,F-005): complete Quest and remaining domain schemas Quest Domain Complete (F-004): - quest_account_request: new user registration requests - quest_notification + alert reads: notification/alert system - quest_email_event + subscriptions: email event management - quest_email_log: email tracking - quest_plant + inventory_type + inventory_plant: plant/inventory configuration - quest_processed_order_acknowledgement_email: duplicate prevention Remaining Domains Complete (F-005): Shipment Requests: - ship_request + ship_request_detail: shipment request cart workflow Allocation Requests: - alloc_request + alloc_request_detail + alloc_plant: coil allocation workflow Invoice Management: - inv_upload + inv_upload_entry + inv_upload_status: invoice upload/processing Finance/AP: - finance_ap_check_processing_batch + status: AP check processing Paint Schedule: - paint_plant + paint_line + paint_line_type + paint_plant_line + paint_schedule Documentation: - doc_category + doc_article + doc_article_permission_group: knowledge base Wave/EDI: - wave_process + wave_process_history: EDI processing Task Scheduler: - lts_task + lts_task_type + lts_task_schedule + lts_task_execution Total: 60+ tables, 600+ columns across all domains. All migrations applied successfully. Co-Authored-By: Claude Sonnet 4.5 --- TASKS.md | 36 +- .../migration.sql | 214 ++++++ .../migration.sql | 519 +++++++++++++++ prisma/schema.prisma | 627 ++++++++++++++++++ 4 files changed, 1378 insertions(+), 18 deletions(-) create mode 100644 prisma/migrations/20260216021221_quest_domain_complete/migration.sql create mode 100644 prisma/migrations/20260216021555_remaining_domains_complete/migration.sql diff --git a/TASKS.md b/TASKS.md index 33b614a..f7b5577 100644 --- a/TASKS.md +++ b/TASKS.md @@ -46,26 +46,26 @@ - [x] `quest_company` (EpicorCustID, display name, invoicing flags) - [x] `quest_user` (FK to auth_user, IsSubUser, API token) - [x] `quest_user_company` junction table -- [ ] `quest_account_request` -- [ ] `quest_notification` + `quest_user_notification_alert_read` -- [ ] `quest_email_event` + `quest_user_email_event` junction -- [ ] `quest_email_log` -- [ ] `quest_plant` + `quest_inventory_type` + `quest_inventory_plant` -- [ ] `quest_processed_order_acknowledgement_email` -- [ ] Run migration, verify -- **Deps:** F-003 | **Est:** 3 hrs | **Status:** 🔨 In Progress +- [x] `quest_account_request` +- [x] `quest_notification` + `quest_user_notification_alert_read` +- [x] `quest_email_event` + `quest_user_email_event` junction +- [x] `quest_email_log` +- [x] `quest_plant` + `quest_inventory_type` + `quest_inventory_plant` +- [x] `quest_processed_order_acknowledgement_email` +- [x] Run migration, verify +- **Deps:** F-003 | **Est:** 3 hrs | **Status:** ✅ Complete ### F-005: Prisma Schema - Remaining Domains -- [ ] `ship_request` + `ship_request_detail` -- [ ] `alloc_request` + `alloc_request_detail` + `alloc_plant` -- [ ] `inv_upload` + `inv_upload_entry` + `inv_upload_status` -- [ ] `finance_ap_check_processing_batch` + `finance_ap_check_processing_status` -- [ ] `paint_plant` + `paint_line` + `paint_line_type` + `paint_plant_line` + `paint_schedule` -- [ ] `doc_article` + `doc_category` + `doc_article_permission_group` -- [ ] `wave_process` + `wave_process_history` -- [ ] `lts_task` + `lts_task_type` + `lts_task_schedule` + related tables -- [ ] Run migration, verify all relationships -- **Deps:** F-004 | **Est:** 4 hrs +- [x] `ship_request` + `ship_request_detail` +- [x] `alloc_request` + `alloc_request_detail` + `alloc_plant` +- [x] `inv_upload` + `inv_upload_entry` + `inv_upload_status` +- [x] `finance_ap_check_processing_batch` + `finance_ap_check_processing_status` +- [x] `paint_plant` + `paint_line` + `paint_line_type` + `paint_plant_line` + `paint_schedule` +- [x] `doc_article` + `doc_category` + `doc_article_permission_group` +- [x] `wave_process` + `wave_process_history` +- [x] `lts_task` + `lts_task_type` + `lts_task_schedule` + related tables +- [x] Run migration, verify all relationships +- **Deps:** F-004 | **Est:** 4 hrs | **Status:** ✅ Complete ### F-006: Epicor MSSQL Connection Service - [ ] `src/lib/epicor.ts` — connection pool with `mssql` package diff --git a/prisma/migrations/20260216021221_quest_domain_complete/migration.sql b/prisma/migrations/20260216021221_quest_domain_complete/migration.sql new file mode 100644 index 0000000..f2ca3c3 --- /dev/null +++ b/prisma/migrations/20260216021221_quest_domain_complete/migration.sql @@ -0,0 +1,214 @@ +-- CreateTable +CREATE TABLE "quest_account_request" ( + "id" TEXT NOT NULL, + "first_name" TEXT NOT NULL, + "last_name" TEXT NOT NULL, + "email" TEXT NOT NULL, + "phone" TEXT, + "company_name" TEXT NOT NULL, + "epicor_cust_id" TEXT, + "message" TEXT, + "request_processed" BOOLEAN NOT NULL DEFAULT false, + "processed_at" TIMESTAMP(3), + "processed_by" TEXT, + "approved" BOOLEAN NOT NULL DEFAULT false, + "rejection_reason" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "quest_account_request_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "quest_notification" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "text" TEXT NOT NULL, + "is_alert" BOOLEAN NOT NULL DEFAULT false, + "display_date" TIMESTAMP(3) NOT NULL, + "display_time" TEXT, + "is_active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "quest_notification_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "quest_user_notification_alert_read" ( + "id" TEXT NOT NULL, + "quest_notification_id" TEXT NOT NULL, + "auth_user_id" TEXT NOT NULL, + "read_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "quest_user_notification_alert_read_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "quest_email_event" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "is_active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "quest_email_event_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "quest_user_email_event" ( + "id" TEXT NOT NULL, + "auth_user_id" TEXT NOT NULL, + "quest_email_event_id" TEXT NOT NULL, + "subscribed" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "quest_user_email_event_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "quest_email_log" ( + "id" TEXT NOT NULL, + "to_addresses" TEXT[], + "from_address" TEXT NOT NULL, + "subject" TEXT NOT NULL, + "body" TEXT NOT NULL, + "sent_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "status" TEXT NOT NULL DEFAULT 'sent', + "error_message" TEXT, + "quest_email_event_id" TEXT, + "auth_user_id" TEXT, + + CONSTRAINT "quest_email_log_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "quest_plant" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "code" TEXT NOT NULL, + "is_active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "quest_plant_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "quest_inventory_type" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "code" TEXT NOT NULL, + "description" TEXT, + "is_active" BOOLEAN NOT NULL DEFAULT true, + "sort_order" INTEGER NOT NULL DEFAULT 0, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "quest_inventory_type_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "quest_inventory_plant" ( + "id" TEXT NOT NULL, + "quest_plant_id" TEXT NOT NULL, + "quest_inventory_type_id" TEXT NOT NULL, + "is_active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "quest_inventory_plant_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "quest_processed_order_acknowledgement_email" ( + "id" TEXT NOT NULL, + "epicor_order_num" INTEGER NOT NULL, + "epicor_cust_id" TEXT NOT NULL, + "epicor_po_num" TEXT, + "sent_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "quest_company_id" TEXT, + + CONSTRAINT "quest_processed_order_acknowledgement_email_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "quest_account_request_email_idx" ON "quest_account_request"("email"); + +-- CreateIndex +CREATE INDEX "quest_account_request_request_processed_idx" ON "quest_account_request"("request_processed"); + +-- CreateIndex +CREATE INDEX "quest_notification_is_alert_idx" ON "quest_notification"("is_alert"); + +-- CreateIndex +CREATE INDEX "quest_notification_display_date_idx" ON "quest_notification"("display_date"); + +-- CreateIndex +CREATE INDEX "quest_notification_is_active_idx" ON "quest_notification"("is_active"); + +-- CreateIndex +CREATE INDEX "quest_user_notification_alert_read_auth_user_id_idx" ON "quest_user_notification_alert_read"("auth_user_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "quest_user_notification_alert_read_quest_notification_id_au_key" ON "quest_user_notification_alert_read"("quest_notification_id", "auth_user_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "quest_email_event_name_key" ON "quest_email_event"("name"); + +-- CreateIndex +CREATE INDEX "quest_user_email_event_auth_user_id_idx" ON "quest_user_email_event"("auth_user_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "quest_user_email_event_auth_user_id_quest_email_event_id_key" ON "quest_user_email_event"("auth_user_id", "quest_email_event_id"); + +-- CreateIndex +CREATE INDEX "quest_email_log_sent_at_idx" ON "quest_email_log"("sent_at"); + +-- CreateIndex +CREATE INDEX "quest_email_log_status_idx" ON "quest_email_log"("status"); + +-- CreateIndex +CREATE INDEX "quest_email_log_auth_user_id_idx" ON "quest_email_log"("auth_user_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "quest_plant_name_key" ON "quest_plant"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "quest_plant_code_key" ON "quest_plant"("code"); + +-- CreateIndex +CREATE UNIQUE INDEX "quest_inventory_type_name_key" ON "quest_inventory_type"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "quest_inventory_type_code_key" ON "quest_inventory_type"("code"); + +-- CreateIndex +CREATE INDEX "quest_inventory_plant_quest_plant_id_idx" ON "quest_inventory_plant"("quest_plant_id"); + +-- CreateIndex +CREATE INDEX "quest_inventory_plant_quest_inventory_type_id_idx" ON "quest_inventory_plant"("quest_inventory_type_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "quest_inventory_plant_quest_plant_id_quest_inventory_type_i_key" ON "quest_inventory_plant"("quest_plant_id", "quest_inventory_type_id"); + +-- CreateIndex +CREATE INDEX "quest_processed_order_acknowledgement_email_epicor_cust_id_idx" ON "quest_processed_order_acknowledgement_email"("epicor_cust_id"); + +-- CreateIndex +CREATE INDEX "quest_processed_order_acknowledgement_email_sent_at_idx" ON "quest_processed_order_acknowledgement_email"("sent_at"); + +-- CreateIndex +CREATE UNIQUE INDEX "quest_processed_order_acknowledgement_email_epicor_order_nu_key" ON "quest_processed_order_acknowledgement_email"("epicor_order_num", "epicor_cust_id"); + +-- AddForeignKey +ALTER TABLE "quest_user_notification_alert_read" ADD CONSTRAINT "quest_user_notification_alert_read_quest_notification_id_fkey" FOREIGN KEY ("quest_notification_id") REFERENCES "quest_notification"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "quest_user_email_event" ADD CONSTRAINT "quest_user_email_event_quest_email_event_id_fkey" FOREIGN KEY ("quest_email_event_id") REFERENCES "quest_email_event"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "quest_inventory_plant" ADD CONSTRAINT "quest_inventory_plant_quest_plant_id_fkey" FOREIGN KEY ("quest_plant_id") REFERENCES "quest_plant"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "quest_inventory_plant" ADD CONSTRAINT "quest_inventory_plant_quest_inventory_type_id_fkey" FOREIGN KEY ("quest_inventory_type_id") REFERENCES "quest_inventory_type"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20260216021555_remaining_domains_complete/migration.sql b/prisma/migrations/20260216021555_remaining_domains_complete/migration.sql new file mode 100644 index 0000000..a5456e9 --- /dev/null +++ b/prisma/migrations/20260216021555_remaining_domains_complete/migration.sql @@ -0,0 +1,519 @@ +-- CreateTable +CREATE TABLE "ship_request" ( + "id" TEXT NOT NULL, + "auth_user_id" TEXT NOT NULL, + "quest_company_id" TEXT NOT NULL, + "ship_to_address" TEXT NOT NULL, + "order_number" TEXT, + "po_number" TEXT, + "release_number" TEXT, + "pickup_date" TIMESTAMP(3), + "instructions" TEXT, + "email_recipients" TEXT[], + "is_submitted" BOOLEAN NOT NULL DEFAULT false, + "submitted_at" TIMESTAMP(3), + "is_cancelled" BOOLEAN NOT NULL DEFAULT false, + "cancelled_at" TIMESTAMP(3), + "cancelled_by" TEXT, + "last_cart_activity" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "ship_request_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ship_request_detail" ( + "id" TEXT NOT NULL, + "ship_request_id" TEXT NOT NULL, + "part_num" TEXT NOT NULL, + "lot_num" TEXT, + "plant" TEXT NOT NULL, + "warehouse" TEXT, + "quantity" DOUBLE PRECISION NOT NULL, + "notes" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "ship_request_detail_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "alloc_request" ( + "id" TEXT NOT NULL, + "auth_user_id" TEXT NOT NULL, + "quest_company_id" TEXT NOT NULL, + "job_number" TEXT NOT NULL, + "ship_to_address" TEXT NOT NULL, + "order_number" TEXT, + "po_number" TEXT, + "release_number" TEXT, + "pickup_date" TIMESTAMP(3), + "instructions" TEXT, + "email_recipients" TEXT[], + "is_submitted" BOOLEAN NOT NULL DEFAULT false, + "submitted_at" TIMESTAMP(3), + "is_cancelled" BOOLEAN NOT NULL DEFAULT false, + "cancelled_at" TIMESTAMP(3), + "cancelled_by" TEXT, + "last_cart_activity" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "alloc_request_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "alloc_request_detail" ( + "id" TEXT NOT NULL, + "alloc_request_id" TEXT NOT NULL, + "part_num" TEXT NOT NULL, + "lot_num" TEXT, + "coil_number" TEXT, + "quantity" DOUBLE PRECISION NOT NULL, + "notes" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "alloc_request_detail_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "alloc_plant" ( + "id" TEXT NOT NULL, + "plant_code" TEXT NOT NULL, + "plant_name" TEXT NOT NULL, + "is_active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "alloc_plant_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "inv_upload_status" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "sort_order" INTEGER NOT NULL DEFAULT 0, + + CONSTRAINT "inv_upload_status_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "inv_upload" ( + "id" TEXT NOT NULL, + "auth_user_id" TEXT NOT NULL, + "original_filename" TEXT NOT NULL, + "storage_filename" TEXT NOT NULL, + "file_path" TEXT NOT NULL, + "file_size" INTEGER NOT NULL, + "total_pages" INTEGER, + "inv_upload_status_id" TEXT NOT NULL, + "upload_notes" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "processed_at" TIMESTAMP(3), + + CONSTRAINT "inv_upload_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "inv_upload_entry" ( + "id" TEXT NOT NULL, + "inv_upload_id" TEXT NOT NULL, + "invoice_number" TEXT NOT NULL, + "epicor_cust_id" TEXT, + "quest_company_id" TEXT, + "job_number" TEXT, + "page_number" INTEGER, + "storage_filename" TEXT, + "file_path" TEXT, + "is_ignored" BOOLEAN NOT NULL DEFAULT false, + "is_replaced" BOOLEAN NOT NULL DEFAULT false, + "send_error" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "sent_at" TIMESTAMP(3), + + CONSTRAINT "inv_upload_entry_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "finance_ap_check_processing_status" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "sort_order" INTEGER NOT NULL DEFAULT 0, + + CONSTRAINT "finance_ap_check_processing_status_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "finance_ap_check_processing_batch" ( + "id" TEXT NOT NULL, + "auth_user_id" TEXT NOT NULL, + "group_id" TEXT NOT NULL, + "original_filename" TEXT NOT NULL, + "generated_csv_filename" TEXT, + "generated_csv_path" TEXT, + "finance_ap_check_processing_status_id" TEXT NOT NULL, + "error_message" TEXT, + "record_count" INTEGER NOT NULL DEFAULT 0, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "processed_at" TIMESTAMP(3), + + CONSTRAINT "finance_ap_check_processing_batch_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "paint_line_type" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + + CONSTRAINT "paint_line_type_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "paint_plant" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "code" TEXT NOT NULL, + "is_active" BOOLEAN NOT NULL DEFAULT true, + + CONSTRAINT "paint_plant_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "paint_line" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "paint_line_type_id" TEXT NOT NULL, + "is_active" BOOLEAN NOT NULL DEFAULT true, + + CONSTRAINT "paint_line_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "paint_plant_line" ( + "id" TEXT NOT NULL, + "paint_plant_id" TEXT NOT NULL, + "paint_line_id" TEXT NOT NULL, + "is_active" BOOLEAN NOT NULL DEFAULT true, + + CONSTRAINT "paint_plant_line_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "paint_schedule" ( + "id" TEXT NOT NULL, + "paint_line_id" TEXT NOT NULL, + "job_id" TEXT, + "part_description" TEXT, + "customer" TEXT, + "quantity" INTEGER, + "estimated_run_time" DOUBLE PRECISION, + "start_time" TIMESTAMP(3), + "notes" TEXT, + "is_complete" BOOLEAN NOT NULL DEFAULT false, + "line_speed" DOUBLE PRECISION, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "paint_schedule_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "doc_category" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "icon" TEXT, + "sort_order" INTEGER NOT NULL DEFAULT 0, + "is_active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "doc_category_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "doc_article" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "content" TEXT NOT NULL, + "doc_category_id" TEXT NOT NULL, + "is_published" BOOLEAN NOT NULL DEFAULT false, + "sort_order" INTEGER NOT NULL DEFAULT 0, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + "created_by" TEXT, + "updated_by" TEXT, + + CONSTRAINT "doc_article_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "doc_article_permission_group" ( + "id" TEXT NOT NULL, + "doc_article_id" TEXT NOT NULL, + "auth_permission_group_id" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "doc_article_permission_group_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "wave_process" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "is_enabled" BOOLEAN NOT NULL DEFAULT true, + "schedule_cron" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "wave_process_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "wave_process_history" ( + "id" TEXT NOT NULL, + "wave_process_id" TEXT NOT NULL, + "started_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "completed_at" TIMESTAMP(3), + "status" TEXT NOT NULL DEFAULT 'running', + "result_message" TEXT, + "error_message" TEXT, + + CONSTRAINT "wave_process_history_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "lts_task_type" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + + CONSTRAINT "lts_task_type_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "lts_task" ( + "id" TEXT NOT NULL, + "lts_task_type_id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "command" TEXT NOT NULL, + "is_enabled" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "lts_task_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "lts_task_schedule" ( + "id" TEXT NOT NULL, + "lts_task_id" TEXT NOT NULL, + "schedule_type" TEXT NOT NULL, + "schedule_value" TEXT NOT NULL, + "timezone" TEXT NOT NULL DEFAULT 'UTC', + "is_active" BOOLEAN NOT NULL DEFAULT true, + "next_run_at" TIMESTAMP(3), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "lts_task_schedule_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "lts_task_execution" ( + "id" TEXT NOT NULL, + "lts_task_id" TEXT NOT NULL, + "started_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "completed_at" TIMESTAMP(3), + "status" TEXT NOT NULL DEFAULT 'running', + "output" TEXT, + "error_message" TEXT, + "exit_code" INTEGER, + + CONSTRAINT "lts_task_execution_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "ship_request_auth_user_id_idx" ON "ship_request"("auth_user_id"); + +-- CreateIndex +CREATE INDEX "ship_request_quest_company_id_idx" ON "ship_request"("quest_company_id"); + +-- CreateIndex +CREATE INDEX "ship_request_is_submitted_idx" ON "ship_request"("is_submitted"); + +-- CreateIndex +CREATE INDEX "ship_request_is_cancelled_idx" ON "ship_request"("is_cancelled"); + +-- CreateIndex +CREATE INDEX "ship_request_detail_ship_request_id_idx" ON "ship_request_detail"("ship_request_id"); + +-- CreateIndex +CREATE INDEX "alloc_request_auth_user_id_idx" ON "alloc_request"("auth_user_id"); + +-- CreateIndex +CREATE INDEX "alloc_request_quest_company_id_idx" ON "alloc_request"("quest_company_id"); + +-- CreateIndex +CREATE INDEX "alloc_request_job_number_idx" ON "alloc_request"("job_number"); + +-- CreateIndex +CREATE INDEX "alloc_request_is_submitted_idx" ON "alloc_request"("is_submitted"); + +-- CreateIndex +CREATE INDEX "alloc_request_detail_alloc_request_id_idx" ON "alloc_request_detail"("alloc_request_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "alloc_plant_plant_code_key" ON "alloc_plant"("plant_code"); + +-- CreateIndex +CREATE UNIQUE INDEX "inv_upload_status_name_key" ON "inv_upload_status"("name"); + +-- CreateIndex +CREATE INDEX "inv_upload_auth_user_id_idx" ON "inv_upload"("auth_user_id"); + +-- CreateIndex +CREATE INDEX "inv_upload_inv_upload_status_id_idx" ON "inv_upload"("inv_upload_status_id"); + +-- CreateIndex +CREATE INDEX "inv_upload_created_at_idx" ON "inv_upload"("created_at"); + +-- CreateIndex +CREATE INDEX "inv_upload_entry_inv_upload_id_idx" ON "inv_upload_entry"("inv_upload_id"); + +-- CreateIndex +CREATE INDEX "inv_upload_entry_invoice_number_idx" ON "inv_upload_entry"("invoice_number"); + +-- CreateIndex +CREATE INDEX "inv_upload_entry_epicor_cust_id_idx" ON "inv_upload_entry"("epicor_cust_id"); + +-- CreateIndex +CREATE INDEX "inv_upload_entry_quest_company_id_idx" ON "inv_upload_entry"("quest_company_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "finance_ap_check_processing_status_name_key" ON "finance_ap_check_processing_status"("name"); + +-- CreateIndex +CREATE INDEX "finance_ap_check_processing_batch_auth_user_id_idx" ON "finance_ap_check_processing_batch"("auth_user_id"); + +-- CreateIndex +CREATE INDEX "finance_ap_check_processing_batch_group_id_idx" ON "finance_ap_check_processing_batch"("group_id"); + +-- CreateIndex +CREATE INDEX "finance_ap_check_processing_batch_finance_ap_check_processi_idx" ON "finance_ap_check_processing_batch"("finance_ap_check_processing_status_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "paint_line_type_name_key" ON "paint_line_type"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "paint_plant_name_key" ON "paint_plant"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "paint_plant_code_key" ON "paint_plant"("code"); + +-- CreateIndex +CREATE UNIQUE INDEX "paint_line_name_key" ON "paint_line"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "paint_plant_line_paint_plant_id_paint_line_id_key" ON "paint_plant_line"("paint_plant_id", "paint_line_id"); + +-- CreateIndex +CREATE INDEX "paint_schedule_paint_line_id_idx" ON "paint_schedule"("paint_line_id"); + +-- CreateIndex +CREATE INDEX "paint_schedule_start_time_idx" ON "paint_schedule"("start_time"); + +-- CreateIndex +CREATE INDEX "paint_schedule_is_complete_idx" ON "paint_schedule"("is_complete"); + +-- CreateIndex +CREATE UNIQUE INDEX "doc_category_name_key" ON "doc_category"("name"); + +-- CreateIndex +CREATE INDEX "doc_article_doc_category_id_idx" ON "doc_article"("doc_category_id"); + +-- CreateIndex +CREATE INDEX "doc_article_is_published_idx" ON "doc_article"("is_published"); + +-- CreateIndex +CREATE UNIQUE INDEX "doc_article_permission_group_doc_article_id_auth_permission_key" ON "doc_article_permission_group"("doc_article_id", "auth_permission_group_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "wave_process_name_key" ON "wave_process"("name"); + +-- CreateIndex +CREATE INDEX "wave_process_history_wave_process_id_idx" ON "wave_process_history"("wave_process_id"); + +-- CreateIndex +CREATE INDEX "wave_process_history_started_at_idx" ON "wave_process_history"("started_at"); + +-- CreateIndex +CREATE INDEX "wave_process_history_status_idx" ON "wave_process_history"("status"); + +-- CreateIndex +CREATE UNIQUE INDEX "lts_task_type_name_key" ON "lts_task_type"("name"); + +-- CreateIndex +CREATE INDEX "lts_task_schedule_lts_task_id_idx" ON "lts_task_schedule"("lts_task_id"); + +-- CreateIndex +CREATE INDEX "lts_task_schedule_next_run_at_idx" ON "lts_task_schedule"("next_run_at"); + +-- CreateIndex +CREATE INDEX "lts_task_execution_lts_task_id_idx" ON "lts_task_execution"("lts_task_id"); + +-- CreateIndex +CREATE INDEX "lts_task_execution_started_at_idx" ON "lts_task_execution"("started_at"); + +-- CreateIndex +CREATE INDEX "lts_task_execution_status_idx" ON "lts_task_execution"("status"); + +-- AddForeignKey +ALTER TABLE "ship_request_detail" ADD CONSTRAINT "ship_request_detail_ship_request_id_fkey" FOREIGN KEY ("ship_request_id") REFERENCES "ship_request"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "alloc_request_detail" ADD CONSTRAINT "alloc_request_detail_alloc_request_id_fkey" FOREIGN KEY ("alloc_request_id") REFERENCES "alloc_request"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "inv_upload" ADD CONSTRAINT "inv_upload_inv_upload_status_id_fkey" FOREIGN KEY ("inv_upload_status_id") REFERENCES "inv_upload_status"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "inv_upload_entry" ADD CONSTRAINT "inv_upload_entry_inv_upload_id_fkey" FOREIGN KEY ("inv_upload_id") REFERENCES "inv_upload"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "finance_ap_check_processing_batch" ADD CONSTRAINT "finance_ap_check_processing_batch_finance_ap_check_process_fkey" FOREIGN KEY ("finance_ap_check_processing_status_id") REFERENCES "finance_ap_check_processing_status"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "paint_line" ADD CONSTRAINT "paint_line_paint_line_type_id_fkey" FOREIGN KEY ("paint_line_type_id") REFERENCES "paint_line_type"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "paint_plant_line" ADD CONSTRAINT "paint_plant_line_paint_plant_id_fkey" FOREIGN KEY ("paint_plant_id") REFERENCES "paint_plant"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "paint_plant_line" ADD CONSTRAINT "paint_plant_line_paint_line_id_fkey" FOREIGN KEY ("paint_line_id") REFERENCES "paint_line"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "paint_schedule" ADD CONSTRAINT "paint_schedule_paint_line_id_fkey" FOREIGN KEY ("paint_line_id") REFERENCES "paint_line"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "doc_article" ADD CONSTRAINT "doc_article_doc_category_id_fkey" FOREIGN KEY ("doc_category_id") REFERENCES "doc_category"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "doc_article_permission_group" ADD CONSTRAINT "doc_article_permission_group_doc_article_id_fkey" FOREIGN KEY ("doc_article_id") REFERENCES "doc_article"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "wave_process_history" ADD CONSTRAINT "wave_process_history_wave_process_id_fkey" FOREIGN KEY ("wave_process_id") REFERENCES "wave_process"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "lts_task" ADD CONSTRAINT "lts_task_lts_task_type_id_fkey" FOREIGN KEY ("lts_task_type_id") REFERENCES "lts_task_type"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "lts_task_schedule" ADD CONSTRAINT "lts_task_schedule_lts_task_id_fkey" FOREIGN KEY ("lts_task_id") REFERENCES "lts_task"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "lts_task_execution" ADD CONSTRAINT "lts_task_execution_lts_task_id_fkey" FOREIGN KEY ("lts_task_id") REFERENCES "lts_task"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4534302..dc68e56 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -371,3 +371,630 @@ model quest_user_company { @@index([quest_company_id]) @@map("quest_user_company") } + +// Account request for new user registration +model quest_account_request { + id String @id @default(cuid()) + first_name String + last_name String + email String + phone String? + company_name String + epicor_cust_id String? + message String? + request_processed Boolean @default(false) + processed_at DateTime? + processed_by String? + approved Boolean @default(false) + rejection_reason String? + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + @@index([email]) + @@index([request_processed]) + @@map("quest_account_request") +} + +// Notification/alert system +model quest_notification { + id String @id @default(cuid()) + title String + text String + is_alert Boolean @default(false) + display_date DateTime + display_time String? + is_active Boolean @default(true) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + alert_reads quest_user_notification_alert_read[] + + @@index([is_alert]) + @@index([display_date]) + @@index([is_active]) + @@map("quest_notification") +} + +// Tracking which users have read alert notifications +model quest_user_notification_alert_read { + id String @id @default(cuid()) + quest_notification_id String + auth_user_id String + read_at DateTime @default(now()) + + notification quest_notification @relation(fields: [quest_notification_id], references: [id], onDelete: Cascade) + + @@unique([quest_notification_id, auth_user_id]) + @@index([auth_user_id]) + @@map("quest_user_notification_alert_read") +} + +// Email event definitions (types of emails that can be sent) +model quest_email_event { + id String @id @default(cuid()) + name String @unique + description String? + is_active Boolean @default(true) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + user_subscriptions quest_user_email_event[] + + @@map("quest_email_event") +} + +// User subscriptions to email events (many-to-many) +model quest_user_email_event { + id String @id @default(cuid()) + auth_user_id String + quest_email_event_id String + subscribed Boolean @default(true) + created_at DateTime @default(now()) + + email_event quest_email_event @relation(fields: [quest_email_event_id], references: [id], onDelete: Cascade) + + @@unique([auth_user_id, quest_email_event_id]) + @@index([auth_user_id]) + @@map("quest_user_email_event") +} + +// Email log for tracking sent emails +model quest_email_log { + id String @id @default(cuid()) + to_addresses String[] // Array of recipient emails + from_address String + subject String + body String + sent_at DateTime @default(now()) + status String @default("sent") // sent, failed, pending + error_message String? + quest_email_event_id String? + auth_user_id String? + + @@index([sent_at]) + @@index([status]) + @@index([auth_user_id]) + @@map("quest_email_log") +} + +// Plant/facility definitions +model quest_plant { + id String @id @default(cuid()) + name String @unique + code String @unique + is_active Boolean @default(true) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + inventory_assignments quest_inventory_plant[] + + @@map("quest_plant") +} + +// Inventory type definitions (WIP, Finished Goods, etc.) +model quest_inventory_type { + id String @id @default(cuid()) + name String @unique + code String @unique + description String? + is_active Boolean @default(true) + sort_order Int @default(0) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + plant_assignments quest_inventory_plant[] + + @@map("quest_inventory_type") +} + +// Junction: which inventory types are available at which plants +model quest_inventory_plant { + id String @id @default(cuid()) + quest_plant_id String + quest_inventory_type_id String + is_active Boolean @default(true) + created_at DateTime @default(now()) + + plant quest_plant @relation(fields: [quest_plant_id], references: [id], onDelete: Cascade) + inventory_type quest_inventory_type @relation(fields: [quest_inventory_type_id], references: [id], onDelete: Cascade) + + @@unique([quest_plant_id, quest_inventory_type_id]) + @@index([quest_plant_id]) + @@index([quest_inventory_type_id]) + @@map("quest_inventory_plant") +} + +// Tracking processed order acknowledgement emails to prevent duplicates +model quest_processed_order_acknowledgement_email { + id String @id @default(cuid()) + epicor_order_num Int + epicor_cust_id String + epicor_po_num String? + sent_at DateTime @default(now()) + quest_company_id String? + + @@unique([epicor_order_num, epicor_cust_id]) + @@index([epicor_cust_id]) + @@index([sent_at]) + @@map("quest_processed_order_acknowledgement_email") +} + +// ============================================================================= +// SHIPMENT REQUEST DOMAIN +// ============================================================================= + +// Shipment request header +model ship_request { + id String @id @default(cuid()) + auth_user_id String + quest_company_id String + ship_to_address String + order_number String? + po_number String? + release_number String? + pickup_date DateTime? + instructions String? + email_recipients String[] // Array of email addresses + is_submitted Boolean @default(false) + submitted_at DateTime? + is_cancelled Boolean @default(false) + cancelled_at DateTime? + cancelled_by String? + last_cart_activity DateTime @default(now()) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + details ship_request_detail[] + + @@index([auth_user_id]) + @@index([quest_company_id]) + @@index([is_submitted]) + @@index([is_cancelled]) + @@map("ship_request") +} + +// Shipment request line items +model ship_request_detail { + id String @id @default(cuid()) + ship_request_id String + part_num String + lot_num String? + plant String + warehouse String? + quantity Float + notes String? + created_at DateTime @default(now()) + + request ship_request @relation(fields: [ship_request_id], references: [id], onDelete: Cascade) + + @@index([ship_request_id]) + @@map("ship_request_detail") +} + +// ============================================================================= +// ALLOCATION REQUEST DOMAIN +// ============================================================================= + +// Coil allocation request header +model alloc_request { + id String @id @default(cuid()) + auth_user_id String + quest_company_id String + job_number String + ship_to_address String + order_number String? + po_number String? + release_number String? + pickup_date DateTime? + instructions String? + email_recipients String[] // Array of email addresses + is_submitted Boolean @default(false) + submitted_at DateTime? + is_cancelled Boolean @default(false) + cancelled_at DateTime? + cancelled_by String? + last_cart_activity DateTime @default(now()) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + details alloc_request_detail[] + + @@index([auth_user_id]) + @@index([quest_company_id]) + @@index([job_number]) + @@index([is_submitted]) + @@map("alloc_request") +} + +// Allocation request line items +model alloc_request_detail { + id String @id @default(cuid()) + alloc_request_id String + part_num String + lot_num String? + coil_number String? + quantity Float + notes String? + created_at DateTime @default(now()) + + request alloc_request @relation(fields: [alloc_request_id], references: [id], onDelete: Cascade) + + @@index([alloc_request_id]) + @@map("alloc_request_detail") +} + +// Allocation plant assignments (which plants can allocate) +model alloc_plant { + id String @id @default(cuid()) + plant_code String @unique + plant_name String + is_active Boolean @default(true) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + @@map("alloc_plant") +} + +// ============================================================================= +// INVOICE UPLOAD DOMAIN +// ============================================================================= + +// Invoice upload batch status +model inv_upload_status { + id String @id @default(cuid()) + name String @unique + description String? + sort_order Int @default(0) + + uploads inv_upload[] + + @@map("inv_upload_status") +} + +// Invoice upload batch +model inv_upload { + id String @id @default(cuid()) + auth_user_id String + original_filename String + storage_filename String + file_path String + file_size Int + total_pages Int? + inv_upload_status_id String + upload_notes String? + created_at DateTime @default(now()) + processed_at DateTime? + + status inv_upload_status @relation(fields: [inv_upload_status_id], references: [id]) + entries inv_upload_entry[] + + @@index([auth_user_id]) + @@index([inv_upload_status_id]) + @@index([created_at]) + @@map("inv_upload") +} + +// Individual invoice entries from upload +model inv_upload_entry { + id String @id @default(cuid()) + inv_upload_id String + invoice_number String + epicor_cust_id String? + quest_company_id String? + job_number String? + page_number Int? + storage_filename String? + file_path String? + is_ignored Boolean @default(false) + is_replaced Boolean @default(false) + send_error String? + created_at DateTime @default(now()) + sent_at DateTime? + + upload inv_upload @relation(fields: [inv_upload_id], references: [id], onDelete: Cascade) + + @@index([inv_upload_id]) + @@index([invoice_number]) + @@index([epicor_cust_id]) + @@index([quest_company_id]) + @@map("inv_upload_entry") +} + +// ============================================================================= +// FINANCE/AP CHECK PROCESSING DOMAIN +// ============================================================================= + +// AP check processing status definitions +model finance_ap_check_processing_status { + id String @id @default(cuid()) + name String @unique + description String? + sort_order Int @default(0) + + batches finance_ap_check_processing_batch[] + + @@map("finance_ap_check_processing_status") +} + +// AP check processing batch tracking +model finance_ap_check_processing_batch { + id String @id @default(cuid()) + auth_user_id String + group_id String + original_filename String + generated_csv_filename String? + generated_csv_path String? + finance_ap_check_processing_status_id String + error_message String? + record_count Int @default(0) + created_at DateTime @default(now()) + processed_at DateTime? + + status finance_ap_check_processing_status @relation(fields: [finance_ap_check_processing_status_id], references: [id]) + + @@index([auth_user_id]) + @@index([group_id]) + @@index([finance_ap_check_processing_status_id]) + @@map("finance_ap_check_processing_batch") +} + +// ============================================================================= +// PAINT SCHEDULE DOMAIN +// ============================================================================= + +// Paint line types +model paint_line_type { + id String @id @default(cuid()) + name String @unique + description String? + + lines paint_line[] + + @@map("paint_line_type") +} + +// Paint plants +model paint_plant { + id String @id @default(cuid()) + name String @unique + code String @unique + is_active Boolean @default(true) + + plant_lines paint_plant_line[] + + @@map("paint_plant") +} + +// Paint lines +model paint_line { + id String @id @default(cuid()) + name String @unique + paint_line_type_id String + is_active Boolean @default(true) + + line_type paint_line_type @relation(fields: [paint_line_type_id], references: [id]) + plant_lines paint_plant_line[] + schedules paint_schedule[] + + @@map("paint_line") +} + +// Junction: which lines are at which plants +model paint_plant_line { + id String @id @default(cuid()) + paint_plant_id String + paint_line_id String + is_active Boolean @default(true) + + plant paint_plant @relation(fields: [paint_plant_id], references: [id], onDelete: Cascade) + line paint_line @relation(fields: [paint_line_id], references: [id], onDelete: Cascade) + + @@unique([paint_plant_id, paint_line_id]) + @@map("paint_plant_line") +} + +// Paint schedule entries +model paint_schedule { + id String @id @default(cuid()) + paint_line_id String + job_id String? + part_description String? + customer String? + quantity Int? + estimated_run_time Float? + start_time DateTime? + notes String? + is_complete Boolean @default(false) + line_speed Float? + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + line paint_line @relation(fields: [paint_line_id], references: [id]) + + @@index([paint_line_id]) + @@index([start_time]) + @@index([is_complete]) + @@map("paint_schedule") +} + +// ============================================================================= +// DOCUMENTATION/KNOWLEDGE BASE DOMAIN +// ============================================================================= + +// Documentation categories +model doc_category { + id String @id @default(cuid()) + name String @unique + description String? + icon String? + sort_order Int @default(0) + is_active Boolean @default(true) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + articles doc_article[] + + @@map("doc_category") +} + +// Documentation articles +model doc_article { + id String @id @default(cuid()) + title String + content String + doc_category_id String + is_published Boolean @default(false) + sort_order Int @default(0) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + created_by String? + updated_by String? + + category doc_category @relation(fields: [doc_category_id], references: [id]) + permission_groups doc_article_permission_group[] + + @@index([doc_category_id]) + @@index([is_published]) + @@map("doc_article") +} + +// Junction: which permission groups can see which articles +model doc_article_permission_group { + id String @id @default(cuid()) + doc_article_id String + auth_permission_group_id String + created_at DateTime @default(now()) + + article doc_article @relation(fields: [doc_article_id], references: [id], onDelete: Cascade) + + @@unique([doc_article_id, auth_permission_group_id]) + @@map("doc_article_permission_group") +} + +// ============================================================================= +// WAVE/EDI PROCESSING DOMAIN +// ============================================================================= + +// Wave process definitions +model wave_process { + id String @id @default(cuid()) + name String @unique + description String? + is_enabled Boolean @default(true) + schedule_cron String? + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + history wave_process_history[] + + @@map("wave_process") +} + +// Wave process execution history +model wave_process_history { + id String @id @default(cuid()) + wave_process_id String + started_at DateTime @default(now()) + completed_at DateTime? + status String @default("running") // running, success, failed + result_message String? + error_message String? + + process wave_process @relation(fields: [wave_process_id], references: [id]) + + @@index([wave_process_id]) + @@index([started_at]) + @@index([status]) + @@map("wave_process_history") +} + +// ============================================================================= +// LTS (LONG-TERM SCHEDULER) TASK DOMAIN +// ============================================================================= + +// Task types +model lts_task_type { + id String @id @default(cuid()) + name String @unique + description String? + + tasks lts_task[] + + @@map("lts_task_type") +} + +// Scheduled tasks +model lts_task { + id String @id @default(cuid()) + lts_task_type_id String + name String + description String? + command String + is_enabled Boolean @default(true) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + task_type lts_task_type @relation(fields: [lts_task_type_id], references: [id]) + schedules lts_task_schedule[] + executions lts_task_execution[] + + @@map("lts_task") +} + +// Task schedule definitions +model lts_task_schedule { + id String @id @default(cuid()) + lts_task_id String + schedule_type String // cron, interval, daily, weekly, monthly + schedule_value String // cron expression or interval value + timezone String @default("UTC") + is_active Boolean @default(true) + next_run_at DateTime? + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + task lts_task @relation(fields: [lts_task_id], references: [id], onDelete: Cascade) + + @@index([lts_task_id]) + @@index([next_run_at]) + @@map("lts_task_schedule") +} + +// Task execution log +model lts_task_execution { + id String @id @default(cuid()) + lts_task_id String + started_at DateTime @default(now()) + completed_at DateTime? + status String @default("running") // running, success, failed + output String? + error_message String? + exit_code Int? + + task lts_task @relation(fields: [lts_task_id], references: [id]) + + @@index([lts_task_id]) + @@index([started_at]) + @@index([status]) + @@map("lts_task_execution") +}