feat(web): la cuenta asociada de los documentos de ventas ofrece solo las de activo y la de compras solo las de pasivo (listas locales en la ventana de selección)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
896fa25f2f
commit
57424432f9
12 files changed
+264
-8
No files matched your search
@@ -154,7 +154,7 @@
|
||||
[readonly]="readOnly() || locked(f)"
|
||||
[value]="str(header()[f.key])"
|
||||
(valueChange)="setHeader(f.key, typedLookup(f, $event))"
|
||||
[lookup]="f.lookup"
|
||||
[lookup]="lookupOf(f)"
|
||||
resolveOnLeave
|
||||
(picked)="pickedLookup(f, $event)"
|
||||
/>
|
||||
|
||||
@@ -8,11 +8,12 @@ import { map } from 'rxjs';
|
||||
import { API_URL, apiErrorMessage } from '../../core/api';
|
||||
import { formatNumber, todayIso } from '../../core/format';
|
||||
import { CompanySettingsService } from '../../core/company/company-settings';
|
||||
import { ControlAccountsService } from '../../core/sl/control-accounts';
|
||||
import { SlDocumentApi } from '../../core/sl/sl-document';
|
||||
import { CheckboxField, CheckboxInput, DateField, LookupField, NumberField, NumberInput, SboField, SelectField, SelectInput, TextField, TextInput, TextareaField, TextareaInput } from '../../shared/form';
|
||||
import type { GridLine, GridMultiAdd } from '../../shared/grid/grid.types';
|
||||
import { LinesGrid } from '../../shared/grid/lines-grid';
|
||||
import type { VhLookup } from '../../shared/value-help/value-help.types';
|
||||
import type { VhLookup, VhRow } from '../../shared/value-help/value-help.types';
|
||||
import { Icon, MenuButton, type MenuItem, SboWindow, Tab, Tabs, Toolbar, type StatusMessage, type WindowBadge } from '../../shared/window';
|
||||
import { ValueHelp } from '../../shared/value-help/value-help';
|
||||
import type { ListRow } from '../../shared/list/list.types';
|
||||
@@ -477,6 +478,7 @@ export class DocumentFormPage {
|
||||
|
||||
constructor() {
|
||||
if (this.copyOrigin) void this.loadCopy(this.copyOrigin);
|
||||
if (this.headerKeys.includes('ControlAccount')) void this.loadControlRows();
|
||||
// al cargarse los datos del socio de un documento nuevo, sus direcciones por defecto
|
||||
effect(() => {
|
||||
const data = this.partnerData();
|
||||
@@ -515,6 +517,30 @@ 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<string | null>(null);
|
||||
|
||||
// --- cuenta asociada: de activo en ventas y de pasivo en compras ------------------------------------
|
||||
|
||||
/**
|
||||
* Cuentas asociadas del grupo que corresponde al documento: las de activo en ventas (cuentas por cobrar) y las de
|
||||
* pasivo en compras (por pagar). `null` mientras se calculan; `undefined` si no se pudieron calcular (se ofrecen todas).
|
||||
*/
|
||||
protected readonly controlRows = signal<VhRow[] | null | undefined>(null);
|
||||
private readonly controlAccounts = inject(ControlAccountsService);
|
||||
|
||||
private async loadControlRows(): Promise<void> {
|
||||
try {
|
||||
this.controlRows.set(await this.controlAccounts.rows(this.partner?.type === 'cSupplier' ? 'liabilities' : 'assets'));
|
||||
} catch {
|
||||
this.controlRows.set(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
/** La lista de un campo de cabecera; la de la cuenta asociada lleva solo las cuentas de su grupo. */
|
||||
protected lookupOf(f: { key: string; lookup: VhLookup }): VhLookup {
|
||||
if (f.key !== 'ControlAccount') return f.lookup;
|
||||
const rows = this.controlRows();
|
||||
return rows === undefined ? f.lookup : { ...f.lookup, rows: rows ?? [] };
|
||||
}
|
||||
|
||||
/** Al elegir de una lista de la cabecera, los campos que esa elección completa (el solicitante trae su nombre, correo y departamento). */
|
||||
protected pickedLookup(f: { onPick?: (row: Record<string, unknown>) => Record<string, unknown> }, row: Record<string, unknown>): void {
|
||||
if (f.onPick) this.header.update((h) => ({ ...h, ...f.onPick!(row) }));
|
||||
|
||||
@@ -4,6 +4,7 @@ import { type ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRoute, Router, convertToParamMap, provideRouter } from '@angular/router';
|
||||
import { of } from 'rxjs';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ControlAccountsService } from '../../core/sl/control-accounts';
|
||||
import { DocumentFormPage } from './document-form.page';
|
||||
import { DOCUMENT_SCREENS } from './document-screens';
|
||||
|
||||
@@ -22,6 +23,11 @@ const PARTNER = {
|
||||
],
|
||||
};
|
||||
|
||||
const CONTROL_ROWS = {
|
||||
assets: [{ Code: '_A1', FormatCode: '1212111010', Name: 'Por cobrar' }],
|
||||
liabilities: [{ Code: '_P1', FormatCode: '4212111010', Name: 'Por pagar' }, { Code: '_P2', FormatCode: '4222111010', Name: 'Anticipos recibidos' }],
|
||||
};
|
||||
|
||||
describe('pestañas Contenido, Logística y Finanzas', () => {
|
||||
let fixture: ComponentFixture<DocumentFormPage>;
|
||||
let ctrl: HttpTestingController;
|
||||
@@ -39,7 +45,13 @@ describe('pestañas Contenido, Logística y Finanzas', () => {
|
||||
const route = { snapshot: { data: { screen, title: 'Documento' }, paramMap: convertToParamMap({}) }, paramMap: of(convertToParamMap({})) };
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DocumentFormPage],
|
||||
providers: [provideRouter([]), provideHttpClient(), provideHttpClientTesting(), { provide: ActivatedRoute, useValue: route }],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
{ provide: ActivatedRoute, useValue: route },
|
||||
{ provide: ControlAccountsService, useValue: { rows: async (g: 'assets' | 'liabilities') => CONTROL_ROWS[g] } },
|
||||
],
|
||||
}).compileComponents();
|
||||
ctrl = TestBed.inject(HttpTestingController);
|
||||
vi.spyOn(TestBed.inject(Router), 'navigate').mockResolvedValue(true);
|
||||
@@ -74,6 +86,20 @@ describe('pestañas Contenido, Logística y Finanzas', () => {
|
||||
expect(sales.lookup.baseFilter).toContain('boptIncoming');
|
||||
});
|
||||
|
||||
it('la cuenta asociada de ventas ofrece solo las de activo y la de compras solo las de pasivo', async () => {
|
||||
await open('Orders');
|
||||
await tick();
|
||||
const field = () => page().layout.tabs.finance.left.find((f: { key?: string }) => f.key === 'ControlAccount');
|
||||
expect(page().lookupOf(field()).rows.map((r: { Code: string }) => r.Code)).toEqual(['_A1']);
|
||||
TestBed.resetTestingModule();
|
||||
await open('PurchaseOrders');
|
||||
await tick();
|
||||
expect(page().lookupOf(field()).rows.map((r: { Code: string }) => r.Code)).toEqual(['_P1', '_P2']);
|
||||
// los demás campos de lista no cambian
|
||||
const terms = page().layout.tabs.finance.left.find((f: { key?: string }) => f.key === 'PaymentGroupCode');
|
||||
expect(page().lookupOf(terms).rows).toBeUndefined();
|
||||
});
|
||||
|
||||
it('el proyecto pasó de la cabecera a Finanzas y la persona de contacto está bajo el nombre del socio', async () => {
|
||||
await open('Orders');
|
||||
const left = page().layout.header.left as { type: string; key?: string }[];
|
||||
|
||||
Reference in new issue
Block a user