mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 11:23:18 +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>
427 lines
18 KiB
PL/PgSQL
427 lines
18 KiB
PL/PgSQL
--liquibase formatted sql
|
|
-- PANELS · core · facturas recibidas y emitidas
|
|
|
|
--changeset panel:core-043a-expense-source-invoice endDelimiter:; splitStatements:true
|
|
ALTER TABLE core.expense_entries DROP CONSTRAINT IF EXISTS expense_entries_source_check;
|
|
ALTER TABLE core.expense_entries
|
|
ADD CONSTRAINT expense_entries_source_check
|
|
CHECK (source IN ('manual', 'payroll', 'destajo', 'invoice', 'settlement'));
|
|
|
|
--changeset panel:core-043b-invoices endDelimiter:; splitStatements:true
|
|
--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='invoices'
|
|
CREATE TABLE core.invoices (
|
|
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
tenant_id INTEGER NOT NULL,
|
|
direction TEXT NOT NULL CHECK (direction IN ('recibida', 'emitida')),
|
|
supplier_id BIGINT REFERENCES core.suppliers(id) ON DELETE SET NULL,
|
|
supplier_name TEXT NOT NULL DEFAULT '',
|
|
supplier_rfc TEXT NOT NULL DEFAULT '',
|
|
uuid TEXT NOT NULL DEFAULT '',
|
|
serie TEXT NOT NULL DEFAULT '',
|
|
folio TEXT NOT NULL DEFAULT '',
|
|
invoice_date DATE NOT NULL,
|
|
subtotal NUMERIC(14,2) NOT NULL DEFAULT 0,
|
|
iva_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
|
|
total NUMERIC(14,2) NOT NULL DEFAULT 0,
|
|
currency TEXT NOT NULL DEFAULT 'MXN',
|
|
status TEXT NOT NULL DEFAULT 'registrada' CHECK (status IN ('borrador', 'registrada', 'clasificada', 'anulada')),
|
|
review_note TEXT NOT NULL DEFAULT '',
|
|
company_id BIGINT REFERENCES core.companies(id),
|
|
expense_id BIGINT REFERENCES core.expense_entries(id) ON DELETE SET NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE UNIQUE INDEX idx_core_invoices_uuid
|
|
ON core.invoices (tenant_id, uuid)
|
|
WHERE uuid <> '';
|
|
CREATE INDEX idx_core_invoices_tenant_date ON core.invoices (tenant_id, invoice_date DESC);
|
|
ALTER TABLE core.invoices ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON core.invoices
|
|
USING (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::integer)
|
|
WITH CHECK (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::integer);
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON core.invoices TO panels_core_app;
|
|
|
|
CREATE TABLE core.invoice_lines (
|
|
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
invoice_id BIGINT NOT NULL REFERENCES core.invoices(id) ON DELETE CASCADE,
|
|
description TEXT NOT NULL DEFAULT '',
|
|
quantity NUMERIC(14,4) NOT NULL DEFAULT 0,
|
|
unit TEXT NOT NULL DEFAULT '',
|
|
amount NUMERIC(14,2) NOT NULL DEFAULT 0,
|
|
project_id BIGINT REFERENCES core.projects(id) ON DELETE SET NULL,
|
|
budget_item_id BIGINT REFERENCES core.budget_items(id) ON DELETE SET NULL,
|
|
project_code TEXT NOT NULL DEFAULT '',
|
|
item_code TEXT NOT NULL DEFAULT ''
|
|
);
|
|
CREATE INDEX idx_core_invoice_lines_invoice ON core.invoice_lines (invoice_id);
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON core.invoice_lines TO panels_core_app;
|
|
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA core TO panels_core_app;
|
|
|
|
--changeset panel:core-043c-fn-invoice-list splitStatements:false
|
|
CREATE OR REPLACE FUNCTION core.fn_invoice_list(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_tid integer := core._cost_tenant_id(payload);
|
|
v_direction text := coalesce(nullif(btrim(payload->>'direction'), ''), 'recibida');
|
|
v_supplier_id bigint := NULLIF(payload->>'supplier_id', '')::bigint;
|
|
v_rfc text := upper(coalesce(nullif(btrim(payload->>'rfc'), ''), ''));
|
|
v_uuid text := upper(coalesce(nullif(btrim(payload->>'uuid'), ''), ''));
|
|
v_folio text := coalesce(nullif(btrim(payload->>'folio'), ''), '');
|
|
v_q text := lower(coalesce(nullif(btrim(payload->>'q'), ''), ''));
|
|
v_min numeric := NULLIF(payload->>'amount_min', '')::numeric;
|
|
v_max numeric := NULLIF(payload->>'amount_max', '')::numeric;
|
|
v_from date := NULLIF(payload->>'date_from', '')::date;
|
|
v_to date := NULLIF(payload->>'date_to', '')::date;
|
|
v_project_id bigint := NULLIF(payload->>'project_id', '')::bigint;
|
|
v_classified text := nullif(btrim(payload->>'classified'), '');
|
|
v_rows jsonb;
|
|
BEGIN
|
|
SELECT COALESCE(jsonb_agg(to_jsonb(x) ORDER BY x.invoice_date DESC, x.id DESC), '[]'::jsonb)
|
|
INTO v_rows
|
|
FROM (
|
|
SELECT i.*,
|
|
EXISTS (
|
|
SELECT 1 FROM invoice_lines l
|
|
WHERE l.invoice_id = i.id AND l.budget_item_id IS NOT NULL
|
|
) AS has_concept
|
|
FROM invoices i
|
|
WHERE i.tenant_id = v_tid
|
|
AND i.direction = v_direction
|
|
AND i.status <> 'anulada'
|
|
AND (v_supplier_id IS NULL OR i.supplier_id = v_supplier_id)
|
|
AND (v_rfc = '' OR i.supplier_rfc = v_rfc)
|
|
AND (v_uuid = '' OR i.uuid = v_uuid)
|
|
AND (v_folio = '' OR i.folio ILIKE '%' || v_folio || '%')
|
|
AND (v_min IS NULL OR i.total >= v_min)
|
|
AND (v_max IS NULL OR i.total <= v_max)
|
|
AND (v_from IS NULL OR i.invoice_date >= v_from)
|
|
AND (v_to IS NULL OR i.invoice_date <= v_to)
|
|
AND (
|
|
v_q = ''
|
|
OR lower(i.supplier_name) LIKE '%' || v_q || '%'
|
|
OR lower(i.supplier_rfc) LIKE '%' || v_q || '%'
|
|
OR lower(i.uuid) LIKE '%' || v_q || '%'
|
|
OR lower(i.folio) LIKE '%' || v_q || '%'
|
|
)
|
|
AND (
|
|
v_project_id IS NULL
|
|
OR EXISTS (SELECT 1 FROM invoice_lines l WHERE l.invoice_id = i.id AND l.project_id = v_project_id)
|
|
)
|
|
AND (
|
|
v_classified IS NULL
|
|
OR (v_classified = 'si' AND i.status = 'clasificada')
|
|
OR (v_classified = 'no' AND i.status <> 'clasificada')
|
|
)
|
|
) x;
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('invoices', v_rows, 'count', jsonb_array_length(v_rows)),
|
|
format('Facturas %s: %s registro(s)', v_direction, jsonb_array_length(v_rows)),
|
|
jsonb_build_object('fn', 'fn_invoice_list', 'tenant_id', v_tid)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_invoice_list', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
--changeset panel:core-043d-fn-invoice-get splitStatements:false
|
|
CREATE OR REPLACE FUNCTION core.fn_invoice_get(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_id bigint := NULLIF(payload->>'id', '')::bigint;
|
|
v_inv jsonb;
|
|
v_lines jsonb;
|
|
BEGIN
|
|
SELECT to_jsonb(i) INTO v_inv FROM invoices i WHERE i.id = v_id;
|
|
IF v_inv IS NULL THEN
|
|
RETURN core.rpc_err('NOT_FOUND', format('fn_invoice_get: factura id=%s no encontrada', v_id),
|
|
jsonb_build_object('fn', 'fn_invoice_get', 'id', v_id));
|
|
END IF;
|
|
SELECT COALESCE(jsonb_agg(to_jsonb(l) ORDER BY l.id), '[]'::jsonb)
|
|
INTO v_lines FROM invoice_lines l WHERE l.invoice_id = v_id;
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('invoice', v_inv, 'lines', v_lines),
|
|
format('Factura id=%s cargada', v_id),
|
|
jsonb_build_object('fn', 'fn_invoice_get', 'id', v_id)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_invoice_get', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
--changeset panel:core-043e-fn-invoice-import splitStatements:false
|
|
CREATE OR REPLACE FUNCTION core.fn_invoice_import(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_tid integer := core._cost_tenant_id(payload);
|
|
v_company_id bigint := NULLIF(payload->>'company_id', '')::bigint;
|
|
v_row jsonb;
|
|
v_key text;
|
|
v_groups jsonb := '{}'::jsonb;
|
|
v_group jsonb;
|
|
v_invoice_id bigint;
|
|
v_created integer := 0;
|
|
v_review integer := 0;
|
|
v_skipped integer := 0;
|
|
v_direction text;
|
|
v_rfc text;
|
|
v_uuid text;
|
|
v_serie text;
|
|
v_folio text;
|
|
v_date date;
|
|
v_name text;
|
|
v_supplier_id bigint;
|
|
v_supplier_name text;
|
|
v_status text;
|
|
v_note text;
|
|
v_subtotal numeric;
|
|
v_iva numeric;
|
|
v_total numeric;
|
|
v_project_id bigint;
|
|
v_item_id bigint;
|
|
v_line jsonb;
|
|
v_all_classified boolean;
|
|
v_expense_id bigint;
|
|
BEGIN
|
|
IF v_company_id IS NULL OR NOT EXISTS (SELECT 1 FROM companies WHERE id = v_company_id) THEN
|
|
RETURN core.rpc_err('VALIDATION', 'fn_invoice_import: company_id es obligatorio',
|
|
jsonb_build_object('fn', 'fn_invoice_import', 'field', 'company_id'));
|
|
END IF;
|
|
FOR v_row IN SELECT value FROM jsonb_array_elements(COALESCE(payload->'rows', '[]'::jsonb))
|
|
LOOP
|
|
v_direction := lower(coalesce(nullif(btrim(v_row->>'direction'), ''), 'recibida'));
|
|
IF v_direction IN ('emitida', 'emitidas', 'ingreso') THEN
|
|
v_direction := 'emitida';
|
|
ELSE
|
|
v_direction := 'recibida';
|
|
END IF;
|
|
v_rfc := upper(regexp_replace(coalesce(v_row->>'rfc', ''), '\s+', '', 'g'));
|
|
v_uuid := upper(regexp_replace(coalesce(v_row->>'uuid', ''), '\s+', '', 'g'));
|
|
v_serie := coalesce(btrim(v_row->>'serie'), '');
|
|
v_folio := coalesce(btrim(v_row->>'folio'), '');
|
|
v_date := NULLIF(btrim(v_row->>'invoice_date'), '')::date;
|
|
IF v_date IS NULL THEN
|
|
v_skipped := v_skipped + 1;
|
|
CONTINUE;
|
|
END IF;
|
|
v_key := v_direction || '|' || CASE
|
|
WHEN v_uuid <> '' THEN 'u:' || v_uuid
|
|
ELSE 'k:' || v_rfc || '|' || v_folio || '|' || v_date::text
|
|
END;
|
|
v_groups := jsonb_set(
|
|
v_groups,
|
|
ARRAY[v_key],
|
|
COALESCE(v_groups->v_key, '[]'::jsonb) || jsonb_build_array(v_row || jsonb_build_object('direction', v_direction, 'rfc', v_rfc, 'uuid', v_uuid)),
|
|
true
|
|
);
|
|
END LOOP;
|
|
|
|
FOR v_key, v_group IN SELECT key, value FROM jsonb_each(v_groups)
|
|
LOOP
|
|
v_row := v_group->0;
|
|
v_direction := v_row->>'direction';
|
|
v_rfc := v_row->>'rfc';
|
|
v_uuid := v_row->>'uuid';
|
|
v_serie := coalesce(v_row->>'serie', '');
|
|
v_folio := coalesce(v_row->>'folio', '');
|
|
v_date := (v_row->>'invoice_date')::date;
|
|
v_name := coalesce(nullif(btrim(v_row->>'supplier_name'), ''), '');
|
|
v_supplier_id := NULL;
|
|
v_supplier_name := v_name;
|
|
v_note := '';
|
|
v_status := 'registrada';
|
|
IF v_uuid <> '' AND EXISTS (
|
|
SELECT 1 FROM invoices WHERE tenant_id = v_tid AND uuid = v_uuid
|
|
) THEN
|
|
v_skipped := v_skipped + 1;
|
|
CONTINUE;
|
|
END IF;
|
|
IF v_rfc <> '' THEN
|
|
SELECT id, name INTO v_supplier_id, v_supplier_name
|
|
FROM suppliers
|
|
WHERE tenant_id = v_tid AND rfc = v_rfc AND status = 'activo'
|
|
LIMIT 1;
|
|
IF v_supplier_id IS NULL THEN
|
|
v_status := 'borrador';
|
|
v_note := 'RFC sin proveedor en el catálogo';
|
|
v_review := v_review + 1;
|
|
IF v_supplier_name = '' THEN
|
|
v_supplier_name := v_rfc;
|
|
END IF;
|
|
END IF;
|
|
END IF;
|
|
v_subtotal := 0;
|
|
v_iva := 0;
|
|
v_total := 0;
|
|
FOR v_line IN SELECT value FROM jsonb_array_elements(v_group)
|
|
LOOP
|
|
v_subtotal := v_subtotal + COALESCE((v_line->>'amount')::numeric, 0);
|
|
v_iva := v_iva + COALESCE((v_line->>'iva')::numeric, 0);
|
|
v_total := v_total + COALESCE(NULLIF(v_line->>'total', '')::numeric, COALESCE((v_line->>'amount')::numeric, 0) + COALESCE((v_line->>'iva')::numeric, 0));
|
|
END LOOP;
|
|
INSERT INTO invoices (
|
|
tenant_id, direction, supplier_id, supplier_name, supplier_rfc, uuid, serie, folio,
|
|
invoice_date, subtotal, iva_amount, total, status, review_note, company_id
|
|
) VALUES (
|
|
v_tid, v_direction, v_supplier_id, coalesce(v_supplier_name, ''), v_rfc, v_uuid, v_serie, v_folio,
|
|
v_date, core.round_money(v_subtotal), core.round_money(v_iva), core.round_money(v_total),
|
|
v_status, v_note, v_company_id
|
|
) RETURNING id INTO v_invoice_id;
|
|
v_all_classified := v_direction = 'recibida' AND v_status = 'registrada';
|
|
FOR v_line IN SELECT value FROM jsonb_array_elements(v_group)
|
|
LOOP
|
|
v_project_id := NULL;
|
|
v_item_id := NULL;
|
|
IF coalesce(btrim(v_line->>'project_code'), '') <> '' THEN
|
|
SELECT id INTO v_project_id FROM projects
|
|
WHERE tenant_id = v_tid AND lower(code) = lower(btrim(v_line->>'project_code'))
|
|
LIMIT 1;
|
|
END IF;
|
|
IF v_project_id IS NOT NULL AND coalesce(btrim(v_line->>'item_code'), '') <> '' THEN
|
|
SELECT id INTO v_item_id FROM budget_items
|
|
WHERE project_id = v_project_id AND lower(code) = lower(btrim(v_line->>'item_code'))
|
|
LIMIT 1;
|
|
END IF;
|
|
IF v_project_id IS NULL OR v_item_id IS NULL THEN
|
|
v_all_classified := false;
|
|
END IF;
|
|
INSERT INTO invoice_lines (
|
|
invoice_id, description, quantity, unit, amount, project_id, budget_item_id, project_code, item_code
|
|
) VALUES (
|
|
v_invoice_id,
|
|
coalesce(nullif(btrim(v_line->>'description'), ''), 'Partida'),
|
|
COALESCE((v_line->>'quantity')::numeric, 0),
|
|
coalesce(btrim(v_line->>'unit'), ''),
|
|
COALESCE((v_line->>'amount')::numeric, 0),
|
|
v_project_id, v_item_id,
|
|
coalesce(btrim(v_line->>'project_code'), ''),
|
|
coalesce(btrim(v_line->>'item_code'), '')
|
|
);
|
|
END LOOP;
|
|
IF v_all_classified AND v_status = 'registrada' THEN
|
|
v_item_id := (SELECT budget_item_id FROM invoice_lines WHERE invoice_id = v_invoice_id AND budget_item_id IS NOT NULL ORDER BY id LIMIT 1);
|
|
v_project_id := (SELECT project_id FROM invoice_lines WHERE invoice_id = v_invoice_id AND project_id IS NOT NULL ORDER BY id LIMIT 1);
|
|
INSERT INTO expense_entries (
|
|
tenant_id, company_id, project_id, expense_date, source, source_ref_id, description,
|
|
supplier_id, supplier_name, supplier_rfc, budget_item_id,
|
|
subtotal, iva_amount, total, status, notes
|
|
) VALUES (
|
|
v_tid, v_company_id, v_project_id, v_date, 'invoice', v_invoice_id,
|
|
format('Factura %s %s', NULLIF(v_serie, ''), COALESCE(NULLIF(v_folio, ''), v_uuid)),
|
|
v_supplier_id, coalesce(v_supplier_name, ''), v_rfc, v_item_id,
|
|
core.round_money(v_subtotal), core.round_money(v_iva), core.round_money(v_total),
|
|
'confirmed', 'Generado al importar factura clasificada'
|
|
) RETURNING id INTO v_expense_id;
|
|
UPDATE invoices SET status = 'clasificada', expense_id = v_expense_id WHERE id = v_invoice_id;
|
|
END IF;
|
|
v_created := v_created + 1;
|
|
END LOOP;
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('created', v_created, 'review', v_review, 'skipped', v_skipped),
|
|
format('Importación de facturas: %s creadas, %s en revisión, %s omitidas', v_created, v_review, v_skipped),
|
|
jsonb_build_object('fn', 'fn_invoice_import', 'created', v_created)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_invoice_import', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
--changeset panel:core-043f-fn-invoice-classify splitStatements:false
|
|
CREATE OR REPLACE FUNCTION core.fn_invoice_classify(payload jsonb)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY INVOKER
|
|
SET search_path = core
|
|
AS $$
|
|
DECLARE
|
|
v_tid integer := core._cost_tenant_id(payload);
|
|
v_id bigint := NULLIF(payload->>'id', '')::bigint;
|
|
v_line_id bigint := NULLIF(payload->>'line_id', '')::bigint;
|
|
v_project_id bigint := NULLIF(payload->>'project_id', '')::bigint;
|
|
v_item_id bigint := NULLIF(payload->>'budget_item_id', '')::bigint;
|
|
v_inv invoices%ROWTYPE;
|
|
v_expense_id bigint;
|
|
v_subtotal numeric;
|
|
v_iva numeric;
|
|
BEGIN
|
|
SELECT * INTO v_inv FROM invoices WHERE id = v_id AND tenant_id = v_tid;
|
|
IF NOT FOUND THEN
|
|
RETURN core.rpc_err('NOT_FOUND', format('fn_invoice_classify: factura id=%s no encontrada', v_id),
|
|
jsonb_build_object('fn', 'fn_invoice_classify', 'id', v_id));
|
|
END IF;
|
|
IF v_inv.direction <> 'recibida' THEN
|
|
RETURN core.rpc_err('VALIDATION', 'fn_invoice_classify: solo se clasifican facturas recibidas',
|
|
jsonb_build_object('fn', 'fn_invoice_classify', 'id', v_id));
|
|
END IF;
|
|
IF v_inv.status = 'anulada' THEN
|
|
RETURN core.rpc_err('VALIDATION', 'fn_invoice_classify: la factura está anulada',
|
|
jsonb_build_object('fn', 'fn_invoice_classify', 'id', v_id));
|
|
END IF;
|
|
IF v_line_id IS NOT NULL THEN
|
|
UPDATE invoice_lines
|
|
SET project_id = COALESCE(v_project_id, project_id),
|
|
budget_item_id = COALESCE(v_item_id, budget_item_id)
|
|
WHERE id = v_line_id AND invoice_id = v_id;
|
|
ELSE
|
|
UPDATE invoice_lines
|
|
SET project_id = COALESCE(v_project_id, project_id),
|
|
budget_item_id = COALESCE(v_item_id, budget_item_id)
|
|
WHERE invoice_id = v_id;
|
|
END IF;
|
|
IF EXISTS (
|
|
SELECT 1 FROM invoice_lines WHERE invoice_id = v_id AND (project_id IS NULL OR budget_item_id IS NULL)
|
|
) OR v_inv.supplier_id IS NULL THEN
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('id', v_id, 'status', v_inv.status),
|
|
'Factura actualizada; falta proveedor, obra o concepto para generar el gasto',
|
|
jsonb_build_object('fn', 'fn_invoice_classify', 'id', v_id)
|
|
);
|
|
END IF;
|
|
SELECT project_id, budget_item_id INTO v_project_id, v_item_id
|
|
FROM invoice_lines WHERE invoice_id = v_id ORDER BY id LIMIT 1;
|
|
v_subtotal := v_inv.subtotal;
|
|
v_iva := v_inv.iva_amount;
|
|
IF v_inv.expense_id IS NULL THEN
|
|
INSERT INTO expense_entries (
|
|
tenant_id, company_id, project_id, expense_date, source, source_ref_id, description,
|
|
supplier_id, supplier_name, supplier_rfc, budget_item_id,
|
|
subtotal, iva_amount, total, status, notes
|
|
) VALUES (
|
|
v_tid, v_inv.company_id, v_project_id, v_inv.invoice_date, 'invoice', v_id,
|
|
format('Factura %s', COALESCE(NULLIF(v_inv.folio, ''), NULLIF(v_inv.uuid, ''), v_id::text)),
|
|
v_inv.supplier_id, v_inv.supplier_name, v_inv.supplier_rfc, v_item_id,
|
|
v_subtotal, v_iva, v_inv.total, 'confirmed', 'Clasificación de factura recibida'
|
|
) RETURNING id INTO v_expense_id;
|
|
UPDATE invoices SET status = 'clasificada', expense_id = v_expense_id WHERE id = v_id;
|
|
ELSE
|
|
UPDATE expense_entries
|
|
SET project_id = v_project_id, budget_item_id = v_item_id
|
|
WHERE id = v_inv.expense_id AND status <> 'void';
|
|
v_expense_id := v_inv.expense_id;
|
|
UPDATE invoices SET status = 'clasificada' WHERE id = v_id;
|
|
END IF;
|
|
RETURN core.rpc_ok(
|
|
jsonb_build_object('id', v_id, 'status', 'clasificada', 'expense_id', v_expense_id),
|
|
format('Factura id=%s clasificada; gasto id=%s', v_id, v_expense_id),
|
|
jsonb_build_object('fn', 'fn_invoice_classify', 'id', v_id, 'expense_id', v_expense_id)
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RETURN core.rpc_from_exception('fn_invoice_classify', SQLSTATE, SQLERRM);
|
|
END;
|
|
$$;
|
|
|
|
--changeset panel:core-043g-grants endDelimiter:; splitStatements:true
|
|
GRANT EXECUTE ON FUNCTION core.fn_invoice_list(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_invoice_get(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_invoice_import(jsonb) TO panels_core_app;
|
|
GRANT EXECUTE ON FUNCTION core.fn_invoice_classify(jsonb) TO panels_core_app;
|