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,97 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import type { SlListResponse } from '@basic-erp/sap-types';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { API_URL } from '../../../core/api';
|
||||
import { SapDbApi } from '../../../core/sl/sapdb-api';
|
||||
import { SlMasterApi } from '../../../core/sl/sl-master';
|
||||
import { UserFieldsApi } from '../../../core/sl/user-fields';
|
||||
import { type LotStockRow, toStockRow } from './lotes-flujo';
|
||||
|
||||
export interface Option {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
/** Catálogos de la solicitud de traslado: cada uno sale de la definición del campo de usuario en SAP. */
|
||||
export interface TransferCatalogs {
|
||||
movimientoInventario: Option[];
|
||||
tipoOperacion: Option[];
|
||||
tipoDespacho: Option[];
|
||||
movimientoAlmacen: Option[];
|
||||
}
|
||||
|
||||
const RESOURCE = 'InventoryTransferRequests';
|
||||
/** Objeto de SAP de la solicitud de traslado (`BoObjectTypes`), para pedir sus series. */
|
||||
const OBJECT_TYPE = '1250000001';
|
||||
|
||||
type Row = Record<string, unknown>;
|
||||
|
||||
/** Habla con SAP para los flujos de lotes: la base (stock y orígenes), los campos de usuario y la solicitud de traslado. */
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class LotesFlujoApi {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly sapdb = inject(SapDbApi);
|
||||
private readonly userFields = inject(UserFieldsApi);
|
||||
private readonly master = inject(SlMasterApi);
|
||||
|
||||
/** El stock de un lote por número, en cada artículo y almacén. */
|
||||
async stock(lot: string): Promise<LotStockRow[]> {
|
||||
return (await this.sapdb.run('lot-stock', { lot })).rows.map(toStockRow);
|
||||
}
|
||||
|
||||
/** De qué almacenes vino un lote hasta el almacén donde está: almacén → cantidad histórica. */
|
||||
async sources(item: string, lot: string, warehouse: string): Promise<Record<string, number>> {
|
||||
const rows = (await this.sapdb.run('lot-sources', { item, lot, warehouse })).rows;
|
||||
return Object.fromEntries(rows.map((r) => [String(r['WarehouseCode']), Number(r['Quantity'])]));
|
||||
}
|
||||
|
||||
/** Un campo de usuario de la solicitud como lista: su tabla enlazada o sus valores válidos. */
|
||||
private async options(alias: string): Promise<Option[]> {
|
||||
const info = await this.userFields.get('OWTQ', alias);
|
||||
if (!info) return [];
|
||||
if (info.validValues.length) return info.validValues.map((v) => ({ value: v.value, label: v.description ? `${v.value} · ${v.description}` : v.value }));
|
||||
return info.linkedRows.map((r) => ({ value: r.code, label: r.name.startsWith(r.code) ? r.name : `${r.code} · ${r.name}` }));
|
||||
}
|
||||
|
||||
async catalogs(): Promise<TransferCatalogs> {
|
||||
const [movimientoInventario, tipoOperacion, tipoDespacho, movimientoAlmacen] = await Promise.all([
|
||||
this.options('SIS_MOVINV'),
|
||||
this.options('EXX_TIPOOPER'),
|
||||
this.options('SIS_TIPDESP'),
|
||||
this.options('SIS_MOTALM'),
|
||||
]);
|
||||
return { movimientoInventario, tipoOperacion, tipoDespacho, movimientoAlmacen };
|
||||
}
|
||||
|
||||
/** Las series de la solicitud de traslado que no están bloqueadas. */
|
||||
async series(): Promise<{ value: number; label: string }[]> {
|
||||
const res = await firstValueFrom(this.http.post<SlListResponse<Row>>(`${API_URL}/sl/SeriesService_GetDocumentSeries`, { DocumentTypeParams: { Document: OBJECT_TYPE } }));
|
||||
return res.value.filter((r) => r['Locked'] !== 'tYES').map((r) => ({ value: Number(r['Series']), label: String(r['Name'] ?? r['Series']) }));
|
||||
}
|
||||
|
||||
create(body: Row): Promise<Row> {
|
||||
return this.master.create(RESOURCE, body);
|
||||
}
|
||||
|
||||
/** Las solicitudes abiertas pendientes de confirmar (las más recientes primero). */
|
||||
async pending(motive: string): Promise<Row[]> {
|
||||
const params = {
|
||||
$select: 'DocEntry,DocNum,DocDate,U_SIS_MOTEST,U_SIS_COMEST,U_SIS_ACCTOM',
|
||||
$filter: `DocumentStatus eq 'bost_Open' and U_SIS_MOTALM eq '${motive}'`,
|
||||
$orderby: 'DocEntry desc',
|
||||
$top: 200,
|
||||
};
|
||||
return (await firstValueFrom(this.http.get<SlListResponse<Row>>(`${API_URL}/sl/${RESOURCE}`, { params }))).value;
|
||||
}
|
||||
|
||||
/** La solicitud completa (el Service Layer solo trae los lotes de las líneas al pedirla por su clave). */
|
||||
request(docEntry: number): Promise<Row> {
|
||||
return firstValueFrom(this.http.get<Row>(`${API_URL}/sl/${RESOURCE}/${docEntry}`));
|
||||
}
|
||||
|
||||
/** Reemplaza las líneas de la solicitud (se pueden agregar y quitar al repartir entre almacenes). */
|
||||
replaceLines(docEntry: number, lines: Row[], etag: string | undefined): Promise<void> {
|
||||
return this.master.update(RESOURCE, String(docEntry), { StockTransferLines: lines }, etag, true);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user