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) => {