panels-origin/scripts/verify-work-program-load-seed.sql
Alberto Martinez 0045ccd8ed fix: programado vs costo (warehouse_movements.project_id no existe)
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Problema

Tras cargar el programa, la pantalla pide en paralelo get / concepts / partidas / **vs-cost**. El vs-cost fallaba:

`fn_work_program_vs_cost (sqlstate=42703): column m.project_id does not exist`

`warehouse_movements` no tiene `project_id`. El proyecto está en `warehouses.project_id`.

## Cambio (changeset 038)

- Salidas de almacén por `JOIN warehouses w ON w.id = m.warehouse_id WHERE w.project_id = …`
- Ejecutado por partida **sin duplicar** al cruzar varias semanas (el join anterior multiplicaba el monto)

## Prueba local (hecha antes de pedir merge)

En Postgres 16 corrí las **4 RPCs** que usa la página, con presupuesto, 2 semanas, gasto $50 y salida de almacén $25:

- `fn_work_program_get` — curva 400 / 600
- `fn_work_program_list_concepts` — items
- `fn_work_program_list_partidas` — A001
- `fn_work_program_vs_cost` — ejecutado semanal **75**, por partida **75** (no 150)

Script: `scripts/verify-work-program-load.sh`

## Cómo probar en tu ambiente

1. Mergear y aplicar Liquibase (038).
2. Recargar Programa de obra en la obra ya importada. Sin toast rojo.
3. La pestaña / bloque de programado vs costo debe abrir.

<!-- 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>&nbsp;<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>&nbsp;</div>
2026-09-08 21:55:34 +00:00

91 lines
4.2 KiB
SQL

SET search_path = core;
INSERT INTO projects (name, start_date, end_date) VALUES ('Obra test', '2026-09-07', '2026-09-19');
-- id = 1
INSERT INTO budget_chapters (project_id, code, name, sort_order) VALUES (1, 'A001', 'PRELIMINARES', 1);
INSERT INTO budget_items (project_id, chapter_id, code, description, amount, sort_order)
VALUES (1, 1, '10301-001', 'Trazo', 1000, 1);
INSERT INTO work_programs (project_id, start_date, end_date, duration_days, source, status)
VALUES (1, '2026-09-07', '2026-09-19', 13, 'import', 'active');
INSERT INTO work_program_periods (program_id, period_index, label, start_date, end_date) VALUES
(1, 1, 'Sem 1', '2026-09-07', '2026-09-13'),
(1, 2, 'Sem 2', '2026-09-14', '2026-09-19');
INSERT INTO work_program_partida_amounts (program_id, budget_chapter_id, period_id, amount) VALUES
(1, 1, 1, 400.00),
(1, 1, 2, 600.00);
INSERT INTO work_program_concept_slots (program_id, budget_item_id, period_id, start_pct, end_pct)
VALUES (1, 1, 1, 0, 50);
INSERT INTO expense_entries (project_id, expense_date, subtotal, status, counts_toward_budget, budget_item_id)
VALUES (1, '2026-09-08', 50.00, 'confirmed', true, 1);
INSERT INTO warehouses (tenant_id, kind, project_id) VALUES (1, 'project', 1);
INSERT INTO warehouse_materials (tenant_id, code, name) VALUES (1, 'CEM', 'Cemento');
INSERT INTO warehouse_movements (tenant_id, warehouse_id, material_id, movement_type, movement_date, quantity, total_cost, budget_item_id)
VALUES (1, 1, 1, 'exit', '2026-09-09', 1, 25.00, 1);
DO $$
DECLARE
v_get jsonb;
v_concepts jsonb;
v_partidas jsonb;
v_vs jsonb;
v_broken jsonb;
BEGIN
v_get := fn_work_program_get(jsonb_build_object('project_id', 1));
IF NOT COALESCE((v_get->>'ok')::boolean, false) THEN
RAISE EXCEPTION 'fn_work_program_get failed: %', v_get->>'message';
END IF;
IF jsonb_array_length(v_get->'data'->'curve') <> 2 THEN
RAISE EXCEPTION 'curve expected 2 periods, got %', v_get->'data'->'curve';
END IF;
IF (v_get->'data'->'curve'->0->>'amount')::numeric <> 400 THEN
RAISE EXCEPTION 'curve week1 amount %, expected 400', v_get->'data'->'curve'->0->>'amount';
END IF;
IF (v_get->'data'->'curve'->1->>'amount')::numeric <> 600 THEN
RAISE EXCEPTION 'curve week2 amount %, expected 600', v_get->'data'->'curve'->1->>'amount';
END IF;
v_concepts := fn_work_program_list_concepts(jsonb_build_object('project_id', 1));
IF NOT COALESCE((v_concepts->>'ok')::boolean, false) THEN
RAISE EXCEPTION 'fn_work_program_list_concepts failed: %', v_concepts->>'message';
END IF;
IF jsonb_array_length(v_concepts->'data'->'items') < 1 THEN
RAISE EXCEPTION 'concepts empty: %', v_concepts;
END IF;
v_partidas := fn_work_program_list_partidas(jsonb_build_object('project_id', 1));
IF NOT COALESCE((v_partidas->>'ok')::boolean, false) THEN
RAISE EXCEPTION 'fn_work_program_list_partidas failed: %', v_partidas->>'message';
END IF;
IF (v_partidas->'data'->'chapters'->0->>'code') <> 'A001' THEN
RAISE EXCEPTION 'partida code %, expected A001', v_partidas->'data'->'chapters'->0->>'code';
END IF;
v_vs := fn_work_program_vs_cost(jsonb_build_object('project_id', 1));
IF NOT COALESCE((v_vs->>'ok')::boolean, false) THEN
RAISE EXCEPTION 'fn_work_program_vs_cost failed: %', v_vs->>'message';
END IF;
-- Sem 1: gasto 50 + salida almacén 25 = 75
IF (v_vs->'data'->'by_period'->0->>'executed_amount')::numeric <> 75 THEN
RAISE EXCEPTION 'vs-cost week1 executed %, expected 75. payload=%',
v_vs->'data'->'by_period'->0->>'executed_amount', v_vs;
END IF;
IF (v_vs->'data'->'by_period'->0->>'scheduled_amount')::numeric <> 400 THEN
RAISE EXCEPTION 'vs-cost week1 scheduled %, expected 400', v_vs->'data'->'by_period'->0->>'scheduled_amount';
END IF;
IF (v_vs->'data'->'by_chapter'->0->>'executed_amount')::numeric <> 75 THEN
RAISE EXCEPTION 'vs-cost chapter executed %, expected 75', v_vs->'data'->'by_chapter'->0->>'executed_amount';
END IF;
IF (v_vs->'data'->'by_chapter'->0->>'scheduled_amount')::numeric <> 1000 THEN
RAISE EXCEPTION 'vs-cost chapter scheduled %, expected 1000', v_vs->'data'->'by_chapter'->0->>'scheduled_amount';
END IF;
RAISE NOTICE 'OK get+concepts+partidas+vs-cost';
END;
$$;