151 lines
5.9 KiB
TypeScript
151 lines
5.9 KiB
TypeScript
import type { GridColumn, GridLine } from '../../shared/grid/grid.types';
|
|||
|
|
import type { VhLookup } from '../../shared/value-help/value-help.types';
|
||
|
|
import type { DocRecord, LinesSpec, RequiredField } from './document-model';
|
||
|
|
import { DOCUMENT_LINE_COLUMNS, uomEntryOf } from './line-columns';
|
||
|
|
|
||
|
|
export interface HeaderField {
|
||
|
|
key: string;
|
||
|
|
label: string;
|
||
|
|
/** `warehouse` abre la lista de almacenes. */
|
||
|
|
type: 'text' | 'date' | 'warehouse';
|
||
|
|
required?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Formulario de los documentos sin socio de negocio ni totales (inventario y solicitudes de
|
||
|
|
* compra): qué campos de cabecera y de línea tienen, y cómo se guardan.
|
||
|
|
*/
|
||
|
|
export interface DocumentLayout {
|
||
|
|
lines: LinesSpec;
|
||
|
|
/** Dos columnas como máximo (regla del proyecto base); el número y los comentarios van aparte. */
|
||
|
|
left: readonly HeaderField[];
|
||
|
|
right: readonly HeaderField[];
|
||
|
|
columns: readonly GridColumn[];
|
||
|
|
/** Cabecera de un documento nuevo (siempre lleva `DocDate`). */
|
||
|
|
newHeader: () => DocRecord;
|
||
|
|
/** Línea nueva; las transferencias toman los almacenes de la cabecera. */
|
||
|
|
newLine: (header: DocRecord) => GridLine;
|
||
|
|
/** Una vez contabilizado ya no se modifica (SAP no permite cambiar entradas, salidas ni transferencias). */
|
||
|
|
immutable?: boolean;
|
||
|
|
/** Ajustes a la cabecera antes de crear. */
|
||
|
|
beforeCreate?: (header: DocRecord) => DocRecord;
|
||
|
|
requiredLine: readonly RequiredField[];
|
||
|
|
}
|
||
|
|
|
||
|
|
const today = (): string => new Date().toISOString().slice(0, 10);
|
||
|
|
|
||
|
|
const [baseItemColumn, descriptionColumn, quantityColumn, warehouseColumn, priceColumn] = DOCUMENT_LINE_COLUMNS;
|
||
|
|
|
||
|
|
/** Artículo que además completa la unidad de medida de la línea (`UoMEntry`). */
|
||
|
|
const itemColumn: GridColumn = {
|
||
|
|
...baseItemColumn,
|
||
|
|
lookup: {
|
||
|
|
...baseItemColumn.lookup!,
|
||
|
|
apply: (item, line) => ({ ...baseItemColumn.lookup!.apply(item, line), UoMEntry: uomEntryOf(item) ?? line['UoMEntry'] }),
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
/** Lista de almacenes de la cabecera (la misma de las líneas). */
|
||
|
|
export const WAREHOUSE_LOOKUP: VhLookup = warehouseColumn.lookup!.config;
|
||
|
|
|
||
|
|
const blankLine = (): GridLine => ({ ItemCode: '', ItemDescription: '', Quantity: 1, WarehouseCode: '' });
|
||
|
|
|
||
|
|
/** Entradas y salidas de mercancía: artículo, cantidad, almacén y precio (opcional: si falta, SAP usa el costo). */
|
||
|
|
const goods: DocumentLayout = {
|
||
|
|
lines: { key: 'DocumentLines', fields: ['ItemCode', 'ItemDescription', 'Quantity', 'UoMEntry', 'WarehouseCode', 'Price'], base: false },
|
||
|
|
left: [{ key: 'DocDate', label: 'Fecha de contabilización', type: 'date', required: true }],
|
||
|
|
right: [],
|
||
|
|
columns: [itemColumn, descriptionColumn, quantityColumn, { ...warehouseColumn, required: true }, priceColumn],
|
||
|
|
newHeader: () => ({ DocDate: today() }),
|
||
|
|
newLine: () => ({ ...blankLine(), Price: null }),
|
||
|
|
immutable: true,
|
||
|
|
requiredLine: [{ key: 'WarehouseCode', label: 'el almacén' }],
|
||
|
|
};
|
||
|
|
|
||
|
|
/** Transferencias de stock y solicitudes de traslado: almacén origen y destino en la cabecera y en cada línea. */
|
||
|
|
const transfer = (immutable: boolean): DocumentLayout => ({
|
||
|
|
lines: { key: 'StockTransferLines', fields: ['ItemCode', 'ItemDescription', 'Quantity', 'FromWarehouseCode', 'WarehouseCode'], base: false },
|
||
|
|
left: [
|
||
|
|
{ key: 'FromWarehouse', label: 'Almacén origen', type: 'warehouse', required: true },
|
||
|
|
{ key: 'ToWarehouse', label: 'Almacén destino', type: 'warehouse', required: true },
|
||
|
|
],
|
||
|
|
right: [{ key: 'DocDate', label: 'Fecha de contabilización', type: 'date', required: true }],
|
||
|
|
columns: [
|
||
|
|
itemColumn,
|
||
|
|
descriptionColumn,
|
||
|
|
quantityColumn,
|
||
|
|
{
|
||
|
|
...warehouseColumn,
|
||
|
|
key: 'FromWarehouseCode',
|
||
|
|
title: 'Almacén origen',
|
||
|
|
required: true,
|
||
|
|
lookup: { ...warehouseColumn.lookup!, apply: (wh, line) => ({ ...line, FromWarehouseCode: wh['WarehouseCode'] }) },
|
||
|
|
},
|
||
|
|
{ ...warehouseColumn, title: 'Almacén destino', required: true },
|
||
|
|
],
|
||
|
|
newHeader: () => ({ DocDate: today() }),
|
||
|
|
newLine: (header) => ({
|
||
|
|
...blankLine(),
|
||
|
|
FromWarehouseCode: header['FromWarehouse'] ?? '',
|
||
|
|
WarehouseCode: header['ToWarehouse'] ?? '',
|
||
|
|
}),
|
||
|
|
immutable,
|
||
|
|
requiredLine: [
|
||
|
|
{ key: 'FromWarehouseCode', label: 'el almacén origen' },
|
||
|
|
{ key: 'WarehouseCode', label: 'el almacén destino' },
|
||
|
|
],
|
||
|
|
});
|
||
|
|
|
||
|
|
/** Solicitud de compra: solicitante (usuario de SAP), fecha requerida y, por línea, proveedor sugerido. */
|
||
|
|
const purchaseRequest: DocumentLayout = {
|
||
|
|
lines: { key: 'DocumentLines', fields: ['ItemCode', 'ItemDescription', 'Quantity', 'UoMEntry', 'WarehouseCode', 'LineVendor'], base: false },
|
||
|
|
left: [{ key: 'Requester', label: 'Solicitante (usuario)', type: 'text' }],
|
||
|
|
right: [
|
||
|
|
{ key: 'DocDate', label: 'Fecha de contabilización', type: 'date', required: true },
|
||
|
|
{ key: 'RequriedDate', label: 'Fecha requerida', type: 'date', required: true },
|
||
|
|
],
|
||
|
|
columns: [
|
||
|
|
itemColumn,
|
||
|
|
descriptionColumn,
|
||
|
|
quantityColumn,
|
||
|
|
warehouseColumn,
|
||
|
|
{
|
||
|
|
key: 'LineVendor',
|
||
|
|
title: 'Proveedor',
|
||
|
|
width: '130px',
|
||
|
|
mono: true,
|
||
|
|
lookup: {
|
||
|
|
config: {
|
||
|
|
title: 'Proveedores',
|
||
|
|
resource: 'BusinessPartners',
|
||
|
|
baseFilter: `CardType eq 'cSupplier'`,
|
||
|
|
orderby: 'CardCode',
|
||
|
|
columns: [
|
||
|
|
{ key: 'CardCode', title: 'Código', width: '130px', mono: true },
|
||
|
|
{ key: 'CardName', title: 'Nombre' },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
apply: (bp, line) => ({ ...line, LineVendor: bp['CardCode'] }),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
],
|
||
|
|
newHeader: () => ({ DocDate: today(), RequriedDate: today() }),
|
||
|
|
newLine: blankLine,
|
||
|
|
// El solicitante es un usuario de SAP (`ReqType` 12).
|
||
|
|
beforeCreate: (h) => (h['Requester'] ? { ...h, ReqType: 12 } : h),
|
||
|
|
requiredLine: [],
|
||
|
|
};
|
||
|
|
|
||
|
|
export const LAYOUTS = {
|
||
|
|
goods,
|
||
|
|
transfer: transfer(true),
|
||
|
|
transferRequest: transfer(false),
|
||
|
|
purchaseRequest,
|
||
|
|
} as const;
|
||
|
|
|
||
|
|
/** Campos de cabecera que se editan (y se envían): los de las columnas del formulario y los comentarios. */
|
||
|
|
export const headerKeysOf = (layout: DocumentLayout): readonly string[] => [
|
||
|
|
...layout.left.map((f) => f.key),
|
||
|
|
...layout.right.map((f) => f.key),
|
||
|
|
'Comments',
|
||
|
|
];
|