feat(tagumedica): flujo de lotes (inmovilizar, liberar y confirmar la liberación) con solicitudes de traslado, consultas a HANA y campos de usuario por su definición en SAP

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-20 15:52:01 -05:00
1 parent 72b3cd1d43
commit 5728fc8e5b
15 files changed
+1325 -13

No files matched your search

@@ -0,0 +1,27 @@
import { SL_RESOURCE_MAP } from '../sl/sl-resources.js';
import { SAPDB_QUERIES } from './sapdb-queries.js';
import { bindNamed } from './sapdb-sql.js';
describe('consultas de TAGUMEDICA', () => {
it('registra las del flujo de lotes y cada una es de solo lectura (el registro lo valida al cargar)', () => {
for (const name of ['lot-stock', 'lot-sources']) {
expect(SAPDB_QUERIES.has(name), name).toBe(true);
}
});
it('el origen de un lote se busca con artículo, lote y almacén como parámetros', () => {
const q = SAPDB_QUERIES.get('lot-sources')!;
const b = bindNamed(q.sql.hana!, { item: 'A1', lot: 'L1', warehouse: '302' });
expect(b.args).toEqual(['A1', 'L1', '302']);
expect(b.sql).not.toContain(':');
});
it('las tres pantallas del flujo pueden leer y modificar las solicitudes de traslado', () => {
const r = SL_RESOURCE_MAP.get('InventoryTransferRequests')!;
for (const p of ['/inventario/inmovilizar-lotes', '/inventario/liberar-lotes', '/inventario/confirmar-liberacion-lote']) {
expect(r.read as string[], p).toContain(p);
expect(r.write as string[], p).toContain(p);
}
expect(r.write as string[]).toContain('/inventario/solicitudes-traslado');
});
});
+49 -3
View File
@@ -1,7 +1,53 @@
import type { SapDbQuery } from './sapdb.types.js';
const FLOWS = ['/inventario/inmovilizar-lotes', '/inventario/liberar-lotes'] as const;
/**
* Consultas a la base de SAP que agrega la rama de cada cliente (vacío en `main`): el SQL depende de sus tablas y de
* sus campos de usuario. Cada una declara su SQL por motor (`hana`), sus parámetros con nombre y el permiso de menú.
* Consultas a la base de SAP de TAGUMEDICA (HANA): el flujo de lotes (inmovilizar, liberar y confirmar la liberación).
* Dependen de sus tablas, por eso viven solo en esta rama. Los valores de los campos de usuario de la solicitud (movimiento,
* tipo de operación, despacho, motivo) no necesitan consulta propia: salen de `/api/sapdb/user-fields`.
*/
export const CLIENT_SAPDB_QUERIES: readonly SapDbQuery[] = [];
export const CLIENT_SAPDB_QUERIES: readonly SapDbQuery[] = [
{
// Stock de un lote por número, en cada artículo y almacén (un mismo número de lote puede repetirse en varios artículos).
name: 'lot-stock',
title: 'Stock de un lote por número, artículo y almacén',
sql: {
hana: `SELECT T1."WhsCode" AS "WarehouseCode", T2."WhsName" AS "WarehouseName", T1."Quantity" AS "Quantity",
T1."CommitQty" AS "Committed", T1."ItemCode" AS "ItemCode", T3."ItemName" AS "ItemDescription"
FROM "OBTN" T0
INNER JOIN "OBTQ" T1 ON T1."MdAbsEntry" = T0."AbsEntry"
INNER JOIN "OWHS" T2 ON T2."WhsCode" = T1."WhsCode"
INNER JOIN "OITM" T3 ON T3."ItemCode" = T1."ItemCode"
WHERE T0."DistNumber" = :lot AND T1."Quantity" > 0
ORDER BY T3."ItemName", T2."WhsName"`,
},
params: { lot: { type: 'string' } },
menu: FLOWS,
maxRows: 500,
},
{
// De qué almacenes vino un lote hasta el almacén donde está hoy: transferencias ejecutadas (OWTR, DocType 67) que lo
// ingresaron, con el almacén del que salió cada una. El reparto a prorrata lo hace el frontend.
name: 'lot-sources',
title: 'Almacenes de origen de un lote que llegó a un almacén',
sql: {
hana: `WITH LOT AS (SELECT "AbsEntry" FROM "OBTN" WHERE "ItemCode" = :item AND "DistNumber" = :lot)
SELECT O."Whs" AS "WarehouseCode", SUM(I."Qty") AS "Quantity" FROM
(SELECT T1."DocEntry" AS "DocEntry", MAX(T0."Quantity") AS "Qty"
FROM "ITL1" T0 INNER JOIN "OITL" T1 ON T1."LogEntry" = T0."LogEntry"
WHERE T0."MdAbsEntry" IN (SELECT "AbsEntry" FROM LOT) AND T1."LocCode" = :warehouse
AND T1."StockEff" = 1 AND T0."Quantity" > 0 AND T1."DocType" = 67
GROUP BY T1."DocEntry") I
INNER JOIN
(SELECT T1."DocEntry" AS "DocEntry", MIN(T1."LocCode") AS "Whs"
FROM "ITL1" T0 INNER JOIN "OITL" T1 ON T1."LogEntry" = T0."LogEntry"
WHERE T0."MdAbsEntry" IN (SELECT "AbsEntry" FROM LOT) AND T1."DocType" = 67 AND T0."Quantity" < 0
GROUP BY T1."DocEntry") O ON O."DocEntry" = I."DocEntry"
GROUP BY O."Whs" ORDER BY O."Whs"`,
},
params: { item: { type: 'string' }, lot: { type: 'string' }, warehouse: { type: 'string' } },
menu: '/inventario/confirmar-liberacion-lote',
maxRows: 100,
},
];
+7 -1
View File
@@ -10,6 +10,12 @@ import type { SlResource } from './sl-resources.js';
*
* `CLIENT_SL_RESOURCES`: recursos nuevos (tablas de usuario, objetos propios). Nunca `EmployeesInfo`.
*/
export const CLIENT_RESOURCE_PATHS: Readonly<Record<string, { read?: readonly string[]; write?: readonly string[] }>> = {};
export const CLIENT_RESOURCE_PATHS: Readonly<Record<string, { read?: readonly string[]; write?: readonly string[] }>> = {
// Inmovilizar y liberar crean la solicitud de traslado; confirmar la liberación la lee y la modifica.
InventoryTransferRequests: {
read: ['/inventario/inmovilizar-lotes', '/inventario/liberar-lotes', '/inventario/confirmar-liberacion-lote'],
write: ['/inventario/inmovilizar-lotes', '/inventario/liberar-lotes', '/inventario/confirmar-liberacion-lote'],
},
};
export const CLIENT_SL_RESOURCES: readonly SlResource[] = [];