fix(sql): flujo completo de inventario sin duplicacion
- setpaymentstatusv2: pago no toca stock, cancelacion revierte stock - newexpensev2/updateexpensev4: ya no crean stock_movements - purchaseentry: actualiza id_stock_mov en purchase_detail - Nuevo tipo de movimiento Cancellation - Rollback completo incluido
This commit is contained in:
609
scripts_sql/fix_inventory_flow_complete.sql
Normal file
609
scripts_sql/fix_inventory_flow_complete.sql
Normal file
@@ -0,0 +1,609 @@
|
||||
-- ============================================================
|
||||
-- FIX COMPLETO: Flujo de inventario sin duplicacion
|
||||
-- Fecha: 2026-06-09
|
||||
-- Autor: Analisis automatico del sistema
|
||||
-- ============================================================
|
||||
-- PROBLEMA IDENTIFICADO:
|
||||
-- El stock se duplicaba al pagar un expense porque:
|
||||
-- 1. newexpensev2 y updateexpensev4 creaban movimientos de
|
||||
-- stock al crear/editar un expense (movimientos fantasma).
|
||||
-- 2. purchaseentry tambien creaba movimientos y actualizaba
|
||||
-- el stock al recibir fisicamente.
|
||||
-- 3. setpaymentstatusv2 sumaba de nuevo al stock al pagar.
|
||||
--
|
||||
-- ADEMAS:
|
||||
-- La unica forma de cancelar un expense es rechazando el pago
|
||||
-- (status = 3), pero setpaymentstatusv2 no revertia el stock
|
||||
-- al cancelar.
|
||||
--
|
||||
-- SOLUCION APLICADA:
|
||||
-- 1. newexpensev2 y updateexpensev4 ya NO tocan stock ni
|
||||
-- stock_movements. Solo manejan expenses y purchase_detail.
|
||||
-- 2. purchaseentry es la UNICA funcion que ingresa stock al
|
||||
-- recibir mercancia fisicamente.
|
||||
-- 3. setpaymentstatusv2(status = 1) solo paga, no toca stock.
|
||||
-- 4. setpaymentstatusv2(status = 3) revierte el stock si ya
|
||||
-- se habia recibido fisicamente (purchaseentry), creando
|
||||
-- un movimiento de tipo "Cancellation".
|
||||
-- 5. Se crea el tipo de movimiento "Cancellation" en
|
||||
-- movement_type si no existe.
|
||||
--
|
||||
-- NOTA: Este cambio es reversible. Si algo falla, ejecutar
|
||||
-- rollback_inventory_flow_complete.sql
|
||||
-- ============================================================
|
||||
|
||||
-- ============================================================
|
||||
-- 1. CREAR TIPO DE MOVIMIENTO "Cancellation" SI NO EXISTE
|
||||
-- ============================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM movement_type WHERE name_mov_type = 'Cancellation'
|
||||
) THEN
|
||||
INSERT INTO movement_type(name_mov_type) VALUES ('Cancellation');
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================
|
||||
-- 2. FIX: setpaymentstatusv2
|
||||
-- ============================================================
|
||||
-- Pagar (status = 1): solo actualiza payments.
|
||||
-- Cancelar/Rechazar pago (status = 3): revierte stock si hubo
|
||||
-- recepcion fisica previa (purchaseentry).
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE FUNCTION public.setpaymentstatusv2(
|
||||
expense integer,
|
||||
status integer)
|
||||
RETURNS integer
|
||||
LANGUAGE 'plpgsql'
|
||||
COST 100
|
||||
VOLATILE PARALLEL UNSAFE
|
||||
AS $BODY$
|
||||
DECLARE
|
||||
rows_updated INT := 0;
|
||||
details RECORD;
|
||||
approval INT := 0;
|
||||
stock_anterior BIGINT;
|
||||
stock_actual BIGINT;
|
||||
v_id_stock_mov INT;
|
||||
cancellation_type_id INT;
|
||||
BEGIN
|
||||
SELECT id_approval_stat INTO approval FROM expenses WHERE id_expense = expense;
|
||||
|
||||
-- Solo procesar si el gasto esta aprobado
|
||||
IF approval = 1 THEN
|
||||
|
||||
-- ============================================================
|
||||
-- PAGAR: status = 1
|
||||
-- ============================================================
|
||||
IF status = 1 THEN
|
||||
UPDATE payments
|
||||
SET id_payment_status = status,
|
||||
payment_date = CURRENT_DATE
|
||||
WHERE id_expense = expense;
|
||||
|
||||
GET DIAGNOSTICS rows_updated = ROW_COUNT;
|
||||
|
||||
IF rows_updated = 0 THEN
|
||||
RETURN -1;
|
||||
END IF;
|
||||
|
||||
-- NO se toca stock. purchaseentry ya lo hizo correctamente.
|
||||
RETURN status;
|
||||
|
||||
-- ============================================================
|
||||
-- CANCELAR/RECHAZAR PAGO: status = 3
|
||||
-- ============================================================
|
||||
ELSIF status = 3 THEN
|
||||
UPDATE payments
|
||||
SET id_payment_status = status,
|
||||
payment_date = CURRENT_DATE
|
||||
WHERE id_expense = expense;
|
||||
|
||||
GET DIAGNOSTICS rows_updated = ROW_COUNT;
|
||||
|
||||
IF rows_updated = 0 THEN
|
||||
RETURN -1;
|
||||
END IF;
|
||||
|
||||
-- Obtener id del tipo de movimiento Cancellation
|
||||
SELECT id_mov_type INTO cancellation_type_id
|
||||
FROM movement_type
|
||||
WHERE name_mov_type = 'Cancellation';
|
||||
|
||||
-- Revertir stock si ya se habia recibido fisicamente
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM purchase_detail pd
|
||||
WHERE pd.id_expense = expense
|
||||
AND pd.delivered > 0
|
||||
) THEN
|
||||
FOR details IN
|
||||
SELECT pd.id_product, pd.delivered
|
||||
FROM purchase_detail pd
|
||||
WHERE pd.id_expense = expense
|
||||
AND pd.delivered > 0
|
||||
LOOP
|
||||
SELECT stock INTO stock_anterior
|
||||
FROM stock
|
||||
WHERE id_product = details.id_product
|
||||
FOR UPDATE;
|
||||
|
||||
stock_actual := stock_anterior - details.delivered;
|
||||
|
||||
UPDATE stock
|
||||
SET stock = stock_actual
|
||||
WHERE id_product = details.id_product;
|
||||
|
||||
INSERT INTO stock_movements(
|
||||
id_product,
|
||||
id_mov_type,
|
||||
date_st_mov,
|
||||
id_expense,
|
||||
quantity,
|
||||
stock_before,
|
||||
stock_current
|
||||
)
|
||||
VALUES (
|
||||
details.id_product,
|
||||
cancellation_type_id,
|
||||
CURRENT_DATE,
|
||||
expense,
|
||||
details.delivered * (-1),
|
||||
stock_anterior,
|
||||
stock_actual
|
||||
)
|
||||
RETURNING id_sto_mov INTO v_id_stock_mov;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
RETURN status;
|
||||
|
||||
ELSE
|
||||
-- Status invalido
|
||||
RETURN -2;
|
||||
END IF;
|
||||
|
||||
ELSE
|
||||
-- No esta aprobado, no se puede actualizar el pago
|
||||
RETURN -3;
|
||||
END IF;
|
||||
END;
|
||||
$BODY$;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. FIX: newexpensev2
|
||||
-- ============================================================
|
||||
-- Ya NO crea stock_movements. Solo crea expenses y
|
||||
-- purchase_detail.
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE FUNCTION public.newexpensev2(
|
||||
new_description character varying,
|
||||
suppliers_id integer,
|
||||
new_request_date date,
|
||||
new_payment_deadline date,
|
||||
request_by integer,
|
||||
area integer,
|
||||
expense_cat integer,
|
||||
currency_id integer,
|
||||
needtoapprove boolean,
|
||||
products jsonb DEFAULT NULL::jsonb,
|
||||
new_subtotal numeric DEFAULT NULL::numeric,
|
||||
new_iva numeric DEFAULT NULL::numeric,
|
||||
new_ieps numeric DEFAULT NULL::numeric,
|
||||
new_total numeric DEFAULT NULL::numeric)
|
||||
RETURNS integer
|
||||
LANGUAGE 'plpgsql'
|
||||
COST 100
|
||||
VOLATILE PARALLEL UNSAFE
|
||||
AS $BODY$
|
||||
DECLARE
|
||||
new_expense INT;
|
||||
product JSONB;
|
||||
|
||||
approval_status INT := 3;
|
||||
approval_datevar DATE := NULL;
|
||||
|
||||
monedacambio NUMERIC := 1;
|
||||
fecha_busqueda DATE;
|
||||
|
||||
productsubtotal NUMERIC := 0;
|
||||
producttotal NUMERIC := 0;
|
||||
productsiva NUMERIC := 0;
|
||||
|
||||
subtotalcambiomoneda NUMERIC := 0;
|
||||
totalcambiomoneda NUMERIC := 0;
|
||||
ivacambiomoneda NUMERIC := 0;
|
||||
iepscambiomoneda NUMERIC := 0;
|
||||
BEGIN
|
||||
|
||||
IF needtoapprove THEN
|
||||
approval_status := 3;
|
||||
approval_datevar := NULL;
|
||||
ELSE
|
||||
approval_status := 1;
|
||||
approval_datevar := CURRENT_DATE;
|
||||
END IF;
|
||||
|
||||
-- Si la moneda es dolares (2), buscar tipo de cambio
|
||||
IF (currency_id = 2) THEN
|
||||
IF (new_payment_deadline < CURRENT_DATE) THEN
|
||||
fecha_busqueda := new_payment_deadline;
|
||||
ELSE
|
||||
fecha_busqueda := CURRENT_DATE;
|
||||
END IF;
|
||||
|
||||
LOOP
|
||||
SELECT onegetchange(fecha_busqueda) INTO monedacambio;
|
||||
EXIT WHEN monedacambio IS NOT NULL;
|
||||
fecha_busqueda := fecha_busqueda - INTERVAL '1 day';
|
||||
|
||||
IF fecha_busqueda < CURRENT_DATE - INTERVAL '30 day' THEN
|
||||
RAISE NOTICE 'No se encontro tipo de cambio valido en los ultimos 30 dias desde %', fecha_busqueda;
|
||||
monedacambio := 1;
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
-- Calcular totales de productos
|
||||
IF jsonb_array_length(products) > 0 THEN
|
||||
SELECT SUM((prod->>'quantity')::NUMERIC * (prod->>'unit_cost')::NUMERIC)
|
||||
INTO productsubtotal
|
||||
FROM jsonb_array_elements(products) AS prod;
|
||||
|
||||
SELECT SUM((prod->>'quantity')::NUMERIC * (prod->>'unit_cost')::NUMERIC * t.number)
|
||||
INTO productsiva
|
||||
FROM jsonb_array_elements(products) AS prod
|
||||
LEFT JOIN taxes t ON t.id_tax = (prod->>'id_tax')::INT;
|
||||
|
||||
SELECT SUM((prod->>'quantity')::NUMERIC * (prod->>'unit_cost')::NUMERIC * (1 + t.number))
|
||||
INTO producttotal
|
||||
FROM jsonb_array_elements(products) AS prod
|
||||
LEFT JOIN taxes t ON t.id_tax = (prod->>'id_tax')::INT;
|
||||
|
||||
producttotal := producttotal + COALESCE(new_ieps, 0);
|
||||
ELSE
|
||||
productsubtotal := new_subtotal;
|
||||
producttotal := new_total;
|
||||
productsiva := new_iva;
|
||||
END IF;
|
||||
|
||||
-- Aplicar cambio de moneda
|
||||
IF (currency_id = 2) THEN
|
||||
subtotalcambiomoneda := productsubtotal * monedacambio;
|
||||
ivacambiomoneda := productsiva * monedacambio;
|
||||
iepscambiomoneda := new_ieps * monedacambio;
|
||||
totalcambiomoneda := producttotal * monedacambio;
|
||||
ELSE
|
||||
subtotalcambiomoneda := productsubtotal;
|
||||
totalcambiomoneda := producttotal;
|
||||
ivacambiomoneda := productsiva;
|
||||
iepscambiomoneda := new_ieps;
|
||||
END IF;
|
||||
|
||||
INSERT INTO expenses(
|
||||
id_properties, id_expense_type, id_recurrence,
|
||||
description, id_suppliers, request_date,
|
||||
payment_deadline, approval_date, id_request_by,
|
||||
id_approval_by, id_area, id_expense_cat,
|
||||
id_approval_stat, id_currency,
|
||||
subtotal, iva, ieps, total
|
||||
)
|
||||
VALUES (
|
||||
1, 2, 3,
|
||||
new_description, suppliers_id,
|
||||
new_request_date, new_payment_deadline,
|
||||
approval_datevar, request_by,
|
||||
null, area, expense_cat,
|
||||
approval_status, currency_id,
|
||||
COALESCE(subtotalcambiomoneda, 0),
|
||||
COALESCE(ivacambiomoneda, 0),
|
||||
COALESCE(iepscambiomoneda, 0),
|
||||
COALESCE(totalcambiomoneda, 0)
|
||||
)
|
||||
RETURNING id_expense INTO new_expense;
|
||||
|
||||
INSERT INTO payments(id_expense, id_payment_status, payment_date)
|
||||
VALUES (new_expense, 2, NULL);
|
||||
|
||||
-- Crear purchase_detail. NO se crea stock_movements aqui.
|
||||
IF products IS NOT NULL AND jsonb_array_length(products) > 0 THEN
|
||||
FOR product IN SELECT jsonb_array_elements(products)
|
||||
LOOP
|
||||
INSERT INTO purchase_detail (
|
||||
id_expense, id_product, quantity, id_tax,
|
||||
unit_cost, total, id_stock_mov
|
||||
)
|
||||
VALUES (
|
||||
new_expense,
|
||||
(product->>'id_product')::INT,
|
||||
(product->>'quantity')::INT,
|
||||
(product->>'id_tax')::INT,
|
||||
((product->>'unit_cost')::NUMERIC * monedacambio),
|
||||
COALESCE(
|
||||
(SELECT
|
||||
((product->>'quantity')::NUMERIC *
|
||||
(product->>'unit_cost')::NUMERIC *
|
||||
(1 + COALESCE(t.number, 0))) * monedacambio
|
||||
FROM taxes t
|
||||
WHERE t.id_tax = (product->>'id_tax')::INT),
|
||||
0
|
||||
),
|
||||
NULL -- id_stock_mov se asigna en purchaseentry
|
||||
);
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
RETURN new_expense;
|
||||
END;
|
||||
$BODY$;
|
||||
|
||||
-- ============================================================
|
||||
-- 4. FIX: updateexpensev4
|
||||
-- ============================================================
|
||||
-- Ya NO crea ni elimina stock_movements. Solo actualiza
|
||||
-- expenses y purchase_detail.
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE FUNCTION public.updateexpensev4(
|
||||
expense_id integer,
|
||||
up_description character varying,
|
||||
suppliers_id integer,
|
||||
up_req_date date,
|
||||
up_deadline date,
|
||||
up_currency integer,
|
||||
up_request_by integer,
|
||||
up_area integer,
|
||||
up_category integer,
|
||||
needtoapprove boolean,
|
||||
up_subtotal numeric DEFAULT NULL::numeric,
|
||||
up_iva numeric DEFAULT NULL::numeric,
|
||||
up_ieps numeric DEFAULT NULL::numeric,
|
||||
up_total numeric DEFAULT NULL::numeric,
|
||||
products jsonb DEFAULT NULL::jsonb)
|
||||
RETURNS integer
|
||||
LANGUAGE 'plpgsql'
|
||||
COST 100
|
||||
VOLATILE PARALLEL UNSAFE
|
||||
AS $BODY$
|
||||
DECLARE
|
||||
product JSONB;
|
||||
approval_status INT := 3;
|
||||
approval_datevar date := NULL;
|
||||
|
||||
monedacambio NUMERIC := 1;
|
||||
fecha_busqueda DATE;
|
||||
|
||||
productsubtotal NUMERIC := 0;
|
||||
producttotal NUMERIC := 0;
|
||||
productsiva NUMERIC := 0;
|
||||
|
||||
subtotalcambiomoneda NUMERIC := 0;
|
||||
totalcambiomoneda NUMERIC := 0;
|
||||
ivacambiomoneda NUMERIC := 0;
|
||||
iepscambiomoneda NUMERIC := 0;
|
||||
BEGIN
|
||||
|
||||
IF needtoapprove THEN
|
||||
approval_status := 3;
|
||||
approval_datevar := NULL;
|
||||
ELSE
|
||||
approval_status := 1;
|
||||
approval_datevar := CURRENT_DATE;
|
||||
END IF;
|
||||
|
||||
-- Si la moneda es dolares (2), buscar tipo de cambio
|
||||
IF (up_currency = 2) THEN
|
||||
IF (up_deadline < CURRENT_DATE) THEN
|
||||
fecha_busqueda := up_deadline;
|
||||
ELSE
|
||||
fecha_busqueda := CURRENT_DATE;
|
||||
END IF;
|
||||
|
||||
LOOP
|
||||
SELECT onegetchange(fecha_busqueda) INTO monedacambio;
|
||||
EXIT WHEN monedacambio IS NOT NULL;
|
||||
fecha_busqueda := fecha_busqueda - INTERVAL '1 day';
|
||||
|
||||
IF fecha_busqueda < CURRENT_DATE - INTERVAL '30 day' THEN
|
||||
RAISE NOTICE 'No se encontro tipo de cambio valido en los ultimos 30 dias desde %', fecha_busqueda;
|
||||
monedacambio := 1;
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
-- Calcular totales de productos
|
||||
IF jsonb_array_length(products) > 0 THEN
|
||||
SELECT SUM((prod->>'quantity')::NUMERIC * (prod->>'unit_cost')::NUMERIC)
|
||||
INTO productsubtotal
|
||||
FROM jsonb_array_elements(products) AS prod;
|
||||
|
||||
SELECT SUM((prod->>'quantity')::NUMERIC * (prod->>'unit_cost')::NUMERIC * t.number)
|
||||
INTO productsiva
|
||||
FROM jsonb_array_elements(products) AS prod
|
||||
LEFT JOIN taxes t ON t.id_tax = (prod->>'id_tax')::INT;
|
||||
|
||||
SELECT SUM((prod->>'quantity')::NUMERIC * (prod->>'unit_cost')::NUMERIC * (1 + t.number))
|
||||
INTO producttotal
|
||||
FROM jsonb_array_elements(products) AS prod
|
||||
LEFT JOIN taxes t ON t.id_tax = (prod->>'id_tax')::INT;
|
||||
|
||||
producttotal := producttotal + COALESCE(up_ieps, 0);
|
||||
ELSE
|
||||
productsubtotal := up_subtotal;
|
||||
producttotal := up_total;
|
||||
productsiva := up_iva;
|
||||
END IF;
|
||||
|
||||
-- Aplicar cambio de moneda
|
||||
IF (up_currency = 2) THEN
|
||||
subtotalcambiomoneda := productsubtotal * monedacambio;
|
||||
ivacambiomoneda := productsiva * monedacambio;
|
||||
iepscambiomoneda := up_ieps * monedacambio;
|
||||
totalcambiomoneda := producttotal * monedacambio;
|
||||
ELSE
|
||||
subtotalcambiomoneda := productsubtotal;
|
||||
totalcambiomoneda := producttotal;
|
||||
ivacambiomoneda := productsiva;
|
||||
iepscambiomoneda := up_ieps;
|
||||
END IF;
|
||||
|
||||
-- Actualizar la cabecera del gasto
|
||||
UPDATE expenses
|
||||
SET
|
||||
description = up_description,
|
||||
id_suppliers = suppliers_id,
|
||||
request_date = up_req_date,
|
||||
payment_deadline = up_deadline,
|
||||
id_currency = up_currency,
|
||||
id_request_by = up_request_by,
|
||||
id_area = up_area,
|
||||
id_expense_cat = up_category,
|
||||
approval_date = approval_datevar,
|
||||
id_approval_stat = approval_status,
|
||||
subtotal = COALESCE(subtotalcambiomoneda, 0),
|
||||
iva = COALESCE(ivacambiomoneda, 0),
|
||||
ieps = COALESCE(iepscambiomoneda, 0),
|
||||
total = COALESCE(totalcambiomoneda, 0)
|
||||
WHERE id_expense = expense_id;
|
||||
|
||||
-- Manejar detalle de productos
|
||||
IF products IS NOT NULL THEN
|
||||
-- Eliminar purchase_detail que ya no vienen en el JSON
|
||||
DELETE FROM purchase_detail pd
|
||||
WHERE pd.id_expense = expense_id
|
||||
AND pd.id_product NOT IN (
|
||||
SELECT (p->>'id_product')::INT
|
||||
FROM jsonb_array_elements(products) AS p
|
||||
);
|
||||
|
||||
-- Actualizar o insertar cada producto del JSON
|
||||
FOR product IN SELECT jsonb_array_elements(products)
|
||||
LOOP
|
||||
UPDATE purchase_detail
|
||||
SET
|
||||
quantity = (product->>'quantity')::INT,
|
||||
id_tax = (product->>'id_tax')::INT,
|
||||
unit_cost = ((product->>'unit_cost')::NUMERIC * monedacambio),
|
||||
total = COALESCE(
|
||||
(SELECT ((product->>'quantity')::NUMERIC *
|
||||
(product->>'unit_cost')::NUMERIC *
|
||||
(1 + COALESCE(t.number, 0))) * monedacambio
|
||||
FROM taxes t
|
||||
WHERE t.id_tax = (product->>'id_tax')::INT),
|
||||
0
|
||||
)
|
||||
WHERE id_expense = expense_id
|
||||
AND id_product = (product->>'id_product')::INT;
|
||||
|
||||
-- Si no actualizo nada (no existia), insertar
|
||||
IF NOT FOUND THEN
|
||||
INSERT INTO purchase_detail (
|
||||
id_expense, id_product, quantity, id_tax,
|
||||
unit_cost, total, id_stock_mov
|
||||
)
|
||||
VALUES (
|
||||
expense_id,
|
||||
(product->>'id_product')::INT,
|
||||
(product->>'quantity')::INT,
|
||||
(product->>'id_tax')::INT,
|
||||
((product->>'unit_cost')::NUMERIC * monedacambio),
|
||||
COALESCE(
|
||||
(SELECT ((product->>'quantity')::NUMERIC *
|
||||
(product->>'unit_cost')::NUMERIC *
|
||||
(1 + COALESCE(t.number, 0))) * monedacambio
|
||||
FROM taxes t
|
||||
WHERE t.id_tax = (product->>'id_tax')::INT),
|
||||
0
|
||||
),
|
||||
NULL
|
||||
);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
RETURN 1;
|
||||
END;
|
||||
$BODY$;
|
||||
|
||||
-- ============================================================
|
||||
-- 5. FIX: purchaseentry
|
||||
-- ============================================================
|
||||
-- Actualiza purchase_detail.id_stock_mov con el movimiento
|
||||
-- recien creado, para mantener integridad referencial.
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE FUNCTION public.purchaseentry(
|
||||
purchase_id integer,
|
||||
checking integer)
|
||||
RETURNS integer
|
||||
LANGUAGE 'plpgsql'
|
||||
COST 100
|
||||
VOLATILE PARALLEL UNSAFE
|
||||
AS $BODY$
|
||||
DECLARE
|
||||
v_id_product INT;
|
||||
v_id_expense INT;
|
||||
v_quantity INT;
|
||||
v_unit_cost NUMERIC;
|
||||
v_delivered INT;
|
||||
stock_anterior BIGINT;
|
||||
stock_actual BIGINT;
|
||||
v_id_stock_mov INT;
|
||||
v_quantity_received INT;
|
||||
BEGIN
|
||||
-- Recuperamos la informacion de purchase_detail
|
||||
SELECT id_product, id_expense, quantity, unit_cost, delivered
|
||||
INTO v_id_product, v_id_expense, v_quantity, v_unit_cost, v_delivered
|
||||
FROM purchase_detail
|
||||
WHERE id_purchase_dt = purchase_id;
|
||||
|
||||
-- Cantidad efectivamente recibida en este movimiento
|
||||
v_quantity_received := LEAST(checking, v_quantity - COALESCE(v_delivered, 0));
|
||||
|
||||
IF v_quantity_received <= 0 THEN
|
||||
RETURN 0; -- Ya se recibio todo o cantidad invalida
|
||||
END IF;
|
||||
|
||||
UPDATE purchase_detail
|
||||
SET delivered = LEAST(COALESCE(delivered, 0) + checking, quantity)
|
||||
WHERE id_purchase_dt = purchase_id;
|
||||
|
||||
SELECT stock INTO stock_anterior
|
||||
FROM stock WHERE id_product = v_id_product FOR UPDATE;
|
||||
|
||||
stock_actual := stock_anterior + v_quantity_received;
|
||||
|
||||
INSERT INTO stock_movements(
|
||||
id_product, id_mov_type, date_st_mov, id_purchase_dt,
|
||||
id_expense, quantity, stock_before, stock_current
|
||||
)
|
||||
VALUES (
|
||||
v_id_product, 1, CURRENT_DATE, purchase_id,
|
||||
v_id_expense, v_quantity_received, stock_anterior, stock_actual
|
||||
)
|
||||
RETURNING id_sto_mov INTO v_id_stock_mov;
|
||||
|
||||
UPDATE stock
|
||||
SET
|
||||
average_cost = CASE
|
||||
WHEN average_cost IS NULL THEN v_unit_cost
|
||||
ELSE (
|
||||
((average_cost * stock) + (v_quantity_received * v_unit_cost))
|
||||
/ (stock + v_quantity_received)
|
||||
)
|
||||
END,
|
||||
stock = stock_actual
|
||||
WHERE id_product = v_id_product;
|
||||
|
||||
-- Asignar el id del movimiento al purchase_detail
|
||||
UPDATE purchase_detail
|
||||
SET id_stock_mov = v_id_stock_mov
|
||||
WHERE id_purchase_dt = purchase_id;
|
||||
|
||||
RETURN 1;
|
||||
END;
|
||||
$BODY$;
|
||||
|
||||
-- Confirmacion
|
||||
SELECT 'Fix completo de flujo de inventario aplicado correctamente' AS resultado;
|
||||
506
scripts_sql/rollback_inventory_flow_complete.sql
Normal file
506
scripts_sql/rollback_inventory_flow_complete.sql
Normal file
@@ -0,0 +1,506 @@
|
||||
-- ============================================================
|
||||
-- ROLLBACK COMPLETO: Restaurar flujo de inventario original
|
||||
-- Fecha: 2026-06-09
|
||||
-- ============================================================
|
||||
-- Usar este script SOLO si el fix completo causa problemas
|
||||
-- y se necesita restaurar el comportamiento anterior.
|
||||
-- ============================================================
|
||||
|
||||
-- ============================================================
|
||||
-- 1. RESTAURAR setpaymentstatusv2
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE FUNCTION public.setpaymentstatusv2(
|
||||
expense integer,
|
||||
status integer)
|
||||
RETURNS integer
|
||||
LANGUAGE 'plpgsql'
|
||||
COST 100
|
||||
VOLATILE PARALLEL UNSAFE
|
||||
AS $BODY$
|
||||
DECLARE
|
||||
rows_updated INT := 0;
|
||||
details RECORD;
|
||||
approval int :=0;
|
||||
BEGIN
|
||||
SELECT id_approval_stat INTO approval FROM expenses where id_expense = expense;
|
||||
IF(approval = 1 ) --si el gasto esta aprobado se procede al pago
|
||||
THEN
|
||||
-- Validar estados permitidos
|
||||
IF status = 1 THEN
|
||||
UPDATE payments
|
||||
SET id_payment_status = status,
|
||||
payment_date = CURRENT_DATE
|
||||
WHERE id_expense = expense;
|
||||
GET DIAGNOSTICS rows_updated = ROW_COUNT;
|
||||
|
||||
IF rows_updated = 0 THEN
|
||||
RETURN -1;
|
||||
END IF;
|
||||
|
||||
-- Procesar solo si existen movimientos de stock
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM stock_movements st
|
||||
INNER JOIN purchase_detail pd ON st.id_expense = pd.id_expense
|
||||
WHERE pd.id_expense = expense
|
||||
) THEN
|
||||
|
||||
-- Actualizar movimiento
|
||||
UPDATE stock_movements
|
||||
SET date_st_mov = CURRENT_DATE
|
||||
WHERE id_expense = expense;
|
||||
|
||||
-- Actualizar ajuste
|
||||
UPDATE stock_adjusments
|
||||
SET date_adjusment = CURRENT_DATE
|
||||
WHERE id_expense = expense;
|
||||
|
||||
-- Actualizar stock
|
||||
FOR details IN
|
||||
SELECT id_product, quantity
|
||||
FROM purchase_detail
|
||||
WHERE id_expense = expense
|
||||
LOOP
|
||||
UPDATE stock
|
||||
SET stock = stock + details.quantity
|
||||
WHERE id_product = details.id_product;
|
||||
END LOOP;
|
||||
END IF;
|
||||
RETURN status;
|
||||
|
||||
ELSIF status = 3 THEN
|
||||
-- Actualizar pago
|
||||
UPDATE payments
|
||||
SET id_payment_status = status,
|
||||
payment_date = CURRENT_DATE
|
||||
WHERE id_expense = expense;
|
||||
GET DIAGNOSTICS rows_updated = ROW_COUNT;
|
||||
|
||||
IF rows_updated = 0 THEN
|
||||
RETURN -1;
|
||||
END IF;
|
||||
|
||||
RETURN status;
|
||||
|
||||
ELSE
|
||||
-- Status invalido
|
||||
RETURN -2;
|
||||
END IF;
|
||||
ELSE
|
||||
return -3; --si no esta aprobado o rechazado no se puede actualizar el pago
|
||||
END IF;
|
||||
END;
|
||||
$BODY$;
|
||||
|
||||
-- ============================================================
|
||||
-- 2. RESTAURAR newexpensev2
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE FUNCTION public.newexpensev2(
|
||||
new_description character varying,
|
||||
suppliers_id integer,
|
||||
new_request_date date,
|
||||
new_payment_deadline date,
|
||||
request_by integer,
|
||||
area integer,
|
||||
expense_cat integer,
|
||||
currency_id integer,
|
||||
needtoapprove boolean,
|
||||
products jsonb DEFAULT NULL::jsonb,
|
||||
new_subtotal numeric DEFAULT NULL::numeric,
|
||||
new_iva numeric DEFAULT NULL::numeric,
|
||||
new_ieps numeric DEFAULT NULL::numeric,
|
||||
new_total numeric DEFAULT NULL::numeric)
|
||||
RETURNS integer
|
||||
LANGUAGE 'plpgsql'
|
||||
COST 100
|
||||
VOLATILE PARALLEL UNSAFE
|
||||
AS $BODY$
|
||||
DECLARE
|
||||
new_expense INT;
|
||||
product JSONB;
|
||||
|
||||
approval_status INT := 3;
|
||||
approval_datevar DATE := NULL;
|
||||
|
||||
monedacambio NUMERIC := 1;
|
||||
fecha_busqueda DATE;
|
||||
mov_id INT;
|
||||
|
||||
productsubtotal NUMERIC := 0;
|
||||
producttotal NUMERIC := 0;
|
||||
productsiva NUMERIC := 0;
|
||||
|
||||
subtotalcambiomoneda NUMERIC := 0;
|
||||
totalcambiomoneda NUMERIC := 0;
|
||||
ivacambiomoneda NUMERIC := 0;
|
||||
iepscambiomoneda NUMERIC := 0;
|
||||
|
||||
BEGIN
|
||||
|
||||
IF needtoapprove THEN
|
||||
approval_status := 3;
|
||||
approval_datevar := NULL;
|
||||
ELSE
|
||||
approval_status := 1;
|
||||
approval_datevar := CURRENT_DATE;
|
||||
END IF;
|
||||
|
||||
IF (currency_id = 2) THEN
|
||||
IF (new_payment_deadline < CURRENT_DATE) THEN
|
||||
fecha_busqueda := new_payment_deadline;
|
||||
ELSE
|
||||
fecha_busqueda := CURRENT_DATE;
|
||||
END IF;
|
||||
|
||||
LOOP
|
||||
SELECT onegetchange(fecha_busqueda) INTO monedacambio;
|
||||
EXIT WHEN monedacambio IS NOT NULL;
|
||||
fecha_busqueda := fecha_busqueda - INTERVAL '1 day';
|
||||
|
||||
IF fecha_busqueda < CURRENT_DATE - INTERVAL '30 day' THEN
|
||||
RAISE NOTICE 'No se encontro tipo de cambio valido en los ultimos 30 dias desde %', fecha_busqueda;
|
||||
monedacambio := 1;
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
IF jsonb_array_length(products) > 0 THEN
|
||||
SELECT SUM((prod->>'quantity')::NUMERIC * (prod->>'unit_cost')::NUMERIC)
|
||||
INTO productsubtotal
|
||||
FROM jsonb_array_elements(products) AS prod;
|
||||
|
||||
SELECT SUM((prod->>'quantity')::NUMERIC * (prod->>'unit_cost')::NUMERIC * t.number)
|
||||
INTO productsiva
|
||||
FROM jsonb_array_elements(products) AS prod
|
||||
LEFT JOIN taxes t ON t.id_tax = (prod->>'id_tax')::INT;
|
||||
|
||||
SELECT SUM((prod->>'quantity')::NUMERIC * (prod->>'unit_cost')::NUMERIC * (1 + t.number))
|
||||
INTO producttotal
|
||||
FROM jsonb_array_elements(products) AS prod
|
||||
LEFT JOIN taxes t ON t.id_tax = (prod->>'id_tax')::INT;
|
||||
|
||||
producttotal := producttotal + COALESCE(new_ieps, 0);
|
||||
ELSE
|
||||
productsubtotal := new_subtotal;
|
||||
producttotal := new_total;
|
||||
productsiva := new_iva;
|
||||
END IF;
|
||||
|
||||
IF (currency_id = 2) THEN
|
||||
subtotalcambiomoneda := productsubtotal * monedacambio;
|
||||
ivacambiomoneda := productsiva * monedacambio;
|
||||
iepscambiomoneda := new_ieps * monedacambio;
|
||||
totalcambiomoneda := producttotal * monedacambio;
|
||||
ELSE
|
||||
subtotalcambiomoneda := productsubtotal;
|
||||
totalcambiomoneda := producttotal;
|
||||
ivacambiomoneda := productsiva;
|
||||
iepscambiomoneda := new_ieps;
|
||||
END IF;
|
||||
|
||||
INSERT INTO expenses(
|
||||
id_properties, id_expense_type, id_recurrence,
|
||||
description, id_suppliers, request_date,
|
||||
payment_deadline, approval_date, id_request_by,
|
||||
id_approval_by, id_area, id_expense_cat,
|
||||
id_approval_stat, id_currency,
|
||||
subtotal, iva, ieps, total
|
||||
)
|
||||
VALUES (
|
||||
1, 2, 3,
|
||||
new_description, suppliers_id,
|
||||
new_request_date, new_payment_deadline,
|
||||
approval_datevar, request_by,
|
||||
null, area, expense_cat,
|
||||
approval_status, currency_id,
|
||||
COALESCE(subtotalcambiomoneda, 0),
|
||||
COALESCE(ivacambiomoneda, 0),
|
||||
COALESCE(iepscambiomoneda, 0),
|
||||
COALESCE(totalcambiomoneda, 0)
|
||||
)
|
||||
RETURNING id_expense INTO new_expense;
|
||||
|
||||
INSERT INTO payments(id_expense, id_payment_status, payment_date)
|
||||
VALUES (new_expense, 2, NULL);
|
||||
|
||||
IF products IS NOT NULL AND jsonb_array_length(products) > 0 THEN
|
||||
FOR product IN SELECT jsonb_array_elements(products)
|
||||
LOOP
|
||||
INSERT INTO stock_movements(id_product,id_mov_type,date_st_mov,id_expense)
|
||||
VALUES((product->>'id_product')::INT,1,new_request_date,new_expense)
|
||||
RETURNING id_sto_mov INTO mov_id;
|
||||
|
||||
INSERT INTO purchase_detail (id_expense, id_product, quantity, id_tax, unit_cost, total,id_stock_mov)
|
||||
VALUES (
|
||||
new_expense,
|
||||
(product->>'id_product')::INT,
|
||||
(product->>'quantity')::INT,
|
||||
(product->>'id_tax')::INT,
|
||||
((product->>'unit_cost')::NUMERIC * monedacambio),
|
||||
COALESCE(
|
||||
(SELECT
|
||||
((product->>'quantity')::NUMERIC *
|
||||
(product->>'unit_cost')::NUMERIC *
|
||||
(1 + COALESCE(t.number, 0))) * monedacambio
|
||||
FROM taxes t
|
||||
WHERE t.id_tax = (product->>'id_tax')::INT),
|
||||
0
|
||||
),mov_id
|
||||
);
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
RETURN new_expense;
|
||||
END;
|
||||
$BODY$;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. RESTAURAR updateexpensev4
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE FUNCTION public.updateexpensev4(
|
||||
expense_id integer,
|
||||
up_description character varying,
|
||||
suppliers_id integer,
|
||||
up_req_date date,
|
||||
up_deadline date,
|
||||
up_currency integer,
|
||||
up_request_by integer,
|
||||
up_area integer,
|
||||
up_category integer,
|
||||
needtoapprove boolean,
|
||||
up_subtotal numeric DEFAULT NULL::numeric,
|
||||
up_iva numeric DEFAULT NULL::numeric,
|
||||
up_ieps numeric DEFAULT NULL::numeric,
|
||||
up_total numeric DEFAULT NULL::numeric,
|
||||
products jsonb DEFAULT NULL::jsonb)
|
||||
RETURNS integer
|
||||
LANGUAGE 'plpgsql'
|
||||
COST 100
|
||||
VOLATILE PARALLEL UNSAFE
|
||||
AS $BODY$
|
||||
DECLARE
|
||||
product JSONB;
|
||||
approval_status INT := 3;
|
||||
approval_datevar date := NULL;
|
||||
|
||||
monedacambio NUMERIC := 1;
|
||||
fecha_busqueda DATE;
|
||||
mov_id int;
|
||||
|
||||
productsubtotal NUMERIC := 0;
|
||||
producttotal NUMERIC := 0;
|
||||
productsiva NUMERIC := 0;
|
||||
|
||||
subtotalcambiomoneda NUMERIC := 0;
|
||||
totalcambiomoneda NUMERIC := 0;
|
||||
ivacambiomoneda NUMERIC := 0;
|
||||
iepscambiomoneda NUMERIC := 0;
|
||||
BEGIN
|
||||
|
||||
IF needtoapprove THEN
|
||||
approval_status := 3;
|
||||
approval_datevar := NULL;
|
||||
ELSE
|
||||
approval_status := 1;
|
||||
approval_datevar := CURRENT_DATE;
|
||||
END IF;
|
||||
|
||||
IF (up_currency = 2) THEN
|
||||
IF (up_deadline < CURRENT_DATE) THEN
|
||||
fecha_busqueda := up_deadline;
|
||||
ELSE
|
||||
fecha_busqueda := CURRENT_DATE;
|
||||
END IF;
|
||||
|
||||
LOOP
|
||||
SELECT onegetchange(fecha_busqueda) INTO monedacambio;
|
||||
EXIT WHEN monedacambio IS NOT NULL;
|
||||
fecha_busqueda := fecha_busqueda - INTERVAL '1 day';
|
||||
|
||||
IF fecha_busqueda < CURRENT_DATE - INTERVAL '30 day' THEN
|
||||
RAISE NOTICE 'No se encontro tipo de cambio valido en los ultimos 30 dias desde %', fecha_busqueda;
|
||||
monedacambio := 1;
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
IF jsonb_array_length(products) > 0 THEN
|
||||
SELECT SUM((prod->>'quantity')::NUMERIC * (prod->>'unit_cost')::NUMERIC)
|
||||
INTO productsubtotal
|
||||
FROM jsonb_array_elements(products) AS prod;
|
||||
|
||||
SELECT SUM((prod->>'quantity')::NUMERIC * (prod->>'unit_cost')::NUMERIC * t.number)
|
||||
INTO productsiva
|
||||
FROM jsonb_array_elements(products) AS prod
|
||||
LEFT JOIN taxes t ON t.id_tax = (prod->>'id_tax')::INT;
|
||||
|
||||
SELECT SUM((prod->>'quantity')::NUMERIC * (prod->>'unit_cost')::NUMERIC * (1 + t.number))
|
||||
INTO producttotal
|
||||
FROM jsonb_array_elements(products) AS prod
|
||||
LEFT JOIN taxes t ON t.id_tax = (prod->>'id_tax')::INT;
|
||||
|
||||
producttotal := producttotal + COALESCE(up_ieps, 0);
|
||||
ELSE
|
||||
productsubtotal := up_subtotal;
|
||||
producttotal := up_total;
|
||||
productsiva := up_iva;
|
||||
END IF;
|
||||
|
||||
IF (up_currency = 2) THEN
|
||||
subtotalcambiomoneda := productsubtotal * monedacambio;
|
||||
ivacambiomoneda := productsiva * monedacambio;
|
||||
iepscambiomoneda := up_ieps * monedacambio;
|
||||
totalcambiomoneda := producttotal * monedacambio;
|
||||
ELSE
|
||||
subtotalcambiomoneda := productsubtotal;
|
||||
totalcambiomoneda := producttotal;
|
||||
ivacambiomoneda := productsiva;
|
||||
iepscambiomoneda := up_ieps;
|
||||
END IF;
|
||||
|
||||
UPDATE expenses
|
||||
SET
|
||||
description = up_description,
|
||||
id_suppliers = suppliers_id,
|
||||
request_date = up_req_date,
|
||||
payment_deadline = up_deadline,
|
||||
id_currency = up_currency,
|
||||
id_request_by = up_request_by,
|
||||
id_area = up_area,
|
||||
id_expense_cat = up_category,
|
||||
approval_date = approval_datevar,
|
||||
id_approval_stat = approval_status,
|
||||
subtotal = COALESCE(subtotalcambiomoneda, 0),
|
||||
iva = COALESCE(ivacambiomoneda, 0),
|
||||
ieps = COALESCE(iepscambiomoneda, 0),
|
||||
total = COALESCE(totalcambiomoneda, 0)
|
||||
WHERE id_expense = expense_id;
|
||||
|
||||
IF products IS NOT NULL THEN
|
||||
DELETE FROM purchase_detail pd
|
||||
WHERE pd.id_expense = expense_id
|
||||
AND pd.id_product NOT IN (
|
||||
SELECT (p->>'id_product')::INT
|
||||
FROM jsonb_array_elements(products) AS p
|
||||
);
|
||||
|
||||
DELETE FROM stock_movements sm
|
||||
WHERE sm.id_expense = expense_id
|
||||
AND sm.id_sto_mov NOT IN (
|
||||
SELECT pd.id_stock_mov
|
||||
FROM purchase_detail pd
|
||||
WHERE pd.id_expense = expense_id);
|
||||
|
||||
FOR product IN SELECT jsonb_array_elements(products)
|
||||
LOOP
|
||||
UPDATE stock_movements
|
||||
SET date_st_mov = up_req_date
|
||||
WHERE id_expense = expense_id
|
||||
AND id_product = (product->>'id_product')::INT;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
INSERT INTO stock_movements(id_product,id_mov_type,date_st_mov,id_expense)
|
||||
VALUES((product->>'id_product')::INT,1,up_req_date,expense_id)
|
||||
RETURNING id_sto_mov INTO mov_id;
|
||||
END IF;
|
||||
|
||||
UPDATE purchase_detail
|
||||
SET
|
||||
quantity = (product->>'quantity')::INT,
|
||||
id_tax = (product->>'id_tax')::INT,
|
||||
unit_cost = ((product->>'unit_cost')::NUMERIC * monedacambio),
|
||||
total = COALESCE(
|
||||
(SELECT ((product->>'quantity')::NUMERIC *
|
||||
(product->>'unit_cost')::NUMERIC *
|
||||
(1 + COALESCE(t.number, 0))) * monedacambio
|
||||
FROM taxes t
|
||||
WHERE t.id_tax = (product->>'id_tax')::INT),
|
||||
0
|
||||
)
|
||||
WHERE id_expense = expense_id
|
||||
AND id_product = (product->>'id_product')::INT;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
INSERT INTO purchase_detail (
|
||||
id_expense, id_product, quantity, id_tax, unit_cost, total, id_stock_mov
|
||||
)
|
||||
VALUES (
|
||||
expense_id,
|
||||
(product->>'id_product')::INT,
|
||||
(product->>'quantity')::INT,
|
||||
(product->>'id_tax')::INT,
|
||||
((product->>'unit_cost')::NUMERIC * monedacambio),
|
||||
COALESCE(
|
||||
(SELECT ((product->>'quantity')::NUMERIC *
|
||||
(product->>'unit_cost')::NUMERIC *
|
||||
(1 + COALESCE(t.number, 0))) * monedacambio
|
||||
FROM taxes t
|
||||
WHERE t.id_tax = (product->>'id_tax')::INT),
|
||||
0
|
||||
),mov_id
|
||||
);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
RETURN 1;
|
||||
END;
|
||||
$BODY$;
|
||||
|
||||
-- ============================================================
|
||||
-- 4. RESTAURAR purchaseentry
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE FUNCTION public.purchaseentry(
|
||||
purchase_id integer,
|
||||
checking integer)
|
||||
RETURNS integer
|
||||
LANGUAGE 'plpgsql'
|
||||
COST 100
|
||||
VOLATILE PARALLEL UNSAFE
|
||||
AS $BODY$
|
||||
DECLARE
|
||||
v_id_product INT;
|
||||
v_id_expense INT;
|
||||
v_quantity INT;
|
||||
v_unit_cost NUMERIC;
|
||||
v_delivered INT;
|
||||
stock_anterior BIGINT;
|
||||
stock_actual BIGINT;
|
||||
BEGIN
|
||||
SELECT id_product, id_expense, quantity, unit_cost, delivered
|
||||
INTO v_id_product, v_id_expense, v_quantity, v_unit_cost, v_delivered
|
||||
FROM purchase_detail
|
||||
WHERE id_purchase_dt = purchase_id;
|
||||
|
||||
UPDATE purchase_detail
|
||||
SET delivered = LEAST(COALESCE(delivered, 0) + checking, quantity)
|
||||
WHERE id_purchase_dt = purchase_id;
|
||||
|
||||
SELECT stock INTO stock_anterior
|
||||
FROM stock WHERE id_product = v_id_product FOR UPDATE;
|
||||
|
||||
stock_actual := stock_anterior + LEAST(checking, v_quantity - COALESCE(v_delivered, 0));
|
||||
|
||||
INSERT INTO stock_movements(id_product, id_mov_type, date_st_mov, id_purchase_dt, id_expense, quantity, stock_before, stock_current)
|
||||
VALUES (v_id_product, 1, CURRENT_DATE, purchase_id, v_id_expense, LEAST(checking, v_quantity - COALESCE(v_delivered, 0)), stock_anterior, stock_actual);
|
||||
|
||||
UPDATE stock
|
||||
SET
|
||||
average_cost = CASE
|
||||
WHEN average_cost IS NULL THEN v_unit_cost
|
||||
ELSE (
|
||||
((average_cost * stock) + (LEAST(checking, v_quantity - COALESCE(v_delivered, 0)) * v_unit_cost))
|
||||
/ (stock + LEAST(checking, v_quantity - COALESCE(v_delivered, 0)))
|
||||
)
|
||||
END,
|
||||
stock = stock_actual
|
||||
WHERE id_product = v_id_product;
|
||||
|
||||
RETURN 1;
|
||||
END;
|
||||
$BODY$;
|
||||
|
||||
-- Confirmacion
|
||||
SELECT 'Rollback completo de flujo de inventario aplicado correctamente' AS resultado;
|
||||
Reference in New Issue
Block a user