mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 12:43:18 +00:00
<!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## Causa El error `duplicate key value violates unique constraint "projects_code_key"` aparece porque: 1. La columna `projects.code` es **UNIQUE en toda la base** (todos los tenants). 2. Al dar de alta, `fn_project_create` llama a `fn_next_project_code`, que hacía `MAX(PRY…)+1` **solo sobre las filas que el tenant ve** (RLS). 3. Si en la base ya existe `PRY-0001` (seed de dev, otra empresa cliente, u otra obra que este tenant no ve), el sistema vuelve a proponer `PRY-0001` → el `INSERT` falla con CONFLICT. No es el nombre del proyecto ni el presupuesto adjunto: es el **código autogenerado**. ## Cambio (Liquibase 039) - `fn_next_project_code`: `SECURITY DEFINER` para leer la secuencia **global** de códigos PRY/OBR. - `fn_project_create`: `pg_advisory_xact_lock` al asignar código (evita duplicado si envían dos veces seguidas). ## Cómo probar 1. Aplicar migraciones (039). 2. Con un tenant que aún no tenga obras (o en un ambiente donde ya exista `PRY-0001` de otro tenant), dar de alta un proyecto nuevo. 3. Debe crearse con `PRY-0002` (o el siguiente libre), sin toast de CONFLICT. Script de demostración RLS: `scripts/verify-project-code-next.sql` + `verify-project-code-next-run.sql`. <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-d8a5d34a-ead9-4912-8296-322cff799783?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-d8a5d34a-ead9-4912-8296-322cff799783&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
45 lines
1.2 KiB
PL/PgSQL
45 lines
1.2 KiB
PL/PgSQL
SET search_path = core;
|
|
SELECT set_config('app.tenant_id', '2', true);
|
|
|
|
CREATE OR REPLACE FUNCTION fn_next_project_code_old()
|
|
RETURNS text
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE v_n integer; v_code text;
|
|
BEGIN
|
|
SELECT COALESCE(MAX(substring(code FROM 5)::integer), 0) + 1 INTO v_n
|
|
FROM projects WHERE code ~ '^PRY-[0-9]{4}' OR code ~ '^OBR-[0-9]{4}';
|
|
RETURN 'PRY-' || lpad(v_n::text, 4, '0');
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION fn_next_project_code_new()
|
|
RETURNS text
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE v_n integer; v_code text;
|
|
BEGIN
|
|
SELECT COALESCE(MAX(substring(code FROM 5)::integer), 0) + 1 INTO v_n
|
|
FROM projects WHERE code ~ '^(PRY|OBR)-[0-9]+$';
|
|
RETURN 'PRY-' || lpad(v_n::text, 4, '0');
|
|
END;
|
|
$$;
|
|
|
|
DO $$
|
|
DECLARE old_code text; new_code text;
|
|
BEGIN
|
|
old_code := fn_next_project_code_old();
|
|
new_code := fn_next_project_code_new();
|
|
IF old_code <> 'PRY-0001' THEN
|
|
RAISE EXCEPTION 'RLS bug demo: old should propose PRY-0001, got %', old_code;
|
|
END IF;
|
|
IF new_code <> 'PRY-0002' THEN
|
|
RAISE EXCEPTION 'fix should propose PRY-0002, got %', new_code;
|
|
END IF;
|
|
RAISE NOTICE 'OK old=% (conflict) new=% (safe)', old_code, new_code;
|
|
END;
|
|
$$;
|