BEGIN; ALTER TABLE stock_lot ADD COLUMN IF NOT EXISTS next_active BOOLEAN NOT NULL DEFAULT FALSE; CREATE UNIQUE INDEX IF NOT EXISTS uq_stock_lot_one_next_active_per_product ON stock_lot(product_id) WHERE status = 'open' AND next_active = TRUE; CREATE OR REPLACE FUNCTION fn_auto_switch_lot_when_depleted() RETURNS TRIGGER LANGUAGE plpgsql AS $$ DECLARE v_product_id BIGINT; v_open_lot_id BIGINT; v_qualified_count INTEGER; v_current_net NUMERIC; v_event_key TEXT; BEGIN SELECT product_id INTO v_product_id FROM stock_lot WHERE id = NEW.lot_id; IF NOT EXISTS ( SELECT 1 FROM stock_lot WHERE id = NEW.lot_id AND status = 'current' ) THEN RETURN NEW; END IF; SELECT qty_net INTO v_current_net FROM v_stock_lot_balance WHERE stock_lot_id = NEW.lot_id; IF COALESCE(v_current_net, 0) > 0 THEN RETURN NEW; END IF; PERFORM 1 FROM stock_lot WHERE product_id = v_product_id FOR UPDATE; IF NOT EXISTS ( SELECT 1 FROM stock_lot WHERE id = NEW.lot_id AND status = 'current' ) THEN RETURN NEW; END IF; SELECT sl.id INTO v_open_lot_id FROM stock_lot sl INNER JOIN v_stock_lot_balance v ON v.stock_lot_id = sl.id WHERE sl.product_id = v_product_id AND sl.status = 'open' AND sl.next_active = TRUE AND NULLIF(TRIM(sl.lot_number), '') IS NOT NULL AND sl.manufacturer_party_id IS NOT NULL AND NULLIF(TRIM(sl.manufacturer_lot_number), '') IS NOT NULL AND sl.mfg_date IS NOT NULL AND sl.expiry_date IS NOT NULL AND sl.purchase_date IS NOT NULL AND v.qty_net > 0 LIMIT 1; IF v_open_lot_id IS NULL THEN SELECT COUNT(*), MIN(sl.id) INTO v_qualified_count, v_open_lot_id FROM stock_lot sl INNER JOIN v_stock_lot_balance v ON v.stock_lot_id = sl.id WHERE sl.product_id = v_product_id AND sl.status = 'open' AND NULLIF(TRIM(sl.lot_number), '') IS NOT NULL AND sl.manufacturer_party_id IS NOT NULL AND NULLIF(TRIM(sl.manufacturer_lot_number), '') IS NOT NULL AND sl.mfg_date IS NOT NULL AND sl.expiry_date IS NOT NULL AND sl.purchase_date IS NOT NULL AND v.qty_net > 0; IF v_qualified_count <> 1 THEN RETURN NEW; END IF; UPDATE stock_lot SET next_active = TRUE, updated_at = NOW() WHERE id = v_open_lot_id; END IF; UPDATE stock_lot SET status = 'closed', updated_at = NOW() WHERE id = NEW.lot_id; UPDATE stock_lot SET status = 'current', next_active = FALSE, updated_at = NOW() WHERE id = v_open_lot_id; v_event_key := format('lot.auto_switched:%s:%s', v_product_id, txid_current()); PERFORM fn_enqueue_event( 'lot.auto_switched', v_event_key, 'stock_lot', NEW.lot_id::TEXT, jsonb_build_object( 'productId', v_product_id, 'closedLotId', NEW.lot_id, 'newCurrentLotId', v_open_lot_id, 'occurredAt', NOW() ) ); RETURN NEW; END; $$; COMMIT;