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:
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');
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
},
|
||||
];
|
||||
@@ -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[] = [];
|
||||
Reference in new issue
Block a user