feat(web): transferencias de stock y solicitudes de traslado con socio de negocio, persona de contacto, destino, lista de precios y empleado
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b85383dda1
commit
c002c2cf3e
10 files changed
+147
-9
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { HttpErrorResponse, httpResource } from '@angular/common/http';
|
||||
import { NgTemplateOutlet } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, Component, computed, inject, linkedSignal, signal } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, computed, effect, inject, linkedSignal, signal, untracked } from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import type { SlListResponse } from '@basic-erp/sap-types';
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
validateLayout,
|
||||
} from './document-model';
|
||||
import { SERVICE_DOC_TYPE } from './document-layouts';
|
||||
import { type ComputedSource, type GroupCell, type HeaderField, PARTNER_COLUMNS, WAREHOUSE_LOOKUP, hasCurrency, hasExpenses, hasFolio, hasSeries, headerKeysOf, keysOfFields, requiredHeaderOf } 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 CopyOrigin, copyFromDocument, copySources, copyTargets } from './document-copy';
|
||||
import { DOCUMENT_SCREENS, type DocumentScreen, docNum, documentStatus, menuLabel } from './document-screens';
|
||||
import { OBJECT_TYPES } from './document-objects';
|
||||
@@ -392,6 +392,7 @@ export class DocumentFormPage {
|
||||
this.header.update((h) => ({ ...h, ...copied.header }));
|
||||
this.partnerName.set(text(doc['CardName']));
|
||||
this.partnerTaxId.set(text(doc['FederalTaxID']));
|
||||
this.partnerKey.set(text(copied.header['CardCode']));
|
||||
}
|
||||
this.lines.update((ls) => [...ls.filter((l) => !isBlankLine(l, this.serviceMode())), ...copied.lines]);
|
||||
this.message.set({
|
||||
@@ -423,9 +424,10 @@ export class DocumentFormPage {
|
||||
columns: this.layout.lookups?.warehouse ?? WAREHOUSE_LOOKUP.columns,
|
||||
};
|
||||
protected readonly partnerLookup: VhLookup = {
|
||||
title: this.partner?.type === 'cSupplier' ? 'Proveedores' : 'Clientes',
|
||||
title: !this.partner ? 'Socios de negocio' : this.partner.type === 'cSupplier' ? 'Proveedores' : 'Clientes',
|
||||
resource: 'BusinessPartners',
|
||||
baseFilter: `CardType eq '${this.partner?.type ?? 'cCustomer'}'`,
|
||||
// un documento con socio de un solo tipo lo filtra; los demás (transferencias de stock) admiten cualquiera
|
||||
...(this.partner ? { baseFilter: `CardType eq '${this.partner.type}'` } : {}),
|
||||
orderby: 'CardCode',
|
||||
columns: this.layout.lookups?.partner ?? PARTNER_COLUMNS,
|
||||
extraFields: ['FederalTaxID', 'Currency'],
|
||||
@@ -473,6 +475,20 @@ export class DocumentFormPage {
|
||||
|
||||
constructor() {
|
||||
if (this.copyOrigin) void this.loadCopy(this.copyOrigin);
|
||||
// al cargarse los datos del socio de un documento nuevo, sus direcciones por defecto
|
||||
effect(() => {
|
||||
const data = this.partnerData();
|
||||
if (!data || !this.isNew()) return;
|
||||
untracked(() => {
|
||||
const ship = text(data['ShipToDefault']);
|
||||
const bill = text(data['BilltoDefault']);
|
||||
this.header.update((h) => ({
|
||||
...h,
|
||||
...('ShipToCode' in h && text(h['ShipToCode']) === '' && ship ? { ShipToCode: ship } : {}),
|
||||
...('PayToCode' in h && text(h['PayToCode']) === '' && bill ? { PayToCode: bill } : {}),
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected setHeader(key: string, value: unknown): void {
|
||||
@@ -486,6 +502,45 @@ 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);
|
||||
|
||||
// --- datos del socio: personas de contacto y direcciones ---------------------------------------
|
||||
|
||||
private readonly hasPartnerData = hasPartnerData(this.layout);
|
||||
/** Código del socio cuyos datos se cargan: el del documento abierto o el que se acaba de elegir. */
|
||||
protected readonly partnerKey = linkedSignal(() => text(this.doc()?.['CardCode']));
|
||||
private readonly partnerDetail = httpResource<DocRecord>(() => {
|
||||
const key = this.partnerKey();
|
||||
return key && this.hasPartnerData
|
||||
? { url: `${API_URL}/sl/BusinessPartners/${encodeURIComponent(key)}`, params: { $select: 'CardCode,BPAddresses,ContactEmployees,ShipToDefault,BilltoDefault' } }
|
||||
: undefined;
|
||||
});
|
||||
private readonly partnerData = computed(() => (this.partnerDetail.hasValue() ? this.partnerDetail.value() : undefined));
|
||||
private readonly addresses = computed(() => (this.partnerData()?.['BPAddresses'] as DocRecord[] | undefined) ?? []);
|
||||
|
||||
protected readonly contactOptions = computed(() =>
|
||||
((this.partnerData()?.['ContactEmployees'] as DocRecord[] | undefined) ?? []).map((c) => ({ value: Number(c['InternalCode']), label: text(c['Name']) })),
|
||||
);
|
||||
/** Direcciones del socio de ese lado (envío o facturación), por su nombre. */
|
||||
protected readonly addressOptions = (side: 'ship' | 'bill') => {
|
||||
const type = side === 'ship' ? 'bo_ShipTo' : 'bo_BillTo';
|
||||
return this.addresses().filter((a) => a['AddressType'] === type).map((a) => ({ value: text(a['AddressName']), label: text(a['AddressName']) }));
|
||||
};
|
||||
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 {
|
||||
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()?.[f.side === 'ship' ? 'Address' : 'Address2'];
|
||||
return code === text(this.doc()?.[f.key]) ? text(saved).replace(/\r/g, '\n') : '';
|
||||
}
|
||||
|
||||
/** Un código numérico de SAP (lista de precios, empleado) que se escribe o elige como texto se guarda como número. */
|
||||
protected typedLookup(f: { lookup: { valueType?: string } }, value: string): string | number {
|
||||
return f.lookup.valueType === 'number' && /^-?\d+$/.test(value.trim()) ? Number(value) : value;
|
||||
}
|
||||
|
||||
/** La celda de un grupo de campos (el indicador) cuya lista de valores está abierta. */
|
||||
protected readonly groupCell = signal<GroupCell | null>(null);
|
||||
|
||||
@@ -524,6 +579,9 @@ export class DocumentFormPage {
|
||||
this.setHeader('CardCode', code);
|
||||
this.partnerName.set('');
|
||||
this.partnerTaxId.set('');
|
||||
this.partnerKey.set('');
|
||||
// la persona de contacto y las direcciones eran del socio anterior
|
||||
this.header.update((h) => ({ ...h, ContactPerson: '', ShipToCode: '', PayToCode: '' }));
|
||||
}
|
||||
|
||||
protected notify(textMessage: string): void {
|
||||
@@ -531,6 +589,7 @@ export class DocumentFormPage {
|
||||
}
|
||||
|
||||
protected onPartner(row: Record<string, unknown>): void {
|
||||
this.partnerKey.set(text(row['CardCode']));
|
||||
this.partnerName.set(text(row['CardName']));
|
||||
this.partnerTaxId.set(text(row['FederalTaxID']));
|
||||
// el documento toma la moneda del socio (si es una sola y no es la local), como en SAP
|
||||
|
||||
Reference in new issue
Block a user