104 lines
4.8 KiB
TypeScript
104 lines
4.8 KiB
TypeScript
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']) }));
|
|
}
|
|
|
|
/** La serie que SAP usa por defecto para las solicitudes de traslado, o `null` si no se pudo saber. */
|
|
async defaultSeries(): Promise<number | null> {
|
|
const res = await firstValueFrom(this.http.post<{ Series?: number }>(`${API_URL}/sl/SeriesService_GetDefaultSeries`, { DocumentTypeParams: { Document: OBJECT_TYPE } }));
|
|
return typeof res.Series === 'number' ? res.Series : null;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|