Files
basic-erp/apps/web/src/app/features/client/lotes/lotes-flujo-api.ts
T

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);
}
}