Initial commit: sap-intercompany-worker
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>
This commit is contained in:
commit
ca57403663
36 files changed
+5539
No files matched your search
@@ -0,0 +1,117 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user