feat(web): copiar de / a entre documentos de ventas y compras

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-14 11:59:07 -05:00
1 parent cafe4bc035
commit 29e80181fe
8 files changed
+383 -6

No files matched your search

@@ -0,0 +1,98 @@
import type { GridLine } from '../../shared/grid/grid.types';
import type { DocRecord } from './document-model';
import { newDocumentLine, recalcLine } from './line-columns';
/** Tipo de objeto de SAP de cada documento (`BaseType` de las líneas copiadas). */
export const OBJECT_TYPES: Readonly<Record<string, number>> = {
'/ventas/ofertas': 23,
'/ventas/ordenes': 17,
'/ventas/entrega': 15,
'/ventas/devoluciones': 16,
'/ventas/facturas': 13,
'/ventas/notas-credito': 14,
'/compras/ofertas': 540000006,
'/compras/ordenes': 22,
'/compras/recepcion': 20,
'/compras/devoluciones': 21,
'/compras/facturas': 18,
'/compras/notas-credito': 19,
};
/** De qué documento se puede copiar a cuáles (el flujo estándar de SAP B1). */
export const COPY_FLOWS: Readonly<Record<string, readonly string[]>> = {
'/ventas/ofertas': ['/ventas/ordenes', '/ventas/entrega', '/ventas/facturas'],
'/ventas/ordenes': ['/ventas/entrega', '/ventas/facturas'],
'/ventas/entrega': ['/ventas/facturas', '/ventas/devoluciones'],
'/ventas/devoluciones': ['/ventas/notas-credito'],
'/ventas/facturas': ['/ventas/notas-credito'],
'/compras/ofertas': ['/compras/ordenes'],
'/compras/ordenes': ['/compras/recepcion', '/compras/facturas'],
'/compras/recepcion': ['/compras/facturas', '/compras/devoluciones'],
'/compras/devoluciones': ['/compras/notas-credito'],
'/compras/facturas': ['/compras/notas-credito'],
};
/** Documentos a los que se puede copiar desde `path` ("Copiar a"). */
export const copyTargets = (path: string): readonly string[] => COPY_FLOWS[path] ?? [];
/** Documentos desde los que se puede copiar a `path` ("Copiar de"). */
export const copySources = (path: string): readonly string[] =>
Object.entries(COPY_FLOWS)
.filter(([, to]) => to.includes(path))
.map(([from]) => from);
/** Origen de un copiado, enviado en el estado de la navegación hacia el documento nuevo. */
export interface CopyOrigin {
path: string;
docEntry: number;
}
export interface CopiedDocument {
header: DocRecord;
lines: GridLine[];
/** Líneas del origen que no se copian porque ya están cerradas. */
skipped: number;
}
/**
* Datos de un documento origen para uno nuevo: socio y referencias, y sus líneas abiertas con la
* cantidad pendiente y la referencia al origen (`BaseType`/`BaseEntry`/`BaseLine`), con lo que SAP
* enlaza y cierra las cantidades del origen.
*/
export function copyFromDocument(source: DocRecord, sourcePath: string, sourceLabel: string): CopiedDocument {
const baseType = OBJECT_TYPES[sourcePath];
const lines: GridLine[] = [];
let skipped = 0;
for (const l of (source['DocumentLines'] as GridLine[] | undefined) ?? []) {
const remaining = Number(l['RemainingOpenQuantity'] ?? l['Quantity'] ?? 0);
if (l['LineStatus'] === 'bost_Close' || !(remaining > 0)) {
skipped++;
continue;
}
lines.push(
recalcLine({
...newDocumentLine(),
ItemCode: l['ItemCode'] ?? '',
ItemDescription: l['ItemDescription'] ?? '',
Quantity: remaining,
WarehouseCode: l['WarehouseCode'] ?? '',
Price: l['Price'] ?? null,
DiscountPercent: l['DiscountPercent'] ?? 0,
TaxCode: l['TaxCode'] ?? '',
BaseType: baseType,
BaseEntry: source['DocEntry'],
BaseLine: l['LineNum'],
}),
);
}
return {
header: {
CardCode: source['CardCode'] ?? '',
NumAtCard: source['NumAtCard'] ?? '',
DiscountPercent: source['DiscountPercent'] ?? 0,
Comments: `Basado en ${sourceLabel.toLowerCase()} ${String(source['DocNum'])}.`,
},
lines,
skipped,
};
}