mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 15:33:17 +00:00
Port key payroll operations from api/payroll.ts to plpgsql RPCs with round_money helper, internal recalc/ensure logic, and core.rpc_* envelope. Add batch worker import and post-S3 document metadata store RPCs with Spanish validation messages and panels_core_app grants. Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
1658 lines
56 KiB
PL/PgSQL
1658 lines
56 KiB
PL/PgSQL
--liquibase formatted sql
|
|
-- PANELS · core · RPC nómina (port de api/payroll.ts)
|
|
|
|
--changeset panel:core-013a-payroll-helpers endDelimiter:; splitStatements:true
|
|
CREATE OR REPLACE FUNCTION core.round_money(p_value numeric)
|
|
RETURNS numeric
|
|
LANGUAGE sql
|
|
IMMUTABLE
|
|
AS $$
|
|
SELECT round(COALESCE(p_value, 0)::numeric, 2);
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_tenant_id(payload jsonb)
|
|
RETURNS integer
|
|
LANGUAGE sql
|
|
STABLE
|
|
AS $$
|
|
SELECT COALESCE(
|
|
NULLIF(payload->>'tenant_id', '')::integer,
|
|
NULLIF(current_setting('app.tenant_id', true), '')::integer,
|
|
0
|
|
);
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_add_days(p_iso date, p_days integer)
|
|
RETURNS date
|
|
LANGUAGE sql
|
|
IMMUTABLE
|
|
AS $$
|
|
SELECT (p_iso + make_interval(days => p_days))::date;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_weekday_mon0(p_iso date)
|
|
RETURNS integer
|
|
LANGUAGE sql
|
|
IMMUTABLE
|
|
AS $$
|
|
SELECT (extract(isodow FROM p_iso)::integer - 1);
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_week_containing(p_iso date)
|
|
RETURNS TABLE(week_start date, week_end date)
|
|
LANGUAGE sql
|
|
IMMUTABLE
|
|
AS $$
|
|
WITH ws AS (
|
|
SELECT core._payroll_add_days(p_iso, -core._payroll_weekday_mon0(p_iso)) AS week_start
|
|
)
|
|
SELECT ws.week_start, core._payroll_add_days(ws.week_start, 5) AS week_end
|
|
FROM ws;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_destajo_period_bounds(p_week_start date)
|
|
RETURNS TABLE(period_start date, period_end date)
|
|
LANGUAGE sql
|
|
IMMUTABLE
|
|
AS $$
|
|
WITH pe AS (
|
|
SELECT core._payroll_add_days(p_week_start, 3) AS period_end
|
|
)
|
|
SELECT core._payroll_add_days(pe.period_end, -7), pe.period_end
|
|
FROM pe;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_this_saturday(p_iso date)
|
|
RETURNS date
|
|
LANGUAGE sql
|
|
IMMUTABLE
|
|
AS $$
|
|
SELECT week_end FROM core._payroll_week_containing(p_iso);
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_next_saturday_after_week(p_iso date)
|
|
RETURNS date
|
|
LANGUAGE sql
|
|
IMMUTABLE
|
|
AS $$
|
|
SELECT core._payroll_add_days(core._payroll_this_saturday(p_iso), 7);
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_week_assemble_date(p_week_start date)
|
|
RETURNS date
|
|
LANGUAGE sql
|
|
IMMUTABLE
|
|
AS $$
|
|
SELECT core._payroll_add_days(p_week_start, 3);
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_can_assemble_week(p_week_start date, p_today date)
|
|
RETURNS boolean
|
|
LANGUAGE sql
|
|
IMMUTABLE
|
|
AS $$
|
|
SELECT p_today >= core._payroll_week_assemble_date(p_week_start);
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_tenant_today(p_tenant_id integer)
|
|
RETURNS date
|
|
LANGUAGE sql
|
|
STABLE
|
|
SET search_path = core
|
|
AS $$
|
|
SELECT (now() AT TIME ZONE COALESCE(
|
|
(SELECT timezone FROM tenant_settings WHERE tenant_id = p_tenant_id),
|
|
'America/Mexico_City'
|
|
))::date;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_money_label(p_amount numeric)
|
|
RETURNS text
|
|
LANGUAGE sql
|
|
IMMUTABLE
|
|
AS $$
|
|
SELECT '$' || trim(to_char(core.round_money(p_amount), 'FM999,999,999,990.00'));
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_commission_amount(p_delivered numeric, p_pct numeric)
|
|
RETURNS numeric
|
|
LANGUAGE sql
|
|
IMMUTABLE
|
|
AS $$
|
|
SELECT core.round_money(COALESCE(p_delivered, 0) * COALESCE(p_pct, 0) / 100.0);
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_loan_condition_label(
|
|
p_delivered numeric,
|
|
p_commission_pct numeric,
|
|
p_pay_amount numeric,
|
|
p_installment_n integer,
|
|
p_installments_n integer
|
|
)
|
|
RETURNS text
|
|
LANGUAGE sql
|
|
IMMUTABLE
|
|
AS $$
|
|
SELECT format(
|
|
'%s · %s/%s · %s',
|
|
core._payroll_money_label(p_pay_amount),
|
|
p_installment_n,
|
|
GREATEST(COALESCE(p_installments_n, 1), 1),
|
|
CASE
|
|
WHEN COALESCE(p_commission_pct, 0) > 0 THEN
|
|
format('préstamo %s + comisión %s%%',
|
|
core._payroll_money_label(p_delivered),
|
|
trim(to_char(p_commission_pct, 'FM999990.##')))
|
|
ELSE format('préstamo %s', core._payroll_money_label(p_delivered))
|
|
END
|
|
);
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_seed_defaults(p_tenant_id integer)
|
|
RETURNS void
|
|
LANGUAGE plpgsql
|
|
SET search_path = core
|
|
AS $$
|
|
BEGIN
|
|
INSERT INTO payroll_settings (tenant_id, loan_commission_enabled, loan_commission_pct, loan_small_max)
|
|
VALUES (p_tenant_id, true, 10, 500)
|
|
ON CONFLICT (tenant_id) DO NOTHING;
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM destajo_units WHERE tenant_id = p_tenant_id) THEN
|
|
INSERT INTO destajo_units (tenant_id, code, label) VALUES
|
|
(p_tenant_id, 'm', 'Metro (m)'),
|
|
(p_tenant_id, 'm2', 'Metro cuadrado (m²)'),
|
|
(p_tenant_id, 'm3', 'Metro cúbico (m³)'),
|
|
(p_tenant_id, 'ml', 'Metro lineal (ml)'),
|
|
(p_tenant_id, 'pza', 'Pieza');
|
|
END IF;
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_sheet_line(
|
|
p_sheet_id bigint,
|
|
p_worker_id bigint,
|
|
p_destajo_cut_line_id bigint
|
|
)
|
|
RETURNS bigint
|
|
LANGUAGE plpgsql
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_id bigint;
|
|
BEGIN
|
|
IF p_destajo_cut_line_id IS NOT NULL THEN
|
|
SELECT id INTO v_id
|
|
FROM payroll_week_lines
|
|
WHERE sheet_id = p_sheet_id
|
|
AND destajo_cut_line_id = p_destajo_cut_line_id;
|
|
ELSE
|
|
SELECT id INTO v_id
|
|
FROM payroll_week_lines
|
|
WHERE sheet_id = p_sheet_id
|
|
AND worker_id = p_worker_id
|
|
AND destajo_cut_line_id IS NULL;
|
|
END IF;
|
|
|
|
IF v_id IS NOT NULL THEN
|
|
RETURN v_id;
|
|
END IF;
|
|
|
|
INSERT INTO payroll_week_lines (sheet_id, worker_id, destajo_cut_line_id)
|
|
VALUES (p_sheet_id, p_worker_id, p_destajo_cut_line_id)
|
|
RETURNING id INTO v_id;
|
|
|
|
RETURN v_id;
|
|
END;
|
|
$$;
|
|
|
|
--changeset panel:core-013b-payroll-recalc endDelimiter:; splitStatements:true
|
|
CREATE OR REPLACE FUNCTION core._payroll_recalc_week(p_week_id bigint)
|
|
RETURNS void
|
|
LANGUAGE plpgsql
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_week record;
|
|
v_sheet record;
|
|
v_worker record;
|
|
v_cut record;
|
|
v_line record;
|
|
v_loan record;
|
|
v_line_id bigint;
|
|
v_att_n integer;
|
|
v_wage numeric;
|
|
v_gross numeric;
|
|
v_payable_qty numeric;
|
|
v_required_gross numeric;
|
|
v_keep bigint[];
|
|
v_stale record;
|
|
v_tot numeric;
|
|
v_worker_id bigint;
|
|
v_lines record;
|
|
v_gross_sum numeric;
|
|
v_remaining numeric;
|
|
v_placed boolean;
|
|
v_n integer;
|
|
v_is_last boolean;
|
|
v_want numeric;
|
|
v_take numeric;
|
|
v_label text;
|
|
v_host_id bigint;
|
|
v_host_gross numeric;
|
|
v_host_discounts numeric;
|
|
BEGIN
|
|
SELECT * INTO v_week FROM payroll_weeks WHERE id = p_week_id;
|
|
IF NOT FOUND OR v_week.status = 'paid' THEN
|
|
RETURN;
|
|
END IF;
|
|
|
|
UPDATE payroll_week_lines
|
|
SET loan_id = NULL, loan_discount = 0, loan_label = NULL
|
|
WHERE sheet_id IN (SELECT id FROM payroll_sheets WHERE week_id = p_week_id);
|
|
|
|
FOR v_sheet IN SELECT * FROM payroll_sheets WHERE week_id = p_week_id LOOP
|
|
IF v_sheet.kind = 'obra' AND v_sheet.project_id IS NOT NULL THEN
|
|
FOR v_worker IN
|
|
SELECT w.id, w.daily_wage
|
|
FROM workers w
|
|
WHERE w.status = 'activo'
|
|
AND w.pipeline_status != 'baja'
|
|
AND (
|
|
EXISTS (
|
|
SELECT 1 FROM assignments a
|
|
WHERE a.worker_id = w.id AND a.project_id = v_sheet.project_id
|
|
AND a.active = true AND w.work_type = 'N'
|
|
)
|
|
OR EXISTS (
|
|
SELECT 1 FROM attendance att
|
|
WHERE att.worker_id = w.id AND att.project_id = v_sheet.project_id
|
|
AND att.work_date BETWEEN v_week.week_start AND v_week.week_end
|
|
)
|
|
OR EXISTS (
|
|
SELECT 1 FROM payroll_week_lines l
|
|
JOIN payroll_sheets s ON s.id = l.sheet_id
|
|
WHERE l.worker_id = w.id AND s.project_id = v_sheet.project_id
|
|
AND s.kind = 'obra' AND s.week_id = p_week_id
|
|
)
|
|
)
|
|
ORDER BY w.last_name_p, w.first_name
|
|
LOOP
|
|
v_line_id := core._payroll_sheet_line(v_sheet.id, v_worker.id, NULL);
|
|
SELECT COUNT(*)::integer INTO v_att_n
|
|
FROM attendance
|
|
WHERE worker_id = v_worker.id
|
|
AND project_id = v_sheet.project_id
|
|
AND work_date BETWEEN v_week.week_start AND v_week.week_end
|
|
AND present = true;
|
|
SELECT daily_wage INTO v_wage FROM payroll_week_lines WHERE id = v_line_id;
|
|
IF v_week.status = 'assembled' AND COALESCE(v_wage, 0) > 0 THEN
|
|
NULL;
|
|
ELSE
|
|
v_wage := v_worker.daily_wage;
|
|
END IF;
|
|
v_gross := core.round_money(v_att_n * v_wage);
|
|
UPDATE payroll_week_lines
|
|
SET days = v_att_n,
|
|
daily_wage = v_wage,
|
|
amount = v_gross,
|
|
gross = v_gross,
|
|
payable_net = v_gross
|
|
WHERE id = v_line_id;
|
|
END LOOP;
|
|
END IF;
|
|
|
|
IF v_sheet.kind = 'destajo' AND v_sheet.project_id IS NOT NULL THEN
|
|
SELECT id INTO v_line_id FROM destajo_periods WHERE week_id = p_week_id;
|
|
IF v_line_id IS NULL THEN
|
|
CONTINUE;
|
|
END IF;
|
|
v_keep := ARRAY[]::bigint[];
|
|
FOR v_cut IN
|
|
SELECT c.id, c.qty_planned, c.qty_actual, c.qty_extra,
|
|
j.worker_id, j.unit_price, j.unit_code, j.concepto
|
|
FROM destajo_cut_lines c
|
|
JOIN destajo_jobs j ON j.id = c.job_id
|
|
WHERE c.period_id = v_line_id
|
|
AND j.project_id = v_sheet.project_id
|
|
LOOP
|
|
v_line_id := core._payroll_sheet_line(v_sheet.id, v_cut.worker_id, v_cut.id);
|
|
v_keep := array_append(v_keep, v_line_id);
|
|
IF v_week.status = 'draft'
|
|
AND v_cut.qty_actual = 0 AND v_cut.qty_extra = 0 THEN
|
|
v_payable_qty := core.round_money(v_cut.qty_planned);
|
|
ELSE
|
|
v_payable_qty := core.round_money(v_cut.qty_actual + v_cut.qty_extra);
|
|
END IF;
|
|
v_gross := core.round_money(v_payable_qty * v_cut.unit_price);
|
|
v_required_gross := core.round_money(v_cut.qty_planned * v_cut.unit_price);
|
|
UPDATE payroll_week_lines
|
|
SET destajo_cut_line_id = v_cut.id,
|
|
qty_planned = v_cut.qty_planned,
|
|
qty_actual = v_cut.qty_actual,
|
|
qty_extra = v_cut.qty_extra,
|
|
unit_price = v_cut.unit_price,
|
|
unit_code = v_cut.unit_code,
|
|
concepto = v_cut.concepto,
|
|
amount = v_required_gross,
|
|
gross = v_gross,
|
|
payable_net = v_gross
|
|
WHERE id = v_line_id;
|
|
END LOOP;
|
|
FOR v_stale IN
|
|
SELECT id FROM payroll_week_lines WHERE sheet_id = v_sheet.id
|
|
LOOP
|
|
IF NOT (v_stale.id = ANY (v_keep)) THEN
|
|
DELETE FROM payroll_week_lines WHERE id = v_stale.id;
|
|
END IF;
|
|
END LOOP;
|
|
END IF;
|
|
|
|
IF v_sheet.kind = 'admin' THEN
|
|
FOR v_line IN SELECT id, amount, discounts FROM payroll_week_lines WHERE sheet_id = v_sheet.id LOOP
|
|
v_gross := core.round_money(v_line.amount);
|
|
UPDATE payroll_week_lines
|
|
SET gross = v_gross,
|
|
payable_net = core.round_money(v_gross - v_line.discounts)
|
|
WHERE id = v_line.id;
|
|
END LOOP;
|
|
END IF;
|
|
END LOOP;
|
|
|
|
FOR v_worker_id IN
|
|
SELECT DISTINCT l.worker_id
|
|
FROM payroll_week_lines l
|
|
JOIN payroll_sheets s ON s.id = l.sheet_id
|
|
WHERE s.week_id = p_week_id
|
|
LOOP
|
|
SELECT core.round_money(COALESCE(SUM(l.gross - l.discounts), 0))
|
|
INTO v_gross_sum
|
|
FROM payroll_week_lines l
|
|
JOIN payroll_sheets s ON s.id = l.sheet_id
|
|
WHERE s.week_id = p_week_id
|
|
AND l.worker_id = v_worker_id;
|
|
|
|
v_remaining := GREATEST(0, v_gross_sum);
|
|
v_placed := false;
|
|
|
|
FOR v_loan IN
|
|
SELECT *
|
|
FROM loans
|
|
WHERE worker_id = v_worker_id
|
|
AND balance > 0
|
|
AND first_due IS NOT NULL
|
|
AND first_due <= v_week.week_end
|
|
ORDER BY first_due, id
|
|
LOOP
|
|
EXIT WHEN v_remaining <= 0;
|
|
SELECT COUNT(*)::integer + 1 INTO v_n FROM loan_payments WHERE loan_id = v_loan.id;
|
|
v_is_last := v_n >= GREATEST(COALESCE(v_loan.installments_n, 1), 1);
|
|
IF v_is_last THEN
|
|
v_want := v_loan.balance;
|
|
ELSE
|
|
v_want := LEAST(COALESCE(NULLIF(v_loan.weekly_payment, 0), v_loan.balance), v_loan.balance);
|
|
END IF;
|
|
v_take := core.round_money(LEAST(v_want, v_remaining));
|
|
IF v_take <= 0 THEN
|
|
CONTINUE;
|
|
END IF;
|
|
v_remaining := core.round_money(v_remaining - v_take);
|
|
IF NOT v_placed THEN
|
|
SELECT l.id, l.gross, l.discounts
|
|
INTO v_host_id, v_host_gross, v_host_discounts
|
|
FROM payroll_week_lines l
|
|
JOIN payroll_sheets s ON s.id = l.sheet_id
|
|
WHERE s.week_id = p_week_id
|
|
AND l.worker_id = v_worker_id
|
|
ORDER BY l.gross DESC, l.id
|
|
LIMIT 1;
|
|
v_label := core._payroll_loan_condition_label(
|
|
v_loan.delivered, v_loan.commission_pct, v_take, v_n, v_loan.installments_n
|
|
);
|
|
UPDATE payroll_week_lines
|
|
SET loan_id = v_loan.id,
|
|
loan_discount = v_take,
|
|
loan_label = v_label,
|
|
payable_net = core.round_money(v_host_gross - v_host_discounts - v_take)
|
|
WHERE id = v_host_id;
|
|
v_placed := true;
|
|
END IF;
|
|
EXIT;
|
|
END LOOP;
|
|
|
|
IF NOT v_placed THEN
|
|
UPDATE payroll_week_lines l
|
|
SET payable_net = core.round_money(l.gross - l.discounts)
|
|
FROM payroll_sheets s
|
|
WHERE s.id = l.sheet_id
|
|
AND s.week_id = p_week_id
|
|
AND l.worker_id = v_worker_id;
|
|
ELSE
|
|
UPDATE payroll_week_lines l
|
|
SET payable_net = core.round_money(l.gross - l.discounts)
|
|
FROM payroll_sheets s
|
|
WHERE s.id = l.sheet_id
|
|
AND s.week_id = p_week_id
|
|
AND l.worker_id = v_worker_id
|
|
AND l.id <> v_host_id;
|
|
END IF;
|
|
END LOOP;
|
|
|
|
SELECT COALESCE(SUM(l.payable_net), 0) INTO v_tot
|
|
FROM payroll_week_lines l
|
|
JOIN payroll_sheets s ON s.id = l.sheet_id
|
|
WHERE s.week_id = p_week_id;
|
|
|
|
UPDATE payroll_weeks
|
|
SET payable_net = core.round_money(v_tot)
|
|
WHERE id = p_week_id;
|
|
|
|
IF v_week.status = 'draft' THEN
|
|
UPDATE payroll_weeks
|
|
SET required_net = core.round_money(v_tot)
|
|
WHERE id = p_week_id;
|
|
UPDATE payroll_week_lines
|
|
SET required_net = payable_net
|
|
WHERE sheet_id IN (SELECT id FROM payroll_sheets WHERE week_id = p_week_id);
|
|
END IF;
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_ensure_cut_for_open_jobs(p_week_id bigint)
|
|
RETURNS void
|
|
LANGUAGE plpgsql
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_period record;
|
|
v_job record;
|
|
BEGIN
|
|
SELECT id, tenant_id INTO v_period FROM destajo_periods WHERE week_id = p_week_id;
|
|
IF NOT FOUND THEN
|
|
RETURN;
|
|
END IF;
|
|
FOR v_job IN
|
|
SELECT id FROM destajo_jobs WHERE tenant_id = v_period.tenant_id AND status = 'open'
|
|
LOOP
|
|
INSERT INTO destajo_cut_lines (period_id, job_id, qty_planned, qty_actual, qty_extra)
|
|
VALUES (v_period.id, v_job.id, 0, 0, 0)
|
|
ON CONFLICT (period_id, job_id) DO NOTHING;
|
|
END LOOP;
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_ensure_week(p_tenant_id integer, p_week_start date)
|
|
RETURNS bigint
|
|
LANGUAGE plpgsql
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_week_start date;
|
|
v_week_end date;
|
|
v_week_id bigint;
|
|
v_bounds record;
|
|
v_period_id bigint;
|
|
v_proj record;
|
|
BEGIN
|
|
PERFORM core._payroll_seed_defaults(p_tenant_id);
|
|
SELECT wc.week_start, wc.week_end
|
|
INTO v_week_start, v_week_end
|
|
FROM core._payroll_week_containing(p_week_start) wc;
|
|
|
|
SELECT id INTO v_week_id
|
|
FROM payroll_weeks
|
|
WHERE tenant_id = p_tenant_id AND week_start = v_week_start;
|
|
|
|
IF v_week_id IS NULL THEN
|
|
INSERT INTO payroll_weeks (week_start, week_end, status, tenant_id)
|
|
VALUES (v_week_start, v_week_end, 'draft', p_tenant_id)
|
|
RETURNING id INTO v_week_id;
|
|
END IF;
|
|
|
|
SELECT * INTO v_bounds FROM core._payroll_destajo_period_bounds(v_week_start);
|
|
SELECT id INTO v_period_id
|
|
FROM destajo_periods
|
|
WHERE tenant_id = p_tenant_id AND period_end = v_bounds.period_end;
|
|
IF v_period_id IS NULL THEN
|
|
INSERT INTO destajo_periods (period_start, period_end, week_id, tenant_id)
|
|
VALUES (v_bounds.period_start, v_bounds.period_end, v_week_id, p_tenant_id);
|
|
END IF;
|
|
|
|
FOR v_proj IN
|
|
SELECT id FROM projects
|
|
WHERE status IN ('activo', 'pausado')
|
|
AND (p_tenant_id = 0 OR tenant_id = p_tenant_id OR tenant_id IS NULL)
|
|
ORDER BY name
|
|
LOOP
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM payroll_sheets
|
|
WHERE week_id = v_week_id AND kind = 'obra' AND COALESCE(project_id, 0) = v_proj.id
|
|
) THEN
|
|
INSERT INTO payroll_sheets (week_id, kind, project_id) VALUES (v_week_id, 'obra', v_proj.id);
|
|
END IF;
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM payroll_sheets
|
|
WHERE week_id = v_week_id AND kind = 'destajo' AND COALESCE(project_id, 0) = v_proj.id
|
|
) THEN
|
|
INSERT INTO payroll_sheets (week_id, kind, project_id) VALUES (v_week_id, 'destajo', v_proj.id);
|
|
END IF;
|
|
END LOOP;
|
|
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM payroll_sheets
|
|
WHERE week_id = v_week_id AND kind = 'admin' AND project_id IS NULL
|
|
) THEN
|
|
INSERT INTO payroll_sheets (week_id, kind, project_id) VALUES (v_week_id, 'admin', NULL);
|
|
END IF;
|
|
|
|
RETURN v_week_id;
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_first_due_for_loan(
|
|
p_delivered numeric,
|
|
p_small_max numeric,
|
|
p_plan text,
|
|
p_grant_iso date,
|
|
p_week_assembled boolean,
|
|
p_weekday_mon0 integer
|
|
)
|
|
RETURNS date
|
|
LANGUAGE plpgsql
|
|
IMMUTABLE
|
|
AS $$
|
|
BEGIN
|
|
IF p_delivered <= p_small_max AND p_plan = 'single' THEN
|
|
RETURN core._payroll_next_saturday_after_week(p_grant_iso);
|
|
END IF;
|
|
IF p_week_assembled AND p_weekday_mon0 >= 4 THEN
|
|
RETURN core._payroll_next_saturday_after_week(p_grant_iso);
|
|
END IF;
|
|
RETURN core._payroll_this_saturday(p_grant_iso);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core._payroll_prefill_thu_sat(
|
|
p_project_id bigint,
|
|
p_week_start date,
|
|
p_worker_id bigint
|
|
)
|
|
RETURNS void
|
|
LANGUAGE plpgsql
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_offset integer;
|
|
v_day date;
|
|
v_other record;
|
|
v_existing record;
|
|
BEGIN
|
|
FOR v_offset IN SELECT unnest(ARRAY[3, 4, 5]) LOOP
|
|
v_day := core._payroll_add_days(p_week_start, v_offset);
|
|
SELECT a.project_id, p.name AS project_name
|
|
INTO v_other
|
|
FROM attendance a
|
|
JOIN projects p ON p.id = a.project_id
|
|
WHERE a.worker_id = p_worker_id
|
|
AND a.work_date = v_day
|
|
AND a.present = true
|
|
AND a.project_id <> p_project_id
|
|
LIMIT 1;
|
|
IF FOUND THEN
|
|
CONTINUE;
|
|
END IF;
|
|
SELECT id, present INTO v_existing
|
|
FROM attendance
|
|
WHERE worker_id = p_worker_id
|
|
AND project_id = p_project_id
|
|
AND work_date = v_day;
|
|
IF FOUND THEN
|
|
CONTINUE;
|
|
END IF;
|
|
INSERT INTO attendance (worker_id, project_id, work_date, present)
|
|
VALUES (p_worker_id, p_project_id, v_day, true);
|
|
END LOOP;
|
|
END;
|
|
$$;
|
|
|
|
--changeset panel:core-013c-fn-payroll-settings endDelimiter:; splitStatements:true
|
|
CREATE OR REPLACE FUNCTION core.fn_payroll_settings_get(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_tid integer := core._payroll_tenant_id(payload);
|
|
v_settings jsonb;
|
|
v_units jsonb;
|
|
BEGIN
|
|
PERFORM core._payroll_seed_defaults(v_tid);
|
|
SELECT to_jsonb(s) INTO v_settings FROM payroll_settings s WHERE tenant_id = v_tid;
|
|
SELECT COALESCE(jsonb_agg(to_jsonb(u) ORDER BY u.id), '[]'::jsonb)
|
|
INTO v_units
|
|
FROM destajo_units u
|
|
WHERE u.tenant_id = v_tid;
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('settings', v_settings, 'units', v_units),
|
|
format('Configuración de nómina del tenant %s cargada', v_tid),
|
|
jsonb_build_object('fn', 'fn_payroll_settings_get', 'tenant_id', v_tid)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_payroll_settings_get', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core.fn_payroll_settings_save(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_tid integer := core._payroll_tenant_id(payload);
|
|
v_cur payroll_settings%ROWTYPE;
|
|
v_enabled boolean;
|
|
v_pct numeric;
|
|
v_small_max numeric;
|
|
v_settings jsonb;
|
|
BEGIN
|
|
PERFORM core._payroll_seed_defaults(v_tid);
|
|
SELECT * INTO v_cur FROM payroll_settings WHERE tenant_id = v_tid;
|
|
v_enabled := COALESCE((payload->>'loan_commission_enabled')::boolean, v_cur.loan_commission_enabled);
|
|
v_pct := COALESCE((payload->>'loan_commission_pct')::numeric, v_cur.loan_commission_pct, 0);
|
|
v_small_max := COALESCE((payload->>'loan_small_max')::numeric, v_cur.loan_small_max, 0);
|
|
UPDATE payroll_settings
|
|
SET loan_commission_enabled = v_enabled,
|
|
loan_commission_pct = COALESCE(v_pct, 0),
|
|
loan_small_max = COALESCE(v_small_max, 0)
|
|
WHERE tenant_id = v_tid;
|
|
SELECT to_jsonb(s) INTO v_settings FROM payroll_settings s WHERE tenant_id = v_tid;
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('settings', v_settings),
|
|
format('Configuración de nómina del tenant %s guardada', v_tid),
|
|
jsonb_build_object('fn', 'fn_payroll_settings_save', 'tenant_id', v_tid)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_payroll_settings_save', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
--changeset panel:core-013d-fn-payroll-units endDelimiter:; splitStatements:true
|
|
CREATE OR REPLACE FUNCTION core.fn_payroll_unit_list(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_tid integer := core._payroll_tenant_id(payload);
|
|
v_units jsonb;
|
|
BEGIN
|
|
PERFORM core._payroll_seed_defaults(v_tid);
|
|
SELECT COALESCE(jsonb_agg(to_jsonb(u) ORDER BY u.id), '[]'::jsonb)
|
|
INTO v_units
|
|
FROM destajo_units u
|
|
WHERE u.tenant_id = v_tid;
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('units', v_units),
|
|
format('Unidades de destajo del tenant %s: %s registro(s)', v_tid, jsonb_array_length(v_units)),
|
|
jsonb_build_object('fn', 'fn_payroll_unit_list', 'tenant_id', v_tid, 'count', jsonb_array_length(v_units))
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_payroll_unit_list', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core.fn_payroll_unit_add(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_tid integer := core._payroll_tenant_id(payload);
|
|
v_code text := lower(regexp_replace(btrim(COALESCE(payload->>'code', '')), '\s+', '', 'g'));
|
|
v_label text := COALESCE(NULLIF(btrim(payload->>'label'), ''), v_code);
|
|
v_id bigint;
|
|
BEGIN
|
|
IF v_code = '' THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_unit_add: unidad requerida',
|
|
jsonb_build_object('fn', 'fn_payroll_unit_add', 'field', 'code')
|
|
);
|
|
END IF;
|
|
PERFORM core._payroll_seed_defaults(v_tid);
|
|
INSERT INTO destajo_units (tenant_id, code, label)
|
|
VALUES (v_tid, v_code, v_label)
|
|
RETURNING id INTO v_id;
|
|
RETURN core.rpc_created(
|
|
jsonb_build_object('id', v_id, 'code', v_code, 'label', v_label),
|
|
format('Unidad de destajo %s (%s) agregada', v_label, v_code),
|
|
jsonb_build_object('fn', 'fn_payroll_unit_add', 'tenant_id', v_tid, 'id', v_id)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_payroll_unit_add', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
--changeset panel:core-013e-fn-attendance endDelimiter:; splitStatements:true
|
|
CREATE OR REPLACE FUNCTION core.fn_attendance_list(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_project_id bigint := NULLIF(payload->>'project_id', '')::bigint;
|
|
v_from date := NULLIF(btrim(payload->>'from'), '')::date;
|
|
v_to date := NULLIF(btrim(payload->>'to'), '')::date;
|
|
v_rows jsonb;
|
|
BEGIN
|
|
IF v_project_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_attendance_list: project_id es obligatorio',
|
|
jsonb_build_object('fn', 'fn_attendance_list', 'field', 'project_id')
|
|
);
|
|
END IF;
|
|
IF v_from IS NULL OR v_to IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_attendance_list: from y to son obligatorios (fechas ISO)',
|
|
jsonb_build_object('fn', 'fn_attendance_list', 'fields', jsonb_build_array('from', 'to'))
|
|
);
|
|
END IF;
|
|
SELECT COALESCE(jsonb_agg(row_to_json(x)::jsonb ORDER BY x.work_date, x.last_name_p), '[]'::jsonb)
|
|
INTO v_rows
|
|
FROM (
|
|
SELECT a.*, w.first_name, w.last_name_p
|
|
FROM attendance a
|
|
JOIN workers w ON w.id = a.worker_id
|
|
WHERE a.project_id = v_project_id
|
|
AND a.work_date BETWEEN v_from AND v_to
|
|
) x;
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('attendance', v_rows),
|
|
format('Asistencia del proyecto %s: %s registro(s) entre %s y %s',
|
|
v_project_id, jsonb_array_length(v_rows), v_from, v_to),
|
|
jsonb_build_object('fn', 'fn_attendance_list', 'project_id', v_project_id, 'count', jsonb_array_length(v_rows))
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_attendance_list', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core.fn_attendance_set(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_tid integer := core._payroll_tenant_id(payload);
|
|
v_project_id bigint := NULLIF(payload->>'project_id', '')::bigint;
|
|
v_worker_id bigint := NULLIF(payload->>'worker_id', '')::bigint;
|
|
v_work_date date := NULLIF(btrim(payload->>'work_date'), '')::date;
|
|
v_present boolean := COALESCE((payload->>'present')::boolean, false);
|
|
v_other record;
|
|
v_week_start date;
|
|
v_week record;
|
|
BEGIN
|
|
IF v_project_id IS NULL OR v_worker_id IS NULL OR v_work_date IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_attendance_set: project_id, worker_id y work_date son obligatorios',
|
|
jsonb_build_object('fn', 'fn_attendance_set')
|
|
);
|
|
END IF;
|
|
IF v_present THEN
|
|
SELECT a.project_id, p.name AS project_name
|
|
INTO v_other
|
|
FROM attendance a
|
|
JOIN projects p ON p.id = a.project_id
|
|
WHERE a.worker_id = v_worker_id
|
|
AND a.work_date = v_work_date
|
|
AND a.present = true
|
|
AND a.project_id <> v_project_id
|
|
LIMIT 1;
|
|
IF FOUND THEN
|
|
RETURN core.rpc_err(
|
|
'CONFLICT',
|
|
format('Ese día ya está marcado en %s', v_other.project_name),
|
|
jsonb_build_object(
|
|
'fn', 'fn_attendance_set',
|
|
'worker_id', v_worker_id,
|
|
'work_date', v_work_date,
|
|
'other_project_id', v_other.project_id
|
|
),
|
|
jsonb_build_object(
|
|
'other', jsonb_build_object(
|
|
'project_id', v_other.project_id,
|
|
'project_name', v_other.project_name
|
|
)
|
|
)
|
|
);
|
|
END IF;
|
|
END IF;
|
|
INSERT INTO attendance (worker_id, project_id, work_date, present)
|
|
VALUES (v_worker_id, v_project_id, v_work_date, v_present)
|
|
ON CONFLICT (worker_id, project_id, work_date)
|
|
DO UPDATE SET present = EXCLUDED.present;
|
|
|
|
SELECT week_start INTO v_week_start
|
|
FROM core._payroll_week_containing(v_work_date);
|
|
SELECT id, status INTO v_week
|
|
FROM payroll_weeks
|
|
WHERE tenant_id = v_tid AND week_start = v_week_start;
|
|
IF FOUND AND v_week.status <> 'paid' THEN
|
|
PERFORM core._payroll_recalc_week(v_week.id);
|
|
END IF;
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('ok', true),
|
|
format('Asistencia registrada para trabajador %s el %s', v_worker_id, v_work_date),
|
|
jsonb_build_object('fn', 'fn_attendance_set', 'worker_id', v_worker_id, 'work_date', v_work_date)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_attendance_set', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
--changeset panel:core-013f-fn-loan-create endDelimiter:; splitStatements:true
|
|
CREATE OR REPLACE FUNCTION core.fn_loan_create(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_tid integer := core._payroll_tenant_id(payload);
|
|
v_worker_id bigint := NULLIF(payload->>'worker_id', '')::bigint;
|
|
v_delivered numeric;
|
|
v_settings payroll_settings%ROWTYPE;
|
|
v_enabled boolean;
|
|
v_pct numeric;
|
|
v_comm numeric;
|
|
v_balance numeric;
|
|
v_small boolean;
|
|
v_plan text;
|
|
v_installments integer;
|
|
v_weekly numeric;
|
|
v_grant_iso date;
|
|
v_week record;
|
|
v_assembled boolean;
|
|
v_first_due date;
|
|
v_loan_id bigint;
|
|
BEGIN
|
|
IF v_worker_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_loan_create: worker_id es obligatorio',
|
|
jsonb_build_object('fn', 'fn_loan_create', 'field', 'worker_id')
|
|
);
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM workers WHERE id = v_worker_id) THEN
|
|
RETURN core.rpc_err(
|
|
'NOT_FOUND',
|
|
format('fn_loan_create: persona id=%s no encontrada', v_worker_id),
|
|
jsonb_build_object('fn', 'fn_loan_create', 'worker_id', v_worker_id)
|
|
);
|
|
END IF;
|
|
PERFORM core._payroll_seed_defaults(v_tid);
|
|
SELECT * INTO v_settings FROM payroll_settings WHERE tenant_id = v_tid;
|
|
v_delivered := core.round_money(COALESCE(
|
|
(payload->>'delivered')::numeric,
|
|
(payload->>'amount')::numeric,
|
|
0
|
|
));
|
|
IF v_delivered <= 0 THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_loan_create: monto entregado requerido y mayor a cero',
|
|
jsonb_build_object('fn', 'fn_loan_create', 'field', 'delivered')
|
|
);
|
|
END IF;
|
|
v_enabled := COALESCE(
|
|
(payload->>'commission_enabled')::boolean,
|
|
v_settings.loan_commission_enabled
|
|
);
|
|
IF v_enabled THEN
|
|
v_pct := COALESCE((payload->>'commission_pct')::numeric, v_settings.loan_commission_pct, 0);
|
|
ELSE
|
|
v_pct := 0;
|
|
END IF;
|
|
v_comm := core._payroll_commission_amount(v_delivered, v_pct);
|
|
v_balance := core.round_money(v_delivered + v_comm);
|
|
v_small := v_delivered <= v_settings.loan_small_max;
|
|
v_plan := COALESCE(NULLIF(btrim(payload->>'plan'), ''), 'single');
|
|
v_installments := GREATEST(1, floor(COALESCE((payload->>'installments_n')::numeric, 1))::integer);
|
|
IF v_small THEN
|
|
v_plan := 'single';
|
|
v_installments := 1;
|
|
END IF;
|
|
IF v_plan = 'installments' AND v_installments < 2 THEN
|
|
v_installments := 2;
|
|
END IF;
|
|
IF v_plan = 'single' THEN
|
|
v_installments := 1;
|
|
END IF;
|
|
v_weekly := core.round_money(v_balance / v_installments);
|
|
v_grant_iso := COALESCE(
|
|
NULLIF(btrim(payload->>'grant_iso'), '')::date,
|
|
core._payroll_tenant_today(v_tid)
|
|
);
|
|
SELECT id, status INTO v_week
|
|
FROM payroll_weeks
|
|
WHERE tenant_id = v_tid
|
|
AND week_start = (SELECT week_start FROM core._payroll_week_containing(v_grant_iso));
|
|
v_assembled := FOUND AND v_week.status IN ('assembled', 'paid');
|
|
v_first_due := core._payroll_first_due_for_loan(
|
|
v_delivered,
|
|
v_settings.loan_small_max,
|
|
v_plan,
|
|
v_grant_iso,
|
|
v_assembled,
|
|
core._payroll_weekday_mon0(v_grant_iso)
|
|
);
|
|
INSERT INTO loans (
|
|
worker_id, amount, delivered, balance, weekly_payment, note,
|
|
commission_pct, commission_amount, plan, installments_n, first_due
|
|
) VALUES (
|
|
v_worker_id, v_delivered, v_delivered, v_balance, v_weekly,
|
|
NULLIF(btrim(payload->>'note'), ''),
|
|
v_pct, v_comm, v_plan, v_installments, v_first_due
|
|
)
|
|
RETURNING id INTO v_loan_id;
|
|
IF FOUND AND v_week.id IS NOT NULL AND v_week.status <> 'paid' THEN
|
|
PERFORM core._payroll_recalc_week(v_week.id);
|
|
END IF;
|
|
RETURN core.rpc_created(
|
|
jsonb_build_object('id', v_loan_id),
|
|
format('Préstamo de %s registrado para trabajador %s', core._payroll_money_label(v_delivered), v_worker_id),
|
|
jsonb_build_object('fn', 'fn_loan_create', 'id', v_loan_id, 'worker_id', v_worker_id)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_loan_create', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
--changeset panel:core-013g-fn-payroll-week endDelimiter:; splitStatements:true
|
|
CREATE OR REPLACE FUNCTION core.fn_payroll_week_ensure(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_tid integer := core._payroll_tenant_id(payload);
|
|
v_week_start date := NULLIF(btrim(payload->>'week_start'), '')::date;
|
|
v_week_id bigint;
|
|
BEGIN
|
|
IF v_week_start IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_week_ensure: week_start es obligatorio (fecha ISO)',
|
|
jsonb_build_object('fn', 'fn_payroll_week_ensure', 'field', 'week_start')
|
|
);
|
|
END IF;
|
|
v_week_id := core._payroll_ensure_week(v_tid, v_week_start);
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('week_id', v_week_id),
|
|
format('Semana de nómina asegurada (id=%s) para inicio %s', v_week_id, v_week_start),
|
|
jsonb_build_object('fn', 'fn_payroll_week_ensure', 'week_id', v_week_id, 'tenant_id', v_tid)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_payroll_week_ensure', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core.fn_payroll_week_recalc(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_week_id bigint := NULLIF(payload->>'week_id', '')::bigint;
|
|
BEGIN
|
|
IF v_week_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_week_recalc: week_id es obligatorio',
|
|
jsonb_build_object('fn', 'fn_payroll_week_recalc', 'field', 'week_id')
|
|
);
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM payroll_weeks WHERE id = v_week_id) THEN
|
|
RETURN core.rpc_err(
|
|
'NOT_FOUND',
|
|
format('fn_payroll_week_recalc: semana id=%s no encontrada', v_week_id),
|
|
jsonb_build_object('fn', 'fn_payroll_week_recalc', 'week_id', v_week_id)
|
|
);
|
|
END IF;
|
|
PERFORM core._payroll_recalc_week(v_week_id);
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('week_id', v_week_id),
|
|
format('Semana de nómina %s recalculada', v_week_id),
|
|
jsonb_build_object('fn', 'fn_payroll_week_recalc', 'week_id', v_week_id)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_payroll_week_recalc', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core.fn_payroll_week_assemble(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_now_iso date := COALESCE(
|
|
NULLIF(btrim(payload->>'now_iso'), '')::date,
|
|
core._payroll_tenant_today(v_tid)
|
|
);
|
|
v_week record;
|
|
v_sheet record;
|
|
v_worker record;
|
|
v_assigned boolean;
|
|
v_has_line boolean;
|
|
v_period_id bigint;
|
|
v_payable numeric;
|
|
BEGIN
|
|
IF v_week_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_week_assemble: week_id es obligatorio',
|
|
jsonb_build_object('fn', 'fn_payroll_week_assemble', 'field', 'week_id')
|
|
);
|
|
END IF;
|
|
SELECT * INTO v_week FROM payroll_weeks WHERE id = v_week_id;
|
|
IF NOT FOUND THEN
|
|
RETURN core.rpc_err(
|
|
'NOT_FOUND',
|
|
format('fn_payroll_week_assemble: semana id=%s no encontrada', v_week_id),
|
|
jsonb_build_object('fn', 'fn_payroll_week_assemble', 'week_id', v_week_id)
|
|
);
|
|
END IF;
|
|
IF v_week.status = 'paid' THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_week_assemble: la semana ya está pagada',
|
|
jsonb_build_object('fn', 'fn_payroll_week_assemble', 'week_id', v_week_id, 'status', v_week.status)
|
|
);
|
|
END IF;
|
|
IF NOT core._payroll_can_assemble_week(v_week.week_start, v_now_iso) THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_week_assemble: la nómina se arma el jueves o después; aún no es jueves de esta semana',
|
|
jsonb_build_object('fn', 'fn_payroll_week_assemble', 'week_id', v_week_id, 'today', v_now_iso)
|
|
);
|
|
END IF;
|
|
FOR v_sheet IN
|
|
SELECT * FROM payroll_sheets WHERE week_id = v_week_id AND kind = 'obra'
|
|
LOOP
|
|
IF v_sheet.project_id IS NULL THEN
|
|
CONTINUE;
|
|
END IF;
|
|
FOR v_worker IN
|
|
SELECT w.id, w.work_type
|
|
FROM workers w
|
|
WHERE w.status = 'activo'
|
|
AND w.pipeline_status != 'baja'
|
|
AND (
|
|
EXISTS (
|
|
SELECT 1 FROM assignments a
|
|
WHERE a.worker_id = w.id AND a.project_id = v_sheet.project_id
|
|
AND a.active = true AND w.work_type = 'N'
|
|
)
|
|
OR EXISTS (
|
|
SELECT 1 FROM attendance att
|
|
WHERE att.worker_id = w.id AND att.project_id = v_sheet.project_id
|
|
AND att.work_date BETWEEN v_week.week_start AND core._payroll_add_days(v_week.week_start, 5)
|
|
)
|
|
OR EXISTS (
|
|
SELECT 1 FROM payroll_week_lines l
|
|
JOIN payroll_sheets s ON s.id = l.sheet_id
|
|
WHERE l.worker_id = w.id AND s.project_id = v_sheet.project_id
|
|
AND s.kind = 'obra' AND s.week_id = v_week_id
|
|
)
|
|
)
|
|
ORDER BY w.last_name_p, w.first_name
|
|
LOOP
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM assignments
|
|
WHERE worker_id = v_worker.id AND project_id = v_sheet.project_id AND active = true
|
|
) INTO v_assigned;
|
|
IF NOT v_assigned THEN
|
|
CONTINUE;
|
|
END IF;
|
|
IF v_worker.work_type <> 'N' THEN
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM payroll_week_lines
|
|
WHERE sheet_id = v_sheet.id AND worker_id = v_worker.id
|
|
) INTO v_has_line;
|
|
IF NOT v_has_line THEN
|
|
CONTINUE;
|
|
END IF;
|
|
END IF;
|
|
PERFORM core._payroll_prefill_thu_sat(v_sheet.project_id, v_week.week_start, v_worker.id);
|
|
END LOOP;
|
|
END LOOP;
|
|
SELECT id INTO v_period_id FROM destajo_periods WHERE week_id = v_week_id;
|
|
IF v_period_id IS NOT NULL THEN
|
|
UPDATE destajo_cut_lines
|
|
SET qty_actual = qty_planned
|
|
WHERE period_id = v_period_id
|
|
AND qty_actual = 0
|
|
AND qty_planned > 0;
|
|
END IF;
|
|
UPDATE payroll_weeks
|
|
SET status = 'assembled', assembled_at = now()
|
|
WHERE id = v_week_id;
|
|
PERFORM core._payroll_recalc_week(v_week_id);
|
|
SELECT payable_net INTO v_payable FROM payroll_weeks WHERE id = v_week_id;
|
|
UPDATE payroll_weeks SET required_net = v_payable WHERE id = v_week_id;
|
|
UPDATE payroll_week_lines
|
|
SET required_net = payable_net
|
|
WHERE sheet_id IN (SELECT id FROM payroll_sheets WHERE week_id = v_week_id);
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('week_id', v_week_id, 'status', 'assembled'),
|
|
format('Semana de nómina %s armada; neto a pagar %s', v_week_id, core._payroll_money_label(v_payable)),
|
|
jsonb_build_object('fn', 'fn_payroll_week_assemble', 'week_id', v_week_id)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_payroll_week_assemble', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core.fn_payroll_week_pay(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_now_iso date := COALESCE(
|
|
NULLIF(btrim(payload->>'now_iso'), '')::date,
|
|
core._payroll_tenant_today(v_tid)
|
|
);
|
|
v_week record;
|
|
v_line record;
|
|
v_loan record;
|
|
v_take numeric;
|
|
v_n integer;
|
|
v_period_id bigint;
|
|
v_job record;
|
|
v_paid numeric;
|
|
v_env jsonb;
|
|
BEGIN
|
|
IF v_week_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_week_pay: week_id es obligatorio',
|
|
jsonb_build_object('fn', 'fn_payroll_week_pay', 'field', 'week_id')
|
|
);
|
|
END IF;
|
|
SELECT * INTO v_week FROM payroll_weeks WHERE id = v_week_id;
|
|
IF NOT FOUND THEN
|
|
RETURN core.rpc_err(
|
|
'NOT_FOUND',
|
|
format('fn_payroll_week_pay: semana id=%s no encontrada', v_week_id),
|
|
jsonb_build_object('fn', 'fn_payroll_week_pay', 'week_id', v_week_id)
|
|
);
|
|
END IF;
|
|
IF v_week.status = 'paid' THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_week_pay: la semana ya está pagada',
|
|
jsonb_build_object('fn', 'fn_payroll_week_pay', 'week_id', v_week_id)
|
|
);
|
|
END IF;
|
|
IF v_week.status <> 'assembled' THEN
|
|
v_env := core.fn_payroll_week_assemble(jsonb_build_object(
|
|
'week_id', v_week_id,
|
|
'tenant_id', v_tid,
|
|
'now_iso', v_now_iso::text
|
|
));
|
|
IF NOT COALESCE((v_env->>'ok')::boolean, false) THEN
|
|
RETURN v_env;
|
|
END IF;
|
|
END IF;
|
|
PERFORM core._payroll_recalc_week(v_week_id);
|
|
FOR v_line IN
|
|
SELECT l.loan_id, l.loan_discount, l.loan_label, l.worker_id
|
|
FROM payroll_week_lines l
|
|
JOIN payroll_sheets s ON s.id = l.sheet_id
|
|
WHERE s.week_id = v_week_id
|
|
AND l.loan_id IS NOT NULL
|
|
AND l.loan_discount > 0
|
|
LOOP
|
|
SELECT * INTO v_loan FROM loans WHERE id = v_line.loan_id;
|
|
IF NOT FOUND OR v_loan.balance <= 0 THEN
|
|
CONTINUE;
|
|
END IF;
|
|
v_take := core.round_money(LEAST(v_line.loan_discount, v_loan.balance));
|
|
IF v_take <= 0 THEN
|
|
CONTINUE;
|
|
END IF;
|
|
SELECT COUNT(*)::integer + 1 INTO v_n FROM loan_payments WHERE loan_id = v_loan.id;
|
|
INSERT INTO loan_payments (loan_id, week_id, amount, installment_n, label)
|
|
VALUES (v_loan.id, v_week_id, v_take, v_n, v_line.loan_label);
|
|
UPDATE loans
|
|
SET balance = core.round_money(balance - v_take)
|
|
WHERE id = v_loan.id;
|
|
END LOOP;
|
|
SELECT id INTO v_period_id FROM destajo_periods WHERE week_id = v_week_id;
|
|
IF v_period_id IS NOT NULL THEN
|
|
FOR v_job IN SELECT id, qty_total_estimated FROM destajo_jobs LOOP
|
|
SELECT COALESCE(SUM(c.qty_actual + c.qty_extra), 0) INTO v_paid
|
|
FROM destajo_cut_lines c
|
|
JOIN destajo_periods p ON p.id = c.period_id
|
|
JOIN payroll_weeks w ON w.id = p.week_id
|
|
WHERE c.job_id = v_job.id
|
|
AND (w.status = 'paid' OR w.id = v_week_id);
|
|
IF v_paid >= v_job.qty_total_estimated THEN
|
|
UPDATE destajo_jobs SET status = 'done' WHERE id = v_job.id;
|
|
END IF;
|
|
END LOOP;
|
|
END IF;
|
|
UPDATE payroll_weeks SET status = 'paid', paid_at = now() WHERE id = v_week_id;
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('week_id', v_week_id, 'status', 'paid'),
|
|
format('Semana de nómina %s marcada como pagada', v_week_id),
|
|
jsonb_build_object('fn', 'fn_payroll_week_pay', 'week_id', v_week_id)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_payroll_week_pay', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core.fn_payroll_week_bundle(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_tid integer := core._payroll_tenant_id(payload);
|
|
v_week_start date := COALESCE(
|
|
NULLIF(btrim(payload->>'week_start'), '')::date,
|
|
(SELECT week_start FROM core._payroll_week_containing(core._payroll_tenant_today(v_tid)))
|
|
);
|
|
v_week_id bigint;
|
|
v_week jsonb;
|
|
v_sheets jsonb;
|
|
BEGIN
|
|
v_week_id := core._payroll_ensure_week(v_tid, v_week_start);
|
|
PERFORM core._payroll_ensure_cut_for_open_jobs(v_week_id);
|
|
PERFORM core._payroll_recalc_week(v_week_id);
|
|
SELECT to_jsonb(w) INTO v_week FROM payroll_weeks w WHERE id = v_week_id;
|
|
SELECT COALESCE(jsonb_agg(
|
|
to_jsonb(s)::jsonb || jsonb_build_object(
|
|
'project_name', p.name,
|
|
'project_code', p.code,
|
|
'lines', COALESCE((
|
|
SELECT jsonb_agg(row_to_json(lx)::jsonb ORDER BY lx.last_name_p, lx.first_name, lx.id)
|
|
FROM (
|
|
SELECT l.*, w.first_name, w.middle_name, w.last_name_p, w.last_name_m,
|
|
w.position, w.work_type
|
|
FROM payroll_week_lines l
|
|
JOIN workers w ON w.id = l.worker_id
|
|
WHERE l.sheet_id = s.id
|
|
) lx
|
|
), '[]'::jsonb)
|
|
)
|
|
ORDER BY CASE s.kind WHEN 'obra' THEN 0 WHEN 'destajo' THEN 1 ELSE 2 END, p.name
|
|
), '[]'::jsonb)
|
|
INTO v_sheets
|
|
FROM payroll_sheets s
|
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
WHERE s.week_id = v_week_id;
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('week', v_week, 'sheets', v_sheets),
|
|
format('Paquete de semana de nómina %s cargado (inicio %s)', v_week_id, v_week->>'week_start'),
|
|
jsonb_build_object('fn', 'fn_payroll_week_bundle', 'week_id', v_week_id, 'tenant_id', v_tid)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_payroll_week_bundle', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
--changeset panel:core-013h-fn-destajo endDelimiter:; splitStatements:true
|
|
CREATE OR REPLACE FUNCTION core.fn_destajo_job_create(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_project_id bigint := NULLIF(payload->>'project_id', '')::bigint;
|
|
v_worker_id bigint := NULLIF(payload->>'worker_id', '')::bigint;
|
|
v_concepto text := btrim(COALESCE(payload->>'concepto', ''));
|
|
v_unit_code text := btrim(COALESCE(payload->>'unit_code', ''));
|
|
v_qty_total numeric := COALESCE((payload->>'qty_total_estimated')::numeric, 0);
|
|
v_unit_price numeric := COALESCE((payload->>'unit_price')::numeric, 0);
|
|
v_qty_planned numeric := COALESCE((payload->>'qty_planned')::numeric, 0);
|
|
v_qty_extra numeric := COALESCE((payload->>'qty_extra')::numeric, 0);
|
|
v_week_status text;
|
|
v_period_id bigint;
|
|
v_job_id bigint;
|
|
BEGIN
|
|
IF v_week_id IS NULL OR v_project_id IS NULL OR v_worker_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_destajo_job_create: week_id, project_id y worker_id son obligatorios',
|
|
jsonb_build_object('fn', 'fn_destajo_job_create')
|
|
);
|
|
END IF;
|
|
SELECT status INTO v_week_status FROM payroll_weeks WHERE id = v_week_id;
|
|
IF NOT FOUND OR v_week_status = 'paid' THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_destajo_job_create: no se puede editar una semana pagada o inexistente',
|
|
jsonb_build_object('fn', 'fn_destajo_job_create', 'week_id', v_week_id)
|
|
);
|
|
END IF;
|
|
IF v_concepto = '' THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_destajo_job_create: concepto requerido',
|
|
jsonb_build_object('fn', 'fn_destajo_job_create', 'field', 'concepto')
|
|
);
|
|
END IF;
|
|
INSERT INTO destajo_jobs (
|
|
project_id, worker_id, concepto, unit_code, qty_total_estimated, unit_price, tenant_id
|
|
) VALUES (
|
|
v_project_id, v_worker_id, v_concepto, v_unit_code, v_qty_total, v_unit_price, v_tid
|
|
)
|
|
RETURNING id INTO v_job_id;
|
|
SELECT id INTO v_period_id FROM destajo_periods WHERE week_id = v_week_id;
|
|
IF v_period_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
format('fn_destajo_job_create: periodo destajo no encontrado para semana %s', v_week_id),
|
|
jsonb_build_object('fn', 'fn_destajo_job_create', 'week_id', v_week_id)
|
|
);
|
|
END IF;
|
|
INSERT INTO destajo_cut_lines (period_id, job_id, qty_planned, qty_actual, qty_extra)
|
|
VALUES (v_period_id, v_job_id, v_qty_planned, 0, v_qty_extra);
|
|
PERFORM core._payroll_recalc_week(v_week_id);
|
|
RETURN core.rpc_created(
|
|
jsonb_build_object('id', v_job_id),
|
|
format('Trabajo de destajo %s creado para semana %s', v_job_id, v_week_id),
|
|
jsonb_build_object('fn', 'fn_destajo_job_create', 'id', v_job_id, 'week_id', v_week_id)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_destajo_job_create', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core.fn_destajo_cut_patch(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_week_id bigint := NULLIF(payload->>'week_id', '')::bigint;
|
|
v_cut_id bigint := NULLIF(payload->>'cut_id', '')::bigint;
|
|
v_week_status text;
|
|
v_cut record;
|
|
v_planned numeric;
|
|
v_actual numeric;
|
|
v_extra numeric;
|
|
BEGIN
|
|
IF v_week_id IS NULL OR v_cut_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_destajo_cut_patch: week_id y cut_id son obligatorios',
|
|
jsonb_build_object('fn', 'fn_destajo_cut_patch')
|
|
);
|
|
END IF;
|
|
SELECT status INTO v_week_status FROM payroll_weeks WHERE id = v_week_id;
|
|
IF NOT FOUND OR v_week_status = 'paid' THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_destajo_cut_patch: no se puede editar una semana pagada o inexistente',
|
|
jsonb_build_object('fn', 'fn_destajo_cut_patch', 'week_id', v_week_id)
|
|
);
|
|
END IF;
|
|
SELECT * INTO v_cut FROM destajo_cut_lines WHERE id = v_cut_id;
|
|
IF NOT FOUND THEN
|
|
RETURN core.rpc_err(
|
|
'NOT_FOUND',
|
|
format('fn_destajo_cut_patch: partida id=%s no encontrada', v_cut_id),
|
|
jsonb_build_object('fn', 'fn_destajo_cut_patch', 'cut_id', v_cut_id)
|
|
);
|
|
END IF;
|
|
v_planned := COALESCE((payload->>'qty_planned')::numeric, v_cut.qty_planned);
|
|
v_actual := COALESCE((payload->>'qty_actual')::numeric, v_cut.qty_actual);
|
|
v_extra := COALESCE((payload->>'qty_extra')::numeric, v_cut.qty_extra);
|
|
UPDATE destajo_cut_lines
|
|
SET qty_planned = v_planned, qty_actual = v_actual, qty_extra = v_extra
|
|
WHERE id = v_cut_id;
|
|
PERFORM core._payroll_recalc_week(v_week_id);
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('cut_id', v_cut_id, 'week_id', v_week_id),
|
|
format('Partida de destajo %s actualizada', v_cut_id),
|
|
jsonb_build_object('fn', 'fn_destajo_cut_patch', 'cut_id', v_cut_id, 'week_id', v_week_id)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_destajo_cut_patch', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
--changeset panel:core-013i-fn-payroll-admin-jornal endDelimiter:; splitStatements:true
|
|
CREATE OR REPLACE FUNCTION core.fn_payroll_admin_line_add(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_week_id bigint := NULLIF(payload->>'week_id', '')::bigint;
|
|
v_worker_id bigint := NULLIF(payload->>'worker_id', '')::bigint;
|
|
v_amount numeric := core.round_money(COALESCE((payload->>'amount')::numeric, 0));
|
|
v_project_id bigint := NULLIF(payload->>'project_id', '')::bigint;
|
|
v_week_status text;
|
|
v_sheet_id bigint;
|
|
v_line_id bigint;
|
|
BEGIN
|
|
IF v_week_id IS NULL OR v_worker_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_admin_line_add: week_id y worker_id son obligatorios',
|
|
jsonb_build_object('fn', 'fn_payroll_admin_line_add')
|
|
);
|
|
END IF;
|
|
SELECT status INTO v_week_status FROM payroll_weeks WHERE id = v_week_id;
|
|
IF NOT FOUND OR v_week_status = 'paid' THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_admin_line_add: no se puede editar una semana pagada o inexistente',
|
|
jsonb_build_object('fn', 'fn_payroll_admin_line_add', 'week_id', v_week_id)
|
|
);
|
|
END IF;
|
|
SELECT id INTO v_sheet_id
|
|
FROM payroll_sheets
|
|
WHERE week_id = v_week_id AND kind = 'admin' AND project_id IS NULL;
|
|
IF v_sheet_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'NOT_FOUND',
|
|
format('fn_payroll_admin_line_add: hoja administrativa no encontrada para semana %s', v_week_id),
|
|
jsonb_build_object('fn', 'fn_payroll_admin_line_add', 'week_id', v_week_id)
|
|
);
|
|
END IF;
|
|
v_line_id := core._payroll_sheet_line(v_sheet_id, v_worker_id, NULL);
|
|
UPDATE payroll_week_lines
|
|
SET amount = v_amount,
|
|
project_id = v_project_id,
|
|
gross = v_amount,
|
|
payable_net = v_amount
|
|
WHERE id = v_line_id;
|
|
PERFORM core._payroll_recalc_week(v_week_id);
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('line_id', v_line_id, 'week_id', v_week_id),
|
|
format('Línea administrativa %s agregada por %s', v_line_id, core._payroll_money_label(v_amount)),
|
|
jsonb_build_object('fn', 'fn_payroll_admin_line_add', 'line_id', v_line_id, 'week_id', v_week_id)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_payroll_admin_line_add', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core.fn_payroll_admin_line_update(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_week_id bigint := NULLIF(payload->>'week_id', '')::bigint;
|
|
v_line_id bigint := NULLIF(payload->>'line_id', '')::bigint;
|
|
v_amount numeric := core.round_money(COALESCE((payload->>'amount')::numeric, 0));
|
|
v_week_status text;
|
|
BEGIN
|
|
IF v_week_id IS NULL OR v_line_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_admin_line_update: week_id y line_id son obligatorios',
|
|
jsonb_build_object('fn', 'fn_payroll_admin_line_update')
|
|
);
|
|
END IF;
|
|
SELECT status INTO v_week_status FROM payroll_weeks WHERE id = v_week_id;
|
|
IF NOT FOUND OR v_week_status = 'paid' THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_admin_line_update: no se puede editar una semana pagada o inexistente',
|
|
jsonb_build_object('fn', 'fn_payroll_admin_line_update', 'week_id', v_week_id)
|
|
);
|
|
END IF;
|
|
UPDATE payroll_week_lines SET amount = v_amount WHERE id = v_line_id;
|
|
PERFORM core._payroll_recalc_week(v_week_id);
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('line_id', v_line_id, 'amount', v_amount),
|
|
format('Línea administrativa %s actualizada a %s', v_line_id, core._payroll_money_label(v_amount)),
|
|
jsonb_build_object('fn', 'fn_payroll_admin_line_update', 'line_id', v_line_id, 'week_id', v_week_id)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_payroll_admin_line_update', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core.fn_payroll_admin_line_remove(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_week_id bigint := NULLIF(payload->>'week_id', '')::bigint;
|
|
v_line_id bigint := NULLIF(payload->>'line_id', '')::bigint;
|
|
v_week_status text;
|
|
BEGIN
|
|
IF v_week_id IS NULL OR v_line_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_admin_line_remove: week_id y line_id son obligatorios',
|
|
jsonb_build_object('fn', 'fn_payroll_admin_line_remove')
|
|
);
|
|
END IF;
|
|
SELECT status INTO v_week_status FROM payroll_weeks WHERE id = v_week_id;
|
|
IF NOT FOUND OR v_week_status = 'paid' THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_admin_line_remove: no se puede editar una semana pagada o inexistente',
|
|
jsonb_build_object('fn', 'fn_payroll_admin_line_remove', 'week_id', v_week_id)
|
|
);
|
|
END IF;
|
|
DELETE FROM payroll_week_lines WHERE id = v_line_id;
|
|
PERFORM core._payroll_recalc_week(v_week_id);
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('line_id', v_line_id, 'week_id', v_week_id),
|
|
format('Línea administrativa %s eliminada de la semana %s', v_line_id, v_week_id),
|
|
jsonb_build_object('fn', 'fn_payroll_admin_line_remove', 'line_id', v_line_id, 'week_id', v_week_id)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_payroll_admin_line_remove', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION core.fn_payroll_jornal_worker_add(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_week_id bigint := NULLIF(payload->>'week_id', '')::bigint;
|
|
v_project_id bigint := NULLIF(payload->>'project_id', '')::bigint;
|
|
v_worker_id bigint := NULLIF(payload->>'worker_id', '')::bigint;
|
|
v_week_status text;
|
|
v_sheet_id bigint;
|
|
v_line_id bigint;
|
|
BEGIN
|
|
IF v_week_id IS NULL OR v_project_id IS NULL OR v_worker_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_jornal_worker_add: week_id, project_id y worker_id son obligatorios',
|
|
jsonb_build_object('fn', 'fn_payroll_jornal_worker_add')
|
|
);
|
|
END IF;
|
|
SELECT status INTO v_week_status FROM payroll_weeks WHERE id = v_week_id;
|
|
IF NOT FOUND OR v_week_status = 'paid' THEN
|
|
RETURN core.rpc_err(
|
|
'VALIDATION',
|
|
'fn_payroll_jornal_worker_add: no se puede editar una semana pagada o inexistente',
|
|
jsonb_build_object('fn', 'fn_payroll_jornal_worker_add', 'week_id', v_week_id)
|
|
);
|
|
END IF;
|
|
SELECT id INTO v_sheet_id
|
|
FROM payroll_sheets
|
|
WHERE week_id = v_week_id AND kind = 'obra' AND project_id = v_project_id;
|
|
IF v_sheet_id IS NULL THEN
|
|
RETURN core.rpc_err(
|
|
'NOT_FOUND',
|
|
format('fn_payroll_jornal_worker_add: hoja de obra no encontrada (semana %s, proyecto %s)', v_week_id, v_project_id),
|
|
jsonb_build_object('fn', 'fn_payroll_jornal_worker_add', 'week_id', v_week_id, 'project_id', v_project_id)
|
|
);
|
|
END IF;
|
|
v_line_id := core._payroll_sheet_line(v_sheet_id, v_worker_id, NULL);
|
|
PERFORM core._payroll_recalc_week(v_week_id);
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('line_id', v_line_id, 'week_id', v_week_id),
|
|
format('Trabajador %s agregado a jornal (proyecto %s, semana %s)', v_worker_id, v_project_id, v_week_id),
|
|
jsonb_build_object('fn', 'fn_payroll_jornal_worker_add', 'line_id', v_line_id, 'week_id', v_week_id)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_payroll_jornal_worker_add', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
--changeset panel:core-013j-fn-payroll-grants endDelimiter:; splitStatements:true
|
|
GRANT EXECUTE ON FUNCTION core.round_money(numeric) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_payroll_settings_get(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_payroll_settings_save(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_payroll_unit_list(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_payroll_unit_add(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_attendance_list(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_attendance_set(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_loan_create(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_payroll_week_ensure(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_payroll_week_recalc(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_payroll_week_assemble(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_payroll_week_pay(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_payroll_week_bundle(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_destajo_job_create(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_destajo_cut_patch(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_payroll_admin_line_add(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_payroll_admin_line_update(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_payroll_admin_line_remove(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_payroll_jornal_worker_add(jsonb) TO panels_core_app;
|