Migration 077 adds paliad.projects.counterclaim_of (nullable FK ON DELETE SET NULL) plus a partial index. A trigger function rejects two-level CCR chains: a project with counterclaim_of NOT NULL cannot be the target of another CCR — UPC practice has no CCR-of-a-CCR shape, so reject it at the schema level rather than defending in the application layer. ProjectService gains LoadCounterclaimChildrenVisible (list visible CCR sub-projects against a parent) and CreateCounterclaim (atomic: project row + creator-as-lead team membership + audit rows on parent AND child). The CCR child is placed as a sibling under the same patent (§4.4), our side flips claimant↔defendant by default with a "Stimmt nicht?" override for the R.49.2.b CCI edge case, and the proceeding type defaults to UPC_REV. Title auto-suggests from the patent ancestor's patent_number when available. Tracker advances 76 → 77.
90 lines
3.8 KiB
PL/PgSQL
90 lines
3.8 KiB
PL/PgSQL
-- t-paliad-174 — SmartTimeline Slice 3.
|
|
-- Two structural additions for the counterclaim sub-project shape
|
|
-- (§4 of docs/design-smart-timeline-2026-05-08.md):
|
|
--
|
|
-- 1. paliad.projects.counterclaim_of — nullable FK referencing
|
|
-- paliad.projects(id) ON DELETE SET NULL. When non-NULL the row
|
|
-- represents the CCR (counterclaim) sub-project filed against the
|
|
-- target row. Standard parent_id keeps governing the project tree;
|
|
-- counterclaim_of is the *additional* relation describing the CCR
|
|
-- link. parent_id of the CCR child is set to the target's parent
|
|
-- (sibling-under-patent placement, §4.4) — that placement is owned
|
|
-- by ProjectService.CreateCounterclaim, not the schema.
|
|
--
|
|
-- 2. Two-level-CCR rejection trigger — UPC practice does NOT have
|
|
-- counterclaim-of-a-counterclaim chains. Reject the malformed shape
|
|
-- at the schema level so the application can never write it. CHECK
|
|
-- can't reference other rows; trigger function raises explicitly.
|
|
--
|
|
-- Idempotent: re-applying is a no-op. Tracker advances 76 → 77.
|
|
|
|
-- 1. paliad.projects.counterclaim_of ---------------------------------------
|
|
|
|
ALTER TABLE paliad.projects
|
|
ADD COLUMN IF NOT EXISTS counterclaim_of uuid NULL
|
|
REFERENCES paliad.projects(id) ON DELETE SET NULL;
|
|
|
|
COMMENT ON COLUMN paliad.projects.counterclaim_of IS
|
|
'When non-NULL this project is the CCR (counterclaim) filed against '
|
|
'the referenced parent project. parent_id continues to govern the '
|
|
'project tree (CCR is placed as a sibling under the same patent — '
|
|
'see ProjectService.CreateCounterclaim). ON DELETE SET NULL keeps '
|
|
'the CCR row alive when the parent is hard-deleted (rare; default '
|
|
'is archival) so the audit trail survives.';
|
|
|
|
CREATE INDEX IF NOT EXISTS projects_counterclaim_of_idx
|
|
ON paliad.projects (counterclaim_of)
|
|
WHERE counterclaim_of IS NOT NULL;
|
|
|
|
-- 2. Two-level-CCR rejection trigger ---------------------------------------
|
|
|
|
CREATE OR REPLACE FUNCTION paliad.projects_no_two_level_ccr() RETURNS trigger
|
|
LANGUAGE plpgsql AS $$
|
|
BEGIN
|
|
-- A project that is itself a CCR may NOT be the target of another CCR.
|
|
-- Two cases to reject:
|
|
--
|
|
-- (a) NEW row points at a parent that is itself a CCR:
|
|
-- NEW.counterclaim_of -> some row with counterclaim_of NOT NULL.
|
|
--
|
|
-- (b) NEW row claims to be a CCR (NEW.counterclaim_of IS NOT NULL)
|
|
-- but already has another CCR pointing AT it (NEW.id is the
|
|
-- target of some other row's counterclaim_of). The cleaner
|
|
-- phrasing: "no row may simultaneously have a CCR child AND
|
|
-- a CCR parent".
|
|
IF NEW.counterclaim_of IS NOT NULL THEN
|
|
IF EXISTS (
|
|
SELECT 1 FROM paliad.projects p
|
|
WHERE p.id = NEW.counterclaim_of
|
|
AND p.counterclaim_of IS NOT NULL
|
|
) THEN
|
|
RAISE EXCEPTION
|
|
'two-level counterclaim chains are not allowed: parent project % is itself a counterclaim',
|
|
NEW.counterclaim_of;
|
|
END IF;
|
|
|
|
IF EXISTS (
|
|
SELECT 1 FROM paliad.projects p
|
|
WHERE p.counterclaim_of = NEW.id
|
|
) THEN
|
|
RAISE EXCEPTION
|
|
'project % already has a counterclaim child and cannot itself be a counterclaim',
|
|
NEW.id;
|
|
END IF;
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
COMMENT ON FUNCTION paliad.projects_no_two_level_ccr() IS
|
|
'Rejects two-level counterclaim chains. UPC practice does not have '
|
|
'CCR-of-a-CCR; reject the malformed shape at write time so the app '
|
|
'layer never has to defend against it. See migration 077.';
|
|
|
|
DROP TRIGGER IF EXISTS projects_no_two_level_ccr ON paliad.projects;
|
|
CREATE TRIGGER projects_no_two_level_ccr
|
|
BEFORE INSERT OR UPDATE OF counterclaim_of ON paliad.projects
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION paliad.projects_no_two_level_ccr();
|