diff --git a/CLAUDE.md b/CLAUDE.md index 2730f4c..2804533 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,7 +52,7 @@ Es un ERP, no una página de publicidad: **ligera y rápida**. Sin librerías de ## Flujo de las pantallas de documento -Cada menú abre primero una **lista de los últimos documentos** (barra de filtros + búsqueda por columna); desde ahí se crea uno nuevo (`/…/nuevo`, formulario vacío) o se abre uno existente (`/…/:docEntry`, ver/editar; los cerrados/cancelados en solo lectura). Las pantallas se definen por módulo en `apps/web/src/app/features/documents/` (17 documentos): `ventas/ventas.screens.ts`, `compras/compras.screens.ts` (+ `compras.layouts.ts`) e `inventario/inventario.screens.ts` (+ `inventario.layouts.ts`); los maestros (socios de negocio, artículos) irán en su propia carpeta. `screen-kit.ts` tiene el tipo `DocumentScreen` y las columnas y filtros comunes; `document-screens.ts` las reúne en `DOCUMENT_SCREENS`. Las columnas de línea estándar están en `features/documents/line-columns.ts`. **Tipo Artículo / Servicio** (`DocType`): los documentos de ventas y compras con socio llevan en la cabecera la lista «Tipo» (primera de la columna izquierda; no se cambia en uno guardado). Con «Servicio» las líneas pasan a `layout.service` (`serviceLineColumns` en `line-columns.ts`): Cuenta contable (obligatoria), Descripción, Impuesto y Total (se escribe, sin recalcular), dimensiones, proyecto y referencias al origen; sin artículo, cantidad, almacén, precio, lotes ni «Elegir artículos». Se guardan `AccountCode`, `ItemDescription`, `TaxCode`, `LineTotal` (`LinesSpec.service`); cambiar el tipo con líneas escritas pide permiso y las descarta; «Copiar de» trae el tipo y el importe abierto (`OpenAmount`) del origen y no mezcla tipos. Las extensiones de cliente de columnas de línea aplican a las líneas de artículos; las de servicio aún no son extensibles. +Cada menú abre primero una **lista de los últimos documentos** (barra de filtros + búsqueda por columna); desde ahí se crea uno nuevo (`/…/nuevo`, formulario vacío) o se abre uno existente (`/…/:docEntry`, ver/editar; los cerrados/cancelados en solo lectura). Las pantallas se definen por módulo en `apps/web/src/app/features/documents/` (17 documentos): `ventas/ventas.screens.ts`, `compras/compras.screens.ts` (+ `compras.layouts.ts`) e `inventario/inventario.screens.ts` (+ `inventario.layouts.ts`); los maestros (socios de negocio, artículos) irán en su propia carpeta. `screen-kit.ts` tiene el tipo `DocumentScreen` y las columnas y filtros comunes; `document-screens.ts` las reúne en `DOCUMENT_SCREENS`. Las columnas de línea estándar están en `features/documents/line-columns.ts`. **Tipo Artículo / Servicio** (`DocType`): los documentos de ventas y compras con socio llevan en la cabecera la lista «Tipo» (primera de la columna izquierda; no se cambia en uno guardado). Con «Servicio» las líneas pasan a `layout.service` (`serviceLineColumns` en `line-columns.ts`): Cuenta contable (obligatoria), Descripción, Impuesto y Total (se escribe, sin recalcular), dimensiones, proyecto y referencias al origen; sin artículo, cantidad, almacén, precio, lotes ni «Elegir artículos». Se guardan `AccountCode`, `ItemDescription`, `TaxCode`, `LineTotal` (`LinesSpec.service`); cambiar el tipo con líneas escritas pide permiso y las descarta; «Copiar de» trae el tipo y el importe abierto (`OpenAmount`) del origen y no mezcla tipos. Las extensiones de cliente de columnas de línea aplican a las líneas de artículos; las de servicio aún no son extensibles. **Indicador y folio juntos y en ese orden** (`IDENTITY_GROUP`, un `HeaderField` tipo `group` = varias celdas en una fila: Indicador con su lista, prefijo y número del folio) en facturas, boletas, reservas y notas de crédito de ventas y compras (`BILLING_DOCUMENT`) y en el asiento. Una empresa que los sustituye por campos de usuario arma su grupo con `fieldGroup(label, cells)` en `headerRight` de su extensión y oculta el del base con `hideHeaderFields: ['Indicator']` (oculta el grupo completo). Las **tres fechas** de la cabecera de todo documento de ventas y compras van juntas y en este orden, una debajo de la otra: contabilización, vencimiento y del documento (`TaxDate`), las tres con la fecha de hoy al crear. ## Cómo verificar cambios de interfaz diff --git a/apps/web/src/app/features/documents/document-analytics.spec.ts b/apps/web/src/app/features/documents/document-analytics.spec.ts index 3139d6e..0f39fbf 100644 --- a/apps/web/src/app/features/documents/document-analytics.spec.ts +++ b/apps/web/src/app/features/documents/document-analytics.spec.ts @@ -252,10 +252,13 @@ describe('el proyecto de la cabecera se elige de su ventana modal', () => { }); describe('el indicador de la cabecera se elige de su ventana modal', () => { - it('es un campo con lista de valores sobre FactoringIndicators, con código y nombre', () => { - const f = DOCUMENT_SCREENS.find((s) => s.path === '/ventas/facturas')!.layout.header.left.find((x) => x.type === 'lookup' && x.key === 'Indicator'); - expect(f && f.type === 'lookup' && f.lookup.resource).toBe('FactoringIndicators'); - expect(f && f.type === 'lookup' && f.lookup.columns.map((c) => c.key)).toEqual(['IndicatorCode', 'IndicatorName']); + it('es una celda con lista de valores sobre FactoringIndicators, con código y nombre, junto al folio', () => { + const group = DOCUMENT_SCREENS.find((s) => s.path === '/ventas/facturas')!.layout.header.right.find((x) => x.type === 'group'); + const cells = group && group.type === 'group' ? group.cells : []; + expect(cells.map((c) => c.key)).toEqual(['Indicator', 'FolioPrefixString', 'FolioNumber']); + const first = cells[0]; + expect(first.kind === 'lookup' && first.lookup.resource).toBe('FactoringIndicators'); + expect(first.kind === 'lookup' && first.lookup.columns.map((c) => c.key)).toEqual(['IndicatorCode', 'IndicatorName']); }); it('abre su ventana de selección y escribe el código elegido', async () => { @@ -278,8 +281,8 @@ describe('el indicador de la cabecera se elige de su ventana modal', () => { }; await tick(); const el = fixture.nativeElement as HTMLElement; - const row = [...el.querySelectorAll('sbo-lookup-field')].find((f) => f.querySelector('label')?.textContent?.trim() === 'Indicador')!; - expect(row, 'campo Indicador con lista').toBeDefined(); + const row = [...el.querySelectorAll('sbo-field')].find((f) => f.querySelector('label')?.textContent?.trim() === 'Indicador / Folio')!; + expect(row, 'fila Indicador / Folio').toBeDefined(); (row.querySelector('button.sbo-btn') as HTMLElement).click(); await tick(); ctrl.expectOne((r) => r.url === '/api/sl/FactoringIndicators').flush({ value: [{ IndicatorCode: '33', IndicatorName: 'Factura' }, { IndicatorCode: '39', IndicatorName: 'Boleta' }], 'odata.count': '2' }); diff --git a/apps/web/src/app/features/documents/document-folio.spec.ts b/apps/web/src/app/features/documents/document-folio.spec.ts index 4fb4771..abf0f8d 100644 --- a/apps/web/src/app/features/documents/document-folio.spec.ts +++ b/apps/web/src/app/features/documents/document-folio.spec.ts @@ -75,7 +75,7 @@ describe('folio y RUT en los formularios', () => { it('una factura muestra folio, número de folio, referencia y RUT', async () => { await open('PurchaseInvoices', invoice()); - expect(labels()).toEqual(expect.arrayContaining(['Folio', 'Nº referencia', 'RUT'])); + expect(labels()).toEqual(expect.arrayContaining(['Indicador / Folio', 'Nº referencia', 'RUT'])); const inputs = [...el().querySelectorAll('input')].map((i) => i.value); expect(inputs).toEqual(expect.arrayContaining(['F001', '198837', 'REF-1', '76543210-K'])); }); diff --git a/apps/web/src/app/features/documents/document-form.page.html b/apps/web/src/app/features/documents/document-form.page.html index 430fe0a..1aadcf0 100644 --- a/apps/web/src/app/features/documents/document-form.page.html +++ b/apps/web/src/app/features/documents/document-form.page.html @@ -72,6 +72,23 @@ } +@if (groupCell(); as cell) { + @if (cell.kind === 'lookup') { + + } +} + @if (copyListScreen(); as src) { } + @case ('group') { + + @for (c of f.cells; track c.key) { + + @if (c.kind === 'lookup' && !(readOnly() || locked(f))) { + + } + } + + } @case ('series') { { CardCode: 'C1', DocDate: page().header().DocDate, DocDueDate: page().header().DocDueDate, + TaxDate: page().header().TaxDate, DiscountPercent: 0, DocumentLines: [{ ItemCode: 'A1', Quantity: 1, Price: 50, DiscountPercent: 0 }], }); @@ -210,6 +211,14 @@ describe('DocumentFormPage', () => { expect(navigate).toHaveBeenCalledWith(['..', 99], expect.objectContaining({ state: { created: '1099' } })); }); + it('las tres fechas de un documento nuevo van juntas, una debajo de la otra: contabilización, vencimiento y del documento', () => { + const labels = page().layout.header.right.filter((f: { type: string }) => f.type === 'date').map((f: { label: string }) => f.label); + expect(labels).toEqual(['Fecha de contabilización', 'Fecha de vencimiento', 'Fecha del documento']); + const h = page().header(); + expect(h.TaxDate).toBe(h.DocDate); // las tres arrancan con la fecha de hoy + expect(h.DocDueDate).toBe(h.DocDate); + }); + it('al elegir el socio de un documento nuevo sin líneas se agrega una línea, y no se repite', async () => { expect(page().lines()).toEqual([]); page().onPartner({ CardCode: 'C1', CardName: 'ACME' }); diff --git a/apps/web/src/app/features/documents/document-form.page.ts b/apps/web/src/app/features/documents/document-form.page.ts index 4ec3348..118ce22 100644 --- a/apps/web/src/app/features/documents/document-form.page.ts +++ b/apps/web/src/app/features/documents/document-form.page.ts @@ -34,7 +34,7 @@ import { validateLayout, } from './document-model'; import { SERVICE_DOC_TYPE } from './document-layouts'; -import { type ComputedSource, type HeaderField, PARTNER_COLUMNS, WAREHOUSE_LOOKUP, hasExpenses, hasFolio, hasSeries, headerKeysOf, keysOfFields, requiredHeaderOf } from './document-layouts'; +import { type ComputedSource, type GroupCell, type HeaderField, PARTNER_COLUMNS, WAREHOUSE_LOOKUP, hasExpenses, hasFolio, hasSeries, headerKeysOf, keysOfFields, 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'; @@ -447,6 +447,18 @@ export class DocumentFormPage { /** Tipo al que se quiere cambiar y que espera confirmación porque hay líneas escritas que se descartarían. */ protected readonly pendingType = signal(null); + /** La celda de un grupo de campos (el indicador) cuya lista de valores está abierta. */ + protected readonly groupCell = signal(null); + + protected groupPicked(rows: ListRow[]): void { + const cell = this.groupCell(); + const row = rows[0]; + if (cell?.kind === 'lookup' && row) { + this.setHeader(cell.key, row[cell.lookup.valueKey ?? cell.lookup.columns[0]?.key ?? ''] ?? ''); + } + this.groupCell.set(null); + } + /** Cambiar entre artículo y servicio cambia las columnas de línea: las líneas escritas no sirven y se pide permiso para descartarlas. */ private onDocType(type: string): void { if (type === text(this.header()['DocType'])) return; diff --git a/apps/web/src/app/features/documents/document-layouts.ts b/apps/web/src/app/features/documents/document-layouts.ts index 268afbc..8cff85e 100644 --- a/apps/web/src/app/features/documents/document-layouts.ts +++ b/apps/web/src/app/features/documents/document-layouts.ts @@ -6,6 +6,14 @@ import { INDICATOR_LOOKUP, PROJECT_LOOKUP } from './dimension-lookups'; import { SERIES_SUBTYPE } from './document-objects'; import { ANALYTIC_COLUMNS, DOCUMENT_LINE_COLUMNS, lineColumnsFor, lineFieldsOf, newDocumentLine, newServiceLine, recalcLine, serviceLineColumns, uomEntryOf } from './line-columns'; +/** Una celda de un grupo de campos: un texto (con ancho y largo máximo) o un código que se elige de una lista. */ +export type GroupCell = + | { kind: 'text'; key: string; label: string; width?: string; maxlength?: number } + | { kind: 'lookup'; key: string; label: string; lookup: VhLookup; width?: string }; + +/** Fila con varios campos juntos, para la cabecera de un documento. */ +export const fieldGroup = (label: string, cells: readonly GroupCell[]): HeaderField => ({ type: 'group', label, cells }); + /** Valores calculados o del socio que una fila de solo lectura puede mostrar. */ export type ComputedSource = 'docNum' | 'partnerName' | 'partnerTaxId' | 'subtotal' | 'expenses' | 'tax' | 'total' | 'downPayment' | 'applied' | 'balance'; @@ -39,6 +47,11 @@ export type HeaderField = | { type: 'partner'; key: string; label: string; required?: boolean; missing?: string } /** Prefijo y número del folio tributario en una misma fila (`FolioPrefixString` y `FolioNumber`). */ | { type: 'folio'; label: string; prefixKey: string; numberKey: string } + /** + * Varios campos juntos en una sola fila, en el orden dado (el indicador, el prefijo y el número del folio). Una empresa + * que los sustituye por campos de usuario arma su propio grupo con `fieldGroup(...)` y oculta el del base. + */ + | { type: 'group'; label: string; cells: readonly GroupCell[] } /** Serie de numeración y número del documento en una misma fila (`Series` y `DocNum`). */ | { type: 'series'; key: string; label: string } /** Flete: código del gasto adicional y su importe (`DocumentAdditionalExpenses`). */ @@ -139,6 +152,16 @@ export const FIELDS = { requester: { type: 'text', key: 'Requester', label: 'Solicitante (usuario)' }, } as const satisfies Record; +/** + * Indicador y folio tributario juntos y en ese orden (en el Service Layer `Indicator`, `FolioPrefixString` y + * `FolioNumber`). El número se edita como texto y se envía como número. + */ +export const IDENTITY_GROUP: HeaderField = fieldGroup('Indicador / Folio', [ + { kind: 'lookup', key: 'Indicator', label: 'Indicador', lookup: INDICATOR_LOOKUP, width: '80px' }, + { kind: 'text', key: 'FolioPrefixString', label: 'Prefijo del folio', width: '90px', maxlength: 10 }, + { kind: 'text', key: 'FolioNumber', label: 'Número de folio' }, +]); + export const commentsOnly: FieldColumns = { left: [FIELDS.comments], right: [] }; // --- columnas de línea compartidas ---------------------------------------------------------- @@ -237,6 +260,8 @@ export function marketingLayout(opts: { uom: 'sales' | 'purchase'; /** Fila del flete en el pie (por defecto sí). */ expenses?: boolean; + /** Indicador y folio juntos en una fila de la derecha (facturas y notas de crédito) en lugar del indicador suelto a la izquierda y el folio. */ + identity?: boolean; /** Facturas y notas de crédito: el pie suma el anticipo total, el importe aplicado y el saldo pendiente. */ billing?: boolean; /** Valores que se envían siempre al crear (ej. `DocumentSubType: 'bod_Bill'` en las boletas). */ @@ -254,11 +279,12 @@ export function marketingLayout(opts: { { type: 'computed', label: 'Nombre', source: 'partnerName' }, { type: 'computed', label: 'RUT', source: 'partnerTaxId' }, FIELDS.numAtCard, - FIELDS.indicator, + ...(opts.identity ? [] : [FIELDS.indicator]), FIELDS.project, ...(opts.left ?? []), ], - right: [FIELDS.docNumber, FIELDS.docDate, FIELDS.docDueDate, ...(opts.right ?? [])], + // las tres fechas juntas, una debajo de la otra: contabilización, vencimiento y del documento + right: [FIELDS.docNumber, FIELDS.docDate, FIELDS.docDueDate, FIELDS.taxDate, ...(opts.identity ? [IDENTITY_GROUP] : []), ...(opts.right ?? [])], }, footer: { left: [...(opts.expenses === false ? [] : [{ type: 'expenses', label: 'Gastos adicionales' } as const]), FIELDS.comments], @@ -277,7 +303,7 @@ export function marketingLayout(opts: { ], }, columns, - newHeader: () => ({ DocType: ITEMS_DOC_TYPE, DocDate: today(), DocDueDate: today(), DiscountPercent: 0, Rounding: 'tNO', RoundingDiffAmount: 0 }), + newHeader: () => ({ DocType: ITEMS_DOC_TYPE, DocDate: today(), DocDueDate: today(), TaxDate: today(), DiscountPercent: 0, Rounding: 'tNO', RoundingDiffAmount: 0 }), newLine: newDocumentLine, recalc: recalcLine, service: { @@ -309,6 +335,8 @@ export const keysOfFields = (fields: readonly HeaderField[]): string[] => return []; case 'folio': return [f.prefixKey, f.numberKey]; + case 'group': + return f.cells.map((c) => c.key); case 'expenses': return []; case 'rounding': @@ -337,4 +365,4 @@ export const hasSeries = (layout: DocumentLayout): boolean => allFields(layout). export const hasExpenses = (layout: DocumentLayout): boolean => allFields(layout).some((f) => f.type === 'expenses'); -export const hasFolio = (layout: DocumentLayout): boolean => allFields(layout).some((f) => f.type === 'folio'); +export const hasFolio = (layout: DocumentLayout): boolean => allFields(layout).some((f) => f.type === 'folio' || (f.type === 'group' && f.cells.some((c) => c.key === 'FolioNumber'))); diff --git a/apps/web/src/app/features/documents/screen-extensions.spec.ts b/apps/web/src/app/features/documents/screen-extensions.spec.ts index c0392d3..fbc82f4 100644 --- a/apps/web/src/app/features/documents/screen-extensions.spec.ts +++ b/apps/web/src/app/features/documents/screen-extensions.spec.ts @@ -5,7 +5,7 @@ import { ActivatedRoute, Router, convertToParamMap, provideRouter } from '@angul import { of } from 'rxjs'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { CLIENT_EXTENSIONS } from './client-extensions'; -import { headerKeysOf } from './document-layouts'; +import { fieldGroup, headerKeysOf } from './document-layouts'; import { lineFieldsOf } from './line-columns'; import { DocumentFormPage } from './document-form.page'; import { BASE_SCREENS, DOCUMENT_SCREENS } from './document-screens'; @@ -62,6 +62,44 @@ describe('extensiones por cliente', () => { }); }); +describe('indicador y folio juntos', () => { + it('la factura del base lleva Indicador, prefijo y número en una sola fila, en ese orden', () => { + const layout = by(BASE_SCREENS, '/ventas/facturas').layout; + const groups = layout.header.right.filter((f) => f.type === 'group'); + expect(groups).toHaveLength(1); + expect(groups[0].type === 'group' && groups[0].cells.map((c) => c.key)).toEqual(['Indicator', 'FolioPrefixString', 'FolioNumber']); + expect(layout.header.left.some((f) => 'key' in f && f.key === 'Indicator')).toBe(false); // ya no va suelto + }); + + it('lo llevan facturas, boletas, reservas y notas de crédito de ventas y compras', () => { + for (const path of ['/ventas/facturas', '/ventas/boletas', '/ventas/facturas-reserva', '/ventas/boletas-reserva', '/ventas/notas-credito', '/compras/facturas', '/compras/notas-credito']) { + expect(by(BASE_SCREENS, path).layout.header.right.some((f) => f.type === 'group'), path).toBe(true); + } + expect(by(BASE_SCREENS, '/ventas/ordenes').layout.header.right.some((f) => f.type === 'group')).toBe(false); + }); + + it('una empresa con campos de usuario los sustituye por su propio grupo, también juntos', () => { + const screens = applyExtensions(BASE_SCREENS, { + '/ventas/facturas': { + hideHeaderFields: ['Indicator'], // oculta el grupo completo del base + headerRight: [ + fieldGroup('Tipo / Serie / Número', [ + { kind: 'text', key: 'U_Tipo', label: 'Tipo', width: '60px' }, + { kind: 'text', key: 'U_Serie', label: 'Serie', width: '80px' }, + { kind: 'text', key: 'U_Numero', label: 'Número' }, + ]), + ], + }, + }); + const layout = by(screens, '/ventas/facturas').layout; + const groups = layout.header.right.filter((f) => f.type === 'group'); + expect(groups).toHaveLength(1); + expect(groups[0].type === 'group' && groups[0].cells.map((c) => c.key)).toEqual(['U_Tipo', 'U_Serie', 'U_Numero']); + expect(headerKeysOf(layout)).toEqual(expect.arrayContaining(['U_Tipo', 'U_Serie', 'U_Numero'])); + expect(headerKeysOf(layout)).not.toContain('FolioNumber'); + }); +}); + describe('formulario con campos de cliente', () => { let fixture: ComponentFixture; let ctrl: HttpTestingController; diff --git a/apps/web/src/app/features/documents/screen-kit.ts b/apps/web/src/app/features/documents/screen-kit.ts index 143be69..90bd2c7 100644 --- a/apps/web/src/app/features/documents/screen-kit.ts +++ b/apps/web/src/app/features/documents/screen-kit.ts @@ -148,6 +148,8 @@ export interface MarketingExtras { baseFilter?: string; /** Documento de cobro o pago (factura, nota de crédito): el pie muestra anticipo, importe aplicado y saldo pendiente. */ billing?: boolean; + /** Indicador y folio juntos en una fila (`IDENTITY_GROUP`). */ + identity?: boolean; /** Valores que se envían siempre al crear (ej. `DocumentSubType`). */ fixed?: DocRecord; /** Columnas de línea de este documento (de `LINE_COLUMNS`); sin ellas, todas. */ @@ -160,7 +162,7 @@ export interface MarketingExtras { export const TAX_DOCUMENT: MarketingExtras = { headerRight: [FIELDS.folio] }; /** Facturas y notas de crédito: documento tributario que además se cobra o se paga. */ -export const BILLING_DOCUMENT: MarketingExtras = { ...TAX_DOCUMENT, billing: true }; +export const BILLING_DOCUMENT: MarketingExtras = { billing: true, identity: true }; /** Ventas y compras: documentos con socio de negocio y total. */ export const marketing = ( @@ -181,6 +183,7 @@ export const marketing = ( lookups: extras.lookups, fixed: extras.fixed, billing: extras.billing, + identity: extras.identity, uom: path.startsWith('/ventas') ? 'sales' : 'purchase', }), resource, 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 85f8ae4..ff383a6 100644 --- a/apps/web/src/app/features/finance/journal-form.page.html +++ b/apps/web/src/app/features/finance/journal-form.page.html @@ -26,8 +26,11 @@
- - + + + @if (!saved()) { + + } @@ -76,3 +79,15 @@ } {{ saved() ? 'Cerrar' : 'Descartar' }} + +@if (indicatorOpen()) { + +} 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 043ef22..0621e7d 100644 --- a/apps/web/src/app/features/finance/journal-form.page.ts +++ b/apps/web/src/app/features/finance/journal-form.page.ts @@ -7,7 +7,9 @@ import { firstValueFrom, map } from 'rxjs'; import { API_URL, apiErrorMessage } from '../../core/api'; 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 { DateField, SboField, SelectField, TextField, TextInput } from '../../shared/form'; +import type { ListRow } from '../../shared/list/list.types'; +import { ValueHelp } from '../../shared/value-help/value-help'; 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'; @@ -57,7 +59,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, LookupField, RouterLink, RowsTable, SboField, SboWindow, SelectField, TextField, TextInput, Toolbar], + imports: [DateField, Icon, RouterLink, RowsTable, SboField, SboWindow, SelectField, TextField, TextInput, Toolbar, ValueHelp], templateUrl: './journal-form.page.html', styleUrl: './journal-form.page.css', }) @@ -103,6 +105,13 @@ export class JournalFormPage { protected readonly folioPrefix = linkedSignal(() => text(this.doc()?.['FolioPrefixString'])); protected readonly folioNumber = linkedSignal(() => text(this.doc()?.['FolioNumber'])); protected readonly indicatorLookup = INDICATOR_LOOKUP; + protected readonly indicatorOpen = signal(false); + + protected indicatorPicked(rows: ListRow[]): void { + const row = rows[0]; + if (row) this.indicator.set(String(row['IndicatorCode'] ?? '')); + this.indicatorOpen.set(false); + } protected readonly lines = linkedSignal(() => { const rows = ((this.doc()?.['JournalEntryLines'] as Row[] | undefined) ?? []).map((l) => ({ ...l })); return this.doc() ? rows : [{ DueDate: today() }, { DueDate: today() }];