From 52dc1957b766e8df8e2ed767315ad5d80dc1f81d Mon Sep 17 00:00:00 2001 From: George Date: Thu, 15 Oct 2026 17:32:32 -0500 Subject: [PATCH] feat: facturas y boletas de reserva (ReserveInvoice); las facturas y boletas normales excluyen las de reserva Co-Authored-By: Claude Sonnet 5.5 --- CLAUDE.md | 4 +-- apps/api/src/sl/sl-resources.ts | 4 +-- .../features/admin/permissions.page.spec.ts | 2 +- .../features/documents/document-copy.spec.ts | 6 +++- .../app/features/documents/document-copy.ts | 6 ++-- .../features/documents/document-folio.spec.ts | 2 +- .../documents/document-form.page.spec.ts | 7 ++-- .../documents/document-layouts.spec.ts | 2 +- .../app/features/documents/document-paths.ts | 2 ++ .../documents/document-screens.spec.ts | 33 ++++++++++++------- .../documents/ventas/ventas.screens.ts | 27 ++++++++++++--- libs/sap-types/src/menu.ts | 2 ++ 12 files changed, 70 insertions(+), 27 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index bfec927..d8c757d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,7 +36,7 @@ Es un ERP, no una página de publicidad: **ligera y rápida**. Sin librerías de - **TLS:** `SAPB1_SL_CA_FILE` opcional (certificado autofirmado de SAP o CA de firewall); `VERIFY_SSL=true` por defecto y no se puede apagar en producción. `fortinet.cer` no sirve para SAP10: presenta su propio certificado autofirmado (`apps/api/certs/sap10.pem`, ignorado por git). - **Proxy `/api/sl/:recurso`**: el frontend arma `$select/$filter/...`; el backend valida y ejecuta solo recursos registrados (`apps/api/src/sl/sl-resources.ts`), con permiso de menú y cabeceras `B1S-*` de lista blanca. Nunca registrar `EmployeesInfo` (tiene `U_WEB_PASS`). `InventoryRequests` no existe: es `InventoryTransferRequests`. - **Permisos de menú** en SQLite (`SQLITE_PATH`), patrones `*`, `/ventas/*`, `/ventas/ordenes`; `ADMIN_WEB_USERS` garantiza el acceso total; no se puede quitar el último admin. -- **Listas de documentos:** más recientes primero (`DocEntry desc`), 50 por página, **Estado = Abierto por defecto** (como el mini-erp; cambiarlo es un `defaultValue`), filtro de fecha **siempre rango [desde – hasta]**, multi-selección con **chips**, búsqueda numérica por igualdad. **Sin filtro base `DocumentSubType`** salvo las boletas: la boleta de venta es una `Invoices` con `DocumentSubType = bod_Bill` (pantalla `/ventas/boletas`, `baseFilter` + `fixed` al crear); la lista de facturas solo *excluye* `bod_Bill` (`ne`, no `eq bod_None`, para no perder notas de débito u otros subtipos). En SBODemoCL hay `bod_None` y `bod_Bill`. `baseFilter` de la pantalla se aplica también a la navegación primero/anterior/siguiente/último. El PDF y los reportes se migran después vía API Gateway (otro servicio, con su propia cola y sesión). +- **Listas de documentos:** más recientes primero (`DocEntry desc`), 50 por página, **Estado = Abierto por defecto** (como el mini-erp; cambiarlo es un `defaultValue`), filtro de fecha **siempre rango [desde – hasta]**, multi-selección con **chips**, búsqueda numérica por igualdad. **Sin filtro base `DocumentSubType`** salvo las boletas: cuatro pantallas comparten el recurso `Invoices` y se separan por dos campos: `DocumentSubType` (`bod_Bill` = boleta) y `ReserveInvoice` (`tYES` = de reserva): Facturas (`tNO` y `ne bod_Bill`), Boletas (`tNO` y `eq bod_Bill`), Facturas de reserva (`tYES` y `ne bod_Bill`) y Boletas de reserva (`tYES` y `eq bod_Bill`); `baseFilter` para la lista y `fixed` al crear. La factura solo *excluye* `bod_Bill` (`ne`, no `eq bod_None`, para no perder notas de débito u otros subtipos). En SBODemoCL: 295 facturas, 54 boletas, 469 facturas de reserva, 0 boletas de reserva. `baseFilter` de la pantalla se aplica también a la navegación primero/anterior/siguiente/último. El PDF y los reportes se migran después vía API Gateway (otro servicio, con su propia cola y sesión). - Reportes/PDF **no** se generan en la base; el botón PDF queda pendiente hasta migrar el API Gateway. ## Flujo de las pantallas de documento @@ -60,7 +60,7 @@ Pendiente, en orden: **Campos por cliente (estándar adicionales o `U_*`):** no se editan las pantallas base. Cada módulo tiene su archivo de extensiones, vacío en `main`: `ventas/ventas.extensions.ts`, `compras/compras.extensions.ts` e `inventario/inventario.extensions.ts` (reunidos en `client-extensions.ts`). La rama `cliente/` declara ahí, por ruta de pantalla, qué suma: `headerLeft`/`headerRight`/`footerLeft`/`footerRight` (campos; tipos `text`, `date`, `number`, `textarea`, `select` con `options`, `warehouse`…), `lineColumns` (se agregan antes del total y se envían a SAP) y `listColumns`. Solo suma, no quita; una ruta inexistente o ajena al módulo lanza un error al arrancar. El test «el proyecto base no usa campos de usuario» revisa `BASE_SCREENS`, no las extensiones. Verificado: el proxy `/api/sl` deja pasar nombres `U_*` en `$select`/`$orderby` y en el cuerpo (el error de un campo inexistente lo da SAP). Límite: una columna de línea de usuario con lista de valores válidos aún no tiene editor desplegable (solo texto/número). -**Columnas de las ventanas de selección (cliente, artículo, almacén, impuesto):** son configurables por documento. En una pantalla del base (ventas/compras): `lookups: { item: [...], partner: [...], warehouse: [...], tax: [...] }` en el último parámetro de `marketing(...)` *sustituye* las columnas de esa lista (la primera columna es el código que se escribe en el campo). En la rama del cliente: `lookupColumns` en su archivo de extensiones *suma* columnas al final (por ejemplo `U_Marca` en artículos). El recurso, el filtro y el orden de cada lista no cambian; para los documentos de inventario o solicitudes se usa `withLookupColumns(layout, cols, 'replace' | 'add')`. Las columnas de almacén aplican también al almacén de la cabecera y al de origen de una transferencia. Los formularios con socio muestran `NumAtCard` y el RUT (`FederalTaxID`, solo lectura: lo completa SAP desde el socio); facturas, notas de crédito, entregas y recepciones (`FOLIO_PATHS` en `document-screens.ts`) añaden el folio tributario, que en el Service Layer es `FolioPrefixString` + `FolioNumber` (se edita como texto y se envía como número); las listas de facturas también los muestran. Flete hecho: fila «Flete» en el pie de ventas y compras (gasto adicional + importe; `freightPayload` lo guarda como `DocumentAdditionalExpenses`, reemplazando la colección; el catálogo `AdditionalExpenses` está registrado en el proxy; con varios gastos adicionales cargados en SAP solo se muestra). `freight: false` en `marketing(...)` lo quita de un documento. El toolbar del formulario ocupa todo el ancho del contenido (``). Falta: botón PDF. +**Columnas de las ventanas de selección (cliente, artículo, almacén, impuesto):** son configurables por documento. En una pantalla del base (ventas/compras): `lookups: { item: [...], partner: [...], warehouse: [...], tax: [...] }` en el último parámetro de `marketing(...)` *sustituye* las columnas de esa lista (la primera columna es el código que se escribe en el campo). En la rama del cliente: `lookupColumns` en su archivo de extensiones *suma* columnas al final (por ejemplo `U_Marca` en artículos). El recurso, el filtro y el orden de cada lista no cambian; para los documentos de inventario o solicitudes se usa `withLookupColumns(layout, cols, 'replace' | 'add')`. Las columnas de almacén aplican también al almacén de la cabecera y al de origen de una transferencia. Los formularios con socio muestran `NumAtCard` y el RUT (`FederalTaxID`, solo lectura: lo completa SAP desde el socio); facturas, notas de crédito, entregas y recepciones (`FOLIO_PATHS` en `document-screens.ts`) añaden el folio tributario, que en el Service Layer es `FolioPrefixString` + `FolioNumber` (se edita como texto y se envía como número); las listas de facturas también los muestran. Flete hecho: fila «Flete» en el pie de ventas y compras (gasto adicional + importe; `freightPayload` lo guarda como `DocumentAdditionalExpenses`, reemplazando la colección; el catálogo `AdditionalExpenses` está registrado en el proxy; con varios gastos adicionales cargados en SAP solo se muestra). `freight: false` en `marketing(...)` lo quita de un documento. El toolbar del formulario ocupa todo el ancho del contenido (``). Falta: botón PDF. En ventas el flete falla en SBODemoCL ("Define the freight revenue account…"): el gasto adicional «Flete» solo tiene cuenta de gasto, no de ingresos (configuración de SAP, no del código); en compras funciona (orden de compra 2885). 2. Pantallas de maestros (`Items`, `BusinessPartners`: Clientes/Proveedores, Artículos), hoy "no disponible". 3. ~~Pantalla web de permisos~~ — hecha (`/admin/permisos`). 4. Auditoría (`audit_log` en SQLite; hoy solo líneas `AUDIT` en el log) y `nestjs-cls` para el usuario en los logs. diff --git a/apps/api/src/sl/sl-resources.ts b/apps/api/src/sl/sl-resources.ts index 83ab714..c71629e 100644 --- a/apps/api/src/sl/sl-resources.ts +++ b/apps/api/src/sl/sl-resources.ts @@ -44,8 +44,8 @@ export const SL_RESOURCES: readonly SlResource[] = [ document('Quotations', '/ventas/ofertas'), document('Orders', '/ventas/ordenes'), document('DeliveryNotes', '/ventas/entrega'), - // Las boletas de venta son facturas con `DocumentSubType = bod_Bill`: mismo recurso, dos pantallas. - document('Invoices', ['/ventas/facturas', '/ventas/boletas']), + // Boletas (`DocumentSubType = bod_Bill`) y facturas de reserva (`ReserveInvoice = tYES`) son facturas: mismo recurso, cuatro pantallas. + document('Invoices', ['/ventas/facturas', '/ventas/boletas', '/ventas/facturas-reserva', '/ventas/boletas-reserva']), document('Returns', '/ventas/devoluciones'), document('CreditNotes', '/ventas/notas-credito'), diff --git a/apps/web/src/app/features/admin/permissions.page.spec.ts b/apps/web/src/app/features/admin/permissions.page.spec.ts index d781e99..d2d5dae 100644 --- a/apps/web/src/app/features/admin/permissions.page.spec.ts +++ b/apps/web/src/app/features/admin/permissions.page.spec.ts @@ -44,7 +44,7 @@ describe('PermissionsPage', () => { it('al elegir un usuario marca sus pantallas (el módulo completo por /ventas/*)', async () => { await click('.users li button', 0); const checked = [...el().querySelectorAll('fieldset .items input')].filter((i) => i.checked); - expect(checked.length).toBe(8); // 7 de ventas + Clientes + expect(checked.length).toBe(10); // 9 de ventas + Clientes expect(el().querySelector('.actions .main')!.disabled).toBe(true); // sin cambios }); diff --git a/apps/web/src/app/features/documents/document-copy.spec.ts b/apps/web/src/app/features/documents/document-copy.spec.ts index 917c0df..098472a 100644 --- a/apps/web/src/app/features/documents/document-copy.spec.ts +++ b/apps/web/src/app/features/documents/document-copy.spec.ts @@ -20,9 +20,13 @@ describe('flujos de copiado', () => { }); it('copiar a / copiar de son los dos lados del mismo flujo', () => { - expect(copyTargets('/ventas/ordenes')).toEqual(['/ventas/entrega', '/ventas/facturas', '/ventas/boletas']); + expect(copyTargets('/ventas/ordenes')).toEqual([ + '/ventas/entrega', '/ventas/facturas', '/ventas/boletas', '/ventas/facturas-reserva', '/ventas/boletas-reserva', + ]); expect(copySources('/ventas/facturas')).toEqual(['/ventas/ofertas', '/ventas/ordenes', '/ventas/entrega']); expect(copySources('/ventas/boletas')).toEqual(['/ventas/ofertas', '/ventas/ordenes', '/ventas/entrega']); + expect(copySources('/ventas/facturas-reserva')).toEqual(['/ventas/ofertas', '/ventas/ordenes']); + expect(copySources('/ventas/boletas-reserva')).toEqual(['/ventas/ofertas', '/ventas/ordenes']); expect(copyTargets('/ventas/boletas')).toEqual(['/ventas/notas-credito']); expect(copyTargets('/ventas/notas-credito')).toEqual([]); expect(copySources('/ventas/ofertas')).toEqual([]); diff --git a/apps/web/src/app/features/documents/document-copy.ts b/apps/web/src/app/features/documents/document-copy.ts index f3e841c..044122b 100644 --- a/apps/web/src/app/features/documents/document-copy.ts +++ b/apps/web/src/app/features/documents/document-copy.ts @@ -10,6 +10,8 @@ export const OBJECT_TYPES: Readonly> = { '/ventas/devoluciones': 16, '/ventas/facturas': 13, '/ventas/boletas': 13, + '/ventas/facturas-reserva': 13, + '/ventas/boletas-reserva': 13, '/ventas/notas-credito': 14, '/compras/ofertas': 540000006, '/compras/ordenes': 22, @@ -21,8 +23,8 @@ export const OBJECT_TYPES: Readonly> = { /** De qué documento se puede copiar a cuáles (el flujo estándar de SAP B1). */ export const COPY_FLOWS: Readonly> = { - '/ventas/ofertas': ['/ventas/ordenes', '/ventas/entrega', '/ventas/facturas', '/ventas/boletas'], - '/ventas/ordenes': ['/ventas/entrega', '/ventas/facturas', '/ventas/boletas'], + '/ventas/ofertas': ['/ventas/ordenes', '/ventas/entrega', '/ventas/facturas', '/ventas/boletas', '/ventas/facturas-reserva', '/ventas/boletas-reserva'], + '/ventas/ordenes': ['/ventas/entrega', '/ventas/facturas', '/ventas/boletas', '/ventas/facturas-reserva', '/ventas/boletas-reserva'], '/ventas/entrega': ['/ventas/facturas', '/ventas/boletas', '/ventas/devoluciones'], '/ventas/devoluciones': ['/ventas/notas-credito'], '/ventas/facturas': ['/ventas/notas-credito'], diff --git a/apps/web/src/app/features/documents/document-folio.spec.ts b/apps/web/src/app/features/documents/document-folio.spec.ts index 7089f9e..9a70e61 100644 --- a/apps/web/src/app/features/documents/document-folio.spec.ts +++ b/apps/web/src/app/features/documents/document-folio.spec.ts @@ -67,7 +67,7 @@ describe('folio y RUT en los formularios', () => { const withFolio = DOCUMENT_SCREENS.filter((s) => hasFolio(s.layout)).map((s) => s.path); expect(withFolio.sort()).toEqual( [ - '/ventas/facturas', '/ventas/boletas', '/ventas/notas-credito', '/ventas/entrega', + '/ventas/facturas', '/ventas/boletas', '/ventas/facturas-reserva', '/ventas/boletas-reserva', '/ventas/notas-credito', '/ventas/entrega', '/compras/facturas', '/compras/notas-credito', '/compras/recepcion', ].sort(), ); diff --git a/apps/web/src/app/features/documents/document-form.page.spec.ts b/apps/web/src/app/features/documents/document-form.page.spec.ts index be3ae99..4b4b978 100644 --- a/apps/web/src/app/features/documents/document-form.page.spec.ts +++ b/apps/web/src/app/features/documents/document-form.page.spec.ts @@ -225,11 +225,14 @@ describe('DocumentFormPage', () => { describe('copiar a', () => { const items = () => [...el().querySelectorAll('sbo-menu-button')].map((m) => m.textContent?.trim()); - it('un documento abierto y guardado ofrece Copiar a con entrega, factura y boleta', async () => { + it('un documento abierto y guardado ofrece Copiar a con entrega, factura, boleta y sus reservas', async () => { await open('41', order()); expect(items().join(' ')).toContain('Copiar a'); const ids = page().copyToItems().map((i: { id: string; disabled: boolean }) => [i.id, i.disabled]); - expect(ids).toEqual([['/ventas/entrega', false], ['/ventas/facturas', false], ['/ventas/boletas', false]]); + expect(ids.map((i: [string, boolean]) => i[0])).toEqual([ + '/ventas/entrega', '/ventas/facturas', '/ventas/boletas', '/ventas/facturas-reserva', '/ventas/boletas-reserva', + ]); + expect(ids.every((i: [string, boolean]) => i[1] === false)).toBe(true); }); it('abre el documento nuevo de destino llevando el origen', async () => { diff --git a/apps/web/src/app/features/documents/document-layouts.spec.ts b/apps/web/src/app/features/documents/document-layouts.spec.ts index 2ff6a57..a4b273d 100644 --- a/apps/web/src/app/features/documents/document-layouts.spec.ts +++ b/apps/web/src/app/features/documents/document-layouts.spec.ts @@ -19,7 +19,7 @@ describe('modelo de los formularios sin socio', () => { expect(DOCUMENT_SCREENS.every((s) => s.layout)).toBe(true); const withPartner = DOCUMENT_SCREENS.filter((s) => s.partner).map((s) => s.resource); const without = DOCUMENT_SCREENS.filter((s) => !s.partner).map((s) => s.resource); - expect(withPartner).toHaveLength(13); + expect(withPartner).toHaveLength(15); expect(without.sort()).toEqual( ['InventoryGenEntries', 'InventoryGenExits', 'InventoryTransferRequests', 'PurchaseRequests', 'StockTransfers'].sort(), ); diff --git a/apps/web/src/app/features/documents/document-paths.ts b/apps/web/src/app/features/documents/document-paths.ts index 915b74b..6e3eaf6 100644 --- a/apps/web/src/app/features/documents/document-paths.ts +++ b/apps/web/src/app/features/documents/document-paths.ts @@ -10,6 +10,8 @@ export const DOCUMENT_PATHS: readonly string[] = [ '/ventas/entrega', '/ventas/facturas', '/ventas/boletas', + '/ventas/facturas-reserva', + '/ventas/boletas-reserva', '/ventas/devoluciones', '/ventas/notas-credito', // Compras diff --git a/apps/web/src/app/features/documents/document-screens.spec.ts b/apps/web/src/app/features/documents/document-screens.spec.ts index c0b1815..c085234 100644 --- a/apps/web/src/app/features/documents/document-screens.spec.ts +++ b/apps/web/src/app/features/documents/document-screens.spec.ts @@ -121,24 +121,35 @@ describe('referencia y RUT en las listas de ofertas y órdenes', () => { }); }); -describe('facturas y boletas de venta', () => { +describe('facturas, boletas y sus reservas', () => { const by = (p: string) => DOCUMENT_SCREENS.find((s) => s.path === p)!; + const INVOICES = ['/ventas/facturas', '/ventas/boletas', '/ventas/facturas-reserva', '/ventas/boletas-reserva']; - it('comparten el recurso Invoices y se separan por el subtipo de documento', () => { - expect(by('/ventas/facturas').resource).toBe('Invoices'); - expect(by('/ventas/boletas').resource).toBe('Invoices'); - expect(by('/ventas/boletas').baseFilter).toBe(`DocumentSubType eq 'bod_Bill'`); - // la factura solo excluye las boletas: no pierde notas de débito ni otros subtipos - expect(by('/ventas/facturas').baseFilter).toBe(`DocumentSubType ne 'bod_Bill'`); + it('las cuatro comparten el recurso Invoices y se separan por ReserveInvoice y DocumentSubType', () => { + for (const p of INVOICES) expect(by(p).resource, p).toBe('Invoices'); + // la factura solo excluye las boletas (`ne`): no pierde notas de débito ni otros subtipos + expect(by('/ventas/facturas').baseFilter).toBe(`ReserveInvoice eq 'tNO' and DocumentSubType ne 'bod_Bill'`); + expect(by('/ventas/boletas').baseFilter).toBe(`ReserveInvoice eq 'tNO' and DocumentSubType eq 'bod_Bill'`); + expect(by('/ventas/facturas-reserva').baseFilter).toBe(`ReserveInvoice eq 'tYES' and DocumentSubType ne 'bod_Bill'`); + expect(by('/ventas/boletas-reserva').baseFilter).toBe(`ReserveInvoice eq 'tYES' and DocumentSubType eq 'bod_Bill'`); }); - it('una boleta nueva se crea con su subtipo; una factura no lo envía', () => { - expect(by('/ventas/boletas').layout.beforeCreate!({ CardCode: 'C1' })).toEqual({ CardCode: 'C1', DocumentSubType: 'bod_Bill' }); - expect(by('/ventas/facturas').layout.beforeCreate).toBeUndefined(); + it('cada una se crea con su marca y una factura normal no envía nada especial', () => { + const created = (p: string) => by(p).layout.beforeCreate?.({ CardCode: 'C1' }); + expect(created('/ventas/facturas')).toBeUndefined(); + expect(created('/ventas/boletas')).toEqual({ CardCode: 'C1', DocumentSubType: 'bod_Bill' }); + expect(created('/ventas/facturas-reserva')).toEqual({ CardCode: 'C1', ReserveInvoice: 'tYES' }); + expect(created('/ventas/boletas-reserva')).toEqual({ CardCode: 'C1', ReserveInvoice: 'tYES', DocumentSubType: 'bod_Bill' }); + }); + + it('las cuatro son documentos tributarios: llevan folio, referencia y RUT', () => { + for (const p of INVOICES) { + expect(by(p).columns.map((c) => c.key), p).toEqual(expect.arrayContaining(['FolioNumber', 'NumAtCard', 'FederalTaxID'])); + } }); it('el resto de las pantallas no tiene filtro fijo', () => { - for (const s of DOCUMENT_SCREENS.filter((s) => !['/ventas/facturas', '/ventas/boletas'].includes(s.path))) { + for (const s of DOCUMENT_SCREENS.filter((s) => !INVOICES.includes(s.path))) { expect(s.baseFilter, s.path).toBeUndefined(); } }); diff --git a/apps/web/src/app/features/documents/ventas/ventas.screens.ts b/apps/web/src/app/features/documents/ventas/ventas.screens.ts index 361a8d9..0ebb512 100644 --- a/apps/web/src/app/features/documents/ventas/ventas.screens.ts +++ b/apps/web/src/app/features/documents/ventas/ventas.screens.ts @@ -1,5 +1,11 @@ import { TAX_DOCUMENT, type DocumentScreen, invoiceColumns, marketing, referenceColumns } from '../screen-kit'; +/** Filtros que separan las cuatro pantallas de `Invoices`. */ +const RESERVE = `ReserveInvoice eq 'tYES'`; +const NOT_RESERVE = `ReserveInvoice eq 'tNO'`; +const BILL = `DocumentSubType eq 'bod_Bill'`; +const NOT_BILL = `DocumentSubType ne 'bod_Bill'`; + /** Ventas: ofertas, órdenes, entregas, facturas, devoluciones y notas de crédito de cliente. */ export const VENTAS_SCREENS: readonly DocumentScreen[] = [ ] = [ @@ -7,19 +13,32 @@ export const VENTAS_SCREENS: readonly DocumentScreen[] = [ marketing('/ventas/ofertas', 'Quotations', 'Nueva oferta', 'Cliente', { columns: referenceColumns }), marketing('/ventas/ordenes', 'Orders', 'Nueva orden', 'Cliente', { columns: referenceColumns }), marketing('/ventas/entrega', 'DeliveryNotes', 'Nueva entrega', 'Cliente', TAX_DOCUMENT), - // Facturas y boletas son el mismo recurso (`Invoices`): la boleta es la de subtipo `bod_Bill`. La factura - // excluye solo las boletas (no filtra por `bod_None`, para no perder notas de débito u otros subtipos). + // Facturas, boletas y sus reservas son el mismo recurso (`Invoices`); se separan por dos campos: + // `DocumentSubType` (`bod_Bill` = boleta) y `ReserveInvoice` (`tYES` = de reserva). La factura excluye + // solo las boletas (`ne`, no `eq bod_None`, para no perder notas de débito u otros subtipos). marketing('/ventas/facturas', 'Invoices', 'Nueva factura', 'Cliente', { ...TAX_DOCUMENT, columns: invoiceColumns, - baseFilter: `DocumentSubType ne 'bod_Bill'`, + baseFilter: `${NOT_RESERVE} and ${NOT_BILL}`, }), marketing('/ventas/boletas', 'Invoices', 'Nueva boleta', 'Cliente', { ...TAX_DOCUMENT, columns: invoiceColumns, - baseFilter: `DocumentSubType eq 'bod_Bill'`, + baseFilter: `${NOT_RESERVE} and ${BILL}`, fixed: { DocumentSubType: 'bod_Bill' }, }), + marketing('/ventas/facturas-reserva', 'Invoices', 'Nueva factura de reserva', 'Cliente', { + ...TAX_DOCUMENT, + columns: invoiceColumns, + baseFilter: `${RESERVE} and ${NOT_BILL}`, + fixed: { ReserveInvoice: 'tYES' }, + }), + marketing('/ventas/boletas-reserva', 'Invoices', 'Nueva boleta de reserva', 'Cliente', { + ...TAX_DOCUMENT, + columns: invoiceColumns, + baseFilter: `${RESERVE} and ${BILL}`, + fixed: { ReserveInvoice: 'tYES', DocumentSubType: 'bod_Bill' }, + }), marketing('/ventas/devoluciones', 'Returns', 'Nueva devolución', 'Cliente'), marketing('/ventas/notas-credito', 'CreditNotes', 'Nueva nota de crédito', 'Cliente', TAX_DOCUMENT), ]; diff --git a/libs/sap-types/src/menu.ts b/libs/sap-types/src/menu.ts index a62f264..760bf74 100644 --- a/libs/sap-types/src/menu.ts +++ b/libs/sap-types/src/menu.ts @@ -26,6 +26,8 @@ export const MENU: readonly MenuModule[] = [ { path: '/ventas/entrega', label: 'Entregas' }, { path: '/ventas/facturas', label: 'Facturas de venta' }, { path: '/ventas/boletas', label: 'Boletas de venta' }, + { path: '/ventas/facturas-reserva', label: 'Facturas de reserva' }, + { path: '/ventas/boletas-reserva', label: 'Boletas de reserva' }, { path: '/ventas/devoluciones', label: 'Devoluciones' }, { path: '/ventas/notas-credito', label: 'Notas de crédito' }, ],