fix(web): fechas de SAP con hora, enlaces como botones y un solo maestro de impuestos
Probado contra el SAP real (solo lectura): órdenes de venta y facturas de proveedor. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4d4df17d07
commit
6978fed6fb
8 files changed
+34
-10
No files matched your search
+1
-1
@@ -128,7 +128,7 @@ Las tres rutas las gobierna el permiso de menú de la ruta padre. Se generan sol
|
||||
|
||||
Lo usan las pantallas con `partner` en su `DocumentScreen` (ventas y compras); el resto de documentos muestra aún un aviso.
|
||||
|
||||
- **Cabecera**: socio de negocio (lista de valores filtrada por `CardType`; no se cambia en un documento ya creado), Nº de referencia, fechas y descuento %; **líneas** con `sbo-lines-grid`; **totales** informativos (SAP recalcula al guardar) con las tasas de `SalesTaxCodes` (ventas) o `PurchaseTaxCodes` (compras).
|
||||
- **Cabecera**: socio de negocio (lista de valores filtrada por `CardType`; no se cambia en un documento ya creado), Nº de referencia, fechas y descuento %; **líneas** con `sbo-lines-grid`; **totales** informativos (SAP recalcula al guardar) con las tasas de `SalesTaxCodes` (también en compras: el Service Layer no tiene un maestro propio; `partner.taxResource` permite cambiarlo en una rama de cliente).
|
||||
- **Crear**: valida sin SAP (socio, fecha, al menos una línea con artículo, cantidad > 0), quita campos vacíos y filas en blanco, y abre el documento creado.
|
||||
- **Actualizar**: envía solo la cabecera que cambió y, si cambiaron las líneas, todas con `B1S-ReplaceCollectionsOnPatch: true`. Lleva `If-Match` con el `odata.etag` leído: si otro usuario cambió el documento, SAP responde **412**, no se guarda nada y se ofrece "Recargar".
|
||||
- **Cerrado o cancelado**: solo lectura (sin botón de guardar).
|
||||
|
||||
@@ -16,8 +16,8 @@ const order = (over: Record<string, unknown> = {}) => ({
|
||||
CardCode: 'C1',
|
||||
CardName: 'ACME',
|
||||
NumAtCard: '',
|
||||
DocDate: '2026-09-11',
|
||||
DocDueDate: '2026-09-20',
|
||||
DocDate: '2026-09-11T00:00:00Z',
|
||||
DocDueDate: '2026-09-20T00:00:00Z',
|
||||
DiscountPercent: 0,
|
||||
Comments: 'hola',
|
||||
DocumentStatus: 'bost_Open',
|
||||
@@ -77,6 +77,11 @@ describe('DocumentFormPage', () => {
|
||||
expect(buttonText()).toContain('Actualizar');
|
||||
});
|
||||
|
||||
it('las fechas de SAP (con hora) se muestran y se comparan como AAAA-MM-DD', () => {
|
||||
expect(page().header().DocDate).toBe('2026-09-11');
|
||||
expect(page().dirty()).toBe(false);
|
||||
});
|
||||
|
||||
it('sin cambios, Actualizar no envía nada', async () => {
|
||||
(el().querySelector('.actions .btn.main') as HTMLElement).click();
|
||||
await tick();
|
||||
|
||||
@@ -30,9 +30,16 @@ import { ITEMS_MULTI_ADD, lineColumnsFor, newDocumentLine, recalcLine } from './
|
||||
const HEADER_KEYS = ['CardCode', 'NumAtCard', 'DocDate', 'DocDueDate', 'DiscountPercent', 'Comments'] as const;
|
||||
|
||||
const today = (): string => new Date().toISOString().slice(0, 10);
|
||||
/** SAP devuelve las fechas como `2026-01-05T00:00:00Z`; el campo de fecha y el guardado usan `2026-01-05`. */
|
||||
const dateOnly = (v: unknown): string => (typeof v === 'string' ? v.slice(0, 10) : '');
|
||||
const pickHeader = (doc: DocRecord | undefined): DocRecord => {
|
||||
if (!doc) return { DocDate: today(), DocDueDate: today(), DiscountPercent: 0 };
|
||||
return Object.fromEntries(HEADER_KEYS.map((k) => [k, doc[k] ?? (k === 'DiscountPercent' ? 0 : '')]));
|
||||
return Object.fromEntries(
|
||||
HEADER_KEYS.map((k) => [
|
||||
k,
|
||||
k === 'DocDate' || k === 'DocDueDate' ? dateOnly(doc[k]) : (doc[k] ?? (k === 'DiscountPercent' ? 0 : '')),
|
||||
]),
|
||||
);
|
||||
};
|
||||
const text = (v: unknown): string => (v === null || v === undefined ? '' : String(v));
|
||||
|
||||
|
||||
@@ -17,7 +17,12 @@ export interface DocumentScreen {
|
||||
/** Los más recientes primero. */
|
||||
orderby: string;
|
||||
/** Formulario de documentos con socio de negocio, líneas y totales (ventas y compras). */
|
||||
partner?: { label: string; type: 'cCustomer' | 'cSupplier'; taxResource: 'SalesTaxCodes' | 'PurchaseTaxCodes' };
|
||||
partner?: {
|
||||
label: string;
|
||||
type: 'cCustomer' | 'cSupplier';
|
||||
/** Maestro de impuestos (el Service Layer no tiene uno propio de compras: en Chile ambos usan `SalesTaxCodes`). */
|
||||
taxResource: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const menuLabel = (path: string): string =>
|
||||
@@ -91,7 +96,7 @@ const marketing = (path: string, resource: string, createLabel: string, partner:
|
||||
orderby: 'DocEntry desc',
|
||||
partner: path.startsWith('/ventas')
|
||||
? { label: partner, type: 'cCustomer', taxResource: 'SalesTaxCodes' }
|
||||
: { label: partner, type: 'cSupplier', taxResource: 'PurchaseTaxCodes' },
|
||||
: { label: partner, type: 'cSupplier', taxResource: 'SalesTaxCodes' },
|
||||
extraFields: ['Cancelled', 'DocTotalFc', 'DocCurrency'],
|
||||
columns: [
|
||||
docNum,
|
||||
|
||||
@@ -93,7 +93,7 @@ export const DOCUMENT_LINE_COLUMNS: readonly GridColumn[] = [
|
||||
{ key: 'LineTotal', title: 'Total', type: 'number', decimals: 2, width: '110px', readonly: true },
|
||||
];
|
||||
|
||||
/** Columnas de línea con el maestro de impuestos que corresponde (ventas: `SalesTaxCodes`; compras: `PurchaseTaxCodes`). */
|
||||
/** Columnas de línea con el maestro de impuestos que corresponde (por defecto `SalesTaxCodes`). */
|
||||
export function lineColumnsFor(taxResource: string): readonly GridColumn[] {
|
||||
if (taxResource === 'SalesTaxCodes') return DOCUMENT_LINE_COLUMNS;
|
||||
return DOCUMENT_LINE_COLUMNS.map((c) =>
|
||||
|
||||
@@ -85,7 +85,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Botones de acción */
|
||||
/* Botones de acción (también como enlaces: `<a class="btn">`, `<a class="tb">`) */
|
||||
a.btn,
|
||||
a.tb {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
color: var(--ink);
|
||||
}
|
||||
.btn {
|
||||
height: 26px;
|
||||
min-width: 84px;
|
||||
|
||||
Reference in new issue
Block a user