mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 17:33:16 +00:00
Reutiliza el catálogo de proveedores y agrega carga de facturas, conceptos de la semana, préstamos editables, finiquitos, dispersión, historial de personal, reportes y jornal de cotización. Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
239 lines
9.9 KiB
PL/PgSQL
239 lines
9.9 KiB
PL/PgSQL
--liquibase formatted sql
|
|
-- PANELS · core · dispersión de nómina
|
|
|
|
--changeset panel:core-047a-tables endDelimiter:; splitStatements:true
|
|
CREATE TABLE IF NOT EXISTS core.payroll_dispersions (
|
|
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
tenant_id INTEGER NOT NULL,
|
|
week_id BIGINT NOT NULL REFERENCES core.payroll_weeks(id) ON DELETE CASCADE,
|
|
status TEXT NOT NULL DEFAULT 'abierta' CHECK (status IN ('abierta', 'corte', 'definitiva')),
|
|
net NUMERIC(14,2) NOT NULL DEFAULT 0,
|
|
note TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (week_id)
|
|
);
|
|
ALTER TABLE core.payroll_dispersions ENABLE ROW LEVEL SECURITY;
|
|
DROP POLICY IF EXISTS tenant_isolation ON core.payroll_dispersions;
|
|
CREATE POLICY tenant_isolation ON core.payroll_dispersions
|
|
USING (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::integer)
|
|
WITH CHECK (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::integer);
|
|
|
|
CREATE TABLE IF NOT EXISTS core.payroll_dispersion_lines (
|
|
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
dispersion_id BIGINT NOT NULL REFERENCES core.payroll_dispersions(id) ON DELETE CASCADE,
|
|
worker_id BIGINT NOT NULL REFERENCES core.workers(id),
|
|
amount NUMERIC(14,2) NOT NULL DEFAULT 0,
|
|
note TEXT NOT NULL DEFAULT '',
|
|
UNIQUE (dispersion_id, worker_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS core.payroll_dispersion_attachments (
|
|
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
dispersion_id BIGINT NOT NULL REFERENCES core.payroll_dispersions(id) ON DELETE CASCADE,
|
|
original_name TEXT NOT NULL,
|
|
mime TEXT NOT NULL,
|
|
size_bytes BIGINT NOT NULL,
|
|
sha256 TEXT NOT NULL,
|
|
iv TEXT NOT NULL,
|
|
storage_name TEXT NOT NULL,
|
|
uploaded_by_name TEXT NOT NULL DEFAULT '',
|
|
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON core.payroll_dispersions TO panels_core_app;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON core.payroll_dispersion_lines TO panels_core_app;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON core.payroll_dispersion_attachments TO panels_core_app;
|
|
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA core TO panels_core_app;
|
|
|
|
--changeset panel:core-047b-fn-dispersion splitStatements:false
|
|
CREATE OR REPLACE FUNCTION core.fn_dispersion_get(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_tid integer := core._payroll_tenant_id(payload);
|
|
v_week_id bigint := NULLIF(payload->>'week_id', '')::bigint;
|
|
v_id bigint;
|
|
v_row jsonb;
|
|
v_lines jsonb;
|
|
v_files jsonb;
|
|
v_net numeric;
|
|
BEGIN
|
|
IF v_week_id IS NULL THEN
|
|
RETURN core.rpc_err('VALIDATION', 'fn_dispersion_get: week_id es obligatorio',
|
|
jsonb_build_object('fn', 'fn_dispersion_get'));
|
|
END IF;
|
|
SELECT COALESCE(SUM(l.payable_net), 0) INTO v_net
|
|
FROM payroll_week_lines l
|
|
JOIN payroll_sheets s ON s.id = l.sheet_id
|
|
WHERE s.week_id = v_week_id;
|
|
INSERT INTO payroll_dispersions (tenant_id, week_id, net)
|
|
VALUES (v_tid, v_week_id, core.round_money(v_net))
|
|
ON CONFLICT (week_id) DO UPDATE SET updated_at = now()
|
|
RETURNING id INTO v_id;
|
|
SELECT to_jsonb(d) INTO v_row FROM payroll_dispersions d WHERE d.id = v_id;
|
|
SELECT COALESCE(jsonb_agg(to_jsonb(x) ORDER BY x.last_name_p), '[]'::jsonb) INTO v_lines
|
|
FROM (
|
|
SELECT dl.*, w.first_name, w.last_name_p
|
|
FROM payroll_dispersion_lines dl
|
|
JOIN workers w ON w.id = dl.worker_id
|
|
WHERE dl.dispersion_id = v_id
|
|
) x;
|
|
SELECT COALESCE(jsonb_agg(to_jsonb(a) ORDER BY a.uploaded_at), '[]'::jsonb) INTO v_files
|
|
FROM payroll_dispersion_attachments a WHERE a.dispersion_id = v_id;
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('dispersion', v_row, 'lines', v_lines, 'attachments', v_files, 'week_net', v_net),
|
|
'Dispersión cargada', jsonb_build_object('fn', 'fn_dispersion_get', 'id', v_id)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_dispersion_get', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core.fn_dispersion_cut(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_id bigint := NULLIF(payload->>'id', '')::bigint;
|
|
v_d payroll_dispersions%ROWTYPE;
|
|
v_week_status text;
|
|
BEGIN
|
|
SELECT * INTO v_d FROM payroll_dispersions WHERE id = v_id;
|
|
IF NOT FOUND THEN
|
|
RETURN core.rpc_err('NOT_FOUND', 'Dispersión no encontrada', jsonb_build_object('fn', 'fn_dispersion_cut', 'id', v_id));
|
|
END IF;
|
|
IF v_d.status = 'definitiva' THEN
|
|
RETURN core.rpc_err('VALIDATION', 'La dispersión ya es definitiva', jsonb_build_object('fn', 'fn_dispersion_cut', 'id', v_id));
|
|
END IF;
|
|
SELECT status INTO v_week_status FROM payroll_weeks WHERE id = v_d.week_id;
|
|
DELETE FROM payroll_dispersion_lines WHERE dispersion_id = v_id;
|
|
INSERT INTO payroll_dispersion_lines (dispersion_id, worker_id, amount)
|
|
SELECT v_id, l.worker_id, core.round_money(SUM(l.payable_net))
|
|
FROM payroll_week_lines l
|
|
JOIN payroll_sheets s ON s.id = l.sheet_id
|
|
WHERE s.week_id = v_d.week_id
|
|
GROUP BY l.worker_id;
|
|
UPDATE payroll_dispersions SET
|
|
status = 'corte',
|
|
net = (SELECT COALESCE(SUM(amount), 0) FROM payroll_dispersion_lines WHERE dispersion_id = v_id),
|
|
updated_at = now()
|
|
WHERE id = v_id;
|
|
RETURN core.rpc_ok(jsonb_build_object('id', v_id, 'week_status', v_week_status),
|
|
'Corte semanal de dispersión aplicado', jsonb_build_object('fn', 'fn_dispersion_cut', 'id', v_id));
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_dispersion_cut', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core.fn_dispersion_line_update(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_line_id bigint := NULLIF(payload->>'line_id', '')::bigint;
|
|
v_amount numeric := (payload->>'amount')::numeric;
|
|
v_status text;
|
|
v_week_status text;
|
|
v_disp_id bigint;
|
|
BEGIN
|
|
SELECT d.status, w.status, d.id INTO v_status, v_week_status, v_disp_id
|
|
FROM payroll_dispersion_lines l
|
|
JOIN payroll_dispersions d ON d.id = l.dispersion_id
|
|
JOIN payroll_weeks w ON w.id = d.week_id
|
|
WHERE l.id = v_line_id;
|
|
IF NOT FOUND THEN
|
|
RETURN core.rpc_err('NOT_FOUND', 'Línea de dispersión no encontrada',
|
|
jsonb_build_object('fn', 'fn_dispersion_line_update', 'line_id', v_line_id));
|
|
END IF;
|
|
IF v_status = 'definitiva' OR v_week_status = 'paid' THEN
|
|
RETURN core.rpc_err('VALIDATION', 'La dispersión ya no se puede corregir',
|
|
jsonb_build_object('fn', 'fn_dispersion_line_update', 'line_id', v_line_id));
|
|
END IF;
|
|
UPDATE payroll_dispersion_lines
|
|
SET amount = core.round_money(COALESCE(v_amount, amount)),
|
|
note = CASE WHEN payload ? 'note' THEN coalesce(payload->>'note', '') ELSE note END
|
|
WHERE id = v_line_id;
|
|
UPDATE payroll_dispersions
|
|
SET net = (SELECT COALESCE(SUM(amount), 0) FROM payroll_dispersion_lines WHERE dispersion_id = v_disp_id),
|
|
updated_at = now()
|
|
WHERE id = v_disp_id;
|
|
RETURN core.rpc_ok(jsonb_build_object('id', v_line_id), 'Línea de dispersión actualizada',
|
|
jsonb_build_object('fn', 'fn_dispersion_line_update', 'line_id', v_line_id));
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_dispersion_line_update', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core.fn_dispersion_finalize(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_id bigint := NULLIF(payload->>'id', '')::bigint;
|
|
v_status text;
|
|
BEGIN
|
|
SELECT status INTO v_status FROM payroll_dispersions WHERE id = v_id;
|
|
IF NOT FOUND THEN
|
|
RETURN core.rpc_err('NOT_FOUND', 'Dispersión no encontrada', jsonb_build_object('fn', 'fn_dispersion_finalize', 'id', v_id));
|
|
END IF;
|
|
IF v_status = 'abierta' THEN
|
|
RETURN core.rpc_err('VALIDATION', 'Aplique el corte semanal antes de dejar la dispersión definitiva',
|
|
jsonb_build_object('fn', 'fn_dispersion_finalize', 'id', v_id));
|
|
END IF;
|
|
UPDATE payroll_dispersions SET status = 'definitiva', note = coalesce(payload->>'note', note), updated_at = now()
|
|
WHERE id = v_id;
|
|
RETURN core.rpc_ok(jsonb_build_object('id', v_id, 'status', 'definitiva'),
|
|
'Dispersión definitiva', jsonb_build_object('fn', 'fn_dispersion_finalize', 'id', v_id));
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_dispersion_finalize', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core.fn_dispersion_attachment_add(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_id bigint := NULLIF(payload->>'dispersion_id', '')::bigint;
|
|
v_att_id bigint;
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM payroll_dispersions WHERE id = v_id) THEN
|
|
RETURN core.rpc_err('NOT_FOUND', 'Dispersión no encontrada',
|
|
jsonb_build_object('fn', 'fn_dispersion_attachment_add', 'id', v_id));
|
|
END IF;
|
|
INSERT INTO payroll_dispersion_attachments (
|
|
dispersion_id, original_name, mime, size_bytes, sha256, iv, storage_name, uploaded_by_name
|
|
) VALUES (
|
|
v_id,
|
|
coalesce(payload->>'original_name', 'comprobante'),
|
|
coalesce(payload->>'mime', 'application/pdf'),
|
|
COALESCE((payload->>'size_bytes')::bigint, 0),
|
|
coalesce(payload->>'sha256', ''),
|
|
coalesce(payload->>'iv', ''),
|
|
coalesce(payload->>'storage_name', ''),
|
|
coalesce(payload->>'uploaded_by_name', '')
|
|
) RETURNING id INTO v_att_id;
|
|
RETURN core.rpc_created(jsonb_build_object('id', v_att_id), 'Comprobante de dispersión anexado',
|
|
jsonb_build_object('fn', 'fn_dispersion_attachment_add', 'id', v_att_id));
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_dispersion_attachment_add', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
--changeset panel:core-047c-grants endDelimiter:; splitStatements:true
|
|
GRANT EXECUTE ON FUNCTION core.fn_dispersion_get(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_dispersion_cut(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_dispersion_line_update(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_dispersion_finalize(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_dispersion_attachment_add(jsonb) TO panels_core_app;
|