@fayz-ai/db 0.23.2 → 0.24.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,95 @@
1
+ -- 204: a module switched off in a unit (202) stops WRITING there, not only
2
+ -- showing. One guard, a BEFORE INSERT/UPDATE trigger on each module's
3
+ -- operational tables: a sale, a tab, a booking, a stock move, a time entry in
4
+ -- a unit that has the module off is refused.
5
+ --
6
+ -- Who it binds: a signed-in person (auth.role() = 'authenticated'), including
7
+ -- through SECURITY DEFINER RPCs — those still carry the caller's JWT, so the
8
+ -- sale RPCs are covered without touching them. service_role and direct SQL
9
+ -- (no JWT: migrations, ETL, support) pass, as every other guard here does.
10
+ -- Catalog/config tables are left alone (a price list is shared by the network),
11
+ -- and so are rows one module writes on another's behalf — the stock move, the
12
+ -- fiscal document and the cash line a sale produces: the sale is the act.
13
+ -- Additive and idempotent.
14
+
15
+ -- Only the UNIT switch (202). The account-level status is the runtime's call:
16
+ -- a module the account turned off can still run because another needs it
17
+ -- (orders keeps inventory moving), and refusing those writes would fail a sale.
18
+ CREATE OR REPLACE FUNCTION app.module_writes_in_unit(p_tenant uuid, p_unit uuid, p_plugin text, p_facet text DEFAULT '')
19
+ RETURNS boolean
20
+ LANGUAGE sql STABLE SECURITY DEFINER
21
+ SET search_path TO ''
22
+ AS $$
23
+ SELECT NOT EXISTS (
24
+ SELECT 1 FROM app.unit_plugins u
25
+ WHERE u.tenant_id = p_tenant AND u.unit_id = p_unit AND u.plugin_id = p_plugin
26
+ AND u.facet IN ('', coalesce(p_facet, '')) AND u.status = 'disabled')
27
+ $$;
28
+ REVOKE ALL ON FUNCTION app.module_writes_in_unit(uuid, uuid, text, text) FROM PUBLIC;
29
+ GRANT EXECUTE ON FUNCTION app.module_writes_in_unit(uuid, uuid, text, text) TO authenticated, service_role;
30
+
31
+ -- TG_ARGV[0] = plugin, TG_ARGV[1] = facet (optional).
32
+ CREATE OR REPLACE FUNCTION app.trg_module_unit_guard()
33
+ RETURNS trigger
34
+ LANGUAGE plpgsql SECURITY DEFINER
35
+ SET search_path TO ''
36
+ AS $$
37
+ DECLARE
38
+ v_plugin text := TG_ARGV[0];
39
+ v_facet text := coalesce(TG_ARGV[1], '');
40
+ BEGIN
41
+ IF coalesce(auth.role(), '') <> 'authenticated' OR NEW.unit_id IS NULL THEN
42
+ RETURN NEW;
43
+ END IF;
44
+ -- Sales: the channel says which module sold it.
45
+ IF TG_TABLE_NAME = 'orders' AND TG_TABLE_SCHEMA = 'public' THEN
46
+ IF NEW.channel = 'shop' THEN v_plugin := 'shop';
47
+ ELSIF NEW.channel = 'delivery' THEN v_plugin := 'orders'; v_facet := 'delivery';
48
+ ELSIF NEW.channel IS NULL OR NEW.channel NOT IN ('balcao', 'dine_in', 'retirada') THEN RETURN NEW;
49
+ END IF;
50
+ END IF;
51
+ IF NOT app.module_writes_in_unit(NEW.tenant_id, NEW.unit_id, v_plugin, v_facet) THEN
52
+ RAISE EXCEPTION 'module % is switched off in this unit', v_plugin || CASE WHEN v_facet = '' THEN '' ELSE '.' || v_facet END
53
+ USING ERRCODE = '42501',
54
+ HINT = 'Ligue o módulo para esta unidade em Configurações › Módulos.';
55
+ END IF;
56
+ RETURN NEW;
57
+ END $$;
58
+ REVOKE ALL ON FUNCTION app.trg_module_unit_guard() FROM PUBLIC;
59
+
60
+ DO $$
61
+ DECLARE
62
+ r record;
63
+ BEGIN
64
+ FOR r IN SELECT * FROM (VALUES
65
+ ('public', 'orders', 'orders', ''),
66
+ ('public', 'plg_tables_order_guests', 'tables', ''),
67
+ ('public', 'plg_tables_order_payments', 'tables', ''),
68
+ ('public', 'plg_tables_order_transfers', 'tables', ''),
69
+ ('public', 'plg_tables_order_cancellations','tables', ''),
70
+ ('public', 'plg_tables_order_extensions', 'tables', ''),
71
+ ('public', 'plg_tables_order_cover_charges','tables', ''),
72
+ ('public', 'plg_tables_courtesy_events', 'tables', ''),
73
+ ('public', 'plg_tables_waiter_calls', 'tables', ''),
74
+ ('public', 'plg_tables_waitlist', 'tables', 'waitlist'),
75
+ ('public', 'plg_reservations', 'reservations',''),
76
+ ('public', 'appointments', 'agenda', ''),
77
+ ('public', 'appointment_items', 'agenda', ''),
78
+ ('public', 'plg_agenda_turn_entries', 'agenda', ''),
79
+ ('public', 'plg_clinical_sessions', 'clinical', ''),
80
+ ('public', 'plg_clinical_session_products','clinical', ''),
81
+ ('public', 'plg_inventory_count_sessions', 'inventory', ''),
82
+ ('public', 'plg_inventory_production_orders','production',''),
83
+ ('public', 'plg_workforce_time_entries', 'workforce', 'time_clock'),
84
+ ('public', 'plg_workforce_time_adjustments','workforce', ''),
85
+ ('public', 'plg_workforce_schedule_assignments','workforce','')
86
+ ) AS t(sch, tbl, plugin, facet)
87
+ LOOP
88
+ CONTINUE WHEN to_regclass(format('%I.%I', r.sch, r.tbl)) IS NULL;
89
+ CONTINUE WHEN NOT EXISTS (SELECT 1 FROM information_schema.columns
90
+ WHERE table_schema = r.sch AND table_name = r.tbl AND column_name = 'unit_id');
91
+ EXECUTE format('DROP TRIGGER IF EXISTS module_unit_guard ON %I.%I', r.sch, r.tbl);
92
+ EXECUTE format('CREATE TRIGGER module_unit_guard BEFORE INSERT OR UPDATE ON %I.%I
93
+ FOR EACH ROW EXECUTE FUNCTION app.trg_module_unit_guard(%L, %L)', r.sch, r.tbl, r.plugin, r.facet);
94
+ END LOOP;
95
+ END $$;