@fayz-ai/plugin-reports 0.12.3 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ import type { ReportColumnDef } from '../types';
2
+ export type ReportFormat = 'csv' | 'xlsx' | 'pdf';
3
+ export interface ReportRunResult {
4
+ key: string;
5
+ version: number;
6
+ title: string;
7
+ /** 'ok' or 'refused' — a refusal reads no rows and is recorded server-side. */
8
+ status: 'ok' | 'refused';
9
+ requiredPermission?: string;
10
+ columns: ReportColumnDef[];
11
+ rows: Record<string, any>[];
12
+ rowCount: number;
13
+ }
14
+ /** Run a report from the catalogue. Throws only on a real error — a refusal comes back as data. */
15
+ export declare function runReport(key: string, params?: Record<string, unknown>, options?: {
16
+ limit?: number;
17
+ offset?: number;
18
+ format?: ReportFormat;
19
+ }): Promise<ReportRunResult>;
20
+ /**
21
+ * Run it and hand the result to the exporter for the format.
22
+ *
23
+ * A refusal exports NOTHING and says so. Writing an empty spreadsheet for
24
+ * someone who was not allowed to see the data is the version of this that looks
25
+ * like a bug in the report.
26
+ */
27
+ export declare function runAndExportReport(key: string, format: ReportFormat, params?: Record<string, unknown>, options?: {
28
+ limit?: number;
29
+ filename?: string;
30
+ currency?: string;
31
+ }): Promise<ReportRunResult>;
32
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/lib/engine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAkB/C,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAAA;AAEjD,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,+EAA+E;IAC/E,MAAM,EAAE,IAAI,GAAG,SAAS,CAAA;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;CACjB;AAQD,mGAAmG;AACnG,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,CAAA;CAAE,GACnE,OAAO,CAAC,eAAe,CAAC,CAqB1B;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,YAAY,EACpB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACjE,OAAO,CAAC,eAAe,CAAC,CAiB1B"}
@@ -0,0 +1,6 @@
1
+ export declare const MIGRATION_000_BASELINE = "-- ============================================================================\n-- plugins/plugin-reports/src/migrations/000_baseline.sql \u2014 what installing this provisions, as one file.\n--\n-- GENERATED by scripts/emit-unit-baselines.mjs (#338). Do not edit by hand:\n-- change the schema, regenerate, and let scripts/check-chain-equivalence.mjs\n-- prove the result still matches.\n--\n-- This replaced 3 migration files. They are kept, unapplied, in\n-- migrations.archive/ \u2014 a year of decisions is worth reading even when it is no\n-- longer worth replaying.\n--\n-- Object order is fixed rather than dependency-sorted: schemas, types,\n-- sequences, tables, defaults, functions, constraints, views, indexes, foreign\n-- keys, triggers, row security, policies, grants. Dependencies BETWEEN units\n-- are carried by the order packages/db/chain.json declares.\n-- ============================================================================\n\n-- A LANGUAGE sql function binds its body at CREATE time, and 570 functions\n-- sorted by name are not sorted by who calls whom. This is what lets them load\n-- in any order; every body is still compiled on first call, so a genuinely\n-- broken reference surfaces then rather than never. Session-scoped.\nSET check_function_bodies = false;\n\n\n\n-- \u2500\u2500 default privileges: reset \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- Cleared BEFORE anything is created, so every object below gets the ACL the\n-- dump describes rather than the image's defaults on top of it. Restored at\n-- the end of this file.\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\n\n-- \u2500\u2500 table \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nCREATE TABLE public.plg_reports_definitions (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid,\n unit_id uuid,\n key text NOT NULL,\n version integer DEFAULT 1 NOT NULL,\n title text NOT NULL,\n description text,\n module text NOT NULL,\n source_view text NOT NULL,\n params jsonb DEFAULT '[]'::jsonb NOT NULL,\n columns jsonb DEFAULT '[]'::jsonb NOT NULL,\n default_order_by text,\n required_permission text NOT NULL,\n is_active boolean DEFAULT true NOT NULL,\n metadata jsonb DEFAULT '{}'::jsonb NOT NULL,\n created_by uuid,\n owner_id uuid,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_reports_definitions_required_permission_check CHECK ((btrim(required_permission) <> ''::text)),\n CONSTRAINT plg_reports_definitions_version_check CHECK ((version >= 1))\n);\n\nALTER TABLE ONLY public.plg_reports_definitions FORCE ROW LEVEL SECURITY;\n\nCREATE TABLE public.plg_reports_runs (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n unit_id uuid,\n definition_key text NOT NULL,\n definition_version integer DEFAULT 1 NOT NULL,\n params jsonb DEFAULT '{}'::jsonb NOT NULL,\n requested_by uuid,\n row_count bigint,\n duration_ms integer,\n status text DEFAULT 'ok'::text NOT NULL,\n error text,\n format text,\n owner_id uuid,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_reports_runs_status_check CHECK ((status = ANY (ARRAY['ok'::text, 'refused'::text, 'error'::text])))\n);\n\nALTER TABLE ONLY public.plg_reports_runs FORCE ROW LEVEL SECURITY;\n\nCREATE TABLE public.plg_reports_schedules (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n unit_id uuid,\n definition_key text NOT NULL,\n cron text NOT NULL,\n params jsonb DEFAULT '{}'::jsonb NOT NULL,\n recipients jsonb DEFAULT '[]'::jsonb NOT NULL,\n format text DEFAULT 'csv'::text NOT NULL,\n template_key text DEFAULT 'report.delivered'::text NOT NULL,\n is_active boolean DEFAULT true NOT NULL,\n last_run_at timestamp with time zone,\n next_run_at timestamp with time zone,\n owner_id uuid,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_reports_schedules_format_check CHECK ((format = ANY (ARRAY['csv'::text, 'xlsx'::text, 'pdf'::text])))\n);\n\nALTER TABLE ONLY public.plg_reports_schedules FORCE ROW LEVEL SECURITY;\n\n\n-- \u2500\u2500 function \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nCREATE FUNCTION public.reports_definition(p_key text, p_tenant uuid DEFAULT NULL::uuid) RETURNS public.plg_reports_definitions\n LANGUAGE sql STABLE SECURITY DEFINER\n SET search_path TO ''\n AS $$\n SELECT d.* FROM public.plg_reports_definitions d\n WHERE d.key = p_key AND d.is_active\n AND (d.tenant_id = coalesce(p_tenant, app.current_tenant_id()) OR d.tenant_id IS NULL)\n -- A tenant's own definition beats the platform one, and the newest version\n -- beats an older one.\n ORDER BY (d.tenant_id IS NOT NULL) DESC, d.version DESC\n LIMIT 1\n$$;\n\nCREATE FUNCTION public.reports_deliver_due(p_limit integer DEFAULT 50) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n s public.plg_reports_schedules%ROWTYPE;\n v_recipient jsonb;\n v_res jsonb;\n v_sent integer := 0;\n v_runs integer := 0;\nBEGIN\n IF to_regprocedure('public.notifications_enqueue(uuid, text, uuid, jsonb, text, timestamptz, text, text, uuid, uuid)') IS NULL THEN\n RETURN jsonb_build_object('skipped', 'plugin-notifications is not installed');\n END IF;\n\n FOR s IN\n SELECT * FROM public.plg_reports_schedules\n WHERE is_active AND (next_run_at IS NULL OR next_run_at <= now())\n ORDER BY next_run_at NULLS FIRST\n LIMIT greatest(p_limit, 0)\n FOR UPDATE SKIP LOCKED\n LOOP\n v_runs := v_runs + 1;\n FOR v_recipient IN SELECT * FROM jsonb_array_elements(coalesce(s.recipients, '[]'::jsonb)) LOOP\n -- The campaign rule applies to a report too: this ENQUEUES, it does not\n -- send. One engine, one queue, one place a delivery can be retried.\n v_res := to_jsonb(public.notifications_enqueue(\n p_tenant_id => s.tenant_id,\n p_template_key => s.template_key,\n p_person_id => nullif(v_recipient ->> 'person_id', '')::uuid,\n p_payload => jsonb_build_object('report_key', s.definition_key, 'format', s.format),\n p_dedupe_key => 'report:' || s.id::text || ':' || to_char(now(), 'YYYY-MM-DD') || ':' || coalesce(v_recipient ->> 'person_id', ''),\n p_unit_id => s.unit_id));\n v_sent := v_sent + 1;\n END LOOP;\n\n UPDATE public.plg_reports_schedules\n SET last_run_at = now(),\n next_run_at = now() + interval '1 day'\n WHERE id = s.id;\n END LOOP;\n\n RETURN jsonb_build_object('schedules', v_runs, 'queued', v_sent);\nEND $$;\n\nCREATE FUNCTION public.reports_ensure_delivery_template(p_tenant uuid, p_channel text DEFAULT 'email'::text) RETURNS uuid\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE v_id uuid;\nBEGIN\n IF to_regclass('public.plg_notifications_templates') IS NULL THEN\n RAISE EXCEPTION 'reports_ensure_delivery_template: plugin-notifications is not installed' USING ERRCODE = '0A000';\n END IF;\n INSERT INTO public.plg_notifications_templates (tenant_id, key, channel, locale, subject, body, is_default, purpose)\n VALUES (p_tenant, 'report.delivered', p_channel, 'pt-BR',\n 'Seu relat\u00F3rio est\u00E1 pronto',\n 'O relat\u00F3rio {{report_key}} foi gerado em {{format}} e est\u00E1 dispon\u00EDvel.', true, 'transactional')\n ON CONFLICT (tenant_id, key, channel, locale) DO UPDATE SET is_active = true\n RETURNING id INTO v_id;\n RETURN v_id;\nEND $$;\n\nCREATE FUNCTION public.reports_run(p_key text, p_params jsonb DEFAULT '{}'::jsonb, p_limit integer DEFAULT 1000, p_offset integer DEFAULT 0, p_format text DEFAULT NULL::text) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_tenant uuid := app.current_tenant_id();\n v_def public.plg_reports_definitions%ROWTYPE;\n v_sql text;\n v_where text := '';\n v_param jsonb;\n v_name text;\n v_value text;\n v_rows jsonb;\n v_count bigint := 0;\n v_start timestamptz := clock_timestamp();\n v_has_unit boolean;\nBEGIN\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'reports_run: no active tenant' USING ERRCODE = '42501';\n END IF;\n\n v_def := public.reports_definition(p_key, v_tenant);\n IF v_def.id IS NULL THEN\n RAISE EXCEPTION 'reports_run: no active report called %', p_key USING ERRCODE = 'P0002';\n END IF;\n\n -- \u2500\u2500 refuse BEFORE the query, not after the rows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n --\n -- The refusal is RETURNED rather than raised, and the reason is mechanical:\n -- raising rolls the statement back, taking the run record with it, and a\n -- refusal nobody can see afterwards is the thing this history exists for.\n -- No rows are read either way \u2014 which is what \"refuse before the query\" means\n -- \u2014 and a caller that ignores `status` still gets nothing.\n IF NOT (select app.has_permission(v_def.required_permission, NULL)) THEN\n INSERT INTO public.plg_reports_runs (tenant_id, definition_key, definition_version, params, requested_by, status, error)\n VALUES (v_tenant, p_key, v_def.version, coalesce(p_params, '{}'::jsonb), auth.uid(), 'refused',\n format('%s is required', v_def.required_permission));\n RETURN jsonb_build_object(\n 'key', p_key, 'version', v_def.version, 'title', v_def.title,\n 'status', 'refused', 'required_permission', v_def.required_permission,\n 'columns', '[]'::jsonb, 'rows', '[]'::jsonb, 'row_count', 0);\n END IF;\n\n -- \u2500\u2500 scope the engine injects, and no definition can switch off \u2500\u2500\u2500\u2500\u2500\u2500\u2500\n v_where := format('%I = %L', 'tenant_id', v_tenant);\n\n SELECT EXISTS (\n SELECT 1 FROM information_schema.columns\n WHERE table_schema = 'public' AND table_name = v_def.source_view AND column_name = 'unit_id')\n INTO v_has_unit;\n IF v_has_unit THEN\n v_where := v_where || ' AND (unit_id IS NULL OR (select app.has_unit(unit_id)))';\n END IF;\n\n -- \u2500\u2500 the declared parameters, and only those \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n FOR v_param IN SELECT * FROM jsonb_array_elements(coalesce(v_def.params, '[]'::jsonb)) LOOP\n v_name := v_param ->> 'name';\n v_value := coalesce(p_params ->> v_name, v_param ->> 'default');\n IF v_value IS NULL THEN\n IF coalesce((v_param ->> 'required')::boolean, false) THEN\n RAISE EXCEPTION 'reports_run: % requires the parameter %', p_key, v_name USING ERRCODE = '22023';\n END IF;\n CONTINUE;\n END IF;\n IF NOT EXISTS (SELECT 1 FROM information_schema.columns\n WHERE table_schema = 'public' AND table_name = v_def.source_view\n AND column_name = coalesce(v_param ->> 'column', v_name)) THEN\n RAISE EXCEPTION 'reports_run: parameter % has no column on %', v_name, v_def.source_view USING ERRCODE = '42703';\n END IF;\n v_where := v_where || format(' AND %I %s %L',\n coalesce(v_param ->> 'column', v_name),\n CASE v_param ->> 'operator' WHEN '>=' THEN '>=' WHEN '<=' THEN '<=' WHEN '>' THEN '>' WHEN '<' THEN '<' WHEN 'ilike' THEN 'ILIKE' ELSE '=' END,\n v_value);\n END LOOP;\n\n -- A parameter the caller sent that the definition never declared is refused\n -- rather than ignored: silently dropping a filter shows the wrong number.\n IF p_params IS NOT NULL AND jsonb_typeof(p_params) = 'object' THEN\n FOR v_name IN SELECT key FROM jsonb_each_text(p_params) LOOP\n IF NOT EXISTS (SELECT 1 FROM jsonb_array_elements(coalesce(v_def.params, '[]'::jsonb)) d WHERE d ->> 'name' = v_name) THEN\n RAISE EXCEPTION 'reports_run: % does not declare a parameter called %', p_key, v_name USING ERRCODE = '22023';\n END IF;\n END LOOP;\n END IF;\n\n v_sql := format('SELECT coalesce(jsonb_agg(t), ''[]''::jsonb), count(*) FROM (SELECT * FROM public.%I WHERE %s %s LIMIT %s OFFSET %s) t',\n v_def.source_view, v_where,\n CASE WHEN v_def.default_order_by IS NULL THEN '' ELSE 'ORDER BY ' || quote_ident(v_def.default_order_by) END,\n greatest(coalesce(p_limit, 1000), 0), greatest(coalesce(p_offset, 0), 0));\n\n EXECUTE v_sql INTO v_rows, v_count;\n\n INSERT INTO public.plg_reports_runs (tenant_id, definition_key, definition_version, params, requested_by,\n row_count, duration_ms, status, format)\n VALUES (v_tenant, p_key, v_def.version, coalesce(p_params, '{}'::jsonb), auth.uid(),\n v_count, (extract(epoch FROM clock_timestamp() - v_start) * 1000)::integer, 'ok', p_format);\n\n RETURN jsonb_build_object(\n 'key', p_key, 'version', v_def.version, 'title', v_def.title,\n 'status', 'ok', 'columns', v_def.columns, 'rows', v_rows, 'row_count', v_count);\nEND $$;\n\nCREATE FUNCTION public.trg_plg_reports_definitions_source() RETURNS trigger\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $_$\nDECLARE v_kind char;\nBEGIN\n IF NEW.source_view !~ '^[a-z_][a-z0-9_]*$' THEN\n RAISE EXCEPTION 'plg_reports_definitions: source_view % is not a bare relation name in public', NEW.source_view\n USING ERRCODE = '22023';\n END IF;\n\n SELECT c.relkind INTO v_kind\n FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE n.nspname = 'public' AND c.relname = NEW.source_view;\n\n IF v_kind IS NULL THEN\n RAISE EXCEPTION 'plg_reports_definitions: public.% does not exist', NEW.source_view USING ERRCODE = '42P01';\n END IF;\n\n -- A view or a materialized view is a read model by construction. A TABLE is\n -- only allowed when its name says so \u2014 that is the `plg_<x>_rep_*` convention\n -- Phase 1 established, and it is checked rather than trusted.\n IF v_kind NOT IN ('v', 'm') AND NEW.source_view !~ '^plg_[a-z0-9_]+_rep_' THEN\n RAISE EXCEPTION 'plg_reports_definitions: public.% is a business table, not a read model', NEW.source_view\n USING ERRCODE = '23514',\n HINT = 'Point the definition at a view or at a plg_<plugin>_rep_* read model. Reporting off a business table is how a report starts holding a schema still.';\n END IF;\n RETURN NEW;\nEND $_$;\n\n\n-- \u2500\u2500 constraint \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nALTER TABLE ONLY public.plg_reports_definitions\n ADD CONSTRAINT plg_reports_definitions_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_reports_definitions\n ADD CONSTRAINT plg_reports_definitions_tenant_id_id_key UNIQUE (tenant_id, id);\n\nALTER TABLE ONLY public.plg_reports_runs\n ADD CONSTRAINT plg_reports_runs_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_reports_runs\n ADD CONSTRAINT plg_reports_runs_tenant_id_id_key UNIQUE (tenant_id, id);\n\nALTER TABLE ONLY public.plg_reports_schedules\n ADD CONSTRAINT plg_reports_schedules_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_reports_schedules\n ADD CONSTRAINT plg_reports_schedules_tenant_id_id_key UNIQUE (tenant_id, id);\n\n\n-- \u2500\u2500 index \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nCREATE UNIQUE INDEX plg_reports_definitions_key_uq ON public.plg_reports_definitions USING btree (COALESCE(tenant_id, '00000000-0000-0000-0000-000000000000'::uuid), key, version);\n\nCREATE INDEX plg_reports_definitions_module_idx ON public.plg_reports_definitions USING btree (module) WHERE is_active;\n\nCREATE INDEX plg_reports_definitions_tenant_owner_idx ON public.plg_reports_definitions USING btree (tenant_id, owner_id) WHERE (owner_id IS NOT NULL);\n\nCREATE INDEX plg_reports_definitions_tenant_unit_idx ON public.plg_reports_definitions USING btree (tenant_id, unit_id);\n\nCREATE INDEX plg_reports_runs_key_idx ON public.plg_reports_runs USING btree (tenant_id, definition_key, created_at DESC);\n\nCREATE INDEX plg_reports_runs_tenant_owner_idx ON public.plg_reports_runs USING btree (tenant_id, owner_id) WHERE (owner_id IS NOT NULL);\n\nCREATE INDEX plg_reports_runs_tenant_unit_idx ON public.plg_reports_runs USING btree (tenant_id, unit_id);\n\nCREATE INDEX plg_reports_schedules_due_idx ON public.plg_reports_schedules USING btree (next_run_at) WHERE is_active;\n\nCREATE INDEX plg_reports_schedules_tenant_owner_idx ON public.plg_reports_schedules USING btree (tenant_id, owner_id) WHERE (owner_id IS NOT NULL);\n\nCREATE INDEX plg_reports_schedules_tenant_unit_idx ON public.plg_reports_schedules USING btree (tenant_id, unit_id);\n\n\n-- \u2500\u2500 fk constraint \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nALTER TABLE ONLY public.plg_reports_definitions\n ADD CONSTRAINT plg_reports_definitions_owner_fk FOREIGN KEY (tenant_id, owner_id) REFERENCES app.memberships(tenant_id, user_id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_reports_definitions\n ADD CONSTRAINT plg_reports_definitions_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_reports_definitions\n ADD CONSTRAINT plg_reports_definitions_unit_fk FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_reports_runs\n ADD CONSTRAINT plg_reports_runs_owner_fk FOREIGN KEY (tenant_id, owner_id) REFERENCES app.memberships(tenant_id, user_id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_reports_runs\n ADD CONSTRAINT plg_reports_runs_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_reports_runs\n ADD CONSTRAINT plg_reports_runs_unit_fk FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_reports_schedules\n ADD CONSTRAINT plg_reports_schedules_owner_fk FOREIGN KEY (tenant_id, owner_id) REFERENCES app.memberships(tenant_id, user_id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_reports_schedules\n ADD CONSTRAINT plg_reports_schedules_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_reports_schedules\n ADD CONSTRAINT plg_reports_schedules_unit_fk FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL;\n\n\n-- \u2500\u2500 table data \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nCOPY public.plg_reports_definitions (id, tenant_id, unit_id, key, version, title, description, module, source_view, params, columns, default_order_by, required_permission, is_active, metadata, created_by, owner_id, created_at, updated_at) FROM stdin;\nb9bdb96e-2219-4bea-a83d-be54e28c4bda\t\\N\t\\N\torders.sales\t1\tVendas\tEvery sale, whichever door it came through \u2014 the query the order vocabulary made possible.\torders\tv_sales\t[{\"name\": \"channel\", \"column\": \"channel\"}, {\"name\": \"status\", \"column\": \"status\"}, {\"name\": \"from\", \"column\": \"created_at\", \"operator\": \">=\"}, {\"name\": \"to\", \"column\": \"created_at\", \"operator\": \"<=\"}]\t[]\tcreated_at\torders.read\tt\t{}\t\\N\t\\N\t2026-09-04 15:03:27.267053+00\t2026-09-04 15:03:27.267053+00\n706d0d78-193c-4abe-8828-5e00d719d155\t\\N\t\\N\torders.fulfillment\t1\tEntregas pendentes\tOrdered against delivered, per line. A positive pending is what a boolean could never describe.\torders\tv_order_fulfillment\t[{\"name\": \"state\", \"column\": \"state\"}]\t[]\t\\N\torders.read\tt\t{}\t\\N\t\\N\t2026-09-04 15:03:27.267053+00\t2026-09-04 15:03:27.267053+00\n6e27d897-55d8-4f39-b963-b5cb3121069d\t\\N\t\\N\tfinancial.receivables\t1\tContas a receber\tInstalments and what is still owed on them, straight from the allocations.\tfinancial\tv_financial_installment_balances\t[{\"name\": \"status\", \"column\": \"status\"}, {\"name\": \"due_from\", \"column\": \"due_date\", \"operator\": \">=\"}, {\"name\": \"due_to\", \"column\": \"due_date\", \"operator\": \"<=\"}]\t[]\tdue_date\tfinancial.read\tt\t{}\t\\N\t\\N\t2026-09-04 15:03:27.267053+00\t2026-09-04 15:03:27.267053+00\n66d7f336-fff1-40ea-b754-6b158f1b0dea\t\\N\t\\N\tfinancial.uninvoiced\t1\tVendas sem t\u00EDtulo\tSales that produced no invoice. Empty is the bar this phase closes on.\tfinancial\tv_orders_sem_invoice\t[{\"name\": \"kind\", \"column\": \"kind\"}]\t[]\tcreated_at\tfinancial.read\tt\t{}\t\\N\t\\N\t2026-09-04 15:03:27.267053+00\t2026-09-04 15:03:27.267053+00\na5a3ffab-e6e1-46ad-942f-f36582b4e126\t\\N\t\\N\tinventory.balances\t1\tSaldo de estoque\tThe balance, from the one ledger that owns it.\tinventory\tv_inventory_balances\t[]\t[]\t\\N\tinventory.read\tt\t{}\t\\N\t\\N\t2026-09-04 15:03:27.267053+00\t2026-09-04 15:03:27.267053+00\na2d3191e-5a8d-4e03-892f-0c9573675ea5\t\\N\t\\N\tnotifications.queue\t1\tFila de notifica\u00E7\u00F5es\tWhat is waiting, what is overdue, what was abandoned and what was refused.\tnotifications\tv_notification_queue\t[{\"name\": \"status\", \"column\": \"status\"}, {\"name\": \"channel\", \"column\": \"channel\"}]\t[]\t\\N\tnotifications.read\tt\t{}\t\\N\t\\N\t2026-09-04 15:03:27.267053+00\t2026-09-04 15:03:27.267053+00\nc616ae83-b861-41fb-959a-bbb85431ed77\t\\N\t\\N\tagenda.appointments\t1\tAgendamentos\tThe calendar as a table, with the Venda and the payment state each booking carries.\tagenda\tv_appointments\t[{\"name\": \"status\", \"column\": \"status\"}, {\"name\": \"from\", \"column\": \"starts_at\", \"operator\": \">=\"}, {\"name\": \"to\", \"column\": \"starts_at\", \"operator\": \"<=\"}]\t[]\tstarts_at\tagenda.read\tt\t{}\t\\N\t\\N\t2026-09-04 15:03:27.267053+00\t2026-09-04 15:03:27.267053+00\n5af4a8c8-903b-4ea6-8829-98877b017883\t\\N\t\\N\tcrm.funnel\t1\tFunil\tThe funnel over plg_crm_deals, with won_at and lost_at \u2014 which is what makes a cycle length measurable.\tcrm\tv_deals\t[{\"name\": \"status\", \"column\": \"status\"}, {\"name\": \"stage\", \"column\": \"stage_name\"}]\t[]\tcreated_at\tcrm.pipeline.read\tt\t{}\t\\N\t\\N\t2026-09-04 15:03:27.267053+00\t2026-09-04 15:03:27.267053+00\n9143ce67-ecaa-46a3-8028-7c354c34f301\t\\N\t\\N\tmarketing.campaigns\t1\tCampanhas\tReach, delivery and conversion per campaign \u2014 the metric comes from here, never from a counter on the campaign.\tmarketing\tv_marketing_campaign_metrics\t[{\"name\": \"status\", \"column\": \"status\"}]\t[]\t\\N\tmarketing.read\tt\t{}\t\\N\t\\N\t2026-09-04 15:03:27.267053+00\t2026-09-04 15:03:27.267053+00\n\\.\n\n\n-- \u2500\u2500 trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nCREATE TRIGGER plg_reports_definitions_source BEFORE INSERT OR UPDATE OF source_view ON public.plg_reports_definitions FOR EACH ROW EXECUTE FUNCTION public.trg_plg_reports_definitions_source();\n\nCREATE TRIGGER plg_reports_definitions_updated_at BEFORE UPDATE ON public.plg_reports_definitions FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();\n\nCREATE TRIGGER plg_reports_runs_updated_at BEFORE UPDATE ON public.plg_reports_runs FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();\n\nCREATE TRIGGER plg_reports_schedules_updated_at BEFORE UPDATE ON public.plg_reports_schedules FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();\n\n\n-- \u2500\u2500 row security \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nALTER TABLE public.plg_reports_definitions ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_reports_runs ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_reports_schedules ENABLE ROW LEVEL SECURITY;\n\n\n-- \u2500\u2500 policy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nCREATE POLICY plg_reports_definitions_authz_delete ON public.plg_reports_definitions FOR DELETE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_reports_definitions.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('reports.definition'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('reports.definition'::text, plg_reports_definitions.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('reports.read_all'::text, plg_reports_definitions.unit_id) AS has_permission)) AND ( SELECT app.has_permission('reports.delete'::text, plg_reports_definitions.unit_id) AS has_permission)));\n\nCREATE POLICY plg_reports_definitions_authz_insert ON public.plg_reports_definitions FOR INSERT TO authenticated WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_reports_definitions.unit_id) AS has_unit)) AND ( SELECT app.has_permission('reports.create'::text, plg_reports_definitions.unit_id) AS has_permission)));\n\nCREATE POLICY plg_reports_definitions_authz_select ON public.plg_reports_definitions FOR SELECT TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_reports_definitions.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('reports.definition'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('reports.definition'::text, plg_reports_definitions.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('reports.read_all'::text, plg_reports_definitions.unit_id) AS has_permission)) AND ( SELECT app.has_permission('reports.read'::text, plg_reports_definitions.unit_id) AS has_permission)));\n\nCREATE POLICY plg_reports_definitions_authz_update ON public.plg_reports_definitions FOR UPDATE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_reports_definitions.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('reports.definition'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('reports.definition'::text, plg_reports_definitions.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('reports.read_all'::text, plg_reports_definitions.unit_id) AS has_permission)) AND ( SELECT app.has_permission('reports.edit'::text, plg_reports_definitions.unit_id) AS has_permission))) WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_reports_definitions.unit_id) AS has_unit)) AND ( SELECT app.has_permission('reports.edit'::text, plg_reports_definitions.unit_id) AS has_permission)));\n\nCREATE POLICY plg_reports_definitions_platform_read ON public.plg_reports_definitions FOR SELECT TO authenticated USING (((tenant_id IS NULL) AND is_active));\n\nCREATE POLICY plg_reports_runs_authz_delete ON public.plg_reports_runs FOR DELETE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_reports_runs.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('reports.run'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('reports.run'::text, plg_reports_runs.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('reports.read_all'::text, plg_reports_runs.unit_id) AS has_permission)) AND ( SELECT app.has_permission('reports.delete'::text, plg_reports_runs.unit_id) AS has_permission)));\n\nCREATE POLICY plg_reports_runs_authz_insert ON public.plg_reports_runs FOR INSERT TO authenticated WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_reports_runs.unit_id) AS has_unit)) AND ( SELECT app.has_permission('reports.create'::text, plg_reports_runs.unit_id) AS has_permission)));\n\nCREATE POLICY plg_reports_runs_authz_select ON public.plg_reports_runs FOR SELECT TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_reports_runs.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('reports.run'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('reports.run'::text, plg_reports_runs.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('reports.read_all'::text, plg_reports_runs.unit_id) AS has_permission)) AND ( SELECT app.has_permission('reports.read'::text, plg_reports_runs.unit_id) AS has_permission)));\n\nCREATE POLICY plg_reports_runs_authz_update ON public.plg_reports_runs FOR UPDATE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_reports_runs.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('reports.run'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('reports.run'::text, plg_reports_runs.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('reports.read_all'::text, plg_reports_runs.unit_id) AS has_permission)) AND ( SELECT app.has_permission('reports.edit'::text, plg_reports_runs.unit_id) AS has_permission))) WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_reports_runs.unit_id) AS has_unit)) AND ( SELECT app.has_permission('reports.edit'::text, plg_reports_runs.unit_id) AS has_permission)));\n\nCREATE POLICY plg_reports_schedules_authz_delete ON public.plg_reports_schedules FOR DELETE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_reports_schedules.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('reports.schedule'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('reports.schedule'::text, plg_reports_schedules.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('reports.read_all'::text, plg_reports_schedules.unit_id) AS has_permission)) AND ( SELECT app.has_permission('reports.delete'::text, plg_reports_schedules.unit_id) AS has_permission)));\n\nCREATE POLICY plg_reports_schedules_authz_insert ON public.plg_reports_schedules FOR INSERT TO authenticated WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_reports_schedules.unit_id) AS has_unit)) AND ( SELECT app.has_permission('reports.create'::text, plg_reports_schedules.unit_id) AS has_permission)));\n\nCREATE POLICY plg_reports_schedules_authz_select ON public.plg_reports_schedules FOR SELECT TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_reports_schedules.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('reports.schedule'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('reports.schedule'::text, plg_reports_schedules.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('reports.read_all'::text, plg_reports_schedules.unit_id) AS has_permission)) AND ( SELECT app.has_permission('reports.read'::text, plg_reports_schedules.unit_id) AS has_permission)));\n\nCREATE POLICY plg_reports_schedules_authz_update ON public.plg_reports_schedules FOR UPDATE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_reports_schedules.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('reports.schedule'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('reports.schedule'::text, plg_reports_schedules.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('reports.read_all'::text, plg_reports_schedules.unit_id) AS has_permission)) AND ( SELECT app.has_permission('reports.edit'::text, plg_reports_schedules.unit_id) AS has_permission))) WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_reports_schedules.unit_id) AS has_unit)) AND ( SELECT app.has_permission('reports.edit'::text, plg_reports_schedules.unit_id) AS has_permission)));\n\n\n-- \u2500\u2500 acl \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nGRANT MAINTAIN ON TABLE public.plg_reports_definitions TO anon;\nGRANT ALL ON TABLE public.plg_reports_definitions TO authenticated;\nGRANT ALL ON TABLE public.plg_reports_definitions TO service_role;\n\nREVOKE ALL ON FUNCTION public.reports_definition(p_key text, p_tenant uuid) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.reports_definition(p_key text, p_tenant uuid) TO authenticated;\nGRANT ALL ON FUNCTION public.reports_definition(p_key text, p_tenant uuid) TO service_role;\n\nREVOKE ALL ON FUNCTION public.reports_deliver_due(p_limit integer) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.reports_deliver_due(p_limit integer) TO service_role;\n\nREVOKE ALL ON FUNCTION public.reports_ensure_delivery_template(p_tenant uuid, p_channel text) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.reports_ensure_delivery_template(p_tenant uuid, p_channel text) TO authenticated;\nGRANT ALL ON FUNCTION public.reports_ensure_delivery_template(p_tenant uuid, p_channel text) TO service_role;\n\nREVOKE ALL ON FUNCTION public.reports_run(p_key text, p_params jsonb, p_limit integer, p_offset integer, p_format text) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.reports_run(p_key text, p_params jsonb, p_limit integer, p_offset integer, p_format text) TO authenticated;\nGRANT ALL ON FUNCTION public.reports_run(p_key text, p_params jsonb, p_limit integer, p_offset integer, p_format text) TO service_role;\n\nREVOKE ALL ON FUNCTION public.trg_plg_reports_definitions_source() FROM PUBLIC;\nGRANT ALL ON FUNCTION public.trg_plg_reports_definitions_source() TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_reports_runs TO anon;\nGRANT ALL ON TABLE public.plg_reports_runs TO authenticated;\nGRANT ALL ON TABLE public.plg_reports_runs TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_reports_schedules TO anon;\nGRANT ALL ON TABLE public.plg_reports_schedules TO authenticated;\nGRANT ALL ON TABLE public.plg_reports_schedules TO service_role;\n\n\n-- \u2500\u2500 comment \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nCOMMENT ON TABLE public.plg_reports_definitions IS 'The report catalogue (PRD 08). Versioned, so changing a report does not rewrite the history of everyone who ran the previous one; and `required_permission` is declared HERE rather than on a screen, so the engine can refuse before it queries.';\n\nCOMMENT ON FUNCTION public.reports_deliver_due(p_limit integer) IS 'Queue the scheduled reports that are due (PRD 08). It enqueues through the notification engine rather than sending \u2014 the same mechanism as dunning and as a campaign, which is what made building that engine first worth doing.';\n\nCOMMENT ON FUNCTION public.reports_run(p_key text, p_params jsonb, p_limit integer, p_offset integer, p_format text) IS 'Run a report from the catalogue (PRD 08). The permission is checked BEFORE the query is built \u2014 a refusal reads no rows and comes back as status=refused, recorded in plg_reports_runs \u2014 the tenant and unit predicates are injected by the engine and cannot be switched off by a definition, and an undeclared parameter is refused rather than ignored.';\n\nCOMMENT ON TABLE public.plg_reports_runs IS 'Who ran what, with which parameters, against which VERSION of the definition (PRD 08). A refusal is recorded too \u2014 \"the report is empty\" and \"you were not allowed to see it\" are different answers and the history has to keep them apart.';\n\nCOMMENT ON TABLE public.plg_reports_schedules IS 'A report on a schedule (PRD 08). Delivery is the notification engine''s job (PRD 03) \u2014 the same queue, retry and opt-out that carry a receipt carry a report, which is the whole reason that engine was built first.';\n\n\n-- \u2500\u2500 default privileges: restore \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- Put back, so the NEXT thing installed inherits what the chain had.\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT MAINTAIN ON TABLES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT MAINTAIN ON TABLES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$--\n-- PostgreSQL database dump complete\n--$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$--\n-- PostgreSQL database dump complete\n--$stmt$;\nEND $dp$;\n";
2
+ export declare const MIGRATIONS: Array<{
3
+ id: string;
4
+ sql: string;
5
+ }>;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,qxmDA2qBlC,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAEzD,CAAA"}
@@ -0,0 +1,4 @@
1
+ export { exportToPdf, isPdfAvailable } from './chunk-BPTKNPXQ.js';
2
+ import './chunk-4QIHTUXD.js';
3
+ //# sourceMappingURL=pdf-H73MJGB6.js.map
4
+ //# sourceMappingURL=pdf-H73MJGB6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"pdf-H73MJGB6.js"}
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "status": "preview",
5
5
  "dependencies": []
