@fayz-ai/db 0.23.1 → 0.24.0
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,28 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- 196 — FullControl becomes GoControl
|
|
3
|
+
--
|
|
4
|
+
-- The general-purpose ERP of the Control family (app id `full`) ships under a
|
|
5
|
+
-- new name, on its own domain: https://gocontrol.app. The app's own bundle
|
|
6
|
+
-- (FayaLabs/control-app) carries the new wordmark; this row is what every OTHER
|
|
7
|
+
-- surface reads — the switcher between a tenant's apps, the module catalog's
|
|
8
|
+
-- "Extends: …" line, the theme catalog.
|
|
9
|
+
--
|
|
10
|
+
-- The url also moves. It pointed at the `full-control` Worker, a build nobody
|
|
11
|
+
-- has deployed since 2026-09-20, so the switcher sent people to a version three
|
|
12
|
+
-- weeks old. gocontrol.app serves what control-app's CI ships.
|
|
13
|
+
--
|
|
14
|
+
-- The id stays `full`: it keys tenant_apps, presets and every grant, and a
|
|
15
|
+
-- product rename is not a reason to move a primary key.
|
|
16
|
+
--
|
|
17
|
+
-- Idempotent.
|
|
18
|
+
-- ============================================================================
|
|
19
|
+
|
|
20
|
+
UPDATE app.apps
|
|
21
|
+
SET name = 'GoControl',
|
|
22
|
+
url = 'https://gocontrol.app',
|
|
23
|
+
theme = CASE WHEN theme ? 'name' THEN jsonb_set(theme, '{name}', '"GoControl"') ELSE theme END,
|
|
24
|
+
updated_at = now()
|
|
25
|
+
WHERE id = 'full'
|
|
26
|
+
AND (name IS DISTINCT FROM 'GoControl'
|
|
27
|
+
OR url IS DISTINCT FROM 'https://gocontrol.app'
|
|
28
|
+
OR theme ->> 'name' IS DISTINCT FROM 'GoControl');
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- 197 — a unit has its own currency
|
|
3
|
+
--
|
|
4
|
+
-- One company, two shops: Rio sells in BRL, Barcelona in EUR. The `currency`
|
|
5
|
+
-- setting already existed, but only for the whole tenant, and nothing read it:
|
|
6
|
+
-- every order was born 'BRL' from a column default.
|
|
7
|
+
--
|
|
8
|
+
-- The currency is now a UNIT setting (the tenant's value stays the fallback,
|
|
9
|
+
-- which is what "the head office's currency" means), and an order with no
|
|
10
|
+
-- currency takes its unit's. A document keeps the currency it was written in:
|
|
11
|
+
-- changing the shop's currency later does not rewrite what was already sold.
|
|
12
|
+
--
|
|
13
|
+
-- A price list already carries its currency, so "one price per currency" is a
|
|
14
|
+
-- price list per currency. What it could not do was price a variant, and a
|
|
15
|
+
-- shirt is sold by size.
|
|
16
|
+
-- ============================================================================
|
|
17
|
+
|
|
18
|
+
SELECT app.seed_setting_keys($$[
|
|
19
|
+
{ "key": "currency", "type": "string", "default": "BRL", "scope": "unit",
|
|
20
|
+
"description": "Currency this unit sells, buys and keeps its books in (ISO 4217). The tenant's value is the default for every unit." }
|
|
21
|
+
]$$::jsonb);
|
|
22
|
+
|
|
23
|
+
CREATE OR REPLACE FUNCTION app.unit_currency(p_tenant uuid, p_unit uuid)
|
|
24
|
+
RETURNS text
|
|
25
|
+
LANGUAGE sql
|
|
26
|
+
STABLE SECURITY DEFINER
|
|
27
|
+
SET search_path TO ''
|
|
28
|
+
AS $function$
|
|
29
|
+
SELECT upper(coalesce(nullif(btrim(app.unit_setting_value(p_tenant, p_unit, 'currency') #>> '{}'), ''), 'BRL'));
|
|
30
|
+
$function$;
|
|
31
|
+
-- Takes the tenant as an argument, so it is not a client door.
|
|
32
|
+
REVOKE ALL ON FUNCTION app.unit_currency(uuid, uuid) FROM PUBLIC, anon, authenticated;
|
|
33
|
+
GRANT EXECUTE ON FUNCTION app.unit_currency(uuid, uuid) TO service_role;
|
|
34
|
+
|
|
35
|
+
-- What the signed-in user asks: "which currency is this unit of mine in?"
|
|
36
|
+
CREATE OR REPLACE FUNCTION public.my_unit_currency(p_unit uuid DEFAULT NULL)
|
|
37
|
+
RETURNS text
|
|
38
|
+
LANGUAGE sql
|
|
39
|
+
STABLE SECURITY DEFINER
|
|
40
|
+
SET search_path TO ''
|
|
41
|
+
AS $function$
|
|
42
|
+
SELECT app.unit_currency(app.current_tenant_id(), p_unit);
|
|
43
|
+
$function$;
|
|
44
|
+
REVOKE ALL ON FUNCTION public.my_unit_currency(uuid) FROM PUBLIC, anon;
|
|
45
|
+
GRANT EXECUTE ON FUNCTION public.my_unit_currency(uuid) TO authenticated, service_role;
|
|
46
|
+
|
|
47
|
+
-- ── an order is born in its unit's currency ────────────────────────────────
|
|
48
|
+
ALTER TABLE public.orders ALTER COLUMN currency DROP DEFAULT;
|
|
49
|
+
|
|
50
|
+
CREATE OR REPLACE FUNCTION public.trg_orders_currency_default()
|
|
51
|
+
RETURNS trigger
|
|
52
|
+
LANGUAGE plpgsql
|
|
53
|
+
SECURITY DEFINER
|
|
54
|
+
SET search_path TO ''
|
|
55
|
+
AS $function$
|
|
56
|
+
BEGIN
|
|
57
|
+
IF nullif(btrim(coalesce(NEW.currency, '')), '') IS NULL THEN
|
|
58
|
+
NEW.currency := app.unit_currency(NEW.tenant_id, NEW.unit_id);
|
|
59
|
+
ELSE
|
|
60
|
+
NEW.currency := upper(btrim(NEW.currency));
|
|
61
|
+
END IF;
|
|
62
|
+
RETURN NEW;
|
|
63
|
+
END $function$;
|
|
64
|
+
|
|
65
|
+
-- "zz": BEFORE triggers fire in name order, and the unit is stamped by
|
|
66
|
+
-- `orders_scope_stamp`. This one has to read the unit after that.
|
|
67
|
+
DROP TRIGGER IF EXISTS orders_zz_currency_default ON public.orders;
|
|
68
|
+
CREATE TRIGGER orders_zz_currency_default
|
|
69
|
+
BEFORE INSERT ON public.orders
|
|
70
|
+
FOR EACH ROW EXECUTE FUNCTION public.trg_orders_currency_default();
|
|
71
|
+
|
|
72
|
+
-- ── a price list can price a variant ───────────────────────────────────────
|
|
73
|
+
ALTER TABLE public.price_table_items
|
|
74
|
+
ADD COLUMN IF NOT EXISTS variant_id uuid REFERENCES public.product_variants(id) ON DELETE CASCADE;
|
|
75
|
+
COMMENT ON COLUMN public.price_table_items.variant_id IS
|
|
76
|
+
'Price for one variant when it differs; NULL = the price of the whole product.';
|
|
77
|
+
|
|
78
|
+
-- Same constraint, one more column: NULLS NOT DISTINCT keeps "one product-wide
|
|
79
|
+
-- price per quantity break" unique, which a plain UNIQUE would stop enforcing.
|
|
80
|
+
DO $$
|
|
81
|
+
BEGIN
|
|
82
|
+
IF NOT EXISTS (
|
|
83
|
+
SELECT 1 FROM pg_constraint c
|
|
84
|
+
WHERE c.conrelid = 'public.price_table_items'::regclass AND c.conname = 'price_table_items_unique'
|
|
85
|
+
AND pg_get_constraintdef(c.oid) LIKE '%variant_id%') THEN
|
|
86
|
+
ALTER TABLE public.price_table_items DROP CONSTRAINT IF EXISTS price_table_items_unique;
|
|
87
|
+
ALTER TABLE public.price_table_items
|
|
88
|
+
ADD CONSTRAINT price_table_items_unique
|
|
89
|
+
UNIQUE NULLS NOT DISTINCT (price_table_id, product_id, variant_id, min_qty);
|
|
90
|
+
END IF;
|
|
91
|
+
END $$;
|
|
92
|
+
CREATE INDEX IF NOT EXISTS price_table_items_variant_idx
|
|
93
|
+
ON public.price_table_items (tenant_id, variant_id) WHERE variant_id IS NOT NULL;
|
package/package.json
CHANGED