Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
98 lines
4.2 KiB
PL/PgSQL
98 lines
4.2 KiB
PL/PgSQL
CREATE TABLE pickup_contacts (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
child_id uuid NOT NULL REFERENCES children(id) ON DELETE CASCADE,
|
|
name text NOT NULL,
|
|
relationship text NOT NULL,
|
|
phone text NOT NULL DEFAULT '',
|
|
authorized boolean NOT NULL DEFAULT true,
|
|
notes text NOT NULL DEFAULT ''
|
|
);
|
|
CREATE INDEX pickup_contacts_child ON pickup_contacts(child_id);
|
|
|
|
CREATE TABLE alone_rules (
|
|
child_id uuid NOT NULL REFERENCES children(id) ON DELETE CASCADE,
|
|
weekday integer NOT NULL CHECK (weekday BETWEEN 1 AND 7),
|
|
earliest time NOT NULL,
|
|
PRIMARY KEY(child_id, weekday)
|
|
);
|
|
|
|
ALTER TABLE attendance DROP CONSTRAINT attendance_pickup_kind_check;
|
|
ALTER TABLE attendance ADD CONSTRAINT attendance_pickup_kind_check
|
|
CHECK (pickup_kind IN ('planned', 'parents', 'alone'));
|
|
ALTER TABLE attendance ADD CONSTRAINT attendance_chronology
|
|
CHECK (picked_up_at IS NULL OR picked_up_at >= arrived_at);
|
|
|
|
CREATE SEQUENCE attendance_revision;
|
|
ALTER TABLE attendance ADD COLUMN version bigint NOT NULL DEFAULT nextval('attendance_revision');
|
|
CREATE FUNCTION bump_attendance_revision() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
BEGIN
|
|
NEW.version := nextval('attendance_revision');
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
CREATE TRIGGER attendance_revision_trigger BEFORE UPDATE ON attendance
|
|
FOR EACH ROW EXECUTE FUNCTION bump_attendance_revision();
|
|
|
|
CREATE TABLE audit_log (
|
|
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
child_id uuid NOT NULL,
|
|
entity text NOT NULL,
|
|
entity_id text,
|
|
day date,
|
|
operation text NOT NULL CHECK (operation IN ('INSERT', 'UPDATE', 'DELETE')),
|
|
actor_id uuid,
|
|
actor_name text NOT NULL,
|
|
reason text NOT NULL,
|
|
before_data jsonb,
|
|
after_data jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT clock_timestamp()
|
|
);
|
|
CREATE INDEX audit_log_child ON audit_log(child_id, id DESC);
|
|
|
|
CREATE FUNCTION record_child_audit() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
DECLARE
|
|
old_data jsonb;
|
|
new_data jsonb;
|
|
row_data jsonb;
|
|
actor uuid;
|
|
actor_label text;
|
|
BEGIN
|
|
IF TG_OP <> 'INSERT' THEN old_data := to_jsonb(OLD); END IF;
|
|
IF TG_OP <> 'DELETE' THEN new_data := to_jsonb(NEW); END IF;
|
|
IF TG_OP = 'UPDATE' AND old_data - 'version' = new_data - 'version' THEN RETURN NEW; END IF;
|
|
row_data := COALESCE(new_data, old_data);
|
|
actor := NULLIF(current_setting('kibu.actor_id', true), '')::uuid;
|
|
SELECT name || ' (' || email || ')' INTO actor_label FROM staff WHERE id = actor;
|
|
INSERT INTO audit_log(child_id, entity, entity_id, day, operation, actor_id, actor_name,
|
|
reason, before_data, after_data)
|
|
VALUES (
|
|
CASE WHEN TG_TABLE_NAME = 'children' THEN (row_data->>'id')::uuid ELSE (row_data->>'child_id')::uuid END,
|
|
TG_TABLE_NAME, row_data->>'id', (row_data->>'day')::date, TG_OP, actor,
|
|
COALESCE(actor_label, 'Datenbank / Einrichtung'),
|
|
COALESCE(NULLIF(current_setting('kibu.reason', true), ''), 'Direkte Datenbankänderung'),
|
|
old_data, new_data
|
|
);
|
|
RETURN COALESCE(NEW, OLD);
|
|
END;
|
|
$$;
|
|
|
|
CREATE TRIGGER children_audit AFTER INSERT OR UPDATE OR DELETE ON children FOR EACH ROW EXECUTE FUNCTION record_child_audit();
|
|
CREATE TRIGGER weekly_audit AFTER INSERT OR UPDATE OR DELETE ON weekly_schedules FOR EACH ROW EXECUTE FUNCTION record_child_audit();
|
|
CREATE TRIGGER override_audit AFTER INSERT OR UPDATE OR DELETE ON day_overrides FOR EACH ROW EXECUTE FUNCTION record_child_audit();
|
|
CREATE TRIGGER attendance_audit AFTER INSERT OR UPDATE OR DELETE ON attendance FOR EACH ROW EXECUTE FUNCTION record_child_audit();
|
|
CREATE TRIGGER contacts_audit AFTER INSERT OR UPDATE OR DELETE ON pickup_contacts FOR EACH ROW EXECUTE FUNCTION record_child_audit();
|
|
CREATE TRIGGER alone_audit AFTER INSERT OR UPDATE OR DELETE ON alone_rules FOR EACH ROW EXECUTE FUNCTION record_child_audit();
|
|
|
|
CREATE FUNCTION protect_audit_log() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
BEGIN
|
|
RAISE EXCEPTION 'Das Audit-Log ist unveraenderlich.';
|
|
END;
|
|
$$;
|
|
CREATE TRIGGER audit_immutable BEFORE UPDATE OR DELETE OR TRUNCATE ON audit_log
|
|
FOR EACH STATEMENT EXECUTE FUNCTION protect_audit_log();
|
|
|
|
INSERT INTO audit_log(child_id, entity, day, operation, actor_name, reason, after_data)
|
|
SELECT child_id, 'attendance', day, 'INSERT', 'Datenbank / Einrichtung',
|
|
'Bestandsübernahme bei Einführung des Audit-Logs; ursprünglicher Änderungsverlauf nicht verfügbar.',
|
|
to_jsonb(a) FROM attendance a;
|