@fayz-ai/plugin-financial 0.2.0 → 0.8.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.
Files changed (53) hide show
  1. package/README.md +2 -0
  2. package/dist/FinancialContext.d.ts +6 -0
  3. package/dist/FinancialContext.d.ts.map +1 -1
  4. package/dist/FinancialPage.d.ts.map +1 -1
  5. package/dist/components/FinancialGeneralSettings.d.ts.map +1 -1
  6. package/dist/data/supabase.d.ts.map +1 -1
  7. package/dist/data/tables.d.ts +11 -0
  8. package/dist/data/tables.d.ts.map +1 -0
  9. package/dist/index.cjs +345 -81
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.ts +31 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +347 -84
  14. package/dist/index.js.map +1 -1
  15. package/dist/locales/en.d.ts.map +1 -1
  16. package/dist/locales/pt-BR.d.ts.map +1 -1
  17. package/dist/migrations/index.d.ts +18 -0
  18. package/dist/migrations/index.d.ts.map +1 -0
  19. package/dist/registries.d.ts.map +1 -1
  20. package/dist/views/CardsView.d.ts.map +1 -1
  21. package/dist/views/InvoiceFormView.d.ts.map +1 -1
  22. package/dist/views/InvoiceListView.d.ts.map +1 -1
  23. package/dist/views/QuickTransactionForm.d.ts.map +1 -1
  24. package/package.json +10 -4
  25. package/src/FinancialContext.tsx +6 -0
  26. package/src/FinancialPage.tsx +56 -12
  27. package/src/README.md +5 -5
  28. package/src/components/FinancialGeneralSettings.tsx +21 -8
  29. package/src/contract.test.ts +1 -1
  30. package/src/data/supabase.ts +56 -48
  31. package/src/data/tables.ts +12 -0
  32. package/src/index.ts +128 -0
  33. package/src/locales/en.ts +10 -0
  34. package/src/locales/pt-BR.ts +10 -0
  35. package/src/migrations/000_plg_rename.sql +30 -0
  36. package/src/migrations/001_financial_base.sql +30 -30
  37. package/src/migrations/002_chart_of_accounts.sql +10 -10
  38. package/src/migrations/003_card_brands.sql +4 -4
  39. package/src/migrations/004_order_to_cash.sql +43 -29
  40. package/src/migrations/005_seed_defaults.sql +6 -6
  41. package/src/migrations/006_rls_policies.sql +5 -5
  42. package/src/migrations/{006_extract_fee_amount.sql → 006b_extract_fee_amount.sql} +4 -4
  43. package/src/migrations/007_reconciliation.sql +9 -9
  44. package/src/migrations/007b_movement_payment_method_type.sql +6 -0
  45. package/src/migrations/008_split_payment_movements.sql +5 -5
  46. package/src/migrations/009_agent_rpcs.sql +165 -0
  47. package/src/migrations/010_invoice_internal.sql +133 -0
  48. package/src/migrations/index.ts +862 -0
  49. package/src/registries.ts +8 -7
  50. package/src/views/CardsView.tsx +12 -9
  51. package/src/views/InvoiceFormView.tsx +10 -0
  52. package/src/views/InvoiceListView.tsx +98 -9
  53. package/src/views/QuickTransactionForm.tsx +11 -0
@@ -1,9 +1,9 @@
1
1
  -- Financial Plugin: Base Tables
2
2
  -- Pattern: uuid PK, tenant_id FK, timestamps, RLS via project_rls.sql auto-detection
3
3
 
