feat(web): más campos en los maestros (vendedor, contabilidad, unidades, proveedor principal, valoración)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
abc1cc4ac6
commit
e853b1b76c
5 files changed
+120
-2
No files matched your search
@@ -64,6 +64,7 @@ export const ARTICULOS_SCREENS: readonly MasterScreen[] = [
|
|||||||
field({ type: 'checkbox', key: 'InventoryItem', label: 'Artículo de inventario' }),
|
field({ type: 'checkbox', key: 'InventoryItem', label: 'Artículo de inventario' }),
|
||||||
field({ type: 'checkbox', key: 'SalesItem', label: 'Artículo de venta' }),
|
field({ type: 'checkbox', key: 'SalesItem', label: 'Artículo de venta' }),
|
||||||
field({ type: 'checkbox', key: 'PurchaseItem', label: 'Artículo de compra' }),
|
field({ type: 'checkbox', key: 'PurchaseItem', label: 'Artículo de compra' }),
|
||||||
|
field({ type: 'checkbox', key: 'VatLiable', label: 'Sujeto a impuesto' }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -73,6 +74,27 @@ export const ARTICULOS_SCREENS: readonly MasterScreen[] = [
|
|||||||
field({ type: 'warehouse', key: 'DefaultWarehouse', label: 'Almacén por defecto' }),
|
field({ type: 'warehouse', key: 'DefaultWarehouse', label: 'Almacén por defecto' }),
|
||||||
field({ type: 'checkbox', key: 'ManageBatchNumbers', label: 'Gestionar por lotes' }),
|
field({ type: 'checkbox', key: 'ManageBatchNumbers', label: 'Gestionar por lotes' }),
|
||||||
field({ type: 'checkbox', key: 'ManageSerialNumbers', label: 'Gestionar por series' }),
|
field({ type: 'checkbox', key: 'ManageSerialNumbers', label: 'Gestionar por series' }),
|
||||||
|
field({
|
||||||
|
type: 'select',
|
||||||
|
key: 'CostAccountingMethod',
|
||||||
|
label: 'Método de valoración',
|
||||||
|
options: [
|
||||||
|
{ value: 'bis_MovingAverage', label: 'Promedio móvil' },
|
||||||
|
{ value: 'bis_Standard', label: 'Estándar' },
|
||||||
|
{ value: 'bis_FIFO', label: 'FIFO' },
|
||||||
|
{ value: 'bis_SNB', label: 'Por serie o lote' },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
field({
|
||||||
|
type: 'select',
|
||||||
|
key: 'GLMethod',
|
||||||
|
label: 'Cuentas contables por',
|
||||||
|
options: [
|
||||||
|
{ value: 'glm_WH', label: 'Almacén' },
|
||||||
|
{ value: 'glm_ItemClass', label: 'Grupo de artículos' },
|
||||||
|
{ value: 'glm_ItemLevel', label: 'Artículo' },
|
||||||
|
],
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
right: [
|
right: [
|
||||||
field({ type: 'number', key: 'QuantityOnStock', label: 'En stock', readonly: true }),
|
field({ type: 'number', key: 'QuantityOnStock', label: 'En stock', readonly: true }),
|
||||||
@@ -80,6 +102,21 @@ export const ARTICULOS_SCREENS: readonly MasterScreen[] = [
|
|||||||
field({ type: 'number', key: 'MaxInventory', label: 'Stock máximo' }),
|
field({ type: 'number', key: 'MaxInventory', label: 'Stock máximo' }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'ventas',
|
||||||
|
label: 'Ventas',
|
||||||
|
left: [field({ type: 'text', key: 'SalesUnit', label: 'Unidad de medida de venta' })],
|
||||||
|
right: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'compras',
|
||||||
|
label: 'Compras',
|
||||||
|
left: [
|
||||||
|
field({ type: 'lookup', key: 'Mainsupplier', label: 'Proveedor principal', lookup: MASTER_LOOKUPS.supplier }),
|
||||||
|
field({ type: 'text', key: 'PurchaseUnit', label: 'Unidad de medida de compra' }),
|
||||||
|
],
|
||||||
|
right: [],
|
||||||
|
},
|
||||||
{ id: 'observaciones', label: 'Observaciones', left: [field({ type: 'textarea', key: 'User_Text', label: 'Observaciones' })], right: [] },
|
{ id: 'observaciones', label: 'Observaciones', left: [field({ type: 'textarea', key: 'User_Text', label: 'Observaciones' })], right: [] },
|
||||||
],
|
],
|
||||||
newHeader: () => ({
|
newHeader: () => ({
|
||||||
@@ -89,6 +126,9 @@ export const ARTICULOS_SCREENS: readonly MasterScreen[] = [
|
|||||||
PurchaseItem: 'tYES',
|
PurchaseItem: 'tYES',
|
||||||
ManageBatchNumbers: 'tNO',
|
ManageBatchNumbers: 'tNO',
|
||||||
ManageSerialNumbers: 'tNO',
|
ManageSerialNumbers: 'tNO',
|
||||||
|
VatLiable: 'tYES',
|
||||||
|
CostAccountingMethod: 'bis_MovingAverage',
|
||||||
|
GLMethod: 'glm_ItemClass',
|
||||||
MinInventory: 0,
|
MinInventory: 0,
|
||||||
MaxInventory: 0,
|
MaxInventory: 0,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ describe('MasterFormPage', () => {
|
|||||||
it('muestra el código y el nombre, el estado y las pestañas con sus campos', () => {
|
it('muestra el código y el nombre, el estado y las pestañas con sus campos', () => {
|
||||||
expect(el().querySelector('.title')?.textContent).toContain('Cliente · C100');
|
expect(el().querySelector('.title')?.textContent).toContain('Cliente · C100');
|
||||||
expect(el().querySelector('.badge.tone-ok')?.textContent?.trim()).toBe('Activo');
|
expect(el().querySelector('.badge.tone-ok')?.textContent?.trim()).toBe('Activo');
|
||||||
expect([...el().querySelectorAll('[role=tab]')].map((t) => t.textContent?.trim())).toEqual(['General', 'Condiciones de pago', 'Observaciones']);
|
expect([...el().querySelectorAll('[role=tab]')].map((t) => t.textContent?.trim())).toEqual(['General', 'Condiciones de pago', 'Contabilidad', 'Observaciones']);
|
||||||
expect(labels()).toEqual(expect.arrayContaining(['Código', 'Nombre', 'RUT', 'Teléfono 1', 'Correo electrónico', 'Condición de pago', 'Límite de crédito']));
|
expect(labels()).toEqual(expect.arrayContaining(['Código', 'Nombre', 'RUT', 'Teléfono 1', 'Correo electrónico', 'Condición de pago', 'Límite de crédito']));
|
||||||
const values = [...el().querySelectorAll<HTMLInputElement>('input')].map((i) => i.value);
|
const values = [...el().querySelectorAll<HTMLInputElement>('input')].map((i) => i.value);
|
||||||
expect(values).toEqual(expect.arrayContaining(['C100', 'ACME Ltda', '76111222-3', 'ventas@acme.cl']));
|
expect(values).toEqual(expect.arrayContaining(['C100', 'ACME Ltda', '76111222-3', 'ventas@acme.cl']));
|
||||||
@@ -213,6 +213,7 @@ describe('MasterFormPage', () => {
|
|||||||
Frozen: 'tNO',
|
Frozen: 'tNO',
|
||||||
CreditLimit: 0,
|
CreditLimit: 0,
|
||||||
DiscountPercent: 0,
|
DiscountPercent: 0,
|
||||||
|
MaxCommitment: 0,
|
||||||
CardType: 'cCustomer',
|
CardType: 'cCustomer',
|
||||||
});
|
});
|
||||||
req.flush({ CardCode: 'C300' });
|
req.flush({ CardCode: 'C300' });
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { ListColumn } from '../../shared/list/list.types';
|
import type { ListColumn } from '../../shared/list/list.types';
|
||||||
import type { VhLookup } from '../../shared/value-help/value-help.types';
|
import type { VhLookup } from '../../shared/value-help/value-help.types';
|
||||||
|
import { ACCOUNT_LOOKUP } from '../documents/dimension-lookups';
|
||||||
|
|
||||||
const col = (key: string, title: string, extra: Partial<ListColumn> = {}): ListColumn => ({ key, title, ...extra });
|
const col = (key: string, title: string, extra: Partial<ListColumn> = {}): ListColumn => ({ key, title, ...extra });
|
||||||
|
|
||||||
@@ -34,6 +35,24 @@ export const MASTER_LOOKUPS = {
|
|||||||
valueType: 'number',
|
valueType: 'number',
|
||||||
columns: [col('PriceListNo', 'Código', { width: '90px', mono: true }), col('PriceListName', 'Nombre')],
|
columns: [col('PriceListNo', 'Código', { width: '90px', mono: true }), col('PriceListName', 'Nombre')],
|
||||||
} satisfies VhLookup,
|
} satisfies VhLookup,
|
||||||
|
salesPerson: {
|
||||||
|
title: 'Empleados de ventas',
|
||||||
|
resource: 'SalesPersons',
|
||||||
|
orderby: 'SalesEmployeeCode',
|
||||||
|
valueType: 'number',
|
||||||
|
baseFilter: `Active eq 'tYES'`,
|
||||||
|
columns: [col('SalesEmployeeCode', 'Código', { width: '90px', mono: true }), col('SalesEmployeeName', 'Nombre')],
|
||||||
|
} satisfies VhLookup,
|
||||||
|
/** Cuenta contable del socio de negocio (cuentas por cobrar o por pagar). */
|
||||||
|
account: ACCOUNT_LOOKUP,
|
||||||
|
/** Proveedor principal de un artículo. */
|
||||||
|
supplier: {
|
||||||
|
title: 'Proveedores',
|
||||||
|
resource: 'BusinessPartners',
|
||||||
|
orderby: 'CardCode',
|
||||||
|
baseFilter: `CardType eq 'cSupplier'`,
|
||||||
|
columns: [col('CardCode', 'Código', { width: '130px', mono: true }), col('CardName', 'Nombre')],
|
||||||
|
} satisfies VhLookup,
|
||||||
itemGroup: {
|
itemGroup: {
|
||||||
title: 'Grupos de artículos',
|
title: 'Grupos de artículos',
|
||||||
resource: 'ItemGroups',
|
resource: 'ItemGroups',
|
||||||
|
|||||||
@@ -111,3 +111,40 @@ describe('pantallas de maestros', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('más campos de los maestros', () => {
|
||||||
|
it('el cliente suma vendedor, tipo de empresa, compromiso máximo y los datos contables', () => {
|
||||||
|
const keys = masterKeysOf(by('/socios/clientes').layout);
|
||||||
|
expect(keys).toEqual(expect.arrayContaining(['SalesPersonCode', 'CompanyPrivate', 'MaxCommitment', 'DebitorAccount', 'AdditionalID', 'UnifiedFederalTaxID']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('la cuenta del socio se elige del plan de cuentas', () => {
|
||||||
|
const f = by('/socios/proveedores').layout.tabs.find((t) => t.id === 'contabilidad')!.left.find((x) => x.type === 'lookup' && x.key === 'DebitorAccount');
|
||||||
|
expect(f && f.type === 'lookup' && f.lookup.resource).toBe('ChartOfAccounts');
|
||||||
|
expect(f && f.type === 'lookup' && f.label).toBe('Cuenta de proveedor');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('el vendedor es un código numérico elegido de los empleados de ventas activos', () => {
|
||||||
|
const f = by('/socios/clientes').layout.tabs[0].right.find((x) => x.type === 'lookup' && x.key === 'SalesPersonCode');
|
||||||
|
expect(f && f.type === 'lookup' && f.lookup.valueType).toBe('number');
|
||||||
|
expect(f && f.type === 'lookup' && f.lookup.resource).toBe('SalesPersons');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('el artículo suma unidades, proveedor principal, método de valoración y si es sujeto a impuesto', () => {
|
||||||
|
const a = by('/inventario/articulos').layout;
|
||||||
|
expect(masterKeysOf(a)).toEqual(expect.arrayContaining(['SalesUnit', 'PurchaseUnit', 'Mainsupplier', 'CostAccountingMethod', 'GLMethod', 'VatLiable']));
|
||||||
|
expect(a.tabs.map((t) => t.id)).toEqual(['general', 'inventario', 'ventas', 'compras', 'observaciones']);
|
||||||
|
expect(a.newHeader()).toMatchObject({ VatLiable: 'tYES', CostAccountingMethod: 'bis_MovingAverage', GLMethod: 'glm_ItemClass' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('el método de valoración y el tipo de empresa ofrecen los valores del enumerado de SAP', () => {
|
||||||
|
const select = (path: string, key: string) =>
|
||||||
|
by(path).layout.tabs.flatMap((t) => [...t.left, ...t.right]).find((x) => x.type === 'select' && x.key === key);
|
||||||
|
const cost = select('/inventario/articulos', 'CostAccountingMethod');
|
||||||
|
expect(cost && cost.type === 'select' && cost.options.map((o) => o.value)).toEqual(['bis_MovingAverage', 'bis_Standard', 'bis_FIFO', 'bis_SNB']);
|
||||||
|
const gl = select('/inventario/articulos', 'GLMethod');
|
||||||
|
expect(gl && gl.type === 'select' && gl.options.map((o) => o.value)).toEqual(['glm_WH', 'glm_ItemClass', 'glm_ItemLevel']);
|
||||||
|
const type = select('/socios/clientes', 'CompanyPrivate');
|
||||||
|
expect(type && type.type === 'select' && type.options.map((o) => o.value)).toEqual(['cCompany', 'cPrivate', 'cGovernment']);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -66,6 +66,16 @@ const partner = (opts: { path: string; type: 'cCustomer' | 'cSupplier'; singular
|
|||||||
field({ type: 'text', key: 'CardForeignName', label: 'Nombre extranjero' }),
|
field({ type: 'text', key: 'CardForeignName', label: 'Nombre extranjero' }),
|
||||||
field({ type: 'lookup', key: 'GroupCode', label: 'Grupo', lookup: MASTER_LOOKUPS.partnerGroup(opts.type) }),
|
field({ type: 'lookup', key: 'GroupCode', label: 'Grupo', lookup: MASTER_LOOKUPS.partnerGroup(opts.type) }),
|
||||||
field({ type: 'lookup', key: 'Currency', label: 'Moneda', lookup: MASTER_LOOKUPS.currency }),
|
field({ type: 'lookup', key: 'Currency', label: 'Moneda', lookup: MASTER_LOOKUPS.currency }),
|
||||||
|
field({
|
||||||
|
type: 'select',
|
||||||
|
key: 'CompanyPrivate',
|
||||||
|
label: 'Tipo',
|
||||||
|
options: [
|
||||||
|
{ value: 'cCompany', label: 'Empresa' },
|
||||||
|
{ value: 'cPrivate', label: 'Persona natural' },
|
||||||
|
{ value: 'cGovernment', label: 'Gobierno' },
|
||||||
|
],
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
right: [
|
right: [
|
||||||
field({ type: 'text', key: 'Phone1', label: 'Teléfono 1' }),
|
field({ type: 'text', key: 'Phone1', label: 'Teléfono 1' }),
|
||||||
@@ -74,6 +84,7 @@ const partner = (opts: { path: string; type: 'cCustomer' | 'cSupplier'; singular
|
|||||||
field({ type: 'text', key: 'EmailAddress', label: 'Correo electrónico' }),
|
field({ type: 'text', key: 'EmailAddress', label: 'Correo electrónico' }),
|
||||||
field({ type: 'text', key: 'Website', label: 'Sitio web' }),
|
field({ type: 'text', key: 'Website', label: 'Sitio web' }),
|
||||||
field({ type: 'text', key: 'ContactPerson', label: 'Persona de contacto' }),
|
field({ type: 'text', key: 'ContactPerson', label: 'Persona de contacto' }),
|
||||||
|
field({ type: 'lookup', key: 'SalesPersonCode', label: 'Empleado de ventas', lookup: MASTER_LOOKUPS.salesPerson }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -85,11 +96,21 @@ const partner = (opts: { path: string; type: 'cCustomer' | 'cSupplier'; singular
|
|||||||
field({ type: 'number', key: 'CreditLimit', label: 'Límite de crédito', decimals: 2 }),
|
field({ type: 'number', key: 'CreditLimit', label: 'Límite de crédito', decimals: 2 }),
|
||||||
field({ type: 'number', key: 'DiscountPercent', label: 'Descuento %', decimals: 2 }),
|
field({ type: 'number', key: 'DiscountPercent', label: 'Descuento %', decimals: 2 }),
|
||||||
],
|
],
|
||||||
|
right: [field({ type: 'number', key: 'MaxCommitment', label: 'Compromiso máximo', decimals: 2 })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'contabilidad',
|
||||||
|
label: 'Contabilidad',
|
||||||
|
left: [
|
||||||
|
field({ type: 'lookup', key: 'DebitorAccount', label: opts.type === 'cSupplier' ? 'Cuenta de proveedor' : 'Cuenta de cliente', lookup: MASTER_LOOKUPS.account }),
|
||||||
|
field({ type: 'text', key: 'AdditionalID', label: 'Identificación adicional' }),
|
||||||
|
field({ type: 'text', key: 'UnifiedFederalTaxID', label: 'RUT unificado' }),
|
||||||
|
],
|
||||||
right: [],
|
right: [],
|
||||||
},
|
},
|
||||||
{ id: 'observaciones', label: 'Observaciones', left: [field({ type: 'textarea', key: 'Notes', label: 'Observaciones' })], right: [] },
|
{ id: 'observaciones', label: 'Observaciones', left: [field({ type: 'textarea', key: 'Notes', label: 'Observaciones' })], right: [] },
|
||||||
],
|
],
|
||||||
newHeader: () => ({ Frozen: 'tNO', CreditLimit: 0, DiscountPercent: 0 }),
|
newHeader: () => ({ Frozen: 'tNO', CreditLimit: 0, DiscountPercent: 0, MaxCommitment: 0 }),
|
||||||
fixed: { CardType: opts.type },
|
fixed: { CardType: opts.type },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user