feat(web): serie, indicador y proyecto en la cabecera; dimensiones, proyecto, cuenta, referencia base y precio con IVA en el detalle

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-16 11:07:38 -05:00
1 parent 9f003ee4bf
commit f66ef108cf
22 files changed
+698 -100

No files matched your search

@@ -28,9 +28,10 @@ import {
updatePayload,
validateLayout,
} from './document-model';
import { type ComputedSource, PARTNER_COLUMNS, WAREHOUSE_LOOKUP, hasFolio, hasFreight, headerKeysOf, requiredHeaderOf } from './document-layouts';
import { type ComputedSource, PARTNER_COLUMNS, WAREHOUSE_LOOKUP, hasFolio, hasFreight, hasSeries, headerKeysOf, requiredHeaderOf } from './document-layouts';
import { type CopyOrigin, copyFromDocument, copySources, copyTargets } from './document-copy';
import { DOCUMENT_SCREENS, type DocumentScreen, docNum, documentStatus, menuLabel } from './document-screens';
import { OBJECT_TYPES } from './document-objects';
import { ITEMS_MULTI_ADD } from './line-columns';
const today = (): string => new Date().toISOString().slice(0, 10);
@@ -130,6 +131,35 @@ export class DocumentFormPage {
});
protected readonly freightEditable = computed(() => freightOf(this.doc()).editable);
/** Series de numeración de este tipo de documento (acción `SeriesService_GetDocumentSeries` de SAP). */
private readonly hasSeries = hasSeries(this.layout);
private readonly objectType = OBJECT_TYPES[this.screen.path];
private readonly seriesResource = httpResource<SlListResponse<DocRecord>>(() =>
this.hasSeries && this.objectType
? {
url: `${API_URL}/sl/SeriesService_GetDocumentSeries`,
method: 'POST',
body: { DocumentTypeParams: { Document: String(this.objectType) } },
}
: undefined,
);
protected readonly seriesValue = computed<number | null>(() => Number(this.header()['Series']) || null);
/** Series elegibles: del subtipo de la pantalla y sin bloquear; la del documento abierto se incluye siempre. */
protected readonly seriesOptions = computed(() => {
const subType = this.layout.seriesSubType ?? '--';
const rows = this.seriesResource.hasValue() ? this.seriesResource.value().value : [];
const options = rows
.filter((r) => r['DocumentSubType'] === subType && r['Locked'] !== 'tYES')
.map((r) => ({ value: Number(r['Series']), label: text(r['Name']) || String(r['Series']) }));
const current = Number(this.doc()?.['Series']);
if (current && !options.some((o) => o.value === current)) {
const known = rows.find((r) => Number(r['Series']) === current);
options.unshift({ value: current, label: text(known?.['Name']) || `Serie ${current}` });
}
return options;
});
protected readonly doc = computed(() => (this.docResource.hasValue() ? this.docResource.value() : undefined));
protected readonly loading = computed(() => this.docResource.isLoading());
protected readonly loadError = computed(() => {
@@ -278,7 +308,8 @@ export class DocumentFormPage {
protected readonly columns = this.layout.columns;
private readonly itemColumn = this.layout.columns.find((c) => c.key === 'ItemCode') ?? this.layout.columns[0];
protected readonly newLine = () => this.layout.newLine(this.header());
protected readonly recalc = this.layout.recalc;
/** Recalcula la línea con la tasa de cada impuesto (precio con y sin impuesto, total). */
protected readonly recalc = this.layout.recalc ? (line: GridLine, key: string) => this.layout.recalc!(line, key, this.rates()) : undefined;
protected readonly multi: GridMultiAdd = {
...ITEMS_MULTI_ADD,
lookup: this.itemColumn.lookup!.config,