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; /** 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 { 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> { 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 { 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 { 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>(`${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 { 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 { return this.master.create(RESOURCE, body); } /** Las solicitudes abiertas pendientes de confirmar (las más recientes primero). */ async pending(motive: string): Promise { 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>(`${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 { return firstValueFrom(this.http.get(`${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 { return this.master.update(RESOURCE, String(docEntry), { StockTransferLines: lines }, etag, true); } }