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:
GeorgeandClaude Sonnet 5.5 committed 2026-10-19 15:15:03 -05:00
1 parent 57424432f9
commit 74743503f0
7 files changed
+168 -4

No files matched your search

@@ -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 ------------------------------------
/**