Initial commit: sap-legacy-webhook-emulator

Reconstruye el Webhook Messenger nativo de SAP B1 (FP2605) para
clientes que aun no estan en esa version: captura eventos via
SBO_SP_PostTransactionNotice, provisiona UserTables OEVC/EVC1/OEVN/EVN1
y despacha CloudEvents al mismo endpoint que el messenger real.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5 committed 2026-09-16 08:30:45 -05:00
commit e1c8ee9015
42 files changed
+4042

No files matched your search

@@ -0,0 +1,33 @@
-- SBO_SP_PostTransactionNotice — SQL Server / T-SQL
--
-- Corre DESPUÉS del commit de la transacción real de SAP (a diferencia de
-- SBO_SP_TransactionNotification, que es síncrono y puede hacer rollback).
-- Es un hook compartido de SAP (SAP lo crea vacío por cada compañía nueva,
-- y ahí puede convivir código de otros add-ons) — por eso el cuerpo se deja
-- reducido a un solo llamado a SP_Webhook_CaptureEvent
-- (ver sp-webhook-capture-event-mssql.sql), donde vive toda la lógica.
--
-- Instalar, en este orden:
-- 1) sp-webhook-capture-event-mssql.sql
-- 2) este archivo (reemplaza el cuerpo del SBO_SP_PostTransactionNotice
-- existente en la company DB)
ALTER PROCEDURE [dbo].[SBO_SP_PostTransactionNotice]
@object_type NVARCHAR(20),
@transaction_type NCHAR(1),
@num_of_cols_in_key INT,
@list_of_key_cols_tab_del NVARCHAR(255),
@list_of_cols_val_tab_del NVARCHAR(255)
AS
BEGIN
DECLARE @error INT
DECLARE @error_message NVARCHAR(200)
EXEC [dbo].[SP_Webhook_CaptureEvent]
@object_type, @transaction_type, @num_of_cols_in_key,
@list_of_key_cols_tab_del, @list_of_cols_val_tab_del,
@error OUTPUT, @error_message OUTPUT
SELECT @error, @error_message
END
GO
@@ -0,0 +1,40 @@
-- SBO_SP_PostTransactionNotice — SAP HANA / SQLScript
--
-- Corre DESPUÉS del commit de la transacción real de SAP. Es un hook
-- compartido de SAP (SAP lo crea vacío por cada compañía nueva, y ahí
-- puede convivir código de otros add-ons) — por eso el cuerpo se deja
-- reducido a un solo CALL a SP_Webhook_CaptureEvent
-- (ver sp-webhook-capture-event-hana.sql), donde vive toda la lógica.
--
-- A diferencia de SQL Server (que soporta ALTER PROCEDURE), en HANA no se
-- puede modificar el cuerpo de un procedimiento existente — hay que
-- reemplazarlo con DROP + CREATE. El DROP asume que el procedimiento ya
-- existe (SAP lo crea automáticamente al crear la compañía).
--
-- ⚠️ NO VERIFICADO CONTRA UN HANA REAL — ver el aviso completo en
-- sp-webhook-capture-event-hana.sql.
--
-- Instalar, en este orden:
-- 1) sp-webhook-capture-event-hana.sql
-- 2) este archivo
DROP PROCEDURE "SBO_SP_PostTransactionNotice";
CREATE PROCEDURE "SBO_SP_PostTransactionNotice" (
IN object_type NVARCHAR(20),
IN transaction_type NVARCHAR(1),
IN num_of_cols_in_key INTEGER,
IN list_of_key_cols_tab_del NVARCHAR(255),
IN list_of_cols_val_tab_del NVARCHAR(255),
OUT error INTEGER,
OUT error_message NVARCHAR(200)
)
LANGUAGE SQLSCRIPT
AS
BEGIN
CALL "SP_Webhook_CaptureEvent" (
:object_type, :transaction_type, :num_of_cols_in_key,
:list_of_key_cols_tab_del, :list_of_cols_val_tab_del,
error, error_message
);
END;
@@ -0,0 +1,103 @@
-- SP_Webhook_CaptureEvent — SAP HANA / SQLScript
--
-- Equivalente HANA de sp-webhook-capture-event-mssql.sql: toda la lógica
-- de captura vive acá, no en el cuerpo de SBO_SP_PostTransactionNotice
-- (hook compartido de SAP, ver recreate_sbo_sp_posttransactionnotice_hana.sql).
--
-- Requiere que la UserTable "@OEVN" ya exista (creada por
-- `npm run provision`, ver src/provisioning/).
--
-- ✅ Verificado contra un HANA real (SAP Business One), instalado y
-- funcionando — incluida la sintaxis del EXIT HANDLER y el filtro de eco
-- (INSERT...SELECT...WHERE, más abajo): se confirmó que un DELETE vía
-- Service Layer sobre una fila de @OEVN ya no genera ningún eco nuevo.
-- ⚠️ El filtro por suscripción activa (EXISTS contra @EVC1/@OEVC, más
-- abajo) es nuevo y todavía NO se probó contra ese ambiente.
--
-- U_UserID: no se completa. CURRENT_USER da el usuario de la conexión a
-- HANA (una cuenta técnica), no el usuario real de SAP que hizo la
-- transacción — confirmado contra este mismo ambiente. Ninguno de los 5
-- parámetros que recibe el hook trae ese dato, así que por ahora el campo
-- queda sin poblar en las dos variantes (ver también mssql.sql).
--
-- Name: en una UserTable de SAP, "Name" también debe ser único (no solo
-- "Code") — confirmado contra Service Layer real (ODBC -2035 al repetir
-- un Name, con Code distinto). Por eso Name usa el mismo UUID que Code:
-- cualquier otro valor (como el propio ObjectType) se repite entre
-- eventos del mismo tipo y hace fallar el INSERT en el segundo evento
-- (en silencio, por el EXIT HANDLER).
--
-- Eco de nuestras propias escrituras: cualquier POST/PATCH/DELETE que
-- hace el dispatcher sobre @OEVC/@EVC1/@OEVN/@EVN1 vía Service Layer
-- dispara este mismo hook de nuevo, con un ObjectType especial de SAP
-- para "operación genérica sobre una UserTable simple": un código
-- negativo + tab + nombre físico de la tabla (confirmado real:
-- "-3\t@OEVN" para un DELETE). Si insertáramos esa fila iba a generar
-- otro eco al borrarla, indefinidamente — por eso el INSERT usa
-- SELECT...FROM DUMMY WHERE en vez de VALUES, para poder filtrar el
-- ObjectType sin agregar una rama procedural nueva (ver docs/DISENO.md,
-- hallazgo 5).
--
-- Filtro por suscripción activa: además de lo anterior, solo se inserta
-- si existe al menos una fila en @EVC1 (con su @OEVC en State='A') para
-- este ObjectType+TransType exacto. No es "lógica de negocio propia" del
-- SP — consulta la misma tabla de configuración que ya es la fuente de
-- verdad para el dispatcher, no una copia/lista fija a mantener
-- sincronizada. Evita ensuciar @OEVN con todo lo que SAP genera y a
-- nadie le interesa (en el ambiente de prueba, cientos de eventos por
-- minuto sin ninguna suscripción real).
DROP PROCEDURE "SP_Webhook_CaptureEvent";
CREATE PROCEDURE "SP_Webhook_CaptureEvent" (
IN object_type NVARCHAR(20),
IN transaction_type NVARCHAR(1),
IN num_of_cols_in_key INTEGER,
IN list_of_key_cols_tab_del NVARCHAR(255),
IN list_of_cols_val_tab_del NVARCHAR(255),
OUT error INTEGER,
OUT error_message NVARCHAR(200)
)
LANGUAGE SQLSCRIPT
AS
BEGIN
DECLARE v_code NVARCHAR(36);
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
error := ::SQL_ERROR_CODE;
error_message := SUBSTR(::SQL_ERROR_MESSAGE, 1, 200);
END;
error := 0;
error_message := N'Ok';
-- Sin condiciones de negocio: inserta siempre, salvo que sea un eco
-- de una escritura nuestra sobre estas mismas 4 tablas (arriba).
SELECT SYSUUID INTO v_code FROM DUMMY;
INSERT INTO "@OEVN" (
"Code", "Name",
"U_SourceDB", "U_Status", "U_ObjectType", "U_TransType",
"U_FieldsInKe", "U_FieldNames", "U_FieldValue", "U_ReplayStat"
)
SELECT
:v_code, :v_code,
CURRENT_SCHEMA, 'N', :object_type, :transaction_type,
:num_of_cols_in_key, :list_of_key_cols_tab_del, :list_of_cols_val_tab_del, 'N'
FROM DUMMY
WHERE NOT (
:object_type LIKE '-%' || CHAR(9) || '@OEVC'
OR :object_type LIKE '-%' || CHAR(9) || '@EVC1'
OR :object_type LIKE '-%' || CHAR(9) || '@OEVN'
OR :object_type LIKE '-%' || CHAR(9) || '@EVN1'
)
AND EXISTS (
SELECT 1
FROM "@EVC1" f
INNER JOIN "@OEVC" s ON s."Code" = f."U_WebhookID"
WHERE f."U_ObjectType" = :object_type
AND f."U_TransType" = :transaction_type
AND s."U_State" = 'A'
);
END;
@@ -0,0 +1,98 @@
-- SP_Webhook_CaptureEvent — SQL Server / T-SQL
--
-- Toda la lógica de captura del emulador vive acá, en un procedimiento
-- propio y con nombre descriptivo — no directamente en el cuerpo de
-- SBO_SP_PostTransactionNotice (ver mssql.sql), que es un hook compartido
-- de SAP donde puede convivir código de otros add-ons. Así el nuestro
-- queda identificable y fácil de mantener o remover por separado.
--
-- Requiere que la UserTable "@OEVN" ya exista (creada por
-- `npm run provision`, ver src/provisioning/).
--
-- U_UserID: no se completa. SUSER_SNAME() da el login de la conexión a SQL
-- Server (una cuenta técnica), no el usuario real de SAP que hizo la
-- transacción — confirmado equivalente en HANA (CURRENT_USER, ver
-- sp-webhook-capture-event-hana.sql). Ninguno de los 5 parámetros que
-- recibe el hook trae ese dato, así que por ahora el campo queda sin
-- poblar en las dos variantes.
--
-- Name: en una UserTable de SAP, "Name" también debe ser único (no solo
-- "Code") — confirmado contra Service Layer real (ODBC -2035 al repetir
-- un Name, con Code distinto). Por eso Name usa el mismo UUID que Code:
-- cualquier otro valor (como el propio ObjectType) se repite entre
-- eventos del mismo tipo y hace fallar el INSERT en el segundo evento
-- (en silencio, por el TRY/CATCH).
--
-- Eco de nuestras propias escrituras: cualquier POST/PATCH/DELETE que
-- hace el dispatcher sobre @OEVC/@EVC1/@OEVN/@EVN1 vía Service Layer
-- dispara este mismo hook de nuevo, con un ObjectType especial de SAP
-- para "operación genérica sobre una UserTable simple": un código
-- negativo + tab + nombre físico de la tabla (confirmado real:
-- "-3\t@OEVN" para un DELETE). Si insertáramos esa fila iba a generar
-- otro eco al borrarla, indefinidamente — por eso NO se inserta nada
-- cuando el ObjectType matchea ese patrón para una de nuestras 4 tablas
-- (ver docs/DISENO.md, hallazgo 5).
--
-- Filtro por suscripción activa: además de lo anterior, solo se inserta
-- si existe al menos una fila en @EVC1 (con su @OEVC en State='A') para
-- este ObjectType+TransType exacto. No es "lógica de negocio propia" del
-- SP — consulta la misma tabla de configuración que ya es la fuente de
-- verdad para el dispatcher, no una copia/lista fija a mantener
-- sincronizada. Evita ensuciar @OEVN con todo lo que SAP genera y a
-- nadie le interesa (en el ambiente de prueba, cientos de eventos por
-- minuto sin ninguna suscripción real).
CREATE OR ALTER PROCEDURE [dbo].[SP_Webhook_CaptureEvent]
@object_type NVARCHAR(20),
@transaction_type NCHAR(1),
@num_of_cols_in_key INT,
@list_of_key_cols_tab_del NVARCHAR(255),
@list_of_cols_val_tab_del NVARCHAR(255),
@error INT OUTPUT,
@error_message NVARCHAR(200) OUTPUT
AS
BEGIN
SET @error = 0
SET @error_message = N'Ok'
DECLARE @code NVARCHAR(36) = CONVERT(NVARCHAR(36), NEWID())
DECLARE @isOwnTableEcho BIT = CASE
WHEN @object_type LIKE '-%' + CHAR(9) + '@OEVC' THEN 1
WHEN @object_type LIKE '-%' + CHAR(9) + '@EVC1' THEN 1
WHEN @object_type LIKE '-%' + CHAR(9) + '@OEVN' THEN 1
WHEN @object_type LIKE '-%' + CHAR(9) + '@EVN1' THEN 1
ELSE 0
END
DECLARE @hasActiveSubscription BIT = CASE WHEN EXISTS (
SELECT 1
FROM [dbo].[@EVC1] f
INNER JOIN [dbo].[@OEVC] s ON s.Code = f.U_WebhookID
WHERE f.U_ObjectType = @object_type
AND f.U_TransType = @transaction_type
AND s.U_State = 'A'
) THEN 1 ELSE 0 END
-- Solo inserta si no es un eco de nuestras propias tablas (arriba) y
-- si hay una suscripción activa interesada en este ObjectType+TransType.
IF @isOwnTableEcho = 0 AND @hasActiveSubscription = 1
BEGIN
BEGIN TRY
INSERT INTO [dbo].[@OEVN] (
Code, Name,
U_SourceDB, U_Status, U_ObjectType, U_TransType,
U_FieldsInKe, U_FieldNames, U_FieldValue, U_ReplayStat
) VALUES (
@code, @code,
DB_NAME(), 'N', @object_type, @transaction_type,
@num_of_cols_in_key, @list_of_key_cols_tab_del, @list_of_cols_val_tab_del, 'N'
)
END TRY
BEGIN CATCH
SET @error = ERROR_NUMBER()
SET @error_message = LEFT(ERROR_MESSAGE(), 200)
END CATCH
END
END
GO