166 lines
6.5 KiB
SQL
166 lines
6.5 KiB
SQL
-- TIME Phase T1 foundation for shared time-entry structures in the NEW run"time".
|
|
-- No source import logic yet; this migration only creates the shared TIME schema and base state tables.
|
|
|
|
CREATE SCHEMA IF NOT EXISTS TIME;
|
|
|
|
SET search_path TO TIME, DOC, public;
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1
|
|
FROM pg_type t
|
|
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
WHERE t.typname = 'time_source_system' AND n.nspname = 'time'
|
|
) THEN
|
|
CREATE TYPE "time".time_source_system AS ENUM ('LEITSTAND', 'TOGGL_TRACK');
|
|
END IF;
|
|
END
|
|
$$;
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1
|
|
FROM pg_type t
|
|
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
WHERE t.typname = 'time_sync_run_status' AND n.nspname = 'time'
|
|
) THEN
|
|
CREATE TYPE "time".time_sync_run_status AS ENUM ('RUNNING', 'COMPLETED', 'FAILED');
|
|
END IF;
|
|
END
|
|
$$;
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1
|
|
FROM pg_type t
|
|
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
WHERE t.typname = 'time_sync_run_type' AND n.nspname = 'time'
|
|
) THEN
|
|
CREATE TYPE "time".time_sync_run_type AS ENUM ('INITIAL', 'INCREMENTAL', 'RECONCILE', 'MANUAL');
|
|
END IF;
|
|
END
|
|
$$;
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1
|
|
FROM pg_enum e
|
|
JOIN pg_type t ON t.oid = e.enumtypid
|
|
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
WHERE n.nspname = 'doc' AND t.typname = 'doc_document_type' AND e.enumlabel = 'TIME_ENTRY'
|
|
) THEN
|
|
ALTER TYPE doc.doc_document_type ADD VALUE 'TIME_ENTRY';
|
|
END IF;
|
|
END
|
|
$$;
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1
|
|
FROM pg_enum e
|
|
JOIN pg_type t ON t.oid = e.enumtypid
|
|
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
WHERE n.nspname = 'doc' AND t.typname = 'doc_document_family' AND e.enumlabel = 'TIME'
|
|
) THEN
|
|
ALTER TYPE doc.doc_document_family ADD VALUE 'TIME';
|
|
END IF;
|
|
END
|
|
$$;
|
|
|
|
CREATE TABLE IF NOT EXISTS "time".time_entry (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
document_id UUID NOT NULL UNIQUE REFERENCES doc.doc_document(id) ON DELETE CASCADE,
|
|
source_system "time".time_source_system NOT NULL,
|
|
external_id VARCHAR(255) NOT NULL,
|
|
person_external_id VARCHAR(255),
|
|
person_display_name VARCHAR(255),
|
|
entry_start TIMESTAMP WITH TIME ZONE,
|
|
entry_end TIMESTAMP WITH TIME ZONE,
|
|
duration_seconds BIGINT,
|
|
description_short VARCHAR(1000),
|
|
description_long TEXT,
|
|
billable BOOLEAN,
|
|
source_created_at TIMESTAMP WITH TIME ZONE,
|
|
source_updated_at TIMESTAMP WITH TIME ZONE,
|
|
source_deleted_at TIMESTAMP WITH TIME ZONE,
|
|
raw_status VARCHAR(120),
|
|
search_anchor_label VARCHAR(500),
|
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT uq_time_entry_source UNIQUE (source_system, external_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS "time".time_entry_source_link (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
time_entry_id UUID NOT NULL REFERENCES "time".time_entry(id) ON DELETE CASCADE,
|
|
source_system "time".time_source_system NOT NULL,
|
|
source_entity_type VARCHAR(120) NOT NULL,
|
|
source_external_id VARCHAR(255) NOT NULL,
|
|
linked_role VARCHAR(120),
|
|
parent_source_external_id VARCHAR(255),
|
|
source_updated_at TIMESTAMP WITH TIME ZONE,
|
|
source_deleted_at TIMESTAMP WITH TIME ZONE,
|
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT uq_time_entry_source_link UNIQUE (time_entry_id, source_system, source_entity_type, source_external_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS "time".time_sync_run (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
source_system "time".time_source_system NOT NULL,
|
|
run_type "time".time_sync_run_type NOT NULL,
|
|
scope_key VARCHAR(255) NOT NULL,
|
|
status "time".time_sync_run_status NOT NULL,
|
|
started_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
finished_at TIMESTAMP WITH TIME ZONE,
|
|
watermark_from VARCHAR(255),
|
|
watermark_to VARCHAR(255),
|
|
rows_read INTEGER NOT NULL DEFAULT 0,
|
|
rows_created INTEGER NOT NULL DEFAULT 0,
|
|
rows_updated INTEGER NOT NULL DEFAULT 0,
|
|
rows_deactivated INTEGER NOT NULL DEFAULT 0,
|
|
rows_failed INTEGER NOT NULL DEFAULT 0,
|
|
error_message TEXT,
|
|
initiated_by VARCHAR(120),
|
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS "time".time_sync_state (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
source_system "time".time_source_system NOT NULL,
|
|
scope_key VARCHAR(255) NOT NULL,
|
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
last_successful_sync_at TIMESTAMP WITH TIME ZONE,
|
|
last_attempted_sync_at TIMESTAMP WITH TIME ZONE,
|
|
last_seen_watermark VARCHAR(255),
|
|
last_seen_external_id VARCHAR(255),
|
|
last_run_id UUID REFERENCES "time".time_sync_run(id) ON DELETE SET NULL,
|
|
notes TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT uq_time_sync_state_scope UNIQUE (source_system, scope_key)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_time_entry_source_system ON "time".time_entry(source_system);
|
|
CREATE INDEX IF NOT EXISTS idx_time_entry_external_id ON "time".time_entry(external_id);
|
|
CREATE INDEX IF NOT EXISTS idx_time_entry_start ON "time".time_entry(entry_start DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_time_entry_end ON "time".time_entry(entry_end DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_time_entry_source_updated ON "time".time_entry(source_updated_at DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_time_entry_link_entry ON "time".time_entry_source_link(time_entry_id);
|
|
CREATE INDEX IF NOT EXISTS idx_time_entry_link_source_lookup ON "time".time_entry_source_link(source_system, source_entity_type, source_external_id);
|
|
CREATE INDEX IF NOT EXISTS idx_time_entry_link_role ON "time".time_entry_source_link(linked_role);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_time_sync_run_source_system ON "time".time_sync_run(source_system);
|
|
CREATE INDEX IF NOT EXISTS idx_time_sync_run_scope_key ON "time".time_sync_run(scope_key);
|
|
CREATE INDEX IF NOT EXISTS idx_time_sync_run_status ON "time".time_sync_run(status);
|
|
CREATE INDEX IF NOT EXISTS idx_time_sync_run_started_at ON "time".time_sync_run(started_at DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_time_sync_state_source_system ON "time".time_sync_state(source_system);
|
|
CREATE INDEX IF NOT EXISTS idx_time_sync_state_scope_key ON "time".time_sync_state(scope_key);
|