@fayz-ai/db 0.18.1 → 0.18.2

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,140 @@
1
+ -- A supplier, customer or other non-login Person may be shared with selected
2
+ -- units. The grants live in app.*, so the browser gets a narrow public door
3
+ -- instead of direct access to the authorization schema.
4
+
5
+ -- The importer already preserved these V1 fields in metadata. Lift them into
6
+ -- the declared V2 fields without overwriting anything edited in V2.
7
+ UPDATE public.people p
8
+ SET custom_fields = jsonb_strip_nulls(jsonb_build_object(
9
+ 'serviceProviderType', nullif(p.metadata #>> '{contact,service_provider_type}', ''),
10
+ 'registrationNumber', nullif(p.metadata #>> '{legal,state_registration}', '')
11
+ )) || p.custom_fields
12
+ WHERE p.kind = 'supplier'
13
+ AND (
14
+ (NOT (p.custom_fields ? 'serviceProviderType') AND nullif(p.metadata #>> '{contact,service_provider_type}', '') IS NOT NULL)
15
+ OR
16
+ (NOT (p.custom_fields ? 'registrationNumber') AND nullif(p.metadata #>> '{legal,state_registration}', '') IS NOT NULL)
17
+ );
18
+
19
+ -- Suppliers imported before the normalized address book kept one legacy main
20
+ -- address on people. Copy complete addresses once; partial addresses remain on
21
+ -- the Person instead of inventing required postal/city/state values.
22
+ INSERT INTO public.addresses (
23
+ tenant_id, owner_type, owner_id, kind, label, recipient, phone, postal_code,
24
+ street, number, complement, district, city, state, country, is_default, metadata
25
+ )
26
+ SELECT p.tenant_id, 'person', p.id, 'both', 'Principal', p.name, p.phone,
27
+ p.postal_code, p.address,
28
+ nullif(p.metadata #>> '{address,number}', ''),
29
+ nullif(p.metadata #>> '{address,complement}', ''),
30
+ nullif(p.metadata #>> '{address,neighborhood}', ''),
31
+ p.city, p.state, coalesce(nullif(p.country, ''), 'BR'), true,
32
+ jsonb_build_object('source', 'people_legacy_address')
33
+ FROM public.people p
34
+ WHERE p.kind = 'supplier'
35
+ AND nullif(p.postal_code, '') IS NOT NULL
36
+ AND nullif(p.address, '') IS NOT NULL
37
+ AND nullif(p.city, '') IS NOT NULL
38
+ AND nullif(p.state, '') IS NOT NULL
39
+ AND NOT EXISTS (
40
+ SELECT 1 FROM public.addresses a
41
+ WHERE a.owner_type = 'person' AND a.owner_id = p.id
42
+ );
43
+
44
+ CREATE OR REPLACE FUNCTION public.people_available_unit_ids(p_person uuid)
45
+ RETURNS uuid[]
46
+ LANGUAGE plpgsql
47
+ STABLE
48
+ SECURITY DEFINER
49
+ SET search_path TO ''
50
+ AS $$
51
+ DECLARE
52
+ v_tenant uuid := public.people_assert_scope('people.manage');
53
+ v_units uuid[];
54
+ BEGIN
55
+ IF NOT EXISTS (
56
+ SELECT 1 FROM public.people p
57
+ WHERE p.tenant_id = v_tenant AND p.id = p_person AND p.merged_into_id IS NULL
58
+ ) THEN
59
+ RAISE EXCEPTION 'people_available_unit_ids: person not found in this tenant'
60
+ USING ERRCODE = 'P0002';
61
+ END IF;
62
+
63
+ SELECT coalesce(array_agg(g.subject_id ORDER BY g.subject_id), '{}'::uuid[])
64
+ INTO v_units
65
+ FROM app.resource_grants g
66
+ WHERE g.tenant_id = v_tenant
67
+ AND g.resource_type = 'people.person'
68
+ AND g.resource_id = p_person
69
+ AND g.relation = 'available_in'
70
+ AND g.subject_type = 'unit';
71
+
72
+ RETURN v_units;
73
+ END $$;
74
+
75
+ CREATE OR REPLACE FUNCTION public.people_set_available_units(
76
+ p_person uuid,
77
+ p_unit_ids uuid[] DEFAULT '{}'::uuid[]
78
+ )
79
+ RETURNS uuid[]
80
+ LANGUAGE plpgsql
81
+ SECURITY DEFINER
82
+ SET search_path TO ''
83
+ AS $$
84
+ DECLARE
85
+ v_tenant uuid := public.people_assert_scope('people.manage');
86
+ v_unit_ids uuid[];
87
+ BEGIN
88
+ IF NOT EXISTS (
89
+ SELECT 1 FROM public.people p
90
+ WHERE p.tenant_id = v_tenant AND p.id = p_person AND p.merged_into_id IS NULL
91
+ ) THEN
92
+ RAISE EXCEPTION 'people_set_available_units: person not found in this tenant'
93
+ USING ERRCODE = 'P0002';
94
+ END IF;
95
+
96
+ SELECT coalesce(array_agg(DISTINCT requested.unit_id ORDER BY requested.unit_id), '{}'::uuid[])
97
+ INTO v_unit_ids
98
+ FROM unnest(coalesce(p_unit_ids, '{}'::uuid[])) AS requested(unit_id);
99
+
100
+ IF EXISTS (
101
+ SELECT 1
102
+ FROM unnest(v_unit_ids) AS requested(unit_id)
103
+ LEFT JOIN app.units u
104
+ ON u.id = requested.unit_id AND u.tenant_id = v_tenant
105
+ WHERE u.id IS NULL OR NOT app.has_unit(requested.unit_id)
106
+ ) THEN
107
+ RAISE EXCEPTION 'people_set_available_units: unit not found in this tenant'
108
+ USING ERRCODE = '42501';
109
+ END IF;
110
+
111
+ DELETE FROM app.resource_grants g
112
+ WHERE g.tenant_id = v_tenant
113
+ AND g.resource_type = 'people.person'
114
+ AND g.resource_id = p_person
115
+ AND g.relation = 'available_in'
116
+ AND g.subject_type = 'unit';
117
+
118
+ INSERT INTO app.resource_grants (
119
+ tenant_id, resource_type, resource_id, relation, subject_type, subject_id, created_by
120
+ )
121
+ SELECT v_tenant, 'people.person', p_person, 'available_in', 'unit', unit_id, auth.uid()
122
+ FROM unnest(v_unit_ids) AS requested(unit_id);
123
+
124
+ RETURN v_unit_ids;
125
+ END $$;
126
+
127
+ REVOKE ALL ON FUNCTION public.people_available_unit_ids(uuid) FROM PUBLIC;
128
+ REVOKE ALL ON FUNCTION public.people_set_available_units(uuid, uuid[]) FROM PUBLIC;
129
+ GRANT EXECUTE ON FUNCTION public.people_available_unit_ids(uuid) TO authenticated, service_role;
130
+ GRANT EXECUTE ON FUNCTION public.people_set_available_units(uuid, uuid[]) TO authenticated, service_role;
131
+
132
+ COMMENT ON FUNCTION public.people_available_unit_ids(uuid) IS
133
+ 'Units where a Person record is available. Empty means organization-wide.';
134
+ COMMENT ON FUNCTION public.people_set_available_units(uuid, uuid[]) IS
135
+ 'Replaces the unit availability of a Person. Empty means organization-wide.';
136
+
137
+ -- PostgREST may keep serving the previous function catalog after DDL. The
138
+ -- explicit notification makes the new RPCs available as soon as this migration
139
+ -- commits instead of waiting for a later cache refresh.
140
+ NOTIFY pgrst, 'reload schema';
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "fayz": {
4
4
  "status": "beta"
5
5
  },
6
- "version": "0.18.1",
6
+ "version": "0.18.2",
7
7
  "description": "Fayz SDK database layer — Drizzle schema primitives, spine references, and migration helpers shared across plugins.",
8
8
  "type": "module",
9
9
  "sideEffects": false,