From 2873fd3bc66fa887d1f1c821a4d23795cc2e8762 Mon Sep 17 00:00:00 2001 From: george Date: Tue, 13 Oct 2026 14:30:06 -0500 Subject: [PATCH 1/2] Usar PDN1.OpenInvQty en la consulta FIFO y ajustar el despliegue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DP_EntradasAbiertas (v3) lee OpenInvQty en lugar de OpenQty. - publicar: la release no contiene enlaces simbólicos (copia real de @dp/shared). - DEPLOY.md y CLAUDE.md actualizados. Co-Authored-By: Claude Sonnet 5.5 --- CLAUDE.md | 9 +++++--- .../planificaciones.service.spec.ts | 2 +- .../planificaciones.service.ts | 4 ++-- .../sql-queries/sql-queries.definitions.ts | 6 ++--- deploy/windows/DEPLOY.md | 6 ++++- scripts/publicar.mjs | 23 +++++++++++++++++++ 6 files changed, 40 insertions(+), 10 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 717e260..39837a7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,6 +52,8 @@ Pasos de la UI (decididos): crea/actualiza `DP_Stock` y `DP_EntradasAbiertas` sola (variante según `DB_TYPE`). El analizador SQL del Service Layer **no admite `FORMAT`** y SAP reescribe el `SqlText`: al cambiar el SQL hay que subir `version` en `sql-queries.definitions.ts`. Los parámetros van entre comillas simples. + `DP_EntradasAbiertas` (v3) lee la cantidad abierta de `PDN1.OpenInvQty` (no `OpenQty`); el campo + de usuario `U_OpenQty` del UDO conserva su nombre. - **Listado** de planificaciones **paginado en SAP** (`$top/$skip/$inlinecount`, OData v3 del SL v1), más nueva primero, con filtros por estado, almacén y rango de fechas. Por defecto 20 por página, máximo 100. En la pantalla: botones **Atrás** / **Siguiente**; filtros y página quedan en la URL. @@ -69,7 +71,8 @@ Pasos de la UI (decididos): `HTTPS_KEY_PATH`+`HTTPS_CERT_PATH`) y redirección opcional de HTTP (`HTTP_REDIRECT_PORT`). El servicio lo administra **NSSM** (decisión del usuario; se evaluó WinSW y no se lo consideró lo bastante estable); nginx no es necesario. NSSM 2.24 estable puede no arrancar en Windows recientes: usar la 2.24-101. `npm run publicar` arma `release/devolucion-proveedores/` (API + web + solo dependencias de - producción, ~55 MB). Guía completa: `deploy/windows/DEPLOY.md`. + producción, ~55 MB, **sin enlaces simbólicos**: `@dp/shared` va como copia real, porque la API lo + importa al ejecutarse y un enlace no sobrevive a copiar de Mac a Windows). Guía completa: `deploy/windows/DEPLOY.md`. - **Logs**: la aplicación **no escribe logs a archivo**; usa el `Logger` de Nest por consola. En producción NSSM captura la salida y la rota a los 10 MB (no limita cuántos archivos conserva: hay que limpiar los viejos). Con `NO_COLOR=1` el log sale sin códigos de @@ -248,8 +251,8 @@ Reglas: - **HANA**: el addon .NET ya funcionaba en HANA, y las consultas son un port de las suyas, así que su lógica está probada. Lo que no se ha probado es **cómo las recibe el Service Layer de HANA** (`SQLQueries`: su analizador es más estricto que el `DoQuery` del addon; en MSSQL rechazó `FORMAT`) y los - dos cambios respecto al addon: el filtro por almacén (`PDN1.WhsCode`) y los parámetros con nombre - (`:item`, `:whs`). Tampoco el paginado (`$inlinecount`/`odata.count` es de `/b1s/v1`; en `/b1s/v2` sería + tres cambios respecto al addon: el filtro por almacén (`PDN1.WhsCode`), la columna `PDN1.OpenInvQty` + y los parámetros con nombre (`:item`, `:whs`). Tampoco el paginado (`$inlinecount`/`odata.count` es de `/b1s/v1`; en `/b1s/v2` sería `$count`) ni los tipos que HANA informa en los metadatos. - **Reprocesar** solo con tests (no contra SAP). - Fallo parcial al crear Devoluciones (solo se probó el caso feliz). El corte en lotes de diff --git a/apps/api/src/planificaciones/planificaciones.service.spec.ts b/apps/api/src/planificaciones/planificaciones.service.spec.ts index fbf4e5e..76d0246 100644 --- a/apps/api/src/planificaciones/planificaciones.service.spec.ts +++ b/apps/api/src/planificaciones/planificaciones.service.spec.ts @@ -42,7 +42,7 @@ function crear(anterior: ReturnType, opciones: { stock?: unknown[]; const sql = { consultar: vi.fn(async (_s: unknown, consulta: { codigo: string }) => { if (consulta.codigo === 'DP_Stock') return opciones.stock ?? [{ ItemCode: 'A1', ItemName: 'Libro', OnHand: 100 }]; - return [{ DocNum: 101, CardCode: 'P1', CardName: 'Prov', DocDate: '2026-01-31T00:00:00Z', DocEntry: 15, LineNum: 0, OpenQty: 10 }]; + return [{ DocNum: 101, CardCode: 'P1', CardName: 'Prov', DocDate: '2026-01-31T00:00:00Z', DocEntry: 15, LineNum: 0, OpenInvQty: 10 }]; }), }; const catalogos = { series: vi.fn(async () => [{ series: 15, name: 'Primario', nextNumber: 1, esPorDefecto: true }]) }; diff --git a/apps/api/src/planificaciones/planificaciones.service.ts b/apps/api/src/planificaciones/planificaciones.service.ts index b30fec6..bc0eec1 100644 --- a/apps/api/src/planificaciones/planificaciones.service.ts +++ b/apps/api/src/planificaciones/planificaciones.service.ts @@ -48,7 +48,7 @@ interface FilaEntrada { DocDate: string; DocEntry: number; LineNum: number; - OpenQty: number | string; + OpenInvQty: number | string; } interface DocumentoCreado { @@ -242,7 +242,7 @@ export class PlanificacionesService { docDate: fechaIso(String(f.DocDate)), cardCode: f.CardCode, cardName: f.CardName, - openQty: Math.floor(Number(f.OpenQty)), + openQty: Math.floor(Number(f.OpenInvQty)), })), ); }, diff --git a/apps/api/src/sql-queries/sql-queries.definitions.ts b/apps/api/src/sql-queries/sql-queries.definitions.ts index 8d84679..e4c9e59 100644 --- a/apps/api/src/sql-queries/sql-queries.definitions.ts +++ b/apps/api/src/sql-queries/sql-queries.definitions.ts @@ -30,17 +30,17 @@ export const CONSULTA_STOCK: ConsultaGuardada = { export const CONSULTA_ENTRADAS_ABIERTAS: ConsultaGuardada = { codigo: 'DP_EntradasAbiertas', - version: 2, + version: 3, mssql: 'SELECT T0.[DocNum], T0.[CardCode], T0.[CardName], T0.[DocDate], ' + - 'T1.[DocEntry], T1.[LineNum], T1.[OpenQty] ' + + 'T1.[DocEntry], T1.[LineNum], T1.[OpenInvQty] ' + 'FROM OPDN T0 INNER JOIN PDN1 T1 ON T1.[DocEntry] = T0.[DocEntry] ' + "WHERE T0.[U_DOT_TipoRecepcion] = 'C' AND T1.[LineStatus] = 'O' " + 'AND T1.[ItemCode] = :item AND T1.[WhsCode] = :whs ' + 'ORDER BY T0.[DocDate], T1.[DocEntry], T1.[LineNum]', hana: 'SELECT T0."DocNum", T0."CardCode", T0."CardName", T0."DocDate", ' + - 'T1."DocEntry", T1."LineNum", T1."OpenQty" ' + + 'T1."DocEntry", T1."LineNum", T1."OpenInvQty" ' + 'FROM OPDN T0 INNER JOIN PDN1 T1 ON T1."DocEntry" = T0."DocEntry" ' + "WHERE T0.\"U_DOT_TipoRecepcion\" = 'C' AND T1.\"LineStatus\" = 'O' " + 'AND T1."ItemCode" = :item AND T1."WhsCode" = :whs ' + diff --git a/deploy/windows/DEPLOY.md b/deploy/windows/DEPLOY.md index 579ea71..9141de3 100644 --- a/deploy/windows/DEPLOY.md +++ b/deploy/windows/DEPLOY.md @@ -52,7 +52,11 @@ ejecutar en Windows. ## 3. Copiar al servidor -Copie toda la carpeta `devolucion-proveedores` a, por ejemplo, `C:\apps\devolucion-proveedores`: +Copie **solo** la carpeta `release\devolucion-proveedores` (no el resto del proyecto) a, por ejemplo, +`C:\apps\devolucion-proveedores`. No contiene enlaces simbólicos, así que se puede copiar por red, USB o +comprimida en un `.zip` (más rápido: son miles de archivos pequeños; en el servidor, `Expand-Archive`). +`nssm.exe` **no** viene en la carpeta: se descarga aparte y se indica con `-Nssm`. + ``` C:\apps\devolucion-proveedores\ diff --git a/scripts/publicar.mjs b/scripts/publicar.mjs index 7035eba..490f13e 100644 --- a/scripts/publicar.mjs +++ b/scripts/publicar.mjs @@ -64,6 +64,29 @@ copiar('apps/web/dist/web/browser'); ejecutar(npm, ['ci', '--omit=dev', '--workspace=@dp/shared', '--workspace=api', '--no-audit', '--no-fund'], app); +// npm deja enlaces simbólicos (el de cada workspace y los atajos de .bin) que no sobreviven al copiar la +// carpeta de un Mac/Linux a Windows. La API importa @dp/shared al ejecutarse, así que ese enlace se +// reemplaza por una copia real; los demás no se usan y se quitan. +const nodeModules = join(app, 'node_modules'); +rmSync(join(nodeModules, 'api'), { recursive: true, force: true }); +const compartido = join(nodeModules, '@dp', 'shared'); +rmSync(compartido, { recursive: true, force: true }); // quita solo el enlace, no su destino +mkdirSync(compartido, { recursive: true }); +cpSync(join(app, 'libs', 'shared', 'package.json'), join(compartido, 'package.json')); +cpSync(join(app, 'libs', 'shared', 'dist'), join(compartido, 'dist'), { recursive: true }); +rmSync(join(app, 'libs'), { recursive: true, force: true }); +for (const e of readdirSync(nodeModules, { withFileTypes: true, recursive: true })) { + if (e.name === '.bin') rmSync(join(e.parentPath, e.name), { recursive: true, force: true }); +} + +const enlaces = readdirSync(app, { withFileTypes: true, recursive: true }) + .filter((e) => e.isSymbolicLink()) + .map((e) => join(e.parentPath, e.name)); +if (enlaces.length > 0) { + console.error(`\nQuedaron enlaces simbólicos en el paquete (no sobreviven en Windows):\n ${enlaces.join('\n ')}`); + process.exit(1); +} + // Archivos de despliegue para Windows junto a la carpeta app/ for (const archivo of readdirSync(join(raiz, 'deploy', 'windows'))) { cpSync(join(raiz, 'deploy', 'windows', archivo), join(destino, archivo)); From 93aa1568b6591d99fa26ed30493adb487cec0569 Mon Sep 17 00:00:00 2001 From: george Date: Tue, 13 Oct 2026 17:11:51 -0500 Subject: [PATCH 2/2] Documentar que las consultas SQL funcionan en HANA Confirmado por el usuario contra el Service Layer de HANA (DP_Stock y DP_EntradasAbiertas). Quedan por confirmar el paginado y los tipos de metadatos. Co-Authored-By: Claude Sonnet 5.5 --- CLAUDE.md | 10 ++++------ apps/api/README.md | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 39837a7..3485d41 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -248,12 +248,10 @@ Reglas: devuelve siempre un JSON (`{abierta: null}`). **Sin verificar:** -- **HANA**: el addon .NET ya funcionaba en HANA, y las consultas son un port de las suyas, así que - su lógica está probada. Lo que no se ha probado es **cómo las recibe el Service Layer de HANA** - (`SQLQueries`: su analizador es más estricto que el `DoQuery` del addon; en MSSQL rechazó `FORMAT`) y los - tres cambios respecto al addon: el filtro por almacén (`PDN1.WhsCode`), la columna `PDN1.OpenInvQty` - y los parámetros con nombre (`:item`, `:whs`). Tampoco el paginado (`$inlinecount`/`odata.count` es de `/b1s/v1`; en `/b1s/v2` sería - `$count`) ni los tipos que HANA informa en los metadatos. +- **HANA**: el usuario confirmó (2026-10-13) que las consultas `DP_Stock` y `DP_EntradasAbiertas` + (con `PDN1.OpenInvQty`, filtro por almacén y parámetros con nombre) **funcionan en HANA** a través del + Service Layer. Falta confirmar en HANA el paginado (`$inlinecount`/`odata.count` es de `/b1s/v1`; en + `/b1s/v2` sería `$count`) y los tipos que informa en los metadatos (Reconstruir). - **Reprocesar** solo con tests (no contra SAP). - Fallo parcial al crear Devoluciones (solo se probó el caso feliz). El corte en lotes de `U_PrintingLines` líneas está probado con tests del algoritmo portado del addon, no contra SAP. diff --git a/apps/api/README.md b/apps/api/README.md index 48b9d2b..4327234 100644 --- a/apps/api/README.md +++ b/apps/api/README.md @@ -125,7 +125,7 @@ La lógica de dominio pura (FIFO, consolidación por proveedor, parseo del texto que falta; borrar y recrear un campo solo ocurre con autorización explícita. - Los tipos que aparecen en firmas con decoradores se importan con `import type` (por `isolatedModules` + `emitDecoratorMetadata`); los DTO, que son clases, se importan como valor. -- **HANA**: las variantes HANA de las consultas están escritas pero no se han ejecutado todavía. +- **HANA**: las variantes HANA de las consultas ya se probaron contra el Service Layer de HANA y funcionan. Falta confirmar el paginado del listado y los tipos de los metadatos. ## Diagnóstico rápido