feat(web): dirección del documento desde AddressExtension (texto y corrección con «…») y campos de lista que muestran el código con formato (cuenta asociada)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c1f2cea1e1
commit
896fa25f2f
9 files changed
+342
-14
No files matched your search
@@ -9,7 +9,7 @@ import { API_URL, apiErrorMessage } from '../../core/api';
|
||||
import { formatNumber, todayIso } from '../../core/format';
|
||||
import { CompanySettingsService } from '../../core/company/company-settings';
|
||||
import { SlDocumentApi } from '../../core/sl/sl-document';
|
||||
import { CheckboxField, CheckboxInput, DateField, LookupField, NumberField, NumberInput, SboField, SelectField, SelectInput, TextField, TextInput, TextareaField } from '../../shared/form';
|
||||
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';
|
||||
@@ -35,6 +35,8 @@ import {
|
||||
} from './document-model';
|
||||
import { SERVICE_DOC_TYPE } from './document-layouts';
|
||||
import { type ComputedSource, type GroupCell, type HeaderField, PARTNER_COLUMNS, WAREHOUSE_LOOKUP, hasCurrency, hasExpenses, hasFolio, hasPartnerData, hasSeries, headerKeysOf, keysOfFields, requiredHeaderOf } from './document-layouts';
|
||||
import { type AddressSide, type AddressValues, addressFromExtension, addressFromPartner, composeAddress, toExtension } from './address-fields';
|
||||
import { AddressDialog } from './address-dialog';
|
||||
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';
|
||||
@@ -72,7 +74,7 @@ const folioAsNumber = (h: DocRecord): DocRecord => {
|
||||
@Component({
|
||||
selector: 'sbo-document-form-page',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [Tab, Tabs, CheckboxField, CheckboxInput, DateField, Icon, LinesGrid, LookupField, MenuButton, NgTemplateOutlet, NumberField, NumberInput, RouterLink, RowsTable, SboField, SelectField, SelectInput, SboWindow, TextField, TextInput, TextareaField, Toolbar, ValueHelp],
|
||||
imports: [AddressDialog, Tab, Tabs, CheckboxField, CheckboxInput, DateField, Icon, LinesGrid, LookupField, MenuButton, NgTemplateOutlet, NumberField, NumberInput, RouterLink, RowsTable, SboField, SelectField, SelectInput, SboWindow, TextField, TextInput, TextareaField, TextareaInput, Toolbar, ValueHelp],
|
||||
templateUrl: './document-form.page.html',
|
||||
styles: `
|
||||
h1 { margin: 0 0 6px; font-size: 18px; font-weight: 600; }
|
||||
@@ -296,9 +298,9 @@ export class DocumentFormPage {
|
||||
|
||||
protected readonly dirty = computed(() => {
|
||||
const d = this.doc();
|
||||
if (!d) return text(this.header()['CardCode']) !== '' || this.lines().some((l) => !isBlankLine(l, this.serviceMode())) || expensesPayload(this.expenses()).length > 0;
|
||||
if (!d) return text(this.header()['CardCode']) !== '' || this.lines().some((l) => !isBlankLine(l, this.serviceMode())) || expensesPayload(this.expenses()).length > 0 || Object.keys(this.addressEdits()).length > 0;
|
||||
const { body } = updatePayload(this.pick(d), this.header(), this.docLines(d), this.lines(), this.spec());
|
||||
return Object.keys(body).length > 0 || (this.hasExpenses && expensesChanged(d, this.expenses()));
|
||||
return Object.keys(body).length > 0 || (this.hasExpenses && expensesChanged(d, this.expenses())) || Object.keys(this.addressEdits()).length > 0;
|
||||
});
|
||||
|
||||
// --- totales -------------------------------------------------------------------------------
|
||||
@@ -501,6 +503,11 @@ export class DocumentFormPage {
|
||||
if (key === 'DocType' && this.layout.service) return this.onDocType(String(value));
|
||||
this.header.update((h) => ({ ...h, [key]: value }));
|
||||
this.message.set(null);
|
||||
// otra dirección elegida: las correcciones hechas a la anterior ya no valen
|
||||
if (key === 'ShipToCode' || key === 'PayToCode') {
|
||||
const side = key === 'ShipToCode' ? 'ShipTo' : 'BillTo';
|
||||
this.addressEdits.update((e) => Object.fromEntries(Object.entries(e).filter(([k]) => !k.startsWith(side))));
|
||||
}
|
||||
// otra fecha del documento, otro tipo de cambio
|
||||
if (key === 'DocDate' && this.isNew() && !this.isLocalCurrency()) void this.fetchRate(this.currencyValue());
|
||||
}
|
||||
@@ -538,13 +545,49 @@ export class DocumentFormPage {
|
||||
protected readonly shipOptions = computed(() => this.addressOptions('ship'));
|
||||
protected readonly billOptions = computed(() => this.addressOptions('bill'));
|
||||
|
||||
/** El texto de la dirección elegida: el que SAP guardó en el documento o, al elegir otra, el armado con los datos de la dirección del socio. */
|
||||
protected addressText(f: { key: string; side: 'ship' | 'bill' }): string {
|
||||
/** Correcciones del usuario a la dirección de este documento (`AddressExtension`, campos con prefijo `ShipTo`/`BillTo`). */
|
||||
protected readonly addressEdits = linkedSignal<Record<string, string>>(() => (this.doc(), {}));
|
||||
|
||||
private sideOf(f: { side: 'ship' | 'bill' }): AddressSide {
|
||||
return f.side === 'ship' ? 'ShipTo' : 'BillTo';
|
||||
}
|
||||
|
||||
/** Las partes de la dirección sin correcciones: las del documento guardado o, si se eligió otra, las de la dirección del socio. */
|
||||
private baseAddress(f: { key: string; side: 'ship' | 'bill' }): AddressValues {
|
||||
const code = text(this.header()[f.key]);
|
||||
const found = this.addresses().find((a) => text(a['AddressName']) === code && a['AddressType'] === (f.side === 'ship' ? 'bo_ShipTo' : 'bo_BillTo'));
|
||||
if (found) return [found['Street'], found['Block'], found['ZipCode'], found['City'], found['County'], found['State']].map(text).filter((p) => p !== '').join(', ');
|
||||
const saved = this.doc();
|
||||
if (saved && code === text(saved[f.key])) return addressFromExtension(saved['AddressExtension'] as Record<string, unknown> | undefined, this.sideOf(f));
|
||||
const type = f.side === 'ship' ? 'bo_ShipTo' : 'bo_BillTo';
|
||||
return addressFromPartner(this.addresses().find((a) => text(a['AddressName']) === code && a['AddressType'] === type));
|
||||
}
|
||||
|
||||
/** Las partes de la dirección con las correcciones del usuario encima. */
|
||||
protected addressValues(f: { key: string; side: 'ship' | 'bill' }): AddressValues {
|
||||
const side = this.sideOf(f);
|
||||
const values = this.baseAddress(f);
|
||||
for (const [key, value] of Object.entries(this.addressEdits())) if (key.startsWith(side)) values[key.slice(side.length)] = value;
|
||||
return values;
|
||||
}
|
||||
|
||||
/** El texto de la dirección elegida; si no hay datos de ella, el que SAP guardó en el documento. */
|
||||
protected addressText(f: { key: string; side: 'ship' | 'bill' }): string {
|
||||
const composed = composeAddress(this.addressValues(f));
|
||||
if (composed) return composed;
|
||||
const saved = this.doc()?.[f.side === 'ship' ? 'Address' : 'Address2'];
|
||||
return code === text(this.doc()?.[f.key]) ? text(saved).replace(/\r/g, '\n') : '';
|
||||
return text(this.header()[f.key]) === text(this.doc()?.[f.key]) ? text(saved).replace(/\r/g, '\n') : '';
|
||||
}
|
||||
|
||||
/** La ventana de corrección de la dirección abierta. */
|
||||
protected readonly addressDialog = signal<{ key: string; side: 'ship' | 'bill'; label: string } | null>(null);
|
||||
|
||||
/** Guarda lo corregido (solo lo que cambió respecto de la dirección elegida). */
|
||||
protected saveAddress(f: { key: string; side: 'ship' | 'bill' }, values: AddressValues): void {
|
||||
const base = this.baseAddress(f);
|
||||
const side = this.sideOf(f);
|
||||
const changed = Object.fromEntries(Object.entries(toExtension(values, side)).filter(([key, v]) => v !== (base[key.slice(side.length)] ?? '')));
|
||||
this.addressEdits.update((e) => ({ ...Object.fromEntries(Object.entries(e).filter(([k]) => !k.startsWith(side))), ...changed }));
|
||||
this.addressDialog.set(null);
|
||||
this.message.set(null);
|
||||
}
|
||||
|
||||
/** Un código numérico de SAP (lista de precios, empleado) que se escribe o elige como texto se guarda como número. */
|
||||
@@ -659,6 +702,7 @@ export class DocumentFormPage {
|
||||
}
|
||||
const body = createPayload(header, this.lines(), this.spec());
|
||||
if (this.hasExpenses && expensesPayload(this.expenses()).length) body[EXPENSES_COLLECTION] = expensesPayload(this.expenses());
|
||||
if (Object.keys(this.addressEdits()).length) body['AddressExtension'] = this.addressEdits();
|
||||
const created = await this.api.create(this.screen.resource, body);
|
||||
await this.router.navigate(['..', created['DocEntry']], {
|
||||
relativeTo: this.route,
|
||||
@@ -669,6 +713,7 @@ export class DocumentFormPage {
|
||||
const update = updatePayload(this.pick(d), this.header(), this.docLines(d), this.lines(), this.spec());
|
||||
const body = folioAsNumber(update.body);
|
||||
let replaceLines = update.replaceLines;
|
||||
if (Object.keys(this.addressEdits()).length) body['AddressExtension'] = this.addressEdits();
|
||||
if (this.hasExpenses && expensesChanged(d, this.expenses())) {
|
||||
body[EXPENSES_COLLECTION] = expensesPayload(this.expenses());
|
||||
replaceLines = true; // SAP reemplaza la colección entera
|
||||
|
||||
Reference in new issue
Block a user