- Add get_status() SQL and /api/status route; load stale state on login - Replace polling with immediate client-side stale tracking via callbacks - Amber status bar with per-item Generate buttons for sources and stacks - Pivot: add stack selector to view any dfv.stack view via Perspective - Stack views: DROP CASCADE, add id to source views, per-source balance columns - net_balance = sum(all amounts) + total_offset guarantees chase+dcard=net per row - CLAUDE.md: document correct dedup spec (within-batch duplicates always allowed) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
86 lines
3.1 KiB
PL/PgSQL
86 lines
3.1 KiB
PL/PgSQL
--
|
|
-- Status tracking: view_generated_at on sources and stacks
|
|
-- Cleared by triggers when definitions change; set by API when views are generated.
|
|
--
|
|
|
|
SET search_path TO dataflow, public;
|
|
|
|
-- Add view_generated_at columns
|
|
ALTER TABLE dataflow.sources ADD COLUMN IF NOT EXISTS view_generated_at TIMESTAMPTZ;
|
|
ALTER TABLE dataflow.stacks ADD COLUMN IF NOT EXISTS view_generated_at TIMESTAMPTZ;
|
|
|
|
------------------------------------------------------
|
|
-- Trigger: clear source view_generated_at when rules change
|
|
------------------------------------------------------
|
|
CREATE OR REPLACE FUNCTION dataflow.rules_changed()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
UPDATE dataflow.sources SET view_generated_at = NULL
|
|
WHERE name = COALESCE(NEW.source_name, OLD.source_name);
|
|
RETURN NULL;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS trg_rules_changed ON dataflow.rules;
|
|
CREATE TRIGGER trg_rules_changed
|
|
AFTER INSERT OR UPDATE OR DELETE ON dataflow.rules
|
|
FOR EACH ROW EXECUTE FUNCTION dataflow.rules_changed();
|
|
|
|
------------------------------------------------------
|
|
-- Trigger: clear source view_generated_at when mappings change
|
|
------------------------------------------------------
|
|
CREATE OR REPLACE FUNCTION dataflow.mappings_changed()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
UPDATE dataflow.sources SET view_generated_at = NULL
|
|
WHERE name = COALESCE(NEW.source_name, OLD.source_name);
|
|
RETURN NULL;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS trg_mappings_changed ON dataflow.mappings;
|
|
CREATE TRIGGER trg_mappings_changed
|
|
AFTER INSERT OR UPDATE OR DELETE ON dataflow.mappings
|
|
FOR EACH ROW EXECUTE FUNCTION dataflow.mappings_changed();
|
|
|
|
------------------------------------------------------
|
|
-- Trigger: clear stack view_generated_at when sources change
|
|
------------------------------------------------------
|
|
CREATE OR REPLACE FUNCTION dataflow.stack_sources_changed()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
UPDATE dataflow.stacks SET view_generated_at = NULL
|
|
WHERE name = COALESCE(NEW.stack_name, OLD.stack_name);
|
|
RETURN NULL;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS trg_stack_sources_changed ON dataflow.stack_sources;
|
|
CREATE TRIGGER trg_stack_sources_changed
|
|
AFTER INSERT OR UPDATE OR DELETE ON dataflow.stack_sources
|
|
FOR EACH ROW EXECUTE FUNCTION dataflow.stack_sources_changed();
|
|
|
|
------------------------------------------------------
|
|
-- Function: get_status
|
|
-- Returns sources and stacks whose view is stale (null or never generated)
|
|
------------------------------------------------------
|
|
CREATE OR REPLACE FUNCTION get_status()
|
|
RETURNS JSON AS $$
|
|
DECLARE
|
|
v_sources JSON;
|
|
v_stacks JSON;
|
|
BEGIN
|
|
SELECT COALESCE(json_agg(json_build_object('name', name, 'view_generated_at', view_generated_at) ORDER BY name), '[]'::json)
|
|
INTO v_sources
|
|
FROM dataflow.sources
|
|
WHERE view_generated_at IS NULL;
|
|
|
|
SELECT COALESCE(json_agg(json_build_object('name', name, 'view_generated_at', view_generated_at) ORDER BY name), '[]'::json)
|
|
INTO v_stacks
|
|
FROM dataflow.stacks
|
|
WHERE view_generated_at IS NULL;
|
|
|
|
RETURN json_build_object('stale_sources', v_sources, 'stale_stacks', v_stacks);
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|