6
6
  },
7
- "version": "0.12.3",
7
+ "version": "0.13.1",
8
8
  "description": "[experimental] Fayz SDK — plugin-reports plugin",
9
9
  "type": "module",
10
10
  "sideEffects": false,
@@ -28,9 +28,9 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@tanstack/react-table": "^8.20.0",
31
- "@fayz-ai/core": "^0.18.0",
32
- "@fayz-ai/ui": "^0.18.0",
33
- "@fayz-ai/admin": "^0.18.0"
31
+ "@fayz-ai/core": "^0.19.1",
32
+ "@fayz-ai/ui": "^0.19.0",
33
+ "@fayz-ai/admin": "^0.20.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/react": "^18.3.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/export/pdf.ts"],"names":[],"mappings":";;;AAKA,IAAM,IAAA,GAAO,CAAC,CAAA,KAAc,IAAI,SAAS,GAAA,EAAK,kBAAkB,EAAE,CAAC,CAAA;AAEnE,eAAsB,WAAA,CACpB,OAAA,EACA,IAAA,EACA,QAAA,EACA,OAAA,EACe;AACf,EAAA,MAAM,EAAE,OAAA,EAAS,KAAA,EAAM,GAAI,MAAM,KAAK,OAAO,CAAA;AAC7C,EAAA,MAAM,KAAK,iBAAiB,CAAA;AAE5B,EAAA,MAAM,cAAc,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,YAAY,KAAK,CAAA;AAE7D,EAAA,MAAM,GAAA,GAAM,IAAI,KAAA,CAAM,EAAE,WAAA,EAAa,YAAY,MAAA,GAAS,CAAA,GAAI,WAAA,GAAc,UAAA,EAAY,CAAA;AAExF,EAAA,IAAI,SAAS,KAAA,EAAO;AAClB,IAAA,GAAA,CAAI,YAAY,EAAE,CAAA;AAClB,IAAA,GAAA,CAAI,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAA,EAAI,EAAE,CAAA;AAAA,EAChC;AAEC,EAAC,IAAY,SAAA,CAAU;AAAA,IACtB,MAAA,EAAQ,OAAA,EAAS,KAAA,GAAQ,EAAA,GAAK,EAAA;AAAA,IAC9B,IAAA,EAAM,CAAC,WAAA,CAAY,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,KAAK,CAAC,CAAA;AAAA,IACtC,MAAM,IAAA,CAAK,GAAA;AAAA,MAAI,CAAC,GAAA,KACd,WAAA,CAAY,GAAA,CAAI,CAAC,GAAA,KAAQ,gBAAA,CAAiB,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA,EAAG,GAAA,EAAK,OAAA,EAAS,QAAQ,CAAC;AAAA,KACjF;AAAA,IACA,MAAA,EAAQ,EAAE,QAAA,EAAU,CAAA,EAAE;AAAA,IACtB,YAAY,EAAE,SAAA,EAAW,CAAC,GAAA,EAAK,GAAA,EAAK,GAAG,CAAA;AAAE,GAC1C,CAAA;AAED,EAAA,GAAA,CAAI,IAAA,CAAK,CAAA,EAAG,QAAQ,CAAA,IAAA,CAAM,CAAA;AAC5B;AAEA,eAAsB,cAAA,GAAmC;AACvD,EAAA,IAAI;AACF,IAAA,MAAM,KAAK,OAAO,CAAA;AAClB,IAAA,MAAM,KAAK,iBAAiB,CAAA;AAC5B,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF","file":"pdf-DH4CJMV5.js","sourcesContent":["import type { ReportColumnDef } from '../types'\nimport { formatValuePlain } from '../lib/columns'\n\n// Opaque dynamic import — hidden from Vite's static analysis so it won't\n// fail when jspdf is not installed (it's an optional peer dependency).\nconst load = (m: string) => new Function('m', 'return import(m)')(m) as Promise<any>\n\nexport async function exportToPdf(\n columns: ReportColumnDef[],\n data: Record<string, any>[],\n filename: string,\n options?: { title?: string; currency?: string },\n): Promise<void> {\n const { default: jsPDF } = await load('jspdf')\n await load('jspdf-autotable')\n\n const visibleCols = columns.filter((c) => c.visible !== false)\n\n const doc = new jsPDF({ orientation: visibleCols.length > 6 ? 'landscape' : 'portrait' })\n\n if (options?.title) {\n doc.setFontSize(14)\n doc.text(options.title, 14, 20)\n }\n\n ;(doc as any).autoTable({\n startY: options?.title ? 30 : 14,\n head: [visibleCols.map((c) => c.label)],\n body: data.map((row) =>\n visibleCols.map((col) => formatValuePlain(row[col.key], col, options?.currency)),\n ),\n styles: { fontSize: 8 },\n headStyles: { fillColor: [100, 100, 100] },\n })\n\n doc.save(`${filename}.pdf`)\n}\n\nexport async function isPdfAvailable(): Promise<boolean> {\n try {\n await load('jspdf')\n await load('jspdf-autotable')\n return true\n } catch {\n return false\n }\n}\n"]}