4
- CREATE TABLE IF NOT EXISTS public.payment_method_types (
4
+ CREATE TABLE IF NOT EXISTS public.plg_financial_payment_method_types (
5
5
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
6
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
6
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
7
7
  name text NOT NULL,
8
8
  transaction_type text NOT NULL,
9
9
  is_active boolean NOT NULL DEFAULT true,
@@ -11,14 +11,14 @@ CREATE TABLE IF NOT EXISTS public.payment_method_types (
11
11
  created_at timestamptz NOT NULL DEFAULT now(),
12
12
  updated_at timestamptz NOT NULL DEFAULT now()
13
13
  );
14
- ALTER TABLE public.payment_method_types ENABLE ROW LEVEL SECURITY;
15
- CREATE INDEX IF NOT EXISTS idx_payment_method_types_tenant ON public.payment_method_types(tenant_id);
14
+ ALTER TABLE public.plg_financial_payment_method_types ENABLE ROW LEVEL SECURITY;
15
+ CREATE INDEX IF NOT EXISTS idx_plg_financial_payment_method_types_tenant ON public.plg_financial_payment_method_types(tenant_id);
16
16
 
17
- CREATE TABLE IF NOT EXISTS public.payment_methods (
17
+ CREATE TABLE IF NOT EXISTS public.plg_financial_payment_methods (
18
18
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
19
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
19
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
20
20
  name text NOT NULL,
21
- payment_method_type_id uuid REFERENCES public.payment_method_types(id),
21
+ payment_method_type_id uuid REFERENCES public.plg_financial_payment_method_types(id),
22
22
  is_active boolean NOT NULL DEFAULT true,
23
23
  discount_mode text,
24
24
  discount_value numeric(12,2) DEFAULT 0,
@@ -31,12 +31,12 @@ CREATE TABLE IF NOT EXISTS public.payment_methods (
31
31
  created_at timestamptz NOT NULL DEFAULT now(),
32
32
  updated_at timestamptz NOT NULL DEFAULT now()
33
33
  );
34
- ALTER TABLE public.payment_methods ENABLE ROW LEVEL SECURITY;
35
- CREATE INDEX IF NOT EXISTS idx_payment_methods_tenant ON public.payment_methods(tenant_id);
34
+ ALTER TABLE public.plg_financial_payment_methods ENABLE ROW LEVEL SECURITY;
35
+ CREATE INDEX IF NOT EXISTS idx_plg_financial_payment_methods_tenant ON public.plg_financial_payment_methods(tenant_id);
36
36
 
37
- CREATE TABLE IF NOT EXISTS public.bank_accounts (
37
+ CREATE TABLE IF NOT EXISTS public.plg_financial_bank_accounts (
38
38
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
39
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
39
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
40
40
  name text NOT NULL,
41
41
  account_type text NOT NULL DEFAULT 'bank_account',
42
42
  bank_name text,
@@ -52,13 +52,13 @@ CREATE TABLE IF NOT EXISTS public.bank_accounts (
52
52
  created_at timestamptz NOT NULL DEFAULT now(),
53
53
  updated_at timestamptz NOT NULL DEFAULT now()
54
54
  );
55
- ALTER TABLE public.bank_accounts ENABLE ROW LEVEL SECURITY;
56
- CREATE INDEX IF NOT EXISTS idx_bank_accounts_tenant ON public.bank_accounts(tenant_id);
55
+ ALTER TABLE public.plg_financial_bank_accounts ENABLE ROW LEVEL SECURITY;
56
+ CREATE INDEX IF NOT EXISTS idx_plg_financial_bank_accounts_tenant ON public.plg_financial_bank_accounts(tenant_id);
57
57
 
58
- CREATE TABLE IF NOT EXISTS public.cash_register_sessions (
58
+ CREATE TABLE IF NOT EXISTS public.plg_financial_cash_register_sessions (
59
59
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
60
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
61
- bank_account_id uuid NOT NULL REFERENCES public.bank_accounts(id),
60
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
61
+ bank_account_id uuid NOT NULL REFERENCES public.plg_financial_bank_accounts(id),
62
62
  status text NOT NULL DEFAULT 'open',
63
63
  opened_at timestamptz NOT NULL DEFAULT now(),
64
64
  opened_by_user_id uuid,
@@ -75,15 +75,15 @@ CREATE TABLE IF NOT EXISTS public.cash_register_sessions (
75
75
  created_at timestamptz NOT NULL DEFAULT now(),
76
76
  updated_at timestamptz NOT NULL DEFAULT now()
77
77
  );
78
- ALTER TABLE public.cash_register_sessions ENABLE ROW LEVEL SECURITY;
79
- CREATE INDEX IF NOT EXISTS idx_cash_register_sessions_tenant ON public.cash_register_sessions(tenant_id);
78
+ ALTER TABLE public.plg_financial_cash_register_sessions ENABLE ROW LEVEL SECURITY;
79
+ CREATE INDEX IF NOT EXISTS idx_plg_financial_cash_register_sessions_tenant ON public.plg_financial_cash_register_sessions(tenant_id);
80
80
 
81
81
  -- Financial movements (installments, payments) — plugin-specific, not archetype
82
- -- Invoices themselves use saas_core.orders (kind='invoice_payable'/'invoice_receivable')
83
- CREATE TABLE IF NOT EXISTS public.financial_movements (
82
+ -- Invoices themselves use public.orders (kind='invoice_payable'/'invoice_receivable')
83
+ CREATE TABLE IF NOT EXISTS public.plg_financial_movements (
84
84
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
85
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
86
- invoice_id uuid REFERENCES saas_core.orders(id),
85
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
86
+ invoice_id uuid REFERENCES public.orders(id),
87
87
  direction text NOT NULL,
88
88
  movement_kind text NOT NULL,
89
89
  amount numeric(14,2) NOT NULL,
@@ -92,9 +92,9 @@ CREATE TABLE IF NOT EXISTS public.financial_movements (
92
92
  due_date date NOT NULL,
93
93
  payment_date date,
94
94
  installment_number integer,
95
- payment_method_id uuid REFERENCES public.payment_methods(id),
96
- bank_account_id uuid REFERENCES public.bank_accounts(id),
97
- cash_session_id uuid REFERENCES public.cash_register_sessions(id),
95
+ payment_method_id uuid REFERENCES public.plg_financial_payment_methods(id),
96
+ bank_account_id uuid REFERENCES public.plg_financial_bank_accounts(id),
97
+ cash_session_id uuid REFERENCES public.plg_financial_cash_register_sessions(id),
98
98
  card_brand text,
99
99
  card_installments integer,
100
100
  notes text,
@@ -102,8 +102,8 @@ CREATE TABLE IF NOT EXISTS public.financial_movements (
102
102
  created_at timestamptz NOT NULL DEFAULT now(),
103
103
  updated_at timestamptz NOT NULL DEFAULT now()
104
104
  );
105
- ALTER TABLE public.financial_movements ENABLE ROW LEVEL SECURITY;
106
- CREATE INDEX IF NOT EXISTS idx_financial_movements_tenant ON public.financial_movements(tenant_id);
107
- CREATE INDEX IF NOT EXISTS idx_financial_movements_invoice ON public.financial_movements(invoice_id);
108
- CREATE INDEX IF NOT EXISTS idx_financial_movements_due ON public.financial_movements(tenant_id, due_date);
109
- CREATE INDEX IF NOT EXISTS idx_financial_movements_bank ON public.financial_movements(bank_account_id);
105
+ ALTER TABLE public.plg_financial_movements ENABLE ROW LEVEL SECURITY;
106
+ CREATE INDEX IF NOT EXISTS idx_plg_financial_movements_tenant ON public.plg_financial_movements(tenant_id);
107
+ CREATE INDEX IF NOT EXISTS idx_plg_financial_movements_invoice ON public.plg_financial_movements(invoice_id);
108
+ CREATE INDEX IF NOT EXISTS idx_plg_financial_movements_due ON public.plg_financial_movements(tenant_id, due_date);
109
+ CREATE INDEX IF NOT EXISTS idx_plg_financial_movements_bank ON public.plg_financial_movements(bank_account_id);
@@ -1,28 +1,28 @@
1
1
  -- Financial Plugin: Chart of Accounts + Cost Centers
2
2
 
3
- CREATE TABLE IF NOT EXISTS public.chart_of_accounts (
3
+ CREATE TABLE IF NOT EXISTS public.plg_financial_chart_of_accounts (
4
4
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
5
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
5
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
6
6
  code text NOT NULL,
7
7
  name text NOT NULL,
8
8
  node_type text NOT NULL DEFAULT 'leaf',
9
- parent_id uuid REFERENCES public.chart_of_accounts(id),
9
+ parent_id uuid REFERENCES public.plg_financial_chart_of_accounts(id),
10
10
  is_active boolean NOT NULL DEFAULT true,
11
11
  created_at timestamptz NOT NULL DEFAULT now(),
12
12
  updated_at timestamptz NOT NULL DEFAULT now()
13
13
  );
14
- ALTER TABLE public.chart_of_accounts ENABLE ROW LEVEL SECURITY;
15
- CREATE INDEX IF NOT EXISTS idx_chart_of_accounts_tenant ON public.chart_of_accounts(tenant_id);
16
- CREATE INDEX IF NOT EXISTS idx_chart_of_accounts_parent ON public.chart_of_accounts(parent_id);
14
+ ALTER TABLE public.plg_financial_chart_of_accounts ENABLE ROW LEVEL SECURITY;
15
+ CREATE INDEX IF NOT EXISTS idx_plg_financial_chart_of_accounts_tenant ON public.plg_financial_chart_of_accounts(tenant_id);
16
+ CREATE INDEX IF NOT EXISTS idx_plg_financial_chart_of_accounts_parent ON public.plg_financial_chart_of_accounts(parent_id);
17
17
 
18
- CREATE TABLE IF NOT EXISTS public.cost_centers (
18
+ CREATE TABLE IF NOT EXISTS public.plg_financial_cost_centers (
19
19
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
20
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
20
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
21
21
  code text NOT NULL,
22
22
  name text NOT NULL,
23
23
  is_active boolean NOT NULL DEFAULT true,
24
24
  created_at timestamptz NOT NULL DEFAULT now(),
25
25
  updated_at timestamptz NOT NULL DEFAULT now()
26
26
  );
27
- ALTER TABLE public.cost_centers ENABLE ROW LEVEL SECURITY;
28
- CREATE INDEX IF NOT EXISTS idx_cost_centers_tenant ON public.cost_centers(tenant_id);
27
+ ALTER TABLE public.plg_financial_cost_centers ENABLE ROW LEVEL SECURITY;
28
+ CREATE INDEX IF NOT EXISTS idx_plg_financial_cost_centers_tenant ON public.plg_financial_cost_centers(tenant_id);
@@ -1,12 +1,12 @@
1
1
  -- Financial Plugin: Card Brands
2
2
 
3
- CREATE TABLE IF NOT EXISTS public.card_brands (
3
+ CREATE TABLE IF NOT EXISTS public.plg_financial_card_brands (
4
4
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
5
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
5
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
6
6
  name text NOT NULL,
7
7
  is_active boolean NOT NULL DEFAULT true,
8
8
  created_at timestamptz NOT NULL DEFAULT now(),
9
9
  updated_at timestamptz NOT NULL DEFAULT now()
10
10
  );
11
- ALTER TABLE public.card_brands ENABLE ROW LEVEL SECURITY;
12
- CREATE INDEX IF NOT EXISTS idx_card_brands_tenant ON public.card_brands(tenant_id);
11
+ ALTER TABLE public.plg_financial_card_brands ENABLE ROW LEVEL SECURITY;
12
+ CREATE INDEX IF NOT EXISTS idx_plg_financial_card_brands_tenant ON public.plg_financial_card_brands(tenant_id);
@@ -1,45 +1,59 @@
1
1
  -- Financial plugin — order-to-cash companion SQL.
2
2
  -- The atomic "promote an order into an invoice + installment ledger" function
3
3
  -- that CRM (approveQuote) and the agenda financial bridge both RPC. It belongs
4
- -- to the financial plugin (it writes financial_movements + reads orders), and it
4
+ -- to the financial plugin (it writes plg_financial_movements + reads orders), and it
5
5
  -- ships WITH the plugin so enabling Payments provisions it. Idempotent.
6
- -- Depends on: saas_core.orders, public.financial_movements (001_financial_base).
6
+ -- Depends on: public.orders, public.plg_financial_movements (001_financial_base).
7
7
 
8
8
  -- 1. Sequence generator (REC-00001 / PAG-00001 numbering)
9
- CREATE TABLE IF NOT EXISTS saas_core.sequences (
10
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id),
9
+ CREATE TABLE IF NOT EXISTS public.sequences (
10
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id),
11
11
  kind text NOT NULL,
12
12
  current_value bigint NOT NULL DEFAULT 0,
13
13
  PRIMARY KEY (tenant_id, kind)
14
14
  );
15
- CREATE OR REPLACE FUNCTION saas_core.next_sequence(p_tenant_id uuid, p_kind text)
16
- RETURNS bigint LANGUAGE plpgsql AS $$
15
+ -- Baseline: RLS ON, deny-all (no policies), like the salon pool. The counter is
16
+ -- an internal, non-API table written ONLY through next_sequence() below, which is
17
+ -- SECURITY DEFINER and so bypasses RLS as the table owner. Legacy pools that
18
+ -- shipped this table without RLS are also remediated by packages/db 013_sequences_rls.sql.
19
+ ALTER TABLE public.sequences ENABLE ROW LEVEL SECURITY;
20
+
21
+ -- SECURITY DEFINER: next_sequence is called directly via PostgREST RPC by
22
+ -- authenticated users (src/data/supabase.ts → createInvoice), so with deny-all
23
+ -- RLS on public.sequences it MUST run as the table owner to write the counter.
24
+ -- SET search_path pins schema resolution for the definer context.
25
+ CREATE OR REPLACE FUNCTION public.next_sequence(p_tenant_id uuid, p_kind text)
26
+ RETURNS bigint
27
+ LANGUAGE plpgsql
28
+ SECURITY DEFINER
29
+ SET search_path = public
30
+ AS $$
17
31
  DECLARE v_next bigint;
18
32
  BEGIN
19
- INSERT INTO saas_core.sequences (tenant_id, kind, current_value)
33
+ INSERT INTO public.sequences (tenant_id, kind, current_value)
20
34
  VALUES (p_tenant_id, p_kind, 1)
21
35
  ON CONFLICT (tenant_id, kind)
22
- DO UPDATE SET current_value = saas_core.sequences.current_value + 1
36
+ DO UPDATE SET current_value = public.sequences.current_value + 1
23
37
  RETURNING current_value INTO v_next;
24
38
  RETURN v_next;
25
39
  END;
26
40
  $$;
27
41
 
28
- -- 2. Order-to-cash columns on the spine orders (plugin extends saas_core.orders)
29
- ALTER TABLE saas_core.orders
42
+ -- 2. Order-to-cash columns on the spine orders (plugin extends public.orders)
43
+ ALTER TABLE public.orders
30
44
  ADD COLUMN IF NOT EXISTS stage text,
31
45
  ADD COLUMN IF NOT EXISTS direction text;
32
46
 
33
- -- 3. RLS + grants for financial_movements (table from 001_financial_base.sql)
34
- ALTER TABLE public.financial_movements ENABLE ROW LEVEL SECURITY;
35
- GRANT SELECT, INSERT, UPDATE, DELETE ON public.financial_movements TO authenticated;
47
+ -- 3. RLS + grants for plg_financial_movements (table from 001_financial_base.sql)
48
+ ALTER TABLE public.plg_financial_movements ENABLE ROW LEVEL SECURITY;
49
+ GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_financial_movements TO authenticated;
36
50
  DO $$
37
51
  BEGIN
38
- IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='financial_movements' AND policyname='financial_movements_select') THEN
39
- CREATE POLICY financial_movements_select ON public.financial_movements FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
40
- CREATE POLICY financial_movements_insert ON public.financial_movements FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
41
- CREATE POLICY financial_movements_update ON public.financial_movements FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
42
- CREATE POLICY financial_movements_delete ON public.financial_movements FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
52
+ IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='plg_financial_movements' AND policyname='plg_financial_movements_select') THEN
53
+ CREATE POLICY plg_financial_movements_select ON public.plg_financial_movements FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
54
+ CREATE POLICY plg_financial_movements_insert ON public.plg_financial_movements FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
55
+ CREATE POLICY plg_financial_movements_update ON public.plg_financial_movements FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
56
+ CREATE POLICY plg_financial_movements_delete ON public.plg_financial_movements FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
43
57
  END IF;
44
58
  END $$;
45
59
 
@@ -54,10 +68,10 @@ CREATE OR REPLACE FUNCTION public.fn_invoice_from_order(
54
68
  ) RETURNS uuid
55
69
  LANGUAGE plpgsql
56
70
  SECURITY DEFINER
57
- SET search_path = public, saas_core
71
+ SET search_path = public
58
72
  AS $$
59
73
  DECLARE
60
- v_order saas_core.orders%ROWTYPE;
74
+ v_order public.orders%ROWTYPE;
61
75
  v_ref text;
62
76
  v_seq bigint;
63
77
  v_summary text;
@@ -73,27 +87,27 @@ DECLARE
73
87
  v_due date;
74
88
  i int;
75
89
  BEGIN
76
- SELECT * INTO v_order FROM saas_core.orders WHERE id = p_order_id;
90
+ SELECT * INTO v_order FROM public.orders WHERE id = p_order_id;
77
91
  IF NOT FOUND THEN RAISE EXCEPTION 'fn_invoice_from_order: order % not found', p_order_id; END IF;
78
92
  IF v_order.tenant_id NOT IN (SELECT public.user_tenant_ids()) THEN
79
93
  RAISE EXCEPTION 'fn_invoice_from_order: forbidden';
80
94
  END IF;
81
95
 
82
- SELECT id INTO v_existing FROM public.financial_movements
96
+ SELECT id INTO v_existing FROM public.plg_financial_movements
83
97
  WHERE invoice_id = p_order_id AND movement_kind = 'bill' LIMIT 1;
84
98
  IF v_existing IS NOT NULL THEN RETURN p_order_id; END IF;
85
99
 
86
100
  IF p_direction = 'debit' THEN v_kind := 'invoice_payable'; v_prefix := 'PAG';
87
101
  ELSE v_kind := 'invoice_receivable'; v_prefix := 'REC'; END IF;
88
102
 
89
- SELECT saas_core.next_sequence(v_order.tenant_id, v_kind) INTO v_seq;
103
+ SELECT public.next_sequence(v_order.tenant_id, v_kind) INTO v_seq;
90
104
  v_ref := v_prefix || '-' || lpad(v_seq::text, 5, '0');
91
105
 
92
106
  SELECT string_agg(name, ', ' ORDER BY sort_order) INTO v_summary
93
- FROM saas_core.order_items WHERE order_id = p_order_id;
94
- SELECT name INTO v_contact FROM saas_core.persons WHERE id = v_order.party_id;
107
+ FROM public.order_items WHERE order_id = p_order_id;
108
+ SELECT name INTO v_contact FROM public.people WHERE id = v_order.party_id;
95
109
 
96
- UPDATE saas_core.orders SET
110
+ UPDATE public.orders SET
97
111
  status = COALESCE(p_status, status),
98
112
  stage = 'invoiced',
99
113
  direction = p_direction,
@@ -114,7 +128,7 @@ BEGIN
114
128
  FOR i IN 1..v_n LOOP
115
129
  IF i = v_n THEN v_amt := round(v_total - v_alloc, 2);
116
130
  ELSE v_amt := v_base; v_alloc := v_alloc + v_base; END IF;
117
- INSERT INTO public.financial_movements
131
+ INSERT INTO public.plg_financial_movements
118
132
  (tenant_id, invoice_id, direction, movement_kind, amount, paid_amount, status, due_date, installment_number)
119
133
  VALUES
120
134
  (v_order.tenant_id, p_order_id, p_direction, 'bill', v_amt, 0, 'pending',
@@ -134,7 +148,7 @@ WITH bill AS (
134
148
  SUM(amount) FILTER (WHERE status <> 'cancelled') AS billed,
135
149
  SUM(paid_amount) FILTER (WHERE status <> 'cancelled') AS paid,
136
150
  bool_or(status NOT IN ('paid','cancelled') AND due_date < current_date) AS has_overdue
137
- FROM public.financial_movements
151
+ FROM public.plg_financial_movements
138
152
  WHERE movement_kind = 'bill'
139
153
  GROUP BY invoice_id
140
154
  )
@@ -154,7 +168,7 @@ SELECT
154
168
  ELSE 'open'
155
169
  END AS status,
156
170
  o.created_at, o.updated_at
157
- FROM saas_core.orders o
171
+ FROM public.orders o
158
172
  LEFT JOIN bill b ON b.invoice_id = o.id
159
173
  WHERE o.kind IN ('invoice_receivable','invoice_payable') OR b.invoice_id IS NOT NULL;
160
174
  ALTER VIEW public.v_invoice_balances SET (security_invoker = true);
@@ -8,11 +8,11 @@ DO $$
8
8
  DECLARE t_id uuid;
9
9
  BEGIN
10
10
  FOR t_id IN (
11
- SELECT id FROM saas_core.tenants
12
- WHERE id NOT IN (SELECT DISTINCT tenant_id FROM public.payment_method_types)
11
+ SELECT id FROM public.tenants
12
+ WHERE id NOT IN (SELECT DISTINCT tenant_id FROM public.plg_financial_payment_method_types)
13
13
  )
14
14
  LOOP
15
- INSERT INTO public.payment_method_types (tenant_id, name, transaction_type) VALUES
15
+ INSERT INTO public.plg_financial_payment_method_types (tenant_id, name, transaction_type) VALUES
16
16
  (t_id, 'Cash', 'cash'),
17
17
  (t_id, 'PIX', 'pix'),
18
18
  (t_id, 'Credit Card', 'credit_card'),
@@ -26,11 +26,11 @@ DO $$
26
26
  DECLARE t_id uuid;
27
27
  BEGIN
28
28
  FOR t_id IN (
29
- SELECT id FROM saas_core.tenants
30
- WHERE id NOT IN (SELECT DISTINCT tenant_id FROM public.card_brands)
29
+ SELECT id FROM public.tenants
30
+ WHERE id NOT IN (SELECT DISTINCT tenant_id FROM public.plg_financial_card_brands)
31
31
  )
32
32
  LOOP
33
- INSERT INTO public.card_brands (tenant_id, name) VALUES
33
+ INSERT INTO public.plg_financial_card_brands (tenant_id, name) VALUES
34
34
  (t_id, 'Visa'), (t_id, 'Mastercard'), (t_id, 'American Express'),
35
35
  (t_id, 'Elo'), (t_id, 'Hipercard'), (t_id, 'Diners Club'),
36
36
  (t_id, 'Discover'), (t_id, 'JCB'), (t_id, 'Aura'), (t_id, 'Hiper');
@@ -3,10 +3,10 @@
3
3
  -- The base migrations (001/002/003) ENABLE row-level security but relied on an
4
4
  -- external `project_rls.sql` auto-detection step to create the actual policies.
5
5
  -- That step does not run in the Drizzle/companion pipeline, so every table
6
- -- except financial_movements was RLS-enabled with NO policy → all inserts denied
6
+ -- except plg_financial_movements was RLS-enabled with NO policy → all inserts denied
7
7
  -- ("new row violates row-level security policy"). This creates the canonical
8
8
  -- per-tenant policies idempotently. Re-running is a no-op (IF NOT EXISTS guards;
9
- -- financial_movements policies from 004 are skipped).
9
+ -- plg_financial_movements policies from 004 are skipped).
10
10
  --
11
11
  -- Canonical RLS form: tenant_id IN (SELECT public.user_tenant_ids()).
12
12
 
@@ -14,9 +14,9 @@ DO $$
14
14
  DECLARE t text;
15
15
  BEGIN
16
16
  FOR t IN SELECT unnest(ARRAY[
17
- 'payment_method_types','payment_methods','bank_accounts',
18
- 'cash_register_sessions','financial_movements',
19
- 'chart_of_accounts','cost_centers','card_brands'
17
+ 'plg_financial_payment_method_types','plg_financial_payment_methods','plg_financial_bank_accounts',
18
+ 'plg_financial_cash_register_sessions','plg_financial_movements',
19
+ 'plg_financial_chart_of_accounts','plg_financial_cost_centers','plg_financial_card_brands'
20
20
  ])
21
21
  LOOP
22
22
  EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', t);
@@ -5,14 +5,14 @@
5
5
  -- the running balance reflects the NET cash that actually lands in the account.
6
6
  -- gross = paid_amount (settles the receivable/payable)
7
7
  -- net cash = paid_amount - fee_amount (credits only; v1 has no fee on debits)
8
- ALTER TABLE public.financial_movements
8
+ ALTER TABLE public.plg_financial_movements
9
9
  ADD COLUMN IF NOT EXISTS fee_amount numeric(14,2) NOT NULL DEFAULT 0;
10
10
 
11
- COMMENT ON COLUMN public.financial_movements.fee_amount IS
11
+ COMMENT ON COLUMN public.plg_financial_movements.fee_amount IS
12
12
  'Processing/MDR fee deducted at settlement. Net cash impact = paid_amount - fee_amount (credit). v1: same-day net, no D+N settlement-date modeling.';
13
13
 
14
- CREATE INDEX IF NOT EXISTS idx_financial_movements_statement
15
- ON public.financial_movements (bank_account_id, payment_date)
14
+ CREATE INDEX IF NOT EXISTS idx_plg_financial_movements_statement
15
+ ON public.plg_financial_movements (bank_account_id, payment_date)
16
16
  WHERE status IN ('paid', 'partial');
17
17
 
18
18
  -- Transfers between accounts = a debit row (source) + credit row (destination),
@@ -2,31 +2,31 @@
2
2
  -- Reconciliation (conciliação): bank-statement lines ↔ internal movements
3
3
  -- ---------------------------------------------------------------------------
4
4
  -- An integration (e.g. open banking) imports bank-statement lines as
5
- -- financial_movements tagged with (external_source, external_id). The matching
5
+ -- plg_financial_movements tagged with (external_source, external_id). The matching
6
6
  -- UI then links each imported line to the internal movement it settles, or
7
7
  -- accepts it standalone. These columns make a movement carry its bank-line
8
8
  -- identity and its reconciliation state.
9
9
  -- external_source / external_id : the bank line's provider + unique id
10
10
  -- reconciled_at : when the line was matched/accepted (NULL = pending)
11
11
  -- matched_movement_id : the internal movement this line reconciles (NULL = standalone)
12
- ALTER TABLE public.financial_movements
12
+ ALTER TABLE public.plg_financial_movements
13
13
  ADD COLUMN IF NOT EXISTS external_id text,
14
14
  ADD COLUMN IF NOT EXISTS external_source text,
15
15
  ADD COLUMN IF NOT EXISTS reconciled_at timestamptz,
16
16
  ADD COLUMN IF NOT EXISTS matched_movement_id uuid
17
- REFERENCES public.financial_movements(id) ON DELETE SET NULL;
17
+ REFERENCES public.plg_financial_movements(id) ON DELETE SET NULL;
18
18
 
19
- COMMENT ON COLUMN public.financial_movements.external_source IS
19
+ COMMENT ON COLUMN public.plg_financial_movements.external_source IS
20
20
  'Provider of an imported bank-statement line (e.g. plugbank, inter). NULL for app-native movements.';
21
- COMMENT ON COLUMN public.financial_movements.external_id IS
21
+ COMMENT ON COLUMN public.plg_financial_movements.external_id IS
22
22
  'Provider-unique id of the imported bank line. Forms the import idempotency key with external_source.';
23
23
 
24
24
  -- Idempotency: a given bank line imports at most once per tenant.
25
- CREATE UNIQUE INDEX IF NOT EXISTS uq_financial_movements_external
26
- ON public.financial_movements (tenant_id, external_source, external_id)
25
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_plg_financial_movements_external
26
+ ON public.plg_financial_movements (tenant_id, external_source, external_id)
27
27
  WHERE external_id IS NOT NULL AND external_source IS NOT NULL;
28
28
 
29
29
  -- Fast lookup of imported-but-unreconciled lines for the matching screen.
30
- CREATE INDEX IF NOT EXISTS idx_financial_movements_unreconciled
31
- ON public.financial_movements (tenant_id, payment_date)
30
+ CREATE INDEX IF NOT EXISTS idx_plg_financial_movements_unreconciled
31
+ ON public.plg_financial_movements (tenant_id, payment_date)
32
32
  WHERE external_source IS NOT NULL AND reconciled_at IS NULL;
@@ -0,0 +1,6 @@
1
+ -- payment_method_type_id existed on pre-pool installs (added out-of-band on
2
+ -- the salon/beauty DB) but was never captured in the file series; 008 requires
3
+ -- it on plg_financial_movements. No-op where the column already exists.
4
+
5
+ ALTER TABLE public.plg_financial_movements
6
+ ADD COLUMN IF NOT EXISTS payment_method_type_id uuid REFERENCES public.plg_financial_payment_method_types(id);
@@ -3,9 +3,9 @@
3
3
  -- two payments on one installment show as two extract rows instead of one summed row.
4
4
  -- Bills stay pure obligations; v_invoice_balances already filters movement_kind='bill',
5
5
  -- so the new payment rows don't affect invoice balances. Backfill is idempotent.
6
- -- Requires fee_amount (006_extract_fee_amount.sql).
6
+ -- Requires fee_amount (006b_extract_fee_amount.sql).
7
7
 
8
- INSERT INTO public.financial_movements
8
+ INSERT INTO public.plg_financial_movements
9
9
  (tenant_id, invoice_id, direction, movement_kind, amount, paid_amount, fee_amount, status,
10
10
  due_date, payment_date, installment_number, payment_method_id, payment_method_type_id,
11
11
  bank_account_id, cash_session_id, card_brand, card_installments, notes, metadata)
@@ -16,14 +16,14 @@ SELECT
16
16
  b.payment_method_id, b.payment_method_type_id, b.bank_account_id, b.cash_session_id,
17
17
  b.card_brand, b.card_installments, b.notes,
18
18
  jsonb_build_object('backfilledFromBill', b.id)
19
- FROM public.financial_movements b
19
+ FROM public.plg_financial_movements b
20
20
  WHERE b.movement_kind = 'bill' AND b.paid_amount > 0
21
21
  AND NOT EXISTS (
22
- SELECT 1 FROM public.financial_movements p
22
+ SELECT 1 FROM public.plg_financial_movements p
23
23
  WHERE p.movement_kind = 'payment' AND p.metadata->>'backfilledFromBill' = b.id::text
24
24
  );
25
25
 
26
- UPDATE public.financial_movements
26
+ UPDATE public.plg_financial_movements
27
27
  SET payment_date = NULL, bank_account_id = NULL, payment_method_id = NULL,
28
28
  payment_method_type_id = NULL, cash_session_id = NULL, card_brand = NULL, card_installments = NULL
29
29
  WHERE movement_kind = 'bill';
@@ -0,0 +1,165 @@
1
+ -- ============================================================================
2
+ -- plugin-financial 009: server-plane agent write RPC.
3
+ --
4
+ -- public.agent_financial_mark_payment_received — "marca o atendimento como
5
+ -- pago em cartão 2x". Resolves the order (directly or via a booking id),
6
+ -- guard-checks the actor (agent_guard, spine 015), promotes the order into an
7
+ -- invoice + installment ledger through the EXISTING invariant function
8
+ -- fn_invoice_from_order (004) — never re-implementing its numbering/allocation
9
+ -- logic — then settles the installments following 008's split model (bill rows
10
+ -- flip to paid; each cash event is its own movement_kind='payment' row).
11
+ --
12
+ -- fn_invoice_from_order authorizes via user_tenant_ids() (auth.uid()). This
13
+ -- RPC runs under the broker's service key, so it impersonates the ALREADY
14
+ -- GUARD-VERIFIED actor for the duration of the transaction by setting
15
+ -- request.jwt.claims locally — the invariant function keeps working unchanged
16
+ -- and the audit trail carries the real acting user.
17
+ --
18
+ -- Contract: (p_tenant_id, p_actor_user_id, p_payload) → jsonb
19
+ -- payload {order_id? uuid, booking_id? uuid, method text
20
+ -- ('cash'|'pix'|'credit_card'|'debit_card'|'transfer'),
21
+ -- installments? int (credit_card only, 1–12), amount? numeric}
22
+ -- ============================================================================
23
+
24
+ CREATE OR REPLACE FUNCTION public.agent_financial_mark_payment_received(
25
+ p_tenant_id uuid,
26
+ p_actor_user_id uuid,
27
+ p_payload jsonb
28
+ ) RETURNS jsonb
29
+ LANGUAGE plpgsql SECURITY DEFINER
30
+ SET search_path = public
31
+ AS $$
32
+ DECLARE
33
+ v_denial jsonb;
34
+ v_order_id uuid;
35
+ v_booking_id uuid;
36
+ v_method text;
37
+ v_installments int;
38
+ v_amount numeric;
39
+ v_order record;
40
+ v_bill record;
41
+ v_paid_count int := 0;
42
+ v_total_paid numeric := 0;
43
+ BEGIN
44
+ -- ── payload ──────────────────────────────────────────────────────────────
45
+ BEGIN
46
+ v_order_id := (p_payload->>'order_id')::uuid;
47
+ v_booking_id := (p_payload->>'booking_id')::uuid;
48
+ v_amount := (p_payload->>'amount')::numeric;
49
+ v_installments := COALESCE((p_payload->>'installments')::int, 1);
50
+ EXCEPTION WHEN OTHERS THEN
51
+ RETURN jsonb_build_object('ok', false, 'error', 'invalid payload: ' || SQLERRM);
52
+ END;
53
+ v_method := lower(COALESCE(p_payload->>'method', 'cash'));
54
+ IF v_method NOT IN ('cash', 'pix', 'credit_card', 'debit_card', 'transfer') THEN
55
+ RETURN jsonb_build_object('ok', false, 'error', 'unknown payment method');
56
+ END IF;
57
+ IF v_installments NOT BETWEEN 1 AND 12
58
+ OR (v_installments > 1 AND v_method <> 'credit_card') THEN
59
+ RETURN jsonb_build_object('ok', false, 'error', 'installments require credit_card (1-12)');
60
+ END IF;
61
+
62
+ -- booking → order
63
+ IF v_order_id IS NULL AND v_booking_id IS NOT NULL THEN
64
+ SELECT b.order_id INTO v_order_id
65
+ FROM appointments b
66
+ WHERE b.id = v_booking_id AND b.tenant_id = p_tenant_id;
67
+ END IF;
68
+ IF v_order_id IS NULL THEN
69
+ RETURN jsonb_build_object('ok', false, 'error', 'order_id or booking_id required (booking must have an order)');
70
+ END IF;
71
+
72
+ SELECT o.id, o.status, o.total INTO v_order
73
+ FROM orders o
74
+ WHERE o.id = v_order_id AND o.tenant_id = p_tenant_id;
75
+ IF NOT FOUND THEN
76
+ RETURN jsonb_build_object('ok', false, 'error', 'unknown order for this tenant');
77
+ END IF;
78
+ IF v_order.status = 'cancelled' THEN
79
+ RETURN jsonb_build_object('ok', false, 'error', 'cannot receive payment on a cancelled order');
80
+ END IF;
81
+
82
+ -- ── authorization ────────────────────────────────────────────────────────
83
+ v_denial := agent_guard(p_tenant_id, p_actor_user_id, 'financial.receivables', 'update');
84
+ IF v_denial IS NOT NULL THEN
85
+ RETURN jsonb_build_object('ok', false, 'denial', v_denial);
86
+ END IF;
87
+
88
+ -- already fully paid?
89
+ IF EXISTS (
90
+ SELECT 1 FROM plg_financial_movements m
91
+ WHERE m.invoice_id = v_order_id AND m.movement_kind = 'bill'
92
+ ) AND NOT EXISTS (
93
+ SELECT 1 FROM plg_financial_movements m
94
+ WHERE m.invoice_id = v_order_id AND m.movement_kind = 'bill' AND m.status <> 'paid'
95
+ ) THEN
96
+ RETURN jsonb_build_object('ok', false, 'error', 'order is already fully paid');
97
+ END IF;
98
+
99
+ -- ── invoice via the existing invariant function (impersonate the verified
100
+ -- actor so fn_invoice_from_order's user_tenant_ids() check passes) ─────
101
+ PERFORM set_config('request.jwt.claims',
102
+ jsonb_build_object('sub', p_actor_user_id, 'role', 'authenticated')::text,
103
+ true);
104
+ PERFORM fn_invoice_from_order(v_order_id, current_date, NULL, v_installments, 'credit');
105
+
106
+ -- ── settle: flip pending bill installments to paid + emit payment rows ───
107
+ FOR v_bill IN
108
+ SELECT m.* FROM plg_financial_movements m
109
+ WHERE m.invoice_id = v_order_id
110
+ AND m.tenant_id = p_tenant_id
111
+ AND m.movement_kind = 'bill'
112
+ AND m.status <> 'paid'
113
+ ORDER BY m.installment_number NULLS FIRST
114
+ LOOP
115
+ UPDATE plg_financial_movements SET
116
+ status = 'paid',
117
+ paid_amount = v_bill.amount,
118
+ payment_date = current_date,
119
+ card_installments = CASE WHEN v_method = 'credit_card' THEN v_installments ELSE NULL END,
120
+ metadata = COALESCE(metadata, '{}'::jsonb)
121
+ || jsonb_build_object('paidVia', 'agent', 'method', v_method),
122
+ updated_at = now()
123
+ WHERE id = v_bill.id;
124
+
125
+ INSERT INTO plg_financial_movements
126
+ (tenant_id, invoice_id, direction, movement_kind, amount, paid_amount, status,
127
+ due_date, payment_date, installment_number, card_installments, metadata)
128
+ VALUES
129
+ (p_tenant_id, v_order_id, v_bill.direction, 'payment', v_bill.amount, v_bill.amount,
130
+ 'paid', v_bill.due_date, current_date, v_bill.installment_number,
131
+ CASE WHEN v_method = 'credit_card' THEN v_installments ELSE NULL END,
132
+ jsonb_build_object('source', 'agent', 'method', v_method));
133
+
134
+ v_paid_count := v_paid_count + 1;
135
+ v_total_paid := v_total_paid + v_bill.amount;
136
+ END LOOP;
137
+
138
+ IF v_paid_count = 0 THEN
139
+ RETURN jsonb_build_object('ok', false, 'error', 'no open installments found to settle');
140
+ END IF;
141
+
142
+ INSERT INTO audit_logs (tenant_id, user_id, action, entity_type, entity_id, metadata)
143
+ VALUES (p_tenant_id, p_actor_user_id, 'agent.markPaymentReceived', 'order', v_order_id::text,
144
+ jsonb_build_object('payload', p_payload, 'installmentsSettled', v_paid_count,
145
+ 'totalPaid', v_total_paid));
146
+
147
+ RETURN jsonb_build_object(
148
+ 'ok', true,
149
+ 'id', v_order_id,
150
+ 'record', jsonb_build_object(
151
+ 'ref', jsonb_build_object('id', v_order_id, 'resource', 'orders',
152
+ 'archetype', 'order:service_order'),
153
+ 'method', v_method,
154
+ 'installments', v_installments,
155
+ 'installments_settled', v_paid_count,
156
+ 'total_paid', v_total_paid
157
+ )
158
+ );
159
+ END;
160
+ $$;
161
+
162
+ REVOKE ALL ON FUNCTION public.agent_financial_mark_payment_received(uuid, uuid, jsonb) FROM public;
163
+ REVOKE EXECUTE ON FUNCTION public.agent_financial_mark_payment_received(uuid, uuid, jsonb) FROM anon;
164
+ GRANT EXECUTE ON FUNCTION public.agent_financial_mark_payment_received(uuid, uuid, jsonb)
165
+ TO authenticated, service_role;