From eb84d737194e5ac840805e64ffddba074a759ffe Mon Sep 17 00:00:00 2001 From: George Date: Fri, 16 Oct 2026 14:44:15 -0500 Subject: [PATCH] =?UTF-8?q?feat(web):=20asiento=20con=20indicador,=20folio?= =?UTF-8?q?,=20referencias=201=20a=203=20(cabecera=20y=20l=C3=ADnea),=20cu?= =?UTF-8?q?enta/socio=20y=20vencimiento=20por=20l=C3=ADnea?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- .../features/finance/journal-form.page.html | 8 ++++++- .../app/features/finance/journal-form.page.ts | 22 +++++++++++++---- .../features/finance/journal-model.spec.ts | 24 +++++++++++++++++++ .../src/app/features/finance/journal-model.ts | 12 +++++++++- 4 files changed, 60 insertions(+), 6 deletions(-) diff --git a/apps/web/src/app/features/finance/journal-form.page.html b/apps/web/src/app/features/finance/journal-form.page.html index eaf1e70..0afb7e8 100644 --- a/apps/web/src/app/features/finance/journal-form.page.html +++ b/apps/web/src/app/features/finance/journal-form.page.html @@ -26,10 +26,16 @@
- + + + + +
+ +
diff --git a/apps/web/src/app/features/finance/journal-form.page.ts b/apps/web/src/app/features/finance/journal-form.page.ts index ba474ca..2f3b749 100644 --- a/apps/web/src/app/features/finance/journal-form.page.ts +++ b/apps/web/src/app/features/finance/journal-form.page.ts @@ -7,11 +7,11 @@ import { map } from 'rxjs'; import { API_URL, apiErrorMessage } from '../../core/api'; import { formatNumber } from '../../core/format'; import { SlDocumentApi } from '../../core/sl/sl-document'; -import { DateField, SelectField, TextField } from '../../shared/form'; +import { DateField, LookupField, SboField, SelectField, TextField, TextInput } from '../../shared/form'; import type { DetailColumn } from '../../shared/grid/grid.types'; import { RowsTable } from '../../shared/grid/rows-table'; import { Icon, SboWindow, Toolbar, type StatusMessage, type WindowBadge } from '../../shared/window'; -import { ACCOUNT_LOOKUP, PROJECT_LOOKUP, dimensionLookup } from '../documents/dimension-lookups'; +import { ACCOUNT_LOOKUP, INDICATOR_LOOKUP, PROJECT_LOOKUP, dimensionLookup } from '../documents/dimension-lookups'; import type { NavTarget } from '../documents/document-model'; import { isManualJournal, journalOrigin, journalPayload, journalTotals, validateJournal } from './journal-model'; @@ -32,7 +32,7 @@ const dateOnly = (v: unknown): string => (typeof v === 'string' ? v.slice(0, 10) @Component({ selector: 'sbo-journal-form-page', changeDetection: ChangeDetectionStrategy.OnPush, - imports: [DateField, Icon, RouterLink, RowsTable, SboWindow, SelectField, TextField, Toolbar], + imports: [DateField, Icon, LookupField, RouterLink, RowsTable, SboField, SboWindow, SelectField, TextField, TextInput, Toolbar], templateUrl: './journal-form.page.html', styleUrl: './journal-form.page.css', }) @@ -71,6 +71,11 @@ export class JournalFormPage { protected readonly memo = linkedSignal(() => text(this.doc()?.['Memo'])); protected readonly reference = linkedSignal(() => text(this.doc()?.['Reference'])); protected readonly reference2 = linkedSignal(() => text(this.doc()?.['Reference2'])); + protected readonly reference3 = linkedSignal(() => text(this.doc()?.['Reference3'])); + protected readonly indicator = linkedSignal(() => text(this.doc()?.['Indicator'])); + protected readonly folioPrefix = linkedSignal(() => text(this.doc()?.['FolioPrefixString'])); + protected readonly folioNumber = linkedSignal(() => text(this.doc()?.['FolioNumber'])); + protected readonly indicatorLookup = INDICATOR_LOOKUP; protected readonly lines = linkedSignal(() => { const rows = ((this.doc()?.['JournalEntryLines'] as Row[] | undefined) ?? []).map((l) => ({ ...l })); return this.doc() ? rows : [{}, {}]; @@ -102,8 +107,13 @@ export class JournalFormPage { protected readonly columns: readonly DetailColumn[] = [ { key: 'AccountCode', title: 'Cuenta', type: 'lookup', required: true, lookup: ACCOUNT_LOOKUP, width: '170px' }, + { key: 'ShortName', title: 'Cuenta / socio', type: 'text', width: '150px' }, { key: 'Debit', title: 'Debe', type: 'number', decimals: 2, width: '120px' }, { key: 'Credit', title: 'Haber', type: 'number', decimals: 2, width: '120px' }, + { key: 'DueDate', title: 'Vencimiento', type: 'date', width: '120px' }, + { key: 'Reference1', title: 'Ref. 1', type: 'text', width: '110px' }, + { key: 'Reference2', title: 'Ref. 2', type: 'text', width: '110px' }, + { key: 'AdditionalReference', title: 'Ref. 3', type: 'text', width: '110px' }, { key: 'LineMemo', title: 'Comentarios', type: 'text', width: '220px' }, ...([1, 2, 3, 4, 5] as const).map((n): DetailColumn => ({ key: n === 1 ? 'CostingCode' : `CostingCode${n}`, @@ -163,7 +173,7 @@ export class JournalFormPage { this.setLines(rows); } - protected set(key: 'memo' | 'reference' | 'reference2', value: string): void { + protected set(key: 'memo' | 'reference' | 'reference2' | 'reference3' | 'folioPrefix' | 'folioNumber', value: string): void { this[key].set(value); this.message.set(null); } @@ -192,6 +202,10 @@ export class JournalFormPage { memo: this.memo(), reference: this.reference(), reference2: this.reference2(), + reference3: this.reference3(), + indicator: this.indicator(), + folioPrefix: this.folioPrefix(), + folioNumber: this.folioNumber(), lines: this.lines(), }; const errors = validateJournal(draft); diff --git a/apps/web/src/app/features/finance/journal-model.spec.ts b/apps/web/src/app/features/finance/journal-model.spec.ts index e9cc8e7..7bc5909 100644 --- a/apps/web/src/app/features/finance/journal-model.spec.ts +++ b/apps/web/src/app/features/finance/journal-model.spec.ts @@ -9,6 +9,10 @@ const draft = (over: Partial = {}): JournalDraft => ({ memo: '', reference: '', reference2: '', + reference3: '', + indicator: '', + folioPrefix: '', + folioNumber: '', lines: [ { AccountCode: '1-1-010-10-001', Debit: 1000 }, { AccountCode: '6-1-010-40-000', Credit: 1000 }, @@ -60,6 +64,26 @@ describe('asiento contable', () => { }); }); + it('indicador, folio y referencias de cabecera y de línea viajan si se indicaron', () => { + const body = journalPayload( + draft({ + indicator: '33', + folioPrefix: 'FA', + folioNumber: '123', + reference: 'R1', + reference2: 'R2', + reference3: 'R3', + lines: [ + { AccountCode: 'A', ShortName: 'C1', Debit: 5, DueDate: '2026-11-16', Reference1: 'L1', Reference2: 'L2', AdditionalReference: 'L3' }, + { AccountCode: 'B', Credit: 5 }, + ], + }), + ); + expect(body).toMatchObject({ Indicator: '33', FolioPrefixString: 'FA', FolioNumber: 123, Reference: 'R1', Reference2: 'R2', Reference3: 'R3' }); + expect((body['JournalEntryLines'] as unknown[])[0]).toEqual({ AccountCode: 'A', ShortName: 'C1', Debit: 5, DueDate: '2026-11-16', Reference1: 'L1', Reference2: 'L2', AdditionalReference: 'L3' }); + expect(validateJournal(draft({ folioNumber: '12a' }))).toEqual(['El número de folio debe ser numérico.']); + }); + it('solo los asientos manuales se cancelan; el origen se muestra en español', () => { expect(isManualJournal({ OriginalJournal: 'ttJournalEntry' })).toBe(true); expect(isManualJournal({ OriginalJournal: 'ttARInvoice' })).toBe(false); diff --git a/apps/web/src/app/features/finance/journal-model.ts b/apps/web/src/app/features/finance/journal-model.ts index 4606f29..fc5bbe0 100644 --- a/apps/web/src/app/features/finance/journal-model.ts +++ b/apps/web/src/app/features/finance/journal-model.ts @@ -5,7 +5,7 @@ const round2 = (n: number): number => Math.round((n + Number.EPSILON) * 100) / 1 const has = (v: unknown): boolean => !(v === null || v === undefined || (typeof v === 'string' && v.trim() === '')); /** Campos de la línea que se envían a SAP además de la cuenta y los importes. */ -export const JOURNAL_LINE_TEXT_FIELDS = ['LineMemo', 'CostingCode', 'CostingCode2', 'CostingCode3', 'CostingCode4', 'CostingCode5', 'ProjectCode'] as const; +export const JOURNAL_LINE_TEXT_FIELDS = ['ShortName', 'DueDate', 'Reference1', 'Reference2', 'AdditionalReference', 'LineMemo', 'CostingCode', 'CostingCode2', 'CostingCode3', 'CostingCode4', 'CostingCode5', 'ProjectCode'] as const; export interface JournalDraft { referenceDate: string; @@ -15,6 +15,11 @@ export interface JournalDraft { memo: string; reference: string; reference2: string; + reference3: string; + indicator: string; + folioPrefix: string; + /** Se edita como texto; SAP lo espera como número. */ + folioNumber: string; lines: Row[]; } @@ -38,6 +43,7 @@ export function journalTotals(lines: readonly Row[]): JournalTotals { export function validateJournal(d: JournalDraft): string[] { const errors: string[] = []; if (!has(d.referenceDate)) errors.push('Indique la fecha de contabilización.'); + if (!/^\d*$/.test(d.folioNumber.trim())) errors.push('El número de folio debe ser numérico.'); const lines = d.lines.filter((l) => !isBlank(l)); if (lines.length < 2) errors.push('Un asiento necesita al menos dos líneas.'); lines.forEach((l, i) => { @@ -62,6 +68,10 @@ export function journalPayload(d: JournalDraft): Row { if (has(d.memo)) body['Memo'] = d.memo; if (has(d.reference)) body['Reference'] = d.reference; if (has(d.reference2)) body['Reference2'] = d.reference2; + if (has(d.reference3)) body['Reference3'] = d.reference3; + if (has(d.indicator)) body['Indicator'] = d.indicator; + if (has(d.folioPrefix)) body['FolioPrefixString'] = d.folioPrefix.trim(); + if (has(d.folioNumber)) body['FolioNumber'] = Number(d.folioNumber); body['JournalEntryLines'] = d.lines .filter((l) => !isBlank(l)) .map((l) => {