import { MENU } from '@basic-erp/sap-types'; import { formatNumber } from '../../core/format'; import type { ListFilter } from '../../core/sl/list-filters'; import type { ListColumn, ListRow, Tone } from '../../shared/list/list.types'; /** Pantalla de un documento: lista de los últimos ingresados + formulario (nuevo / ver-editar). */ export interface DocumentScreen { /** Ruta de menú (`/ventas/ordenes`); de ella cuelgan `nuevo` y `:docEntry`. */ path: string; /** Recurso del proxy `/api/sl`. */ resource: string; createLabel: string; columns: readonly ListColumn[]; /** Campos necesarios para pintar que no tienen columna propia. */ extraFields?: readonly string[]; filters: readonly ListFilter[]; /** Los más recientes primero. */ orderby: string; /** Formulario de documentos con socio de negocio, líneas y totales (ventas y compras). */ partner?: { label: string; type: 'cCustomer' | 'cSupplier'; taxResource: 'SalesTaxCodes' | 'PurchaseTaxCodes' }; } export const menuLabel = (path: string): string => MENU.flatMap((m) => m.items).find((i) => i.path === path)?.label ?? path; export function documentStatus(row: ListRow): { label: string; tone: Tone } | undefined { if (row['Cancelled'] === 'tYES') return { label: 'Cancelado', tone: 'err' }; switch (row['DocumentStatus']) { case 'bost_Open': return { label: 'Abierto', tone: 'ok' }; case 'bost_Close': return { label: 'Cerrado', tone: 'muted' }; default: return undefined; } } /** Total en la moneda del documento: SAP guarda en `DocTotalFc` el importe de los documentos en moneda extranjera. */ export function documentTotal(row: ListRow): string { const fc = Number(row['DocTotalFc'] ?? 0); const amount = fc !== 0 ? fc : row['DocTotal']; if (amount === null || amount === undefined) return ''; return `${row['DocCurrency'] ?? ''} ${formatNumber(amount, 2)}`.trim(); } const statusColumn: ListColumn = { key: 'DocumentStatus', title: 'Estado', width: '100px', format: (r) => documentStatus(r)?.label ?? '', tone: (r) => documentStatus(r)?.tone, }; /** Número de documento: sin separador de miles, y buscable por igualdad (es numérico). */ export const docNum: ListColumn = { key: 'DocNum', title: 'Número', type: 'number', align: 'left', width: '110px', mono: true, searchable: true, format: (r) => String(r['DocNum'] ?? ''), }; const docDate: ListColumn = { key: 'DocDate', title: 'Fecha contab.', type: 'date', align: 'center', width: '115px' }; const statusFilter: ListFilter = { type: 'select', key: 'DocumentStatus', label: 'Estado', defaultValue: 'bost_Open', options: [ { value: 'bost_Open', label: 'Abierto' }, { value: 'bost_Close', label: 'Cerrado' }, ], }; const dateFilter: ListFilter = { type: 'date', key: 'DocDate', label: 'Fecha contab.' }; const currencyFilter: ListFilter = { type: 'multi', key: 'DocCurrency', label: 'Moneda', resource: 'Currencies', valueField: 'Code', labelField: 'Name', }; /** Ventas y compras: documentos con socio de negocio y total. */ const marketing = (path: string, resource: string, createLabel: string, partner: string): DocumentScreen => ({ path, resource, createLabel, orderby: 'DocEntry desc', partner: path.startsWith('/ventas') ? { label: partner, type: 'cCustomer', taxResource: 'SalesTaxCodes' } : { label: partner, type: 'cSupplier', taxResource: 'PurchaseTaxCodes' }, extraFields: ['Cancelled', 'DocTotalFc', 'DocCurrency'], columns: [ docNum, statusColumn, { key: 'CardCode', title: partner, width: '130px', mono: true, searchable: true }, { key: 'CardName', title: 'Nombre' }, docDate, { key: 'DocDueDate', title: 'Fecha venc.', type: 'date', align: 'center', width: '115px' }, { key: 'DocTotal', title: 'Total', align: 'right', width: '140px', format: documentTotal }, ], filters: [statusFilter, currencyFilter, dateFilter], }); const inventory = (path: string, resource: string, createLabel: string, withStatus: boolean): DocumentScreen => ({ path, resource, createLabel, orderby: 'DocEntry desc', extraFields: withStatus ? ['Cancelled'] : [], columns: [ docNum, ...(withStatus ? [statusColumn] : []), docDate, ...(resource === 'StockTransfers' || resource === 'InventoryTransferRequests' ? [ { key: 'FromWarehouse', title: 'Almacén origen', width: '130px', mono: true, searchable: true }, { key: 'ToWarehouse', title: 'Almacén destino', width: '130px', mono: true, searchable: true }, ] : []), { key: 'Comments', title: 'Comentarios', searchable: true }, ], filters: withStatus ? [statusFilter, dateFilter] : [dateFilter], }); export const DOCUMENT_SCREENS: readonly DocumentScreen[] = [ // Ventas marketing('/ventas/ofertas', 'Quotations', 'Nueva oferta', 'Cliente'), marketing('/ventas/ordenes', 'Orders', 'Nueva orden', 'Cliente'), marketing('/ventas/entrega', 'DeliveryNotes', 'Nueva entrega', 'Cliente'), marketing('/ventas/facturas', 'Invoices', 'Nueva factura', 'Cliente'), marketing('/ventas/devoluciones', 'Returns', 'Nueva devolución', 'Cliente'), marketing('/ventas/notas-credito', 'CreditNotes', 'Nueva nota de crédito', 'Cliente'), // Compras { path: '/compras/solicitudes', resource: 'PurchaseRequests', createLabel: 'Nueva solicitud', orderby: 'DocEntry desc', extraFields: ['Cancelled'], columns: [ docNum, statusColumn, docDate, { key: 'RequriedDate', title: 'Fecha requerida', type: 'date', align: 'center', width: '125px' }, { key: 'RequesterName', title: 'Solicitante', searchable: true }, ], filters: [statusFilter, dateFilter], }, marketing('/compras/ofertas', 'PurchaseQuotations', 'Nueva oferta', 'Proveedor'), marketing('/compras/ordenes', 'PurchaseOrders', 'Nueva orden', 'Proveedor'), marketing('/compras/recepcion', 'PurchaseDeliveryNotes', 'Nueva recepción', 'Proveedor'), marketing('/compras/facturas', 'PurchaseInvoices', 'Nueva factura', 'Proveedor'), marketing('/compras/devoluciones', 'PurchaseReturns', 'Nueva devolución', 'Proveedor'), marketing('/compras/notas-credito', 'PurchaseCreditNotes', 'Nueva nota de crédito', 'Proveedor'), // Inventario inventory('/inventario/entradas', 'InventoryGenEntries', 'Nueva entrada', true), inventory('/inventario/salidas', 'InventoryGenExits', 'Nueva salida', true), inventory('/inventario/transferencias', 'StockTransfers', 'Nueva transferencia', false), inventory('/inventario/solicitudes-traslado', 'InventoryTransferRequests', 'Nueva solicitud', true), ];