feat(web): tablas en los maestros (direcciones, personas de contacto, precios por lista, stock por almacén)
sbo-rows-table reutilizable (también en la ventana de lotes). Direcciones y contactos se envían completos con reemplazo; los precios solo por filas cambiadas. Probado contra SBODemoCL. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e853b1b76c
commit
4e9d6a757d
14 files changed
+679
-96
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { escapeODataString } from '../../core/sl/odata';
|
||||
import { type HeaderField, keysOfFields, requiredOfFields } from '../documents/document-layouts';
|
||||
import { type DocRecord, compact } from '../documents/document-model';
|
||||
import type { MasterLayout } from './master-types';
|
||||
import type { MasterCollection, MasterLayout } from './master-types';
|
||||
|
||||
const isEmpty = (v: unknown): boolean => v === null || v === undefined || (typeof v === 'string' && v.trim() === '');
|
||||
|
||||
@@ -58,3 +58,61 @@ export function masterNavigationQuery(
|
||||
return current === null ? { $orderby: asc } : { $filter: `${keyField} gt ${quoted}`, $orderby: asc };
|
||||
}
|
||||
}
|
||||
|
||||
// --- tablas (colecciones) ----------------------------------------------------------------
|
||||
|
||||
export type CollectionRow = Record<string, unknown>;
|
||||
|
||||
/** Las tablas que declara el formulario, de todas sus pestañas. */
|
||||
export const masterCollectionsOf = (layout: MasterLayout): MasterCollection[] =>
|
||||
layout.tabs.flatMap((t) => (t.collection ? [t.collection] : []));
|
||||
|
||||
/** Copia de las filas que trae SAP de una colección (sobre ellas se aplican los cambios). */
|
||||
export const collectionRowsOf = (doc: DocRecord | undefined, c: MasterCollection): CollectionRow[] =>
|
||||
((doc?.[c.key] as CollectionRow[] | undefined) ?? []).map((r) => ({ ...r }));
|
||||
|
||||
const has = (v: unknown): boolean => !(v === null || v === undefined || (typeof v === 'string' && v.trim() === ''));
|
||||
|
||||
/** Una fila vacía: ninguno de los campos que la identifican tiene valor. */
|
||||
export const isBlankRow = (c: MasterCollection, row: CollectionRow): boolean => !c.identity.some((k) => has(row[k]));
|
||||
|
||||
const editableKeys = (c: MasterCollection): string[] => c.columns.filter((col) => !col.readonly).map((col) => col.key);
|
||||
/** Dos valores de una celda son iguales (vacío = vacío; un número escrito como texto = el número). */
|
||||
function same(a: unknown, b: unknown): boolean {
|
||||
if (!has(a) && !has(b)) return true;
|
||||
if (!has(a) || !has(b)) return false;
|
||||
return a === b || (Number.isFinite(Number(a)) && Number(a) === Number(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Lo que se envía a SAP de una tabla tras editarla, o `null` si no cambió. En `replace` la colección completa
|
||||
* (las filas leídas con los cambios y las nuevas, sin las vacías); en `merge` solo las filas cambiadas con su
|
||||
* `rowKey` y los campos que cambiaron.
|
||||
*/
|
||||
export function collectionPayload(c: MasterCollection, original: readonly CollectionRow[], current: readonly CollectionRow[]): CollectionRow[] | null {
|
||||
if (c.readonly) return null;
|
||||
const keys = editableKeys(c);
|
||||
const rows = current.filter((r) => !isBlankRow(c, r));
|
||||
if (c.mode === 'merge') {
|
||||
const rk = c.rowKey ?? '';
|
||||
const changed = rows.flatMap((r) => {
|
||||
const before = original.find((o) => o[rk] === r[rk]);
|
||||
const diff = keys.filter((k) => !same(before?.[k], r[k]));
|
||||
return before && diff.length ? [{ [rk]: r[rk], ...Object.fromEntries(diff.map((k) => [k, has(r[k]) ? r[k] : null])) }] : [];
|
||||
});
|
||||
return changed.length ? changed : null;
|
||||
}
|
||||
const unchanged = rows.length === original.length && rows.every((r, i) => keys.every((k) => same(original[i]?.[k], r[k])));
|
||||
return unchanged ? null : rows;
|
||||
}
|
||||
|
||||
/** Tablas con filas para enviar al crear el registro (las vacías no). */
|
||||
export function collectionsCreatePayload(layout: MasterLayout, current: Readonly<Record<string, readonly CollectionRow[]>>): DocRecord {
|
||||
const body: DocRecord = {};
|
||||
for (const c of masterCollectionsOf(layout)) {
|
||||
if (c.readonly) continue;
|
||||
const rows = (current[c.key] ?? []).filter((r) => !isBlankRow(c, r)).map((r) => compact(r));
|
||||
if (rows.length) body[c.key] = rows;
|
||||
}
|
||||
return body;
|
||||
}
|
||||
Reference in new issue
Block a user