Usar PDN1.OpenInvQty en la consulta FIFO y ajustar el despliegue
- 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 <noreply@anthropic.com>
This commit is contained in:
1 parent
699dd983d0
commit
2873fd3bc6
6 files changed
+40
-10
No files matched your search
@@ -52,6 +52,8 @@ Pasos de la UI (decididos):
|
|||||||
crea/actualiza `DP_Stock` y `DP_EntradasAbiertas` sola (variante según `DB_TYPE`). El analizador
|
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
|
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.
|
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),
|
- **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á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.
|
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
|
`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);
|
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
|
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
|
- **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
|
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
|
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
|
- **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**
|
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
|
(`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
|
tres cambios respecto al addon: el filtro por almacén (`PDN1.WhsCode`), la columna `PDN1.OpenInvQty`
|
||||||
(`:item`, `:whs`). Tampoco el paginado (`$inlinecount`/`odata.count` es de `/b1s/v1`; en `/b1s/v2` sería
|
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.
|
`$count`) ni los tipos que HANA informa en los metadatos.
|
||||||
- **Reprocesar** solo con tests (no contra SAP).
|
- **Reprocesar** solo con tests (no contra SAP).
|
||||||
- Fallo parcial al crear Devoluciones (solo se probó el caso feliz). El corte en lotes de
|
- Fallo parcial al crear Devoluciones (solo se probó el caso feliz). El corte en lotes de
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ function crear(anterior: ReturnType<typeof udo>, opciones: { stock?: unknown[];
|
|||||||
const sql = {
|
const sql = {
|
||||||
consultar: vi.fn(async (_s: unknown, consulta: { codigo: string }) => {
|
consultar: vi.fn(async (_s: unknown, consulta: { codigo: string }) => {
|
||||||
if (consulta.codigo === 'DP_Stock') return opciones.stock ?? [{ ItemCode: 'A1', ItemName: 'Libro', OnHand: 100 }];
|
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 }]) };
|
const catalogos = { series: vi.fn(async () => [{ series: 15, name: 'Primario', nextNumber: 1, esPorDefecto: true }]) };
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ interface FilaEntrada {
|
|||||||
DocDate: string;
|
DocDate: string;
|
||||||
DocEntry: number;
|
DocEntry: number;
|
||||||
LineNum: number;
|
LineNum: number;
|
||||||
OpenQty: number | string;
|
OpenInvQty: number | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DocumentoCreado {
|
interface DocumentoCreado {
|
||||||
@@ -242,7 +242,7 @@ export class PlanificacionesService {
|
|||||||
docDate: fechaIso(String(f.DocDate)),
|
docDate: fechaIso(String(f.DocDate)),
|
||||||
cardCode: f.CardCode,
|
cardCode: f.CardCode,
|
||||||
cardName: f.CardName,
|
cardName: f.CardName,
|
||||||
openQty: Math.floor(Number(f.OpenQty)),
|
openQty: Math.floor(Number(f.OpenInvQty)),
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,17 +30,17 @@ export const CONSULTA_STOCK: ConsultaGuardada = {
|
|||||||
|
|
||||||
export const CONSULTA_ENTRADAS_ABIERTAS: ConsultaGuardada = {
|
export const CONSULTA_ENTRADAS_ABIERTAS: ConsultaGuardada = {
|
||||||
codigo: 'DP_EntradasAbiertas',
|
codigo: 'DP_EntradasAbiertas',
|
||||||
version: 2,
|
version: 3,
|
||||||
mssql:
|
mssql:
|
||||||
'SELECT T0.[DocNum], T0.[CardCode], T0.[CardName], T0.[DocDate], ' +
|
'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] ' +
|
'FROM OPDN T0 INNER JOIN PDN1 T1 ON T1.[DocEntry] = T0.[DocEntry] ' +
|
||||||
"WHERE T0.[U_DOT_TipoRecepcion] = 'C' AND T1.[LineStatus] = 'O' " +
|
"WHERE T0.[U_DOT_TipoRecepcion] = 'C' AND T1.[LineStatus] = 'O' " +
|
||||||
'AND T1.[ItemCode] = :item AND T1.[WhsCode] = :whs ' +
|
'AND T1.[ItemCode] = :item AND T1.[WhsCode] = :whs ' +
|
||||||
'ORDER BY T0.[DocDate], T1.[DocEntry], T1.[LineNum]',
|
'ORDER BY T0.[DocDate], T1.[DocEntry], T1.[LineNum]',
|
||||||
hana:
|
hana:
|
||||||
'SELECT T0."DocNum", T0."CardCode", T0."CardName", T0."DocDate", ' +
|
'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" ' +
|
'FROM OPDN T0 INNER JOIN PDN1 T1 ON T1."DocEntry" = T0."DocEntry" ' +
|
||||||
"WHERE T0.\"U_DOT_TipoRecepcion\" = 'C' AND T1.\"LineStatus\" = 'O' " +
|
"WHERE T0.\"U_DOT_TipoRecepcion\" = 'C' AND T1.\"LineStatus\" = 'O' " +
|
||||||
'AND T1."ItemCode" = :item AND T1."WhsCode" = :whs ' +
|
'AND T1."ItemCode" = :item AND T1."WhsCode" = :whs ' +
|
||||||
|
|||||||
@@ -52,7 +52,11 @@ ejecutar en Windows.
|
|||||||
|
|
||||||
## 3. Copiar al servidor
|
## 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\
|
C:\apps\devolucion-proveedores\
|
||||||
|
|||||||
@@ -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);
|
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/
|
// Archivos de despliegue para Windows junto a la carpeta app/
|
||||||
for (const archivo of readdirSync(join(raiz, 'deploy', 'windows'))) {
|
for (const archivo of readdirSync(join(raiz, 'deploy', 'windows'))) {
|
||||||
cpSync(join(raiz, 'deploy', 'windows', archivo), join(destino, archivo));
|
cpSync(join(raiz, 'deploy', 'windows', archivo), join(destino, archivo));
|
||||||
|
|||||||
Reference in new issue
Block a user