feat(platform): harden access scoping and delivery baseline
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
-- GIN indexes for JSONB dynamic field filtering
|
||||
-- Run: psql $DATABASE_URL -f packages/db/prisma/migrations/20260310_jsonb_gin_indexes.sql
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_dynamic_fields_gin
|
||||
ON resources USING gin ("dynamicFields" jsonb_path_ops);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_project_dynamic_fields_gin
|
||||
ON projects USING gin ("dynamicFields" jsonb_path_ops);
|
||||
@@ -0,0 +1,71 @@
|
||||
-- Additive persistence split for planning demand vs assignment
|
||||
-- Run: psql $DATABASE_URL -f packages/db/prisma/migrations/20260313_demand_assignment_additive.sql
|
||||
|
||||
CREATE TABLE IF NOT EXISTS demand_requirements (
|
||||
id text PRIMARY KEY,
|
||||
"legacyAllocationId" text UNIQUE,
|
||||
"projectId" text NOT NULL REFERENCES projects(id),
|
||||
"startDate" date NOT NULL,
|
||||
"endDate" date NOT NULL,
|
||||
"hoursPerDay" double precision NOT NULL,
|
||||
percentage double precision NOT NULL,
|
||||
role text,
|
||||
"roleId" text REFERENCES roles(id),
|
||||
headcount integer NOT NULL DEFAULT 1,
|
||||
status "AllocationStatus" NOT NULL DEFAULT 'PROPOSED',
|
||||
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS assignments (
|
||||
id text PRIMARY KEY,
|
||||
"legacyAllocationId" text UNIQUE,
|
||||
"demandRequirementId" text REFERENCES demand_requirements(id),
|
||||
"resourceId" text NOT NULL REFERENCES resources(id),
|
||||
"projectId" text NOT NULL REFERENCES projects(id),
|
||||
"startDate" date NOT NULL,
|
||||
"endDate" date NOT NULL,
|
||||
"hoursPerDay" double precision NOT NULL,
|
||||
percentage double precision NOT NULL,
|
||||
role text,
|
||||
"roleId" text REFERENCES roles(id),
|
||||
"dailyCostCents" integer NOT NULL,
|
||||
status "AllocationStatus" NOT NULL DEFAULT 'PROPOSED',
|
||||
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS unique_assignment
|
||||
ON assignments ("resourceId", "projectId", "startDate", "endDate");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_demand_requirements_legacy_allocation_id
|
||||
ON demand_requirements ("legacyAllocationId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_demand_requirements_project_id
|
||||
ON demand_requirements ("projectId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_demand_requirements_start_end
|
||||
ON demand_requirements ("startDate", "endDate");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_demand_requirements_status
|
||||
ON demand_requirements (status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_assignments_legacy_allocation_id
|
||||
ON assignments ("legacyAllocationId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_assignments_demand_requirement_id
|
||||
ON assignments ("demandRequirementId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_assignments_resource_id
|
||||
ON assignments ("resourceId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_assignments_project_id
|
||||
ON assignments ("projectId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_assignments_start_end
|
||||
ON assignments ("startDate", "endDate");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_assignments_status
|
||||
ON assignments (status);
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TYPE "DispoImportSourceKind" ADD VALUE IF NOT EXISTS 'ROSTER';
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "staged_resources"
|
||||
ADD COLUMN IF NOT EXISTS "lcrCents" INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS "ucrCents" INTEGER;
|
||||
@@ -0,0 +1,328 @@
|
||||
-- Dispo import staging foundation
|
||||
-- Run: psql $DATABASE_URL -f packages/db/prisma/migrations/20260314_dispo_import_staging.sql
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "ImportBatchStatus" AS ENUM (
|
||||
'DRAFT',
|
||||
'STAGING',
|
||||
'STAGED',
|
||||
'REVIEW_READY',
|
||||
'APPROVED',
|
||||
'COMMITTING',
|
||||
'COMMITTED',
|
||||
'FAILED',
|
||||
'CANCELLED'
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "StagedRecordStatus" AS ENUM (
|
||||
'PARSED',
|
||||
'NORMALIZED',
|
||||
'UNRESOLVED',
|
||||
'APPROVED',
|
||||
'REJECTED',
|
||||
'COMMITTED',
|
||||
'FAILED'
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "DispoImportSourceKind" AS ENUM (
|
||||
'REFERENCE',
|
||||
'PLANNING',
|
||||
'CHARGEABILITY'
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "DispoStagedRecordType" AS ENUM (
|
||||
'RESOURCE',
|
||||
'CLIENT',
|
||||
'PROJECT',
|
||||
'ASSIGNMENT',
|
||||
'VACATION',
|
||||
'AVAILABILITY_RULE',
|
||||
'UNRESOLVED'
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS import_batches (
|
||||
id text PRIMARY KEY,
|
||||
"sourceSystem" text NOT NULL DEFAULT 'DISPO_V2',
|
||||
status "ImportBatchStatus" NOT NULL DEFAULT 'DRAFT',
|
||||
"referenceSourceFile" text,
|
||||
"planningSourceFile" text,
|
||||
"chargeabilitySourceFile" text,
|
||||
notes text,
|
||||
summary jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
"startedAt" timestamptz,
|
||||
"stagedAt" timestamptz,
|
||||
"approvedAt" timestamptz,
|
||||
"committedAt" timestamptz,
|
||||
"failedAt" timestamptz,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS staged_resources (
|
||||
id text PRIMARY KEY,
|
||||
"importBatchId" text NOT NULL REFERENCES import_batches(id) ON DELETE CASCADE,
|
||||
status "StagedRecordStatus" NOT NULL DEFAULT 'PARSED',
|
||||
"sourceKind" "DispoImportSourceKind" NOT NULL,
|
||||
"sourceWorkbook" text NOT NULL,
|
||||
"sourceSheet" text NOT NULL,
|
||||
"sourceRow" integer NOT NULL,
|
||||
"sourceColumn" text,
|
||||
"canonicalExternalId" text NOT NULL,
|
||||
"enterpriseId" text,
|
||||
eid text,
|
||||
"displayName" text,
|
||||
email text,
|
||||
chapter text,
|
||||
"chapterCode" text,
|
||||
"managementLevelGroupName" text,
|
||||
"managementLevelName" text,
|
||||
"countryCode" text,
|
||||
"metroCityName" text,
|
||||
"clientUnitName" text,
|
||||
"resourceType" "ResourceType",
|
||||
"chargeabilityTarget" double precision,
|
||||
fte double precision,
|
||||
availability jsonb,
|
||||
"roleTokens" text[] NOT NULL DEFAULT ARRAY[]::text[],
|
||||
warnings text[] NOT NULL DEFAULT ARRAY[]::text[],
|
||||
"errorMessage" text,
|
||||
"rawPayload" jsonb NOT NULL,
|
||||
"normalizedData" jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS staged_clients (
|
||||
id text PRIMARY KEY,
|
||||
"importBatchId" text NOT NULL REFERENCES import_batches(id) ON DELETE CASCADE,
|
||||
status "StagedRecordStatus" NOT NULL DEFAULT 'PARSED',
|
||||
"sourceKind" "DispoImportSourceKind" NOT NULL,
|
||||
"sourceWorkbook" text NOT NULL,
|
||||
"sourceSheet" text NOT NULL,
|
||||
"sourceRow" integer NOT NULL,
|
||||
"sourceColumn" text,
|
||||
"clientCode" text,
|
||||
"parentClientCode" text,
|
||||
name text NOT NULL,
|
||||
"sortOrder" integer,
|
||||
"isActive" boolean NOT NULL DEFAULT true,
|
||||
warnings text[] NOT NULL DEFAULT ARRAY[]::text[],
|
||||
"errorMessage" text,
|
||||
"rawPayload" jsonb NOT NULL,
|
||||
"normalizedData" jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS staged_projects (
|
||||
id text PRIMARY KEY,
|
||||
"importBatchId" text NOT NULL REFERENCES import_batches(id) ON DELETE CASCADE,
|
||||
status "StagedRecordStatus" NOT NULL DEFAULT 'PARSED',
|
||||
"sourceKind" "DispoImportSourceKind" NOT NULL,
|
||||
"sourceWorkbook" text NOT NULL,
|
||||
"sourceSheet" text NOT NULL,
|
||||
"sourceRow" integer NOT NULL,
|
||||
"sourceColumn" text,
|
||||
"projectKey" text NOT NULL,
|
||||
"shortCode" text,
|
||||
name text,
|
||||
"clientCode" text,
|
||||
"utilizationCategoryCode" text,
|
||||
"orderType" "OrderType",
|
||||
"allocationType" "AllocationType",
|
||||
"winProbability" integer,
|
||||
"isInternal" boolean NOT NULL DEFAULT false,
|
||||
"isTbd" boolean NOT NULL DEFAULT false,
|
||||
"startDate" date,
|
||||
"endDate" date,
|
||||
warnings text[] NOT NULL DEFAULT ARRAY[]::text[],
|
||||
"errorMessage" text,
|
||||
"rawPayload" jsonb NOT NULL,
|
||||
"normalizedData" jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS staged_assignments (
|
||||
id text PRIMARY KEY,
|
||||
"importBatchId" text NOT NULL REFERENCES import_batches(id) ON DELETE CASCADE,
|
||||
status "StagedRecordStatus" NOT NULL DEFAULT 'PARSED',
|
||||
"sourceKind" "DispoImportSourceKind" NOT NULL,
|
||||
"sourceWorkbook" text NOT NULL,
|
||||
"sourceSheet" text NOT NULL,
|
||||
"sourceRow" integer NOT NULL,
|
||||
"sourceColumn" text,
|
||||
"resourceExternalId" text NOT NULL,
|
||||
"projectKey" text,
|
||||
"assignmentDate" date,
|
||||
"startDate" date,
|
||||
"endDate" date,
|
||||
"hoursPerDay" double precision,
|
||||
percentage double precision,
|
||||
"slotFraction" double precision,
|
||||
"roleToken" text,
|
||||
"roleName" text,
|
||||
"chapterToken" text,
|
||||
"utilizationCategoryCode" text,
|
||||
"winProbability" integer,
|
||||
"isInternal" boolean NOT NULL DEFAULT false,
|
||||
"isUnassigned" boolean NOT NULL DEFAULT false,
|
||||
"isTbd" boolean NOT NULL DEFAULT false,
|
||||
warnings text[] NOT NULL DEFAULT ARRAY[]::text[],
|
||||
"errorMessage" text,
|
||||
"rawPayload" jsonb NOT NULL,
|
||||
"normalizedData" jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS staged_vacations (
|
||||
id text PRIMARY KEY,
|
||||
"importBatchId" text NOT NULL REFERENCES import_batches(id) ON DELETE CASCADE,
|
||||
status "StagedRecordStatus" NOT NULL DEFAULT 'PARSED',
|
||||
"sourceKind" "DispoImportSourceKind" NOT NULL,
|
||||
"sourceWorkbook" text NOT NULL,
|
||||
"sourceSheet" text NOT NULL,
|
||||
"sourceRow" integer NOT NULL,
|
||||
"sourceColumn" text,
|
||||
"resourceExternalId" text NOT NULL,
|
||||
"vacationType" "VacationType" NOT NULL,
|
||||
"startDate" date NOT NULL,
|
||||
"endDate" date NOT NULL,
|
||||
note text,
|
||||
"holidayName" text,
|
||||
"isHalfDay" boolean NOT NULL DEFAULT false,
|
||||
"halfDayPart" text,
|
||||
"isPublicHoliday" boolean NOT NULL DEFAULT false,
|
||||
warnings text[] NOT NULL DEFAULT ARRAY[]::text[],
|
||||
"errorMessage" text,
|
||||
"rawPayload" jsonb NOT NULL,
|
||||
"normalizedData" jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS staged_availability_rules (
|
||||
id text PRIMARY KEY,
|
||||
"importBatchId" text NOT NULL REFERENCES import_batches(id) ON DELETE CASCADE,
|
||||
status "StagedRecordStatus" NOT NULL DEFAULT 'PARSED',
|
||||
"sourceKind" "DispoImportSourceKind" NOT NULL,
|
||||
"sourceWorkbook" text NOT NULL,
|
||||
"sourceSheet" text NOT NULL,
|
||||
"sourceRow" integer NOT NULL,
|
||||
"sourceColumn" text,
|
||||
"resourceExternalId" text NOT NULL,
|
||||
"ruleType" text NOT NULL,
|
||||
weekday integer,
|
||||
"effectiveStartDate" date,
|
||||
"effectiveEndDate" date,
|
||||
"availableHours" double precision,
|
||||
percentage double precision,
|
||||
"isResolved" boolean NOT NULL DEFAULT false,
|
||||
warnings text[] NOT NULL DEFAULT ARRAY[]::text[],
|
||||
"errorMessage" text,
|
||||
"rawPayload" jsonb NOT NULL,
|
||||
"normalizedData" jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS staged_unresolved_records (
|
||||
id text PRIMARY KEY,
|
||||
"importBatchId" text NOT NULL REFERENCES import_batches(id) ON DELETE CASCADE,
|
||||
status "StagedRecordStatus" NOT NULL DEFAULT 'UNRESOLVED',
|
||||
"sourceKind" "DispoImportSourceKind" NOT NULL,
|
||||
"sourceWorkbook" text NOT NULL,
|
||||
"sourceSheet" text NOT NULL,
|
||||
"sourceRow" integer NOT NULL,
|
||||
"sourceColumn" text,
|
||||
"recordType" "DispoStagedRecordType" NOT NULL,
|
||||
"resourceExternalId" text,
|
||||
"projectKey" text,
|
||||
message text NOT NULL,
|
||||
"resolutionHint" text,
|
||||
warnings text[] NOT NULL DEFAULT ARRAY[]::text[],
|
||||
"rawPayload" jsonb NOT NULL,
|
||||
"normalizedData" jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_import_batches_status
|
||||
ON import_batches (status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_resources_batch_status
|
||||
ON staged_resources ("importBatchId", status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_resources_canonical_external_id
|
||||
ON staged_resources ("canonicalExternalId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_clients_batch_status
|
||||
ON staged_clients ("importBatchId", status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_clients_client_code
|
||||
ON staged_clients ("clientCode");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_projects_batch_status
|
||||
ON staged_projects ("importBatchId", status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_projects_project_key
|
||||
ON staged_projects ("projectKey");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_assignments_batch_status
|
||||
ON staged_assignments ("importBatchId", status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_assignments_resource_external_id
|
||||
ON staged_assignments ("resourceExternalId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_assignments_project_key
|
||||
ON staged_assignments ("projectKey");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_assignments_assignment_date
|
||||
ON staged_assignments ("assignmentDate");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_vacations_batch_status
|
||||
ON staged_vacations ("importBatchId", status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_vacations_resource_external_id
|
||||
ON staged_vacations ("resourceExternalId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_vacations_start_end
|
||||
ON staged_vacations ("startDate", "endDate");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_availability_rules_batch_status
|
||||
ON staged_availability_rules ("importBatchId", status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_availability_rules_resource_external_id
|
||||
ON staged_availability_rules ("resourceExternalId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_unresolved_records_batch_status
|
||||
ON staged_unresolved_records ("importBatchId", status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_unresolved_records_record_type
|
||||
ON staged_unresolved_records ("recordType");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_unresolved_records_resource_external_id
|
||||
ON staged_unresolved_records ("resourceExternalId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_staged_unresolved_records_project_key
|
||||
ON staged_unresolved_records ("projectKey");
|
||||
@@ -0,0 +1,29 @@
|
||||
CREATE TYPE "AssistantApprovalStatus" AS ENUM ('PENDING', 'APPROVED', 'CANCELLED', 'EXPIRED');
|
||||
|
||||
CREATE TABLE "assistant_approvals" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"conversationId" TEXT NOT NULL,
|
||||
"toolName" TEXT NOT NULL,
|
||||
"toolArguments" TEXT NOT NULL,
|
||||
"summary" TEXT NOT NULL,
|
||||
"status" "AssistantApprovalStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"approvedAt" TIMESTAMP(3),
|
||||
"cancelledAt" TIMESTAMP(3),
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "assistant_approvals_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "assistant_approvals_userId_conversationId_status_expiresAt_idx"
|
||||
ON "assistant_approvals"("userId", "conversationId", "status", "expiresAt");
|
||||
|
||||
CREATE INDEX "assistant_approvals_status_expiresAt_idx"
|
||||
ON "assistant_approvals"("status", "expiresAt");
|
||||
|
||||
ALTER TABLE "assistant_approvals"
|
||||
ADD CONSTRAINT "assistant_approvals_userId_fkey"
|
||||
FOREIGN KEY ("userId") REFERENCES "users"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,143 @@
|
||||
BEGIN;
|
||||
|
||||
UPDATE "holiday_calendars"
|
||||
SET "stateCode" = UPPER("stateCode")
|
||||
WHERE "stateCode" IS NOT NULL;
|
||||
|
||||
UPDATE "holiday_calendars"
|
||||
SET "stateCode" = NULL,
|
||||
"metroCityId" = NULL
|
||||
WHERE "scopeType" = 'COUNTRY';
|
||||
|
||||
UPDATE "holiday_calendars"
|
||||
SET "metroCityId" = NULL
|
||||
WHERE "scopeType" = 'STATE';
|
||||
|
||||
CREATE TEMP TABLE "tmp_holiday_calendar_merge_map" (
|
||||
"duplicate_id" TEXT PRIMARY KEY,
|
||||
"keeper_id" TEXT NOT NULL
|
||||
) ON COMMIT DROP;
|
||||
|
||||
WITH ranked AS (
|
||||
SELECT
|
||||
"id",
|
||||
FIRST_VALUE("id") OVER (
|
||||
PARTITION BY "countryId"
|
||||
ORDER BY "priority" DESC, "createdAt" DESC, "id" DESC
|
||||
) AS "keeper_id",
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY "countryId"
|
||||
ORDER BY "priority" DESC, "createdAt" DESC, "id" DESC
|
||||
) AS "rn"
|
||||
FROM "holiday_calendars"
|
||||
WHERE "scopeType" = 'COUNTRY'
|
||||
)
|
||||
INSERT INTO "tmp_holiday_calendar_merge_map" ("duplicate_id", "keeper_id")
|
||||
SELECT "id", "keeper_id"
|
||||
FROM ranked
|
||||
WHERE "rn" > 1;
|
||||
|
||||
WITH ranked AS (
|
||||
SELECT
|
||||
"id",
|
||||
FIRST_VALUE("id") OVER (
|
||||
PARTITION BY "countryId", "stateCode"
|
||||
ORDER BY "priority" DESC, "createdAt" DESC, "id" DESC
|
||||
) AS "keeper_id",
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY "countryId", "stateCode"
|
||||
ORDER BY "priority" DESC, "createdAt" DESC, "id" DESC
|
||||
) AS "rn"
|
||||
FROM "holiday_calendars"
|
||||
WHERE "scopeType" = 'STATE'
|
||||
AND "stateCode" IS NOT NULL
|
||||
)
|
||||
INSERT INTO "tmp_holiday_calendar_merge_map" ("duplicate_id", "keeper_id")
|
||||
SELECT "id", "keeper_id"
|
||||
FROM ranked
|
||||
WHERE "rn" > 1
|
||||
ON CONFLICT ("duplicate_id") DO NOTHING;
|
||||
|
||||
WITH ranked AS (
|
||||
SELECT
|
||||
"id",
|
||||
FIRST_VALUE("id") OVER (
|
||||
PARTITION BY "countryId", "metroCityId"
|
||||
ORDER BY "priority" DESC, "createdAt" DESC, "id" DESC
|
||||
) AS "keeper_id",
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY "countryId", "metroCityId"
|
||||
ORDER BY "priority" DESC, "createdAt" DESC, "id" DESC
|
||||
) AS "rn"
|
||||
FROM "holiday_calendars"
|
||||
WHERE "scopeType" = 'CITY'
|
||||
AND "metroCityId" IS NOT NULL
|
||||
)
|
||||
INSERT INTO "tmp_holiday_calendar_merge_map" ("duplicate_id", "keeper_id")
|
||||
SELECT "id", "keeper_id"
|
||||
FROM ranked
|
||||
WHERE "rn" > 1
|
||||
ON CONFLICT ("duplicate_id") DO NOTHING;
|
||||
|
||||
UPDATE "holiday_calendar_entries" AS "entry"
|
||||
SET "holidayCalendarId" = "map"."keeper_id"
|
||||
FROM "tmp_holiday_calendar_merge_map" AS "map"
|
||||
WHERE "entry"."holidayCalendarId" = "map"."duplicate_id"
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "holiday_calendar_entries" AS "existing"
|
||||
WHERE "existing"."holidayCalendarId" = "map"."keeper_id"
|
||||
AND "existing"."date" = "entry"."date"
|
||||
);
|
||||
|
||||
WITH ranked_entries AS (
|
||||
SELECT
|
||||
"id",
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY "holidayCalendarId", "date"
|
||||
ORDER BY "isRecurringAnnual" DESC, "updatedAt" DESC, "createdAt" DESC, "id" DESC
|
||||
) AS "rn"
|
||||
FROM "holiday_calendar_entries"
|
||||
)
|
||||
DELETE FROM "holiday_calendar_entries" AS "entry"
|
||||
USING ranked_entries
|
||||
WHERE "entry"."id" = ranked_entries."id"
|
||||
AND ranked_entries."rn" > 1;
|
||||
|
||||
DELETE FROM "holiday_calendars" AS "calendar"
|
||||
USING "tmp_holiday_calendar_merge_map" AS "map"
|
||||
WHERE "calendar"."id" = "map"."duplicate_id";
|
||||
|
||||
DROP INDEX IF EXISTS "holiday_calendar_entries_holidayCalendarId_date_name_key";
|
||||
DROP INDEX IF EXISTS "holiday_calendar_entries_holidayCalendarId_date_key";
|
||||
CREATE UNIQUE INDEX "holiday_calendar_entries_holidayCalendarId_date_key"
|
||||
ON "holiday_calendar_entries" ("holidayCalendarId", "date");
|
||||
|
||||
ALTER TABLE "holiday_calendars"
|
||||
DROP CONSTRAINT IF EXISTS "holiday_calendars_scope_fields_check";
|
||||
|
||||
ALTER TABLE "holiday_calendars"
|
||||
ADD CONSTRAINT "holiday_calendars_scope_fields_check"
|
||||
CHECK (
|
||||
("scopeType" = 'COUNTRY' AND "stateCode" IS NULL AND "metroCityId" IS NULL)
|
||||
OR ("scopeType" = 'STATE' AND "stateCode" IS NOT NULL AND "metroCityId" IS NULL)
|
||||
OR ("scopeType" = 'CITY' AND "metroCityId" IS NOT NULL)
|
||||
);
|
||||
|
||||
DROP INDEX IF EXISTS "holiday_calendars_country_scope_unique";
|
||||
DROP INDEX IF EXISTS "holiday_calendars_state_scope_unique";
|
||||
DROP INDEX IF EXISTS "holiday_calendars_city_scope_unique";
|
||||
|
||||
CREATE UNIQUE INDEX "holiday_calendars_country_scope_unique"
|
||||
ON "holiday_calendars" ("countryId")
|
||||
WHERE "scopeType" = 'COUNTRY';
|
||||
|
||||
CREATE UNIQUE INDEX "holiday_calendars_state_scope_unique"
|
||||
ON "holiday_calendars" ("countryId", "stateCode")
|
||||
WHERE "scopeType" = 'STATE' AND "stateCode" IS NOT NULL;
|
||||
|
||||
CREATE UNIQUE INDEX "holiday_calendars_city_scope_unique"
|
||||
ON "holiday_calendars" ("countryId", "metroCityId")
|
||||
WHERE "scopeType" = 'CITY' AND "metroCityId" IS NOT NULL;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,52 @@
|
||||
CREATE TYPE "HolidayCalendarScope" AS ENUM ('COUNTRY', 'STATE', 'CITY');
|
||||
|
||||
CREATE TABLE "holiday_calendars" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"scopeType" "HolidayCalendarScope" NOT NULL,
|
||||
"countryId" TEXT NOT NULL,
|
||||
"stateCode" TEXT,
|
||||
"metroCityId" TEXT,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"priority" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "holiday_calendars_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "holiday_calendar_entries" (
|
||||
"id" TEXT NOT NULL,
|
||||
"holidayCalendarId" TEXT NOT NULL,
|
||||
"date" DATE NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"isRecurringAnnual" BOOLEAN NOT NULL DEFAULT false,
|
||||
"source" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "holiday_calendar_entries_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "holiday_calendars_countryId_scopeType_idx" ON "holiday_calendars"("countryId", "scopeType");
|
||||
CREATE INDEX "holiday_calendars_countryId_stateCode_idx" ON "holiday_calendars"("countryId", "stateCode");
|
||||
CREATE INDEX "holiday_calendars_metroCityId_idx" ON "holiday_calendars"("metroCityId");
|
||||
CREATE INDEX "holiday_calendar_entries_date_idx" ON "holiday_calendar_entries"("date");
|
||||
|
||||
CREATE UNIQUE INDEX "holiday_calendar_entries_holidayCalendarId_date_name_key"
|
||||
ON "holiday_calendar_entries"("holidayCalendarId", "date", "name");
|
||||
|
||||
ALTER TABLE "holiday_calendars"
|
||||
ADD CONSTRAINT "holiday_calendars_countryId_fkey"
|
||||
FOREIGN KEY ("countryId") REFERENCES "countries"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "holiday_calendars"
|
||||
ADD CONSTRAINT "holiday_calendars_metroCityId_fkey"
|
||||
FOREIGN KEY ("metroCityId") REFERENCES "metro_cities"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "holiday_calendar_entries"
|
||||
ADD CONSTRAINT "holiday_calendar_entries_holidayCalendarId_fkey"
|
||||
FOREIGN KEY ("holidayCalendarId") REFERENCES "holiday_calendars"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,26 @@
|
||||
CREATE TYPE "ReportTemplateEntity" AS ENUM ('RESOURCE', 'PROJECT', 'ASSIGNMENT', 'RESOURCE_MONTH');
|
||||
|
||||
CREATE TABLE "report_templates" (
|
||||
"id" TEXT NOT NULL,
|
||||
"ownerId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"entity" "ReportTemplateEntity" NOT NULL,
|
||||
"config" JSONB NOT NULL,
|
||||
"isShared" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "report_templates_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "report_templates_ownerId_updatedAt_idx"
|
||||
ON "report_templates"("ownerId", "updatedAt");
|
||||
|
||||
CREATE UNIQUE INDEX "report_templates_ownerId_name_key"
|
||||
ON "report_templates"("ownerId", "name");
|
||||
|
||||
ALTER TABLE "report_templates"
|
||||
ADD CONSTRAINT "report_templates_ownerId_fkey"
|
||||
FOREIGN KEY ("ownerId") REFERENCES "users"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
Reference in New Issue
Block a user