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

@@ -198,6 +198,7 @@ describe('DocumentFormPage', () => {
const req = ctrl.expectOne((r) => r.method === 'POST' && !r.url.includes('SeriesService'));
expect(req.request.url).toBe('/api/sl/Orders');
expect(req.request.body).toEqual({
DocType: 'dDocument_Items',
CardCode: 'C1',
DocDate: page().header().DocDate,
DocDueDate: page().header().DocDueDate,
@@ -209,6 +210,58 @@ describe('DocumentFormPage', () => {
expect(navigate).toHaveBeenCalledWith(['..', 99], expect.objectContaining({ state: { created: '1099' } }));
});
describe('tipo Servicio', () => {
const headers = () => [...el().querySelectorAll('.lines-head th, table.grid thead th')].map((t) => t.textContent?.trim());
it('un documento de servicio cambia las columnas: cuenta contable, descripción, impuesto y total, sin artículo ni cantidad', async () => {
page().setHeader('DocType', 'dDocument_Service');
await tick();
const titles = headers();
expect(titles).toEqual(expect.arrayContaining(['Cuenta contable', 'Descripción', 'Impuesto', 'Total']));
expect(titles).not.toContain('Nº de artículo');
expect(titles).not.toContain('Cantidad');
});
it('crea el documento con DocType de servicio y líneas de cuenta y total', async () => {
page().setHeader('DocType', 'dDocument_Service');
page().setHeader('CardCode', 'P1');
page().setLines([{ AccountCode: '_SYS1', ItemDescription: 'Alquiler', TaxCode: 'IGV', LineTotal: 100, ItemCode: '' }]);
await tick();
(el().querySelector('.actions .btn.main') as HTMLElement).click();
await tick();
const req = ctrl.expectOne((r) => r.method === 'POST' && !r.url.includes('SeriesService'));
expect(req.request.body).toMatchObject({ DocType: 'dDocument_Service', CardCode: 'P1' });
expect((req.request.body as { DocumentLines: unknown[] }).DocumentLines).toEqual([{ AccountCode: '_SYS1', ItemDescription: 'Alquiler', TaxCode: 'IGV', LineTotal: 100 }]);
req.flush({ DocEntry: 7, DocNum: 1007 });
await tick();
});
it('exige cuenta contable y un total mayor que cero', async () => {
page().setHeader('DocType', 'dDocument_Service');
page().setHeader('CardCode', 'P1');
page().setLines([{ ItemDescription: 'Sin cuenta', LineTotal: 10 }]);
await tick();
(el().querySelector('.actions .btn.main') as HTMLElement).click();
await tick();
ctrl.expectNone((r) => r.method === 'POST' && !r.url.includes('SeriesService'));
expect(el().querySelector('.status')!.textContent).toContain('indique la cuenta contable');
});
it('cambiar el tipo con líneas escritas pide permiso y, si se acepta, las descarta', async () => {
page().setLines([{ ItemCode: 'A1', Quantity: 1, Price: 50, LineTotal: 50 }]);
await tick();
page().setHeader('DocType', 'dDocument_Service');
await tick();
expect(el().querySelector('.ask')!.textContent).toContain('descarta la línea escrita');
expect(page().header().DocType).toBe('dDocument_Items'); // todavía no cambió
(el().querySelector('.ask .btn.main') as HTMLElement).click();
await tick();
expect(page().header().DocType).toBe('dDocument_Service');
expect(page().lines()).toEqual([]);
expect(el().querySelector('.ask')).toBeNull();
});
});
it('muestra el error de SAP si rechaza el documento', async () => {
page().setHeader('CardCode', 'C1');
page().setLines([{ ItemCode: 'A1', Quantity: 1, Price: 50, LineTotal: 50 }]);