feat(web): asiento nuevo con todas las fechas de hoy y pregunta antes de pasar el vencimiento de la cabecera a las líneas

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-19 11:55:50 -05:00
1 parent f9482bc884
commit ba0ab025de
6 files changed
+50 -10

No files matched your search

@@ -5,7 +5,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import type { SlListResponse } from '@basic-erp/sap-types';
import { map } from 'rxjs';
import { API_URL, apiErrorMessage } from '../../core/api';
import { formatNumber } from '../../core/format';
import { formatNumber, todayIso } from '../../core/format';
import { SlDocumentApi } from '../../core/sl/sl-document';
import { DateField, LookupField, NumberField, SelectField, TextField, TextareaField } from '../../shared/form';
import type { DetailColumn } from '../../shared/grid/grid.types';
@@ -21,7 +21,7 @@ import type { PaymentDocType, PaymentScreen } from './payment-types';
type Row = Record<string, unknown>;
const text = (v: unknown): string => (v === null || v === undefined ? '' : String(v));
const today = (): string => new Date().toISOString().slice(0, 10);
const today = (): string => todayIso();
const dateOnly = (v: unknown): string => (typeof v === 'string' ? v.slice(0, 10) : '');
const money = (n: number): string => formatNumber(n, 2);
@@ -6,3 +6,4 @@ h3 { margin: 14px 0 4px; font-size: 13px; font-weight: 600; }
.totals td:last-child { text-align: right; font-variant-numeric: tabular-nums; }
.totals tr.strong td { font-weight: 600; border-top: 1px solid var(--field-b); }
.confirm { color: var(--danger, #b00020); margin-right: 8px; }
.ask { display: flex; align-items: center; gap: 8px; margin: 4px 0 6px; padding: 4px 8px; background: var(--warn-bg); border: 1px solid var(--line); border-radius: var(--radius); }
@@ -20,7 +20,7 @@
</div>
<div class="sbo-col">
<sbo-date-field label="Fecha de contabilización" [(value)]="referenceDate" [readonly]="saved()" required />
<sbo-date-field label="Fecha de vencimiento" [(value)]="dueDate" [readonly]="saved()" />
<sbo-date-field label="Fecha de vencimiento" [value]="dueDate()" (valueChange)="onDueDate($event)" [readonly]="saved()" />
<sbo-date-field label="Fecha del documento" [(value)]="taxDate" [readonly]="saved()" />
</div>
</div>
@@ -40,6 +40,13 @@
</div>
<h3>Líneas</h3>
@if (pendingDue(); as due) {
<p class="ask" role="alert">
¿Poner también el vencimiento {{ formatDate(due) }} en las líneas?
<button type="button" class="btn main" (click)="applyDue(true)">Sí, actualizar</button>
<button type="button" class="btn" (click)="applyDue(false)">No, dejarlas</button>
</p>
}
<sbo-rows-table [columns]="columns" [rows]="lines()" (rowsChange)="setLines($event)" [readonly]="saved()" [removable]="!saved()" emptyText="Sin líneas." />
@if (!saved()) {
<p class="row">
@@ -5,7 +5,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import type { SlListResponse } from '@basic-erp/sap-types';
import { firstValueFrom, map } from 'rxjs';
import { API_URL, apiErrorMessage } from '../../core/api';
import { formatNumber } from '../../core/format';
import { formatDate, formatNumber, todayIso } from '../../core/format';
import { SlDocumentApi } from '../../core/sl/sl-document';
import { DateField, LookupField, SboField, SelectField, TextField, TextInput } from '../../shared/form';
import type { DetailColumn } from '../../shared/grid/grid.types';
@@ -46,7 +46,7 @@ const ASSOCIATED_ACCOUNT_LOOKUP: VhLookup = {
const OBJECT_TYPE = 30;
const text = (v: unknown): string => (v === null || v === undefined ? '' : String(v));
const today = (): string => new Date().toISOString().slice(0, 10);
const today = (): string => todayIso();
const dateOnly = (v: unknown): string => (typeof v === 'string' ? v.slice(0, 10) : '');
/**
@@ -91,8 +91,9 @@ export class JournalFormPage {
// --- el borrador (o el asiento guardado, mostrado) -----------------------------------------
protected readonly referenceDate = linkedSignal(() => (this.doc() ? dateOnly(this.doc()!['ReferenceDate']) : today()));
protected readonly dueDate = linkedSignal(() => dateOnly(this.doc()?.['DueDate']));
protected readonly taxDate = linkedSignal(() => dateOnly(this.doc()?.['TaxDate']));
// un asiento nuevo arranca con todas las fechas de hoy (cabecera y líneas)
protected readonly dueDate = linkedSignal(() => (this.doc() ? dateOnly(this.doc()!['DueDate']) : today()));
protected readonly taxDate = linkedSignal(() => (this.doc() ? dateOnly(this.doc()!['TaxDate']) : today()));
protected readonly series = linkedSignal<number | null>(() => (this.doc() ? Number(this.doc()!['Series']) || null : null));
protected readonly memo = linkedSignal(() => text(this.doc()?.['Memo']));
protected readonly reference = linkedSignal(() => text(this.doc()?.['Reference']));
@@ -104,7 +105,7 @@ export class JournalFormPage {
protected readonly indicatorLookup = INDICATOR_LOOKUP;
protected readonly lines = linkedSignal<Row[]>(() => {
const rows = ((this.doc()?.['JournalEntryLines'] as Row[] | undefined) ?? []).map((l) => ({ ...l }));
return this.doc() ? rows : [{}, {}];
return this.doc() ? rows : [{ DueDate: today() }, { DueDate: today() }];
});
protected readonly saving = signal(false);
@@ -227,8 +228,26 @@ export class JournalFormPage {
this.message.set(null);
}
/** Vencimiento nuevo de la cabecera que espera la respuesta del usuario para pasarlo a las líneas. */
protected readonly pendingDue = signal<string | null>(null);
protected formatDate = formatDate;
/** Al cambiar el vencimiento de la cabecera se pregunta si las líneas con otro vencimiento lo toman también. */
protected onDueDate(value: string): void {
this.dueDate.set(value);
const differ = this.lines().some((l) => (l['DueDate'] ?? '') !== value);
this.pendingDue.set(value && differ ? value : null);
}
protected applyDue(yes: boolean): void {
const value = this.pendingDue();
this.pendingDue.set(null);
if (yes && value) this.setLines(this.lines().map((l) => ({ ...l, DueDate: value })));
}
protected addLine(): void {
this.setLines([...this.lines(), {}]);
this.setLines([...this.lines(), { DueDate: this.dueDate() }]);
}
/** Lleva a la última línea lo que falta para cuadrar: el debe o el haber, según el lado que sobra. */