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
@@ -152,6 +152,11 @@ export function copyTransferFromDocument(source: DocRecord, sourcePath: string,
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
header: {
|
header: {
|
||||||
|
CardCode: source['CardCode'] ?? '',
|
||||||
|
ContactPerson: source['ContactPerson'] ?? '',
|
||||||
|
ShipToCode: source['ShipToCode'] ?? '',
|
||||||
|
PriceList: source['PriceList'] ?? '',
|
||||||
|
SalesPersonCode: source['SalesPersonCode'] ?? '',
|
||||||
FromWarehouse: source['FromWarehouse'] ?? '',
|
FromWarehouse: source['FromWarehouse'] ?? '',
|
||||||
ToWarehouse: source['ToWarehouse'] ?? '',
|
ToWarehouse: source['ToWarehouse'] ?? '',
|
||||||
Project: source['Project'] ?? '',
|
Project: source['Project'] ?? '',
|
||||||
|
|||||||
@@ -63,12 +63,13 @@ describe('folio y RUT en los formularios', () => {
|
|||||||
|
|
||||||
beforeEach(() => TestBed.resetTestingModule());
|
beforeEach(() => TestBed.resetTestingModule());
|
||||||
|
|
||||||
it('lo llevan facturas, boletas, notas de crédito, entregas y recepciones (ventas y compras)', () => {
|
it('lo llevan facturas, boletas, notas de crédito, entregas y recepciones (ventas y compras) y las transferencias de stock', () => {
|
||||||
const withFolio = DOCUMENT_SCREENS.filter((s) => hasFolio(s.layout)).map((s) => s.path);
|
const withFolio = DOCUMENT_SCREENS.filter((s) => hasFolio(s.layout)).map((s) => s.path);
|
||||||
expect(withFolio.sort()).toEqual(
|
expect(withFolio.sort()).toEqual(
|
||||||
[
|
[
|
||||||
'/ventas/facturas', '/ventas/boletas', '/ventas/facturas-reserva', '/ventas/boletas-reserva', '/ventas/notas-credito', '/ventas/entrega',
|
'/ventas/facturas', '/ventas/boletas', '/ventas/facturas-reserva', '/ventas/boletas-reserva', '/ventas/notas-credito', '/ventas/entrega',
|
||||||
'/compras/facturas', '/compras/notas-credito', '/compras/recepcion',
|
'/compras/facturas', '/compras/notas-credito', '/compras/recepcion',
|
||||||
|
'/inventario/transferencias', '/inventario/solicitudes-traslado',
|
||||||
].sort(),
|
].sort(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -131,7 +131,7 @@
|
|||||||
[required]="!!f.required"
|
[required]="!!f.required"
|
||||||
[readonly]="readOnly() || locked(f)"
|
[readonly]="readOnly() || locked(f)"
|
||||||
[value]="str(header()[f.key])"
|
[value]="str(header()[f.key])"
|
||||||
(valueChange)="setHeader(f.key, $event)"
|
(valueChange)="setHeader(f.key, typedLookup(f, $event))"
|
||||||
[lookup]="f.lookup"
|
[lookup]="f.lookup"
|
||||||
resolveOnLeave
|
resolveOnLeave
|
||||||
/>
|
/>
|
||||||
@@ -248,6 +248,27 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@case ('contact') {
|
||||||
|
<sbo-select-field
|
||||||
|
[label]="f.label"
|
||||||
|
[readonly]="readOnly() || locked(f)"
|
||||||
|
[options]="contactOptions()"
|
||||||
|
placeholder=""
|
||||||
|
[value]="str(header()[f.key]) || null"
|
||||||
|
(valueChange)="setHeader(f.key, $event === null || $event === '' ? '' : +$event)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
@case ('address') {
|
||||||
|
<sbo-select-field
|
||||||
|
[label]="f.label"
|
||||||
|
[readonly]="readOnly() || locked(f)"
|
||||||
|
[options]="f.side === 'ship' ? shipOptions() : billOptions()"
|
||||||
|
placeholder=""
|
||||||
|
[value]="str(header()[f.key]) || null"
|
||||||
|
(valueChange)="setHeader(f.key, $event ?? '')"
|
||||||
|
/>
|
||||||
|
<sbo-textarea-field label="" [rows]="3" readonly [value]="addressText(f)" />
|
||||||
|
}
|
||||||
@case ('currency') {
|
@case ('currency') {
|
||||||
<sbo-field [label]="f.label">
|
<sbo-field [label]="f.label">
|
||||||
<sbo-select
|
<sbo-select
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { HttpErrorResponse, httpResource } from '@angular/common/http';
|
import { HttpErrorResponse, httpResource } from '@angular/common/http';
|
||||||
import { NgTemplateOutlet } from '@angular/common';
|
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 { toSignal } from '@angular/core/rxjs-interop';
|
||||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||||
import type { SlListResponse } from '@basic-erp/sap-types';
|
import type { SlListResponse } from '@basic-erp/sap-types';
|
||||||
@@ -34,7 +34,7 @@ import {
|
|||||||
validateLayout,
|
validateLayout,
|
||||||
} from './document-model';
|
} from './document-model';
|
||||||
import { SERVICE_DOC_TYPE } from './document-layouts';
|
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 { type CopyOrigin, copyFromDocument, copySources, copyTargets } from './document-copy';
|
||||||
import { DOCUMENT_SCREENS, type DocumentScreen, docNum, documentStatus, menuLabel } from './document-screens';
|
import { DOCUMENT_SCREENS, type DocumentScreen, docNum, documentStatus, menuLabel } from './document-screens';
|
||||||
import { OBJECT_TYPES } from './document-objects';
|
import { OBJECT_TYPES } from './document-objects';
|
||||||
@@ -392,6 +392,7 @@ export class DocumentFormPage {
|
|||||||
this.header.update((h) => ({ ...h, ...copied.header }));
|
this.header.update((h) => ({ ...h, ...copied.header }));
|
||||||
this.partnerName.set(text(doc['CardName']));
|
this.partnerName.set(text(doc['CardName']));
|
||||||
this.partnerTaxId.set(text(doc['FederalTaxID']));
|
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.lines.update((ls) => [...ls.filter((l) => !isBlankLine(l, this.serviceMode())), ...copied.lines]);
|
||||||
this.message.set({
|
this.message.set({
|
||||||
@@ -423,9 +424,10 @@ export class DocumentFormPage {
|
|||||||
columns: this.layout.lookups?.warehouse ?? WAREHOUSE_LOOKUP.columns,
|
columns: this.layout.lookups?.warehouse ?? WAREHOUSE_LOOKUP.columns,
|
||||||
};
|
};
|
||||||
protected readonly partnerLookup: VhLookup = {
|
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',
|
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',
|
orderby: 'CardCode',
|
||||||
columns: this.layout.lookups?.partner ?? PARTNER_COLUMNS,
|
columns: this.layout.lookups?.partner ?? PARTNER_COLUMNS,
|
||||||
extraFields: ['FederalTaxID', 'Currency'],
|
extraFields: ['FederalTaxID', 'Currency'],
|
||||||
@@ -473,6 +475,20 @@ export class DocumentFormPage {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (this.copyOrigin) void this.loadCopy(this.copyOrigin);
|
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 {
|
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. */
|
/** 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);
|
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. */
|
/** La celda de un grupo de campos (el indicador) cuya lista de valores está abierta. */
|
||||||
protected readonly groupCell = signal<GroupCell | null>(null);
|
protected readonly groupCell = signal<GroupCell | null>(null);
|
||||||
|
|
||||||
@@ -524,6 +579,9 @@ export class DocumentFormPage {
|
|||||||
this.setHeader('CardCode', code);
|
this.setHeader('CardCode', code);
|
||||||
this.partnerName.set('');
|
this.partnerName.set('');
|
||||||
this.partnerTaxId.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 {
|
protected notify(textMessage: string): void {
|
||||||
@@ -531,6 +589,7 @@ export class DocumentFormPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected onPartner(row: Record<string, unknown>): void {
|
protected onPartner(row: Record<string, unknown>): void {
|
||||||
|
this.partnerKey.set(text(row['CardCode']));
|
||||||
this.partnerName.set(text(row['CardName']));
|
this.partnerName.set(text(row['CardName']));
|
||||||
this.partnerTaxId.set(text(row['FederalTaxID']));
|
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
|
// el documento toma la moneda del socio (si es una sola y no es la local), como en SAP
|
||||||
|
|||||||
@@ -62,6 +62,13 @@ export type HeaderField =
|
|||||||
| { type: 'rounding'; label: string }
|
| { type: 'rounding'; label: string }
|
||||||
/** Moneda del documento (`DocCurrency`) y su tipo de cambio (`DocRate`): el de la fecha del documento si no es la local. */
|
/** Moneda del documento (`DocCurrency`) y su tipo de cambio (`DocRate`): el de la fecha del documento si no es la local. */
|
||||||
| { type: 'currency'; label: string }
|
| { type: 'currency'; label: string }
|
||||||
|
/** Persona de contacto del socio (`ContactPerson`): se elige de las del socio de negocio. */
|
||||||
|
| { type: 'contact'; key: string; label: string }
|
||||||
|
/**
|
||||||
|
* Dirección de envío (`ShipToCode`) o de facturación (`PayToCode`) del socio: se elige de sus direcciones y debajo se
|
||||||
|
* muestra su texto. `side` dice cuáles direcciones de la lista de direcciones del socio se ofrecen.
|
||||||
|
*/
|
||||||
|
| { type: 'address'; key: string; label: string; side: 'ship' | 'bill' }
|
||||||
| { type: 'computed'; label: string; source: ComputedSource };
|
| { type: 'computed'; label: string; source: ComputedSource };
|
||||||
|
|
||||||
/** Las listas de valores (ventanas modales) de un formulario. */
|
/** Las listas de valores (ventanas modales) de un formulario. */
|
||||||
@@ -122,7 +129,40 @@ export const today = (): string => todayIso();
|
|||||||
// --- catálogo de campos ---------------------------------------------------------------------
|
// --- catálogo de campos ---------------------------------------------------------------------
|
||||||
|
|
||||||
/** Catálogo de campos: cada pantalla elige cuáles lleva. */
|
/** Catálogo de campos: cada pantalla elige cuáles lleva. */
|
||||||
|
/** Lista de precios de un documento (`PriceList`): las números de SAP y, en negativo, los costos (-2 último precio de compra). */
|
||||||
|
export const PRICE_LIST_LOOKUP: VhLookup = {
|
||||||
|
title: 'Listas de precios',
|
||||||
|
resource: 'PriceLists',
|
||||||
|
orderby: 'PriceListNo',
|
||||||
|
valueType: 'number',
|
||||||
|
columns: [
|
||||||
|
{ key: 'PriceListNo', title: 'Código', width: '90px', mono: true },
|
||||||
|
{ key: 'PriceListName', title: 'Nombre' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Empleado de ventas o compras del documento (`SalesPersonCode`). */
|
||||||
|
export const SALES_PERSON_LOOKUP: VhLookup = {
|
||||||
|
title: 'Empleados de ventas',
|
||||||
|
resource: 'SalesPersons',
|
||||||
|
orderby: 'SalesEmployeeCode',
|
||||||
|
valueType: 'number',
|
||||||
|
baseFilter: `Active eq 'tYES'`,
|
||||||
|
columns: [
|
||||||
|
{ key: 'SalesEmployeeCode', title: 'Código', width: '90px', mono: true },
|
||||||
|
{ key: 'SalesEmployeeName', title: 'Nombre' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export const FIELDS = {
|
export const FIELDS = {
|
||||||
|
/** Persona de contacto del socio. */
|
||||||
|
contact: { type: 'contact', key: 'ContactPerson', label: 'Persona de contacto' },
|
||||||
|
/** Dirección de destino (envío) del socio. */
|
||||||
|
shipTo: { type: 'address', key: 'ShipToCode', label: 'Destino', side: 'ship' },
|
||||||
|
/** Dirección de facturación del socio. */
|
||||||
|
payTo: { type: 'address', key: 'PayToCode', label: 'Dirección de facturación', side: 'bill' },
|
||||||
|
priceList: { type: 'lookup', key: 'PriceList', label: 'Lista de precios', lookup: PRICE_LIST_LOOKUP },
|
||||||
|
salesPerson: { type: 'lookup', key: 'SalesPersonCode', label: 'Empleado del departamento', lookup: SALES_PERSON_LOOKUP },
|
||||||
/** Tipo del documento: de artículos o de servicio (las líneas cambian). */
|
/** Tipo del documento: de artículos o de servicio (las líneas cambian). */
|
||||||
docType: {
|
docType: {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
@@ -371,6 +411,8 @@ export const hasSeries = (layout: DocumentLayout): boolean => allFields(layout).
|
|||||||
|
|
||||||
export const hasExpenses = (layout: DocumentLayout): boolean => allFields(layout).some((f) => f.type === 'expenses');
|
export const hasExpenses = (layout: DocumentLayout): boolean => allFields(layout).some((f) => f.type === 'expenses');
|
||||||
|
|
||||||
|
export const hasPartnerData = (layout: DocumentLayout): boolean => allFields(layout).some((f) => f.type === 'contact' || f.type === 'address');
|
||||||
|
|
||||||
export const hasCurrency = (layout: DocumentLayout): boolean => allFields(layout).some((f) => f.type === 'currency');
|
export const hasCurrency = (layout: DocumentLayout): boolean => allFields(layout).some((f) => f.type === 'currency');
|
||||||
|
|
||||||
export const hasFolio = (layout: DocumentLayout): boolean => allFields(layout).some((f) => f.type === 'folio' || (f.type === 'group' && f.cells.some((c) => c.key === 'FolioNumber')));
|
export const hasFolio = (layout: DocumentLayout): boolean => allFields(layout).some((f) => f.type === 'folio' || (f.type === 'group' && f.cells.some((c) => c.key === 'FolioNumber')));
|
||||||
@@ -55,11 +55,21 @@ const transfer = (immutable: boolean): DocumentLayout => {
|
|||||||
lines: { key: 'StockTransferLines', fields: [...lineFieldsOf(columns), 'UoMEntry', ...(immutable ? DETAIL_KEYS : [])], base: true },
|
lines: { key: 'StockTransferLines', fields: [...lineFieldsOf(columns), 'UoMEntry', ...(immutable ? DETAIL_KEYS : [])], base: true },
|
||||||
// Las transferencias de stock indican lotes y series; la solicitud de traslado no.
|
// Las transferencias de stock indican lotes y series; la solicitud de traslado no.
|
||||||
...(immutable ? { details: LINE_DETAILS } : {}),
|
...(immutable ? { details: LINE_DETAILS } : {}),
|
||||||
|
// Como la ventana de SAP: socio de negocio con su persona de contacto, destino y lista de precios a la izquierda;
|
||||||
|
// número, fechas, folio y almacenes a la derecha.
|
||||||
header: {
|
header: {
|
||||||
left: [FIELDS.fromWarehouse, FIELDS.toWarehouse, FIELDS.indicator, FIELDS.project],
|
left: [
|
||||||
right: [FIELDS.docNumber, FIELDS.docDate, FIELDS.taxDate, FIELDS.dueDate, FIELDS.reference2, FIELDS.journalMemo],
|
{ type: 'partner', key: 'CardCode', label: 'Socio de negocios' },
|
||||||
|
{ type: 'computed', label: 'Nombre', source: 'partnerName' },
|
||||||
|
FIELDS.contact,
|
||||||
|
FIELDS.shipTo,
|
||||||
|
FIELDS.priceList,
|
||||||
|
FIELDS.indicator,
|
||||||
|
FIELDS.project,
|
||||||
|
],
|
||||||
|
right: [FIELDS.docNumber, FIELDS.docDate, FIELDS.taxDate, FIELDS.dueDate, FIELDS.reference2, FIELDS.folio, FIELDS.fromWarehouse, FIELDS.toWarehouse],
|
||||||
},
|
},
|
||||||
footer: commentsOnly,
|
footer: { left: [FIELDS.salesPerson, FIELDS.comments], right: [FIELDS.journalMemo] },
|
||||||
columns,
|
columns,
|
||||||
newHeader: () => ({ DocDate: today() }),
|
newHeader: () => ({ DocDate: today() }),
|
||||||
newLine: (header) => ({
|
newLine: (header) => ({
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 380 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 302 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 286 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 195 KiB |
Reference in new issue
Block a user