mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 19:43:18 +00:00
Implementa el módulo integral de costos según el plan acordado: - Esquema Liquibase: expense_entries, almacén (central + obra), IVA, tenant_cost_settings, permisos IAM y RLS - Funciones RPC core: gastos, almacén, control presupuestal, sync nómina, cierre de almacén al concluir proyecto - API HTTP: expenses_http, warehouse_http, cost_control_http, iam_http - Middleware requirePermission con matriz IAM - UI web-panel: /gastos, /almacen, /control-presupuesto, /usuarios - Componentes BudgetItemPicker, CostSemaphore y semáforos CSS - Smoke tests extendidos para gastos/almacén/control Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
125 lines
4.6 KiB
PL/PgSQL
125 lines
4.6 KiB
PL/PgSQL
--liquibase formatted sql
|
|
-- PANELS · core · cierre de almacén al concluir proyecto
|
|
|
|
--changeset panel:core-026a-fn-project-update-warehouse splitStatements:false
|
|
CREATE OR REPLACE FUNCTION core.fn_project_update(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_id bigint := (payload->>'id')::bigint;
|
|
v_cur projects%ROWTYPE;
|
|
v_name text;
|
|
v_address text;
|
|
v_theme_id text;
|
|
v_status text;
|
|
v_company_id integer;
|
|
v_contract_amount numeric(14,2);
|
|
v_start_date date;
|
|
v_end_date date;
|
|
v_resident_name text;
|
|
v_siroc text;
|
|
v_payroll_tax_pct numeric(5,2);
|
|
v_close_env jsonb;
|
|
BEGIN
|
|
IF v_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_project_update: id es obligatorio',
|
|
jsonb_build_object('fn', 'fn_project_update', 'field', 'id')
|
|
);
|
|
END IF;
|
|
SELECT * INTO v_cur FROM projects WHERE id = v_id;
|
|
IF NOT FOUND THEN
|
|
RETURN core.rpc_err(
|
|
'NOT_FOUND',
|
|
format('fn_project_update: proyecto id=%s no encontrado', v_id),
|
|
jsonb_build_object('fn', 'fn_project_update', 'id', v_id)
|
|
);
|
|
END IF;
|
|
v_name := coalesce(nullif(btrim(payload->>'name'), ''), v_cur.name);
|
|
v_address := CASE WHEN payload ? 'address' THEN coalesce(nullif(btrim(payload->>'address'), ''), '') ELSE v_cur.address END;
|
|
v_theme_id := coalesce(nullif(btrim(payload->>'theme_id'), ''), v_cur.theme_id);
|
|
v_status := CASE
|
|
WHEN payload ? 'status' AND btrim(payload->>'status') IN ('activo', 'pausado', 'concluido', 'cancelado')
|
|
THEN btrim(payload->>'status')
|
|
ELSE v_cur.status
|
|
END;
|
|
v_company_id := coalesce((payload->>'company_id')::integer, v_cur.company_id);
|
|
v_contract_amount := CASE
|
|
WHEN payload ? 'contract_amount' THEN (payload->>'contract_amount')::numeric(14,2)
|
|
ELSE v_cur.contract_amount
|
|
END;
|
|
v_start_date := CASE
|
|
WHEN payload ? 'start_date' THEN nullif(btrim(payload->>'start_date'), '')::date
|
|
ELSE v_cur.start_date
|
|
END;
|
|
v_end_date := CASE
|
|
WHEN payload ? 'end_date' THEN nullif(btrim(payload->>'end_date'), '')::date
|
|
ELSE v_cur.end_date
|
|
END;
|
|
v_resident_name := CASE
|
|
WHEN payload ? 'resident_name' THEN coalesce(nullif(btrim(payload->>'resident_name'), ''), '')
|
|
ELSE v_cur.resident_name
|
|
END;
|
|
v_siroc := CASE
|
|
WHEN payload ? 'siroc' THEN upper(coalesce(nullif(btrim(payload->>'siroc'), ''), ''))
|
|
ELSE v_cur.siroc
|
|
END;
|
|
v_payroll_tax_pct := coalesce((payload->>'payroll_tax_pct')::numeric(5,2), v_cur.payroll_tax_pct);
|
|
IF v_company_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_project_update: company_id es obligatorio',
|
|
jsonb_build_object('fn', 'fn_project_update', 'field', 'company_id', 'id', v_id)
|
|
);
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM companies WHERE id = v_company_id) THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
format('fn_project_update: empresa id=%s no encontrada', v_company_id),
|
|
jsonb_build_object('fn', 'fn_project_update', 'company_id', v_company_id, 'id', v_id)
|
|
);
|
|
END IF;
|
|
IF v_start_date IS NOT NULL AND v_end_date IS NOT NULL AND v_end_date < v_start_date THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_project_update: la fecha de término no puede ser anterior al inicio',
|
|
jsonb_build_object('fn', 'fn_project_update', 'field', 'end_date', 'id', v_id)
|
|
);
|
|
END IF;
|
|
IF v_status IN ('concluido', 'cancelado') AND v_cur.status NOT IN ('concluido', 'cancelado') THEN
|
|
v_close_env := core.fn_warehouse_close_project(jsonb_build_object(
|
|
'project_id', v_id,
|
|
'force', COALESCE((payload->>'force_warehouse_close')::boolean, false),
|
|
'closed_by_name', coalesce(nullif(btrim(payload->>'closed_by_name'), ''), 'sistema'),
|
|
'close_notes', coalesce(nullif(btrim(payload->>'warehouse_close_notes'), ''), 'Cierre automático al concluir obra')
|
|
));
|
|
IF NOT COALESCE((v_close_env->>'ok')::boolean, false) THEN
|
|
RETURN v_close_env;
|
|
END IF;
|
|
END IF;
|
|
UPDATE projects SET
|
|
name = v_name,
|
|
address = v_address,
|
|
theme_id = v_theme_id,
|
|
status = v_status,
|
|
company_id = v_company_id,
|
|
contract_amount = v_contract_amount,
|
|
start_date = v_start_date,
|
|
end_date = v_end_date,
|
|
resident_name = v_resident_name,
|
|
siroc = v_siroc,
|
|
payroll_tax_pct = v_payroll_tax_pct
|
|
WHERE id = v_id;
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('id', v_id, 'code', v_cur.code, 'status', v_status),
|
|
format('Proyecto %s actualizado', v_cur.code),
|
|
jsonb_build_object('fn', 'fn_project_update', 'id', v_id, 'code', v_cur.code)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_project_update', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|