Files
probo/pkg/coredata/migrations/20260315T100001Z.sql
Sacha Al Himdani 851e585b9b Add ISC license headers to Go, TypeScript, and SQL files
Add ISC license headers to all .go, .ts, .tsx, and .sql files
using each file's git history to determine the correct copyright
year or year range. Trademarked icons (brand logos, vendor logos,
compliance framework logos) are excluded.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-03-31 13:31:30 +02:00

244 lines
9.4 KiB
SQL

-- Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted, provided that the above
-- copyright notice and this permission notice appear in all copies.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-- PERFORMANCE OF THIS SOFTWARE.
-- Merge nonconformities and continual_improvements into findings
CREATE TYPE findings_kind AS ENUM ('NONCONFORMITY', 'OBSERVATION', 'EXCEPTION');
CREATE TYPE findings_status AS ENUM ('OPEN', 'IN_PROGRESS', 'CLOSED', 'RISK_ACCEPTED', 'MITIGATED', 'FALSE_POSITIVE');
CREATE TYPE findings_priority AS ENUM ('LOW', 'MEDIUM', 'HIGH');
CREATE TABLE findings (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
organization_id TEXT NOT NULL,
snapshot_id TEXT,
source_id TEXT,
kind findings_kind NOT NULL,
reference_id TEXT NOT NULL,
description TEXT,
source TEXT,
risk_id TEXT,
identified_on DATE,
root_cause TEXT,
corrective_action TEXT,
owner_id TEXT,
due_date DATE,
status findings_status NOT NULL,
priority findings_priority NOT NULL,
effectiveness_check TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
CONSTRAINT fk_findings_organization
FOREIGN KEY (organization_id)
REFERENCES organizations(id) ON DELETE CASCADE,
CONSTRAINT fk_findings_owner
FOREIGN KEY (owner_id)
REFERENCES iam_membership_profiles(id),
CONSTRAINT fk_findings_snapshot
FOREIGN KEY (snapshot_id)
REFERENCES snapshots(id) ON DELETE CASCADE,
CONSTRAINT fk_findings_risk
FOREIGN KEY (risk_id)
REFERENCES risks(id) ON DELETE RESTRICT,
UNIQUE (source_id, snapshot_id)
);
CREATE INDEX idx_findings_organization_snapshot ON findings (organization_id, snapshot_id);
CREATE UNIQUE INDEX idx_findings_org_reference_id ON findings (organization_id, reference_id) WHERE snapshot_id IS NULL;
-- Create findings_audits junction table
CREATE TABLE findings_audits (
finding_id TEXT NOT NULL REFERENCES findings(id) ON DELETE CASCADE ON UPDATE CASCADE,
audit_id TEXT NOT NULL REFERENCES audits(id) ON DELETE CASCADE ON UPDATE CASCADE,
reference_id TEXT NOT NULL,
organization_id TEXT NOT NULL,
tenant_id TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
PRIMARY KEY (finding_id, audit_id)
);
CREATE INDEX idx_findings_audits_audit_id ON findings_audits (audit_id);
-- Pre-generate IDs and FND-XXX reference_ids for live nonconformities
CREATE TEMP TABLE nc_id_map AS
SELECT
nc.id AS old_id,
generate_gid(decode_base64_unpadded(nc.tenant_id), 67) AS new_id,
nc.organization_id,
nc.reference_id AS old_reference_id
FROM nonconformities nc WHERE nc.snapshot_id IS NULL;
-- Assign FND-XXX reference_ids per organization across all live records
-- First, combine all live records from both tables with a stable ordering
CREATE TEMP TABLE all_live_findings AS
SELECT
nc_id_map.old_id,
nc_id_map.new_id,
nc_id_map.organization_id,
nc_id_map.old_reference_id,
'NC' AS source_type,
nonconformities.created_at
FROM nc_id_map
JOIN nonconformities ON nonconformities.id = nc_id_map.old_id;
-- Pre-generate IDs for live continual improvements
CREATE TEMP TABLE ci_id_map AS
SELECT
ci.id AS old_id,
generate_gid(decode_base64_unpadded(ci.tenant_id), 67) AS new_id,
ci.organization_id,
ci.reference_id AS old_reference_id
FROM continual_improvements ci WHERE ci.snapshot_id IS NULL;
INSERT INTO all_live_findings (old_id, new_id, organization_id, old_reference_id, source_type, created_at)
SELECT
ci_id_map.old_id,
ci_id_map.new_id,
ci_id_map.organization_id,
ci_id_map.old_reference_id,
'CI',
ci.created_at
FROM ci_id_map
JOIN continual_improvements ci ON ci.id = ci_id_map.old_id;
-- Generate sequential FND-XXX reference_ids per organization
CREATE TEMP TABLE finding_ref_map AS
SELECT
old_id,
new_id,
organization_id,
old_reference_id,
source_type,
'FND-' || LPAD(ROW_NUMBER() OVER (PARTITION BY organization_id ORDER BY created_at, old_id)::TEXT, 3, '0') AS new_reference_id
FROM all_live_findings;
-- Insert live nonconformities with FND-XXX reference_ids
INSERT INTO findings (
id, tenant_id, organization_id, snapshot_id, source_id,
kind, reference_id, description, source,
identified_on, root_cause, corrective_action,
owner_id, due_date, status, priority,
effectiveness_check, created_at, updated_at
)
SELECT
m.new_id,
nc.tenant_id, nc.organization_id, NULL, NULL,
'NONCONFORMITY'::findings_kind, m.new_reference_id, nc.description, NULL,
nc.date_identified, nc.root_cause, nc.corrective_action,
nc.owner_profile_id, nc.due_date, nc.status::text::findings_status, 'MEDIUM'::findings_priority,
nc.effectiveness_check, nc.created_at, nc.updated_at
FROM nonconformities nc
JOIN finding_ref_map m ON nc.id = m.old_id AND m.source_type = 'NC';
-- Migrate nonconformity audit associations (old reference_id goes on junction table)
INSERT INTO findings_audits (finding_id, audit_id, reference_id, organization_id, tenant_id, created_at)
SELECT m.new_id, nc.audit_id, nc.reference_id, nc.organization_id, nc.tenant_id, nc.created_at
FROM nonconformities nc
JOIN finding_ref_map m ON nc.id = m.old_id AND m.source_type = 'NC'
WHERE nc.audit_id IS NOT NULL;
-- Insert snapshot nonconformities with source_id pointing to live finding
-- Snapshot findings keep the same reference_id as their live counterpart
INSERT INTO findings (
id, tenant_id, organization_id, snapshot_id, source_id,
kind, reference_id, description, source,
identified_on, root_cause, corrective_action,
owner_id, due_date, status, priority,
effectiveness_check, created_at, updated_at
)
SELECT
generate_gid(decode_base64_unpadded(nc.tenant_id), 67),
nc.tenant_id, nc.organization_id, nc.snapshot_id, m.new_id,
'NONCONFORMITY'::findings_kind, m.new_reference_id, nc.description, NULL,
nc.date_identified, nc.root_cause, nc.corrective_action,
nc.owner_profile_id, nc.due_date, nc.status::text::findings_status, 'MEDIUM'::findings_priority,
nc.effectiveness_check, nc.created_at, nc.updated_at
FROM nonconformities nc
LEFT JOIN finding_ref_map m ON nc.source_id = m.old_id AND m.source_type = 'NC'
WHERE nc.snapshot_id IS NOT NULL;
-- Migrate snapshot nonconformity audit associations
INSERT INTO findings_audits (finding_id, audit_id, reference_id, organization_id, tenant_id, created_at)
SELECT f.id, nc.audit_id, nc.reference_id, nc.organization_id, nc.tenant_id, nc.created_at
FROM nonconformities nc
JOIN findings f ON f.snapshot_id = nc.snapshot_id
AND f.source_id IS NOT NULL
AND f.kind = 'NONCONFORMITY'
AND EXISTS (
SELECT 1 FROM finding_ref_map m
WHERE m.old_id = nc.source_id
AND m.source_type = 'NC'
AND f.source_id = m.new_id
)
WHERE nc.snapshot_id IS NOT NULL AND nc.audit_id IS NOT NULL;
-- Insert live continual improvements with FND-XXX reference_ids
INSERT INTO findings (
id, tenant_id, organization_id, snapshot_id, source_id,
kind, reference_id, description, source,
identified_on, root_cause, corrective_action,
owner_id, due_date, status, priority,
effectiveness_check, created_at, updated_at
)
SELECT
m.new_id,
ci.tenant_id, ci.organization_id, NULL, NULL,
'OBSERVATION'::findings_kind, m.new_reference_id, ci.description, ci.source,
NULL, NULL, NULL,
ci.owner_profile_id, ci.target_date, ci.status::text::findings_status, ci.priority::text::findings_priority,
NULL, ci.created_at, ci.updated_at
FROM continual_improvements ci
JOIN finding_ref_map m ON ci.id = m.old_id AND m.source_type = 'CI';
-- Insert snapshot continual improvements with source_id pointing to live finding
INSERT INTO findings (
id, tenant_id, organization_id, snapshot_id, source_id,
kind, reference_id, description, source,
identified_on, root_cause, corrective_action,
owner_id, due_date, status, priority,
effectiveness_check, created_at, updated_at
)
SELECT
generate_gid(decode_base64_unpadded(ci.tenant_id), 67),
ci.tenant_id, ci.organization_id, ci.snapshot_id, m.new_id,
'OBSERVATION'::findings_kind, m.new_reference_id, ci.description, ci.source,
NULL, NULL, NULL,
ci.owner_profile_id, ci.target_date, ci.status::text::findings_status, ci.priority::text::findings_priority,
NULL, ci.created_at, ci.updated_at
FROM continual_improvements ci
LEFT JOIN finding_ref_map m ON ci.source_id = m.old_id AND m.source_type = 'CI'
WHERE ci.snapshot_id IS NOT NULL;
DROP TABLE finding_ref_map;
DROP TABLE all_live_findings;
DROP TABLE nc_id_map;
DROP TABLE ci_id_map;
-- Update snapshot types
UPDATE snapshots
SET type = 'FINDINGS'
WHERE type IN ('NONCONFORMITIES', 'CONTINUAL_IMPROVEMENTS');
-- Drop old tables
DROP TABLE nonconformities;
DROP TABLE continual_improvements;
DROP TYPE nonconformities_status;
DROP TYPE continual_improvements_status;
DROP TYPE continual_improvement_registries_priority;