Motor de escenarios de replicacion entre bases SAP Business One (Service Layer). Poll periodico sobre webhook_events, escenarios configurables via Prisma (filter + transform por escenario). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
118 lines
5.9 KiB
Markdown
118 lines
5.9 KiB
Markdown
# sap-intercompany-worker
|
|
|
|
Worker que replica datos maestros entre bases de SAP Business One (Service
|
|
Layer), a partir de **escenarios** configurables — no hay una casa matriz
|
|
fija ni una lista fija de subsidiarias: cualquier base registrada puede ser
|
|
origen o destino, según lo que declare cada escenario.
|
|
|
|
## Qué hace
|
|
|
|
Cada `POLL_INTERVAL_SECONDS` (default 30s), revisa la tabla `webhook_events`
|
|
en Postgres — la misma que llena [`sap-webhook-api`](../sap-webhook-api) al
|
|
recibir un webhook de SAP — buscando eventos pendientes. Por cada uno:
|
|
|
|
1. Busca los **escenarios activos** que aplican a ese `businessObject`
|
|
(puede haber más de uno, ej. "clientes" y "proveedores" sobre
|
|
`BusinessPartners`, cada uno con su propio filtro), y descarta los que
|
|
no vinieron realmente de la base origen que ese escenario declara (ver
|
|
protección contra loops en `docs/ARQUITECTURA.md`).
|
|
2. Lee el registro completo desde la base origen y lo pasa por el `filter`
|
|
del escenario (`src/scenarios/<code>.ts`) — si no aplica, sigue con el
|
|
próximo. Si aplica, por cada destino lo pasa por `transform` (con acceso
|
|
a mapeos de valor cargados en Postgres vía `ctx.mapValue`) y resuelve si
|
|
ya existe en ese destino — por clave directa o por un UDF de
|
|
correspondencia, según el `keyStrategy` del escenario — para decidir
|
|
`PATCH` o `POST`.
|
|
3. Registra cada intento (escenario, verbo usado, HTTP status, error, y el
|
|
**payload ya transformado enviado**) en `replication_attempts`.
|
|
4. Calcula el estado final del evento sobre la suma de todos los intentos
|
|
de todos los escenarios que aplicaron: `done` / `partial` / `failed` /
|
|
`filtered` (ningún escenario aceptó el mensaje).
|
|
|
|
Es un proceso separado de `sap-webhook-api` a propósito: SAP tiene un
|
|
timeout de 10s para la respuesta del webhook, y replicar hacia varios
|
|
destinos (con sus propios logins y llamadas) puede tardar más que eso — por
|
|
eso el trabajo pesado corre acá, a su propio ritmo, sin poner en riesgo la
|
|
suscripción del webhook.
|
|
|
|
Ver [`docs/ARQUITECTURA.md`](./docs/ARQUITECTURA.md) para el detalle
|
|
completo del diseño (por qué escenarios y no reglas fijas, `keyStrategy`,
|
|
diagrama de flujo, reglas de agregación de estado),
|
|
[`docs/ESCENARIOS.md`](./docs/ESCENARIOS.md) para la guía paso a paso de
|
|
cómo configurar uno con ejemplos reales, y
|
|
[`docs/FLUJO.md`](./docs/FLUJO.md) para el diagrama de secuencia
|
|
archivo-por-archivo, función por función, de todo el camino de un mensaje
|
|
(incluye `sap-webhook-api`).
|
|
|
|
**Implementado**: datos maestros con destino fijo (`destinationMode = 'fixed'`
|
|
— `BP_PROVEEDORES`, `CHARTOFACCOUNTS`, `PROFITCENTERS`, `SALESPERSONS`) y
|
|
transacciones con destino resuelto desde el propio mensaje
|
|
(`destinationMode = 'payload'` — `JOURNALENTRIES`, primer caso real). Un
|
|
escenario también puede declarar un `destinationBusinessObject` distinto al
|
|
de origen (ej. `PurchaseOrders` → `Orders`), para cuando el objeto cambia
|
|
de nombre entre bases.
|
|
|
|
**Trabajo futuro**: `sap-intercompany-viewer` (frontend) +
|
|
`sap-intercompany-api` (backend nuevo, dueño de `databases`/`scenarios`/
|
|
`scenario_destinations`/`scenario_value_mappings`/`webhook_events`/
|
|
`replication_attempts` vía HTTP, ya que este worker no expone HTTP a
|
|
propósito) — visor único de eventos/log/escenarios con reproceso, en
|
|
reemplazo de `sap-webhook-viewer`.
|
|
|
|
## Quick start
|
|
|
|
```bash
|
|
npm install
|
|
cp .env.example .env # completar DATABASE_URL / POLL_INTERVAL_SECONDS
|
|
|
|
# Postgres: instancia nativa (Homebrew) en localhost:5432 — ya no se usa
|
|
# Docker para esto (docker-compose.yml queda sin uso; si tenés un Postgres
|
|
# corriendo por Homebrew/apt en el mismo puerto, ojo con el conflicto:
|
|
# localhost prioriza la instancia nativa sobre un contenedor Docker).
|
|
npx prisma migrate deploy
|
|
|
|
# Registrar bases y al menos un escenario antes de correr el worker
|
|
# (ver docs/ESCENARIOS.md para la guía completa con ejemplos reales)
|
|
npx tsx scripts/manage-config.ts database:add HQ01 "Casa Matriz" <baseUrl> <companyDb> <user> <pass>
|
|
npx tsx scripts/manage-config.ts scenario:add <code> <name> <businessObject> HQ01 <keyField>
|
|
npx tsx scripts/manage-config.ts scenario:add-destination <code> <databaseCode>
|
|
# editar src/scenarios/<code>.ts (se generó solo con la primera línea)
|
|
|
|
npm run run-once # una sola pasada, útil para probar
|
|
npm run start # corre en loop
|
|
```
|
|
|
|
Sin destinos configurados para un escenario, el worker igual corre: lee el
|
|
registro de la base origen y deja el evento en `failed` (0 intentos), sin
|
|
romper — útil para probar el flujo antes de tener todas las bases reales.
|
|
|
|
## Estructura
|
|
|
|
```
|
|
prisma/schema.prisma # modelos (ver docs/TABLAS.md)
|
|
docs/
|
|
ARQUITECTURA.md # conceptos, keyStrategy, diagrama de flujo, reglas de estado
|
|
TABLAS.md # referencia de columnas por tabla, con ejemplos reales
|
|
ESCENARIOS.md # guía paso a paso con 5 ejemplos reales configurados
|
|
FLUJO.md # diagrama de secuencia + funciones principales por archivo
|
|
scripts/manage-config.ts # CLI de administración (databases, scenarios, destinos, mapeos)
|
|
src/
|
|
config.ts # solo POLL_INTERVAL_SECONDS; todo lo demás vive en Postgres
|
|
serviceLayer.ts # cliente mínimo del Service Layer (Login/GET/POST/PATCH)
|
|
replicationConfig.ts # carga de config activa desde Postgres en cada tick
|
|
scenarioTransform.ts # carga/ejecuta filter+transform de un escenario + ctx.mapValue
|
|
replicateEvents.ts # motor: resuelve N escenarios por evento (datos maestros y transacciones)
|
|
scenarios/ # un archivo <code>.ts por escenario (filter + transform)
|
|
index.ts # cron / --once
|
|
```
|
|
|
|
## Stack
|
|
|
|
- Script plano en TypeScript (ESM), ejecutado con `tsx` — sin framework,
|
|
porque no expone HTTP.
|
|
- Prisma 7 + `@prisma/adapter-pg` contra Postgres.
|
|
- `node-cron` para el loop periódico.
|
|
|
|
Contexto completo de por qué se armó así (decisiones de arquitectura,
|
|
alternativas descartadas) en `CLAUDE.md`, en la raíz del repo.
|