fix(parties): skip nameless suppliers and customers in the party backfill (#2174)

* fix(parties): skip nameless suppliers and customers in the party backfill

The substrate migration failed on prod at the backfill: three rows (one
supplier, two customers) have an empty name and ensure_party refuses a
nameless party. The file never applied there, so it is corrected in place
rather than chased with a migration that could not run before it. Rows
without a name keep party_id NULL; the suggestion pipeline names them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(parties): ensure_party writes only under the caller's own identity

Authenticated callers must pass their own user id; the service role
(auth.uid() NULL: migrations, MCP, cron) may act for another user. Same
guard as apply_party_suggestions and decide_parties. Superagent P2 on
#2172.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-09-02 17:57:34 +02:00
committed by GitHub
co-authored by Claude Fable 5.1 Jakob Wennberg
parent daeab67dca
commit a24982463b
3 changed files with 29 additions and 1 deletions
@@ -269,6 +269,11 @@ DECLARE
v_name text := btrim(coalesce(p_name, ''));
v_id uuid;
BEGIN
-- Authenticated callers write under their own identity; only the service
-- role (auth.uid() NULL: migrations, MCP, cron) may act for another user.
IF auth.uid() IS NOT NULL AND auth.uid() <> p_user_id THEN
RAISE EXCEPTION 'ensure_party: p_user_id must be the caller' USING ERRCODE = '42501';
END IF;
IF v_name = '' THEN
RAISE EXCEPTION 'ensure_party: name is required';
END IF;
@@ -298,7 +303,9 @@ GRANT EXECUTE ON FUNCTION public.ensure_party(uuid, uuid, text, text, text, text
-- Suppliers first (they carry payment data), then customers, so a company
-- that both buys from and sells to the same organisation ends up with one
-- party carrying both roles. Rows without a valid org number get their own
-- party; nothing is merged on name.
-- party; nothing is merged on name. Rows with an empty name (three exist on
-- prod: one supplier, two customers) keep party_id NULL: ensure_party
-- refuses a nameless party, and the suggestion pipeline names them later.
DO $$
DECLARE
r record;
@@ -307,6 +314,7 @@ BEGIN
SELECT id, company_id, user_id, name, org_number
FROM public.suppliers
WHERE party_id IS NULL AND company_id IS NOT NULL
AND nullif(btrim(name), '') IS NOT NULL
ORDER BY created_at, id
LOOP
UPDATE public.suppliers
@@ -318,6 +326,7 @@ BEGIN
SELECT id, company_id, user_id, name, org_number
FROM public.customers
WHERE party_id IS NULL AND company_id IS NOT NULL
AND nullif(btrim(name), '') IS NOT NULL
ORDER BY created_at, id
LOOP
UPDATE public.customers