feat(web): documentos de ventas y compras con tipo Artículo / Servicio (columnas de línea, validación, guardado y copiado)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-19 12:19:18 -05:00
1 parent 68d2cfe6d0
commit 2e74bccd65
11 files changed
+260 -25

No files matched your search

@@ -123,6 +123,8 @@ export interface LinesSpec {
fields: readonly string[];
/** Los documentos de ventas y compras referencian el origen de una línea copiada (`BaseType`…). */
base: boolean;
/** Líneas de un documento de servicio: cuenta contable, descripción, impuesto y total, sin artículo ni cantidad. */
service?: boolean;
}
export const MARKETING_LINES: LinesSpec = { key: 'DocumentLines', fields: LINE_FIELDS, base: true };
@@ -130,13 +132,17 @@ export const MARKETING_LINES: LinesSpec = { key: 'DocumentLines', fields: LINE_F
const BASE_FIELDS = ['BaseType', 'BaseEntry', 'BaseLine'] as const;
const hasBase = (l: GridLine): boolean => hasBaseRef(l['BaseType'], l['BaseEntry']);
/** Una línea vacía (la fila en blanco que deja la grilla) no se envía. */
export const isBlankLine = (line: GridLine): boolean => isEmpty(line['ItemCode']);
/**
* Una línea vacía (la fila en blanco que deja la grilla) no se envía. En un documento de artículos es la que no
* tiene artículo; en uno de servicio, la que no tiene cuenta, descripción ni total.
*/
export const isBlankLine = (line: GridLine, service = false): boolean =>
service ? isEmpty(line['AccountCode']) && isEmpty(line['ItemDescription']) && num(line['LineTotal']) === 0 : isEmpty(line['ItemCode']);
/** Líneas listas para SAP: sin filas en blanco ni campos vacíos. */
export function linesPayload(lines: readonly GridLine[], spec: LinesSpec = MARKETING_LINES): DocRecord[] {
return lines
.filter((l) => !isBlankLine(l))
.filter((l) => !isBlankLine(l, spec.service))
.map((l) => {
const out: DocRecord = {};
for (const f of spec.fields) {
@@ -171,12 +177,18 @@ export function validateLayout(
lines: readonly GridLine[],
requiredHeader: readonly RequiredField[],
requiredLine: readonly RequiredField[],
service = false,
): string[] {
const errors: string[] = [];
for (const f of requiredHeader) if (isEmpty(header[f.key])) errors.push(`Indique ${f.label.toLowerCase()}.`);
const real = lines.filter((l) => !isBlankLine(l));
if (!real.length) errors.push('Agregue al menos una línea con artículo.');
const real = lines.filter((l) => !isBlankLine(l, service));
if (!real.length) errors.push(service ? 'Agregue al menos una línea con cuenta contable.' : 'Agregue al menos una línea con artículo.');
real.forEach((l, i) => {
if (service) {
if (isEmpty(l['AccountCode'])) errors.push(`Línea ${i + 1}: indique la cuenta contable.`);
if (num(l['LineTotal']) <= 0) errors.push(`Línea ${i + 1}: el total debe ser mayor que cero.`);
return;
}
if (num(l['Quantity']) <= 0) errors.push(`Línea ${i + 1}: la cantidad debe ser mayor que cero.`);
for (const f of requiredLine) if (isEmpty(l[f.key])) errors.push(`Línea ${i + 1}: indique ${f.label.toLowerCase()}.`);
});