feat(web): ver documentos cancelados y sus documentos de cancelación (filtro, estado y botón para ir de uno al otro)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
57424432f9
commit
74743503f0
7 files changed
+168
-4
No files matched your search
@@ -52,7 +52,7 @@ Es un ERP, no una página de publicidad: **ligera y rápida**. Sin librerías de
|
||||
|
||||
## Flujo de las pantallas de documento
|
||||
|
||||
Cada menú abre primero una **lista de los últimos documentos** (barra de filtros + búsqueda por columna); desde ahí se crea uno nuevo (`/…/nuevo`, formulario vacío) o se abre uno existente (`/…/:docEntry`, ver/editar; los cerrados/cancelados en solo lectura). Las pantallas se definen por módulo en `apps/web/src/app/features/documents/` (17 documentos): `ventas/ventas.screens.ts`, `compras/compras.screens.ts` (+ `compras.layouts.ts`) e `inventario/inventario.screens.ts` (+ `inventario.layouts.ts`); los maestros (socios de negocio, artículos) irán en su propia carpeta. `screen-kit.ts` tiene el tipo `DocumentScreen` y las columnas y filtros comunes; `document-screens.ts` las reúne en `DOCUMENT_SCREENS`. Las columnas de línea estándar están en `features/documents/line-columns.ts`. **Tipo Artículo / Servicio** (`DocType`): los documentos de ventas y compras con socio llevan en la cabecera la lista «Tipo» (primera de la columna izquierda; no se cambia en uno guardado). Con «Servicio» las líneas pasan a `layout.service` (`serviceLineColumns` en `line-columns.ts`): Cuenta contable (obligatoria), Descripción, Impuesto y Total (se escribe, sin recalcular), dimensiones, proyecto y referencias al origen; sin artículo, cantidad, almacén, precio, lotes ni «Elegir artículos». Se guardan `AccountCode`, `ItemDescription`, `TaxCode`, `LineTotal` (`LinesSpec.service`); cambiar el tipo con líneas escritas pide permiso y las descarta; «Copiar de» trae el tipo y el importe abierto (`OpenAmount`) del origen y no mezcla tipos. Las extensiones de cliente de columnas de línea aplican a las líneas de artículos; las de servicio aún no son extensibles. **Indicador y folio juntos y en ese orden** (`IDENTITY_GROUP`, un `HeaderField` tipo `group` = varias celdas en una fila: Indicador con su lista, prefijo y número del folio) en facturas, boletas, reservas y notas de crédito de ventas y compras (`BILLING_DOCUMENT`) y en el asiento. Una empresa que los sustituye por campos de usuario arma su grupo con `fieldGroup(label, cells)` en `headerRight` de su extensión y oculta el del base con `hideHeaderFields: ['Indicator']` (oculta el grupo completo). Las **tres fechas** de la cabecera de todo documento de ventas y compras van juntas y en este orden, una debajo de la otra: contabilización, vencimiento y del documento (`TaxDate`), las tres con la fecha de hoy al crear. **Pestañas Contenido, Logística y Finanzas** en ventas y compras (`layout.tabs`, `tools/logistica.png` y `tools/finanzas.png`): Logística = destino y dirección de facturación (se eligen de las direcciones del socio, con su texto debajo), forma de envío, número de seguimiento y bloquear reclamaciones; Finanzas = entrada en el diario, cuenta asociada, bloqueo de pago, condiciones y forma de pago (de cobro en ventas, de pago en compras), indicador banco central, plazos, recálculo del vencimiento y proyecto. Al elegir el socio se cargan sus contactos y direcciones (`BusinessPartners(cod)` con `BPAddresses` y `ContactEmployees`) y se prellenan las direcciones, condiciones, forma de pago y cuenta asociada que trae (solo si el campo está vacío). La persona de contacto va bajo el nombre del socio. Las transferencias de stock y las solicitudes de traslado también llevan socio, contacto, destino, lista de precios (`PriceList`) y empleado (`SalesPersonCode`). Los códigos numéricos de las listas se guardan como número (`typedLookup`). Las extensiones de cliente pueden agregar campos a las pestañas (`logisticsLeft/Right`, `financeLeft/Right`). Sin verificar: «Responsable» de Logística (no se identificó su campo del Service Layer), canal del socio, cuota/consolidación de Finanzas. **Solicitudes de compra** (`/compras/solicitudes`, `tools/solicitud de compra.png`) son documentos de marketing sin socio: `marketingLayout({ requester: true })` — cabecera con tipo, solicitante (usuario de SAP: al elegirlo se completan nombre, correo, departamento y sucursal; `ReqType` 12), «Válido hasta», fecha necesaria e indicador/proyecto; líneas con proveedor sugerido (`LineVendor`), fecha necesaria por línea (columna `date` de la grilla) y cantidad/precio/descuento/impuesto/total/almacén; pie con gastos adicionales y totales; sin moneda, redondeo ni pestañas. Para el solicitante la API registra `Users` con `selectOnly` (`UserCode`, `UserName`, `eMail`, `Department`, `Branch`: `$select` obligatorio y limitado; la entidad trae contraseña y permisos), más `Departments` y `Branches`. Los documentos sin socio indican su maestro de impuestos con `DocumentScreen.taxResource`. **La dirección del documento** (`AddressExtension`: `ShipTo…` y `BillTo…`, `address-fields.ts`): el texto bajo el destino y la dirección de facturación se arma con sus partes (calle, número, bloque, edificio, ciudad, código postal, provincia, región, país) del documento guardado o, si se eligió otra, de la dirección del socio; el botón «…» abre una ventana (`aLine truncated
|
||||
Cada menú abre primero una **lista de los últimos documentos** (barra de filtros + búsqueda por columna); desde ahí se crea uno nuevo (`/…/nuevo`, formulario vacío) o se abre uno existente (`/…/:docEntry`, ver/editar; los cerrados/cancelados en solo lectura). Las pantallas se definen por módulo en `apps/web/src/app/features/documents/` (17 documentos): `ventas/ventas.screens.ts`, `compras/compras.screens.ts` (+ `compras.layouts.ts`) e `inventario/inventario.screens.ts` (+ `inventario.layouts.ts`); los maestros (socios de negocio, artículos) irán en su propia carpeta. `screen-kit.ts` tiene el tipo `DocumentScreen` y las columnas y filtros comunes; `document-screens.ts` las reúne en `DOCUMENT_SCREENS`. Las columnas de línea estándar están en `features/documents/line-columns.ts`. **Tipo Artículo / Servicio** (`DocType`): los documentos de ventas y compras con socio llevan en la cabecera la lista «Tipo» (primera de la columna izquierda; no se cambia en uno guardado). Con «Servicio» las líneas pasan a `layout.service` (`serviceLineColumns` en `line-columns.ts`): Cuenta contable (obligatoria), Descripción, Impuesto y Total (se escribe, sin recalcular), dimensiones, proyecto y referencias al origen; sin artículo, cantidad, almacén, precio, lotes ni «Elegir artículos». Se guardan `AccountCode`, `ItemDescription`, `TaxCode`, `LineTotal` (`LinesSpec.service`); cambiar el tipo con líneas escritas pide permiso y las descarta; «Copiar de» trae el tipo y el importe abierto (`OpenAmount`) del origen y no mezcla tipos. Las extensiones de cliente de columnas de línea aplican a las líneas de artículos; las de servicio aún no son extensibles. **Indicador y folio juntos y en ese orden** (`IDENTITY_GROUP`, un `HeaderField` tipo `group` = varias celdas en una fila: Indicador con su lista, prefijo y número del folio) en facturas, boletas, reservas y notas de crédito de ventas y compras (`BILLING_DOCUMENT`) y en el asiento. Una empresa que los sustituye por campos de usuario arma su grupo con `fieldGroup(label, cells)` en `headerRight` de su extensión y oculta el del base con `hideHeaderFields: ['Indicator']` (oculta el grupo completo). Las **tres fechas** de la cabecera de todo documento de ventas y compras van juntas y en este orden, una debajo de la otra: contabilización, vencimiento y del documento (`TaxDate`), las tres con la fecha de hoy al crear. **Pestañas Contenido, Logística y Finanzas** en ventas y compras (`layout.tabs`, `tools/logistica.png` y `tools/finanzas.png`): Logística = destino y dirección de facturación (se eligen de las direcciones del socio, con su texto debajo), forma de envío, número de seguimiento y bloquear reclamaciones; Finanzas = entrada en el diario, cuenta asociada, bloqueo de pago, condiciones y forma de pago (de cobro en ventas, de pago en compras), indicador banco central, plazos, recálculo del vencimiento y proyecto. Al elegir el socio se cargan sus contactos y direcciones (`BusinessPartners(cod)` con `BPAddresses` y `ContactEmployees`) y se prellenan las direcciones, condiciones, forma de pago y cuenta asociada que trae (solo si el campo está vacío). La persona de contacto va bajo el nombre del socio. Las transferencias de stock y las solicitudes de traslado también llevan socio, contacto, destino, lista de precios (`PriceList`) y empleado (`SalesPersonCode`). Los códigos numéricos de las listas se guardan como número (`typedLookup`). Las extensiones de cliente pueden agregar campos a las pestañas (`logisticsLeft/Right`, `financeLeft/Right`). Sin verificar: «Responsable» de Logística (no se identificó su campo del Service Layer), canal del socio, cuota/consolidación de Finanzas. **Solicitudes de compra** (`/compras/solicitudes`, `tools/solicitud de compra.png`) son documentos de marketing sin socio: `marketingLayout({ requester: true })` — cabecera con tipo, solicitante (usuario de SAP: al elegirlo se completan nombre, correo, departamento y sucursal; `ReqType` 12), «Válido hasta», fecha necesaria e indicador/proyecto; líneas con proveedor sugerido (`LineVendor`), fecha necesaria por línea (columna `date` de la grilla) y cantidad/precio/descuento/impuesto/total/almacén; pie con gastos adicionales y totales; sin moneda, redondeo ni pestañas. Para el solicitante la API registra `Users` con `selectOnly` (`UserCode`, `UserName`, `eMail`, `Department`, `Branch`: `$select` obligatorio y limitado; la entidad trae contraseña y permisos), más `Departments` y `Branches`. Los documentos sin socio indican su maestro de impuestos con `DocumentScreen.taxResource`. **La dirección del documento** (`AddressExtension`: `ShipTo…` y `BillTo…`, `address-fields.ts`): el texto bajo el destino y la dirección de facturación se arma con sus partes (calle, número, bloque, edificio, ciudad, código postal, provincia, región, país) del documento guardado o, si se eligió otra, de la dirección del socio; el botón «…» abre una ventana (`aLine truncated
|
||||
|
||||
## Cómo verificar cambios de interfaz
|
||||
|
||||
|
||||
@@ -41,6 +41,14 @@ export class SlDocumentApi {
|
||||
return firstValueFrom(this.http.post<number>(`${API_URL}/sl/SBOBobService_GetCurrencyRate`, { Currency: currency, Date: date }));
|
||||
}
|
||||
|
||||
/** `DocEntry` del primer documento que cumple el filtro (en el orden dado), o `null` si no hay. */
|
||||
async findOne(resource: string, filter: string, orderby: string): Promise<number | null> {
|
||||
const params = new HttpParams().set('$select', 'DocEntry').set('$filter', filter).set('$orderby', orderby).set('$top', 1);
|
||||
const res = await firstValueFrom(this.http.get<SlListResponse<DocRecord>>(this.url(resource), { params }));
|
||||
const entry = res.value[0]?.['DocEntry'];
|
||||
return typeof entry === 'number' ? entry : null;
|
||||
}
|
||||
|
||||
/** Crea el documento; SAP devuelve el documento creado (con `DocEntry` y `DocNum`). */
|
||||
create(resource: string, body: DocRecord): Promise<DocRecord> {
|
||||
return firstValueFrom(this.http.post<DocRecord>(this.url(resource), body));
|
||||
|
||||
@@ -11,7 +11,7 @@ export const COMPRAS_SCREENS: readonly DocumentScreen[] = [
|
||||
resource: 'PurchaseRequests',
|
||||
createLabel: 'Nueva solicitud',
|
||||
orderby: 'DocEntry desc',
|
||||
extraFields: ['Cancelled'],
|
||||
extraFields: ['Cancelled', 'CancelStatus'],
|
||||
columns: [
|
||||
docNum,
|
||||
statusColumn,
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
import { type ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRoute, Router, convertToParamMap, provideRouter } from '@angular/router';
|
||||
import { of } from 'rxjs';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { DocumentFormPage } from './document-form.page';
|
||||
import { DOCUMENT_SCREENS } from './document-screens';
|
||||
import { documentStatus } from './screen-kit';
|
||||
|
||||
describe('estado de los documentos cancelados', () => {
|
||||
it('el cancelado y su documento de cancelación se distinguen del resto', () => {
|
||||
expect(documentStatus({ Cancelled: 'tYES', DocumentStatus: 'bost_Close' })).toEqual({ label: 'Cancelado', tone: 'err' });
|
||||
expect(documentStatus({ Cancelled: 'tNO', CancelStatus: 'csCancellation', DocumentStatus: 'bost_Close' })).toEqual({ label: 'Cancelación', tone: 'warn' });
|
||||
expect(documentStatus({ Cancelled: 'tNO', CancelStatus: 'csNo', DocumentStatus: 'bost_Close' })).toEqual({ label: 'Cerrado', tone: 'muted' });
|
||||
});
|
||||
|
||||
it('las listas de ventas y compras filtran por cancelación y piden CancelStatus', () => {
|
||||
for (const path of ['/ventas/facturas', '/compras/facturas', '/ventas/notas-credito']) {
|
||||
const s = DOCUMENT_SCREENS.find((x) => x.path === path)!;
|
||||
const f = s.filters.find((x) => x.key === 'CancelStatus');
|
||||
expect(f && f.type === 'select' && f.options.map((o) => o.value), path).toEqual(['csYes', 'csCancellation']);
|
||||
expect(s.extraFields, path).toContain('CancelStatus');
|
||||
}
|
||||
expect(DOCUMENT_SCREENS.find((s) => s.path === '/inventario/entradas')!.filters.some((x) => x.key === 'CancelStatus')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('del documento cancelado a su cancelación y al revés', () => {
|
||||
let fixture: ComponentFixture<DocumentFormPage>;
|
||||
let ctrl: HttpTestingController;
|
||||
let navigate: ReturnType<typeof vi.spyOn>;
|
||||
const el = () => fixture.nativeElement as HTMLElement;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const page = () => fixture.componentInstance as any;
|
||||
const tick = async () => {
|
||||
fixture.detectChanges();
|
||||
await new Promise((r) => setTimeout(r));
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
async function open(doc: Record<string, unknown>) {
|
||||
const screen = DOCUMENT_SCREENS.find((s) => s.path === '/compras/facturas')!;
|
||||
const route = { snapshot: { data: { screen, title: 'Documento' }, paramMap: convertToParamMap({ docEntry: '5' }) }, paramMap: of(convertToParamMap({ docEntry: '5' })) };
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DocumentFormPage],
|
||||
providers: [provideRouter([]), provideHttpClient(), provideHttpClientTesting(), { provide: ActivatedRoute, useValue: route }],
|
||||
}).compileComponents();
|
||||
ctrl = TestBed.inject(HttpTestingController);
|
||||
navigate = vi.spyOn(TestBed.inject(Router), 'navigate').mockResolvedValue(true);
|
||||
fixture = TestBed.createComponent(DocumentFormPage);
|
||||
await tick();
|
||||
ctrl.expectOne((r) => r.url === '/api/sl/SalesTaxCodes').flush({ value: [] });
|
||||
ctrl.expectOne((r) => r.url === '/api/sl/AdditionalExpenses').flush({ value: [] });
|
||||
ctrl.expectOne((r) => r.url === '/api/sl/Currencies').flush({ value: [] });
|
||||
ctrl.expectOne('/api/sl/PurchaseInvoices/5').flush({ DocEntry: 5, DocNum: 260013973, CardCode: 'P1', DocDate: '2026-10-19T00:00:00Z', DocumentLines: [], DocumentStatus: 'bost_Close', ...doc });
|
||||
await tick();
|
||||
}
|
||||
|
||||
const button = () => [...el().querySelectorAll<HTMLButtonElement>('.actions button')].find((b) => /cancelaci|cancelado/i.test(b.textContent ?? ''));
|
||||
beforeEach(() => TestBed.resetTestingModule());
|
||||
|
||||
it('un documento normal no ofrece ver cancelaciones', async () => {
|
||||
await open({ Cancelled: 'tNO', CancelStatus: 'csNo' });
|
||||
expect(button()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('el cancelado busca su documento de cancelación por su número en los comentarios del otro', async () => {
|
||||
await open({ Cancelled: 'tYES', CancelStatus: 'csYes' });
|
||||
expect(button()!.textContent).toContain('Ver su cancelación');
|
||||
button()!.click();
|
||||
await tick();
|
||||
const req = ctrl.expectOne((r) => r.url === '/api/sl/PurchaseInvoices' && (r.params.get('$filter') ?? '').includes("contains(Comments,'260013973')"));
|
||||
expect(req.request.params.get('$filter')).toContain("CancelStatus eq 'csCancellation' and CardCode eq 'P1'");
|
||||
req.flush({ value: [{ DocEntry: 6 }] });
|
||||
await tick();
|
||||
expect(navigate).toHaveBeenCalledWith(['..', 6], expect.anything());
|
||||
});
|
||||
|
||||
it('el documento de cancelación abre el documento que nombra en sus comentarios', async () => {
|
||||
await open({ Cancelled: 'tNO', CancelStatus: 'csCancellation', Comments: 'COPAS Basado en Fact.proveedores 260013972.' });
|
||||
expect(button()!.textContent).toContain('Ver el documento cancelado');
|
||||
button()!.click();
|
||||
await tick();
|
||||
const req = ctrl.expectOne((r) => r.url === '/api/sl/PurchaseInvoices' && (r.params.get('$filter') ?? '').includes('DocNum eq 260013972'));
|
||||
req.flush({ value: [{ DocEntry: 4 }] });
|
||||
await tick();
|
||||
expect(navigate).toHaveBeenCalledWith(['..', 4], expect.anything());
|
||||
});
|
||||
|
||||
it('si no encuentra la pareja lo avisa', async () => {
|
||||
await open({ Cancelled: 'tYES', CancelStatus: 'csYes' });
|
||||
button()!.click();
|
||||
await tick();
|
||||
ctrl.expectOne((r) => (r.params.get('$filter') ?? '').includes('contains(Comments')).flush({ value: [] });
|
||||
await tick();
|
||||
ctrl.expectOne((r) => (r.params.get('$filter') ?? '').includes('DocEntry gt 5')).flush({ value: [] });
|
||||
await tick();
|
||||
expect(el().querySelector('.status')!.textContent).toContain('No se encontró su documento de cancelación');
|
||||
});
|
||||
});
|
||||
@@ -82,6 +82,9 @@
|
||||
<button sboActions class="btn" type="button" (click)="reload()">Recargar</button>
|
||||
}
|
||||
<a sboActions class="btn" routerLink="..">{{ readOnly() ? 'Cerrar' : 'Cancelar' }}</a>
|
||||
@if (cancellation(); as c) {
|
||||
<button sboActions class="btn" type="button" (click)="openCancellationPair()">{{ c === 'cancelled' ? 'Ver su cancelación' : 'Ver el documento cancelado' }}</button>
|
||||
}
|
||||
@if (copyFromItems().length) {
|
||||
<sbo-menu-button sboActionsEnd label="Copiar de" [items]="copyFromItems()" (picked)="copyFrom($event)" />
|
||||
}
|
||||
|
||||
@@ -517,6 +517,46 @@ export class DocumentFormPage {
|
||||
/** Tipo al que se quiere cambiar y que espera confirmación porque hay líneas escritas que se descartarían. */
|
||||
protected readonly pendingType = signal<string | null>(null);
|
||||
|
||||
// --- cancelación: del documento cancelado a su documento de cancelación y al revés ---------------
|
||||
|
||||
/** El documento abierto es un cancelado o el documento de cancelación que SAP creó al cancelarlo. */
|
||||
protected readonly cancellation = computed<'cancelled' | 'reversal' | null>(() => {
|
||||
const d = this.doc();
|
||||
return d?.['Cancelled'] === 'tYES' ? 'cancelled' : d?.['CancelStatus'] === 'csCancellation' ? 'reversal' : null;
|
||||
});
|
||||
|
||||
/**
|
||||
* Abre el otro documento de la pareja. SAP no guarda el enlace en un campo: el documento de cancelación nombra al
|
||||
* cancelado en sus comentarios («Basado en … 260013973.») y es posterior y del mismo socio; así se busca.
|
||||
*/
|
||||
protected async openCancellationPair(): Promise<void> {
|
||||
const d = this.doc();
|
||||
const mine = this.cancellation();
|
||||
if (!d || !mine) return;
|
||||
const card = `CardCode eq '${text(d['CardCode']).replace(/'/g, "''")}'`;
|
||||
const base = this.screen.baseFilter;
|
||||
const withBase = (f: string): string => (base ? `(${base}) and ${f}` : f);
|
||||
try {
|
||||
let entry: number | null;
|
||||
if (mine === 'cancelled') {
|
||||
entry = await this.api.findOne(this.screen.resource, withBase(`CancelStatus eq 'csCancellation' and ${card} and contains(Comments,'${text(d['DocNum'])}')`), 'DocEntry asc');
|
||||
entry ??= await this.api.findOne(this.screen.resource, withBase(`CancelStatus eq 'csCancellation' and ${card} and DocEntry gt ${this.docEntry()}`), 'DocEntry asc');
|
||||
} else {
|
||||
// el número del cancelado es el último que nombran los comentarios («Basado en Fact.proveedores 260013973.»)
|
||||
const named = [...text(d['Comments']).matchAll(/(\d{5,})/g)].at(-1)?.[1];
|
||||
entry = named ? await this.api.findOne(this.screen.resource, withBase(`Cancelled eq 'tYES' and ${card} and DocNum eq ${named}`), 'DocEntry desc') : null;
|
||||
entry ??= await this.api.findOne(this.screen.resource, withBase(`Cancelled eq 'tYES' and ${card} and DocEntry lt ${this.docEntry()}`), 'DocEntry desc');
|
||||
}
|
||||
if (entry === null) {
|
||||
this.message.set({ kind: 'warn', text: mine === 'cancelled' ? 'No se encontró su documento de cancelación.' : 'No se encontró el documento que cancela.' });
|
||||
return;
|
||||
}
|
||||
await this.router.navigate(['..', entry], { relativeTo: this.route });
|
||||
} catch (e) {
|
||||
this.message.set({ kind: 'err', text: apiErrorMessage(e) });
|
||||
}
|
||||
}
|
||||
|
||||
// --- cuenta asociada: de activo en ventas y de pasivo en compras ------------------------------------
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,6 +39,8 @@ export const menuLabel = (path: string): string =>
|
||||
|
||||
export function documentStatus(row: ListRow): { label: string; tone: Tone } | undefined {
|
||||
if (row['Cancelled'] === 'tYES') return { label: 'Cancelado', tone: 'err' };
|
||||
// el documento que SAP crea al cancelar otro (su reverso): también está cerrado, pero no es un documento más
|
||||
if (row['CancelStatus'] === 'csCancellation') return { label: 'Cancelación', tone: 'warn' };
|
||||
switch (row['DocumentStatus']) {
|
||||
case 'bost_Open':
|
||||
return { label: 'Abierto', tone: 'ok' };
|
||||
@@ -129,6 +131,16 @@ export const docTypeFilter: ListFilter = {
|
||||
{ value: 'dDocument_Service', label: 'Servicio' },
|
||||
],
|
||||
};
|
||||
/** Cancelaciones: los documentos cancelados y los documentos de cancelación que SAP crea al cancelarlos (por defecto, todos). */
|
||||
export const cancelFilter: ListFilter = {
|
||||
type: 'select',
|
||||
key: 'CancelStatus',
|
||||
label: 'Cancelación',
|
||||
options: [
|
||||
{ value: 'csYes', label: 'Cancelados' },
|
||||
{ value: 'csCancellation', label: 'Documentos de cancelación' },
|
||||
],
|
||||
};
|
||||
export const dateFilter: ListFilter = { type: 'date', key: 'DocDate', label: 'Fecha contab.' };
|
||||
export const currencyFilter: ListFilter = {
|
||||
type: 'multi',
|
||||
@@ -195,7 +207,7 @@ export const marketing = (
|
||||
partner: path.startsWith('/ventas')
|
||||
? { label: partner, type: 'cCustomer', taxResource: 'SalesTaxCodes' }
|
||||
: { label: partner, type: 'cSupplier', taxResource: 'SalesTaxCodes' },
|
||||
extraFields: ['Cancelled', 'DocTotalFc', 'DocCurrency'],
|
||||
extraFields: ['Cancelled', 'CancelStatus', 'DocTotalFc', 'DocCurrency'],
|
||||
columns: [
|
||||
docNum,
|
||||
statusColumn,
|
||||
@@ -207,7 +219,7 @@ export const marketing = (
|
||||
{ key: 'DocDueDate', title: 'Fecha venc.', type: 'date', align: 'center', width: '115px' },
|
||||
{ key: 'DocTotal', title: 'Total', align: 'right', width: '140px', format: documentTotal },
|
||||
],
|
||||
filters: [statusFilter, docTypeFilter, currencyFilter, dateFilter],
|
||||
filters: [statusFilter, cancelFilter, docTypeFilter, currencyFilter, dateFilter],
|
||||
});
|
||||
|
||||
export const inventory = (
|
||||
|
||||
Reference in new issue
Block a user