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:
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. */
|
||||
|
||||
Reference in new issue
Block a user