feat: módulo Finanzas (plan de cuentas y asientos contables con cancelación)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c9d1a714c9
commit
8bbd05d07b
23 files changed
+708
-29
No files matched your search
@@ -0,0 +1,8 @@
|
||||
import type { MasterExtensions } from '../master-extensions';
|
||||
|
||||
/**
|
||||
* Campos que este cliente agrega al plan de cuentas. En `main` va vacío: el proyecto base solo lleva campos
|
||||
* estándar de SAP. La rama `cliente/<nombre>` lo completa, por ruta de pantalla (`/finanzas/plan-de-cuentas`),
|
||||
* igual que las extensiones de artículos y de socios de negocio.
|
||||
*/
|
||||
export const FINANZAS_EXTENSIONS: MasterExtensions = {};
|
||||
@@ -0,0 +1,87 @@
|
||||
import type { SelectFilter } from '../../../core/sl/list-filters';
|
||||
import type { HeaderField } from '../../documents/document-layouts';
|
||||
import { ACCOUNT_LOOKUP } from '../../documents/dimension-lookups';
|
||||
import { MASTER_LOOKUPS } from '../master-lookups';
|
||||
import type { MasterScreen } from '../master-types';
|
||||
|
||||
const field = (f: HeaderField): HeaderField => f;
|
||||
|
||||
/** Tipos de cuenta de SAP (`BoAccountTypes`). */
|
||||
const ACCOUNT_TYPES = [
|
||||
{ value: 'at_Other', label: 'Otras' },
|
||||
{ value: 'at_Revenues', label: 'Ingresos' },
|
||||
{ value: 'at_Expenses', label: 'Gastos' },
|
||||
];
|
||||
|
||||
/** Una cuenta de título agrupa a otras y no recibe asientos; la de movimiento (`ActiveAccount`) sí. */
|
||||
const postableFilter: SelectFilter = {
|
||||
type: 'select',
|
||||
key: 'ActiveAccount',
|
||||
label: 'Clase',
|
||||
options: [
|
||||
{ value: 'tYES', label: 'De movimiento' },
|
||||
{ value: 'tNO', label: 'De título' },
|
||||
],
|
||||
};
|
||||
|
||||
/** Cuentas de título: las únicas que pueden ser la cuenta padre de otra. */
|
||||
const TITLE_ACCOUNTS = { ...ACCOUNT_LOOKUP, title: 'Cuentas de título', baseFilter: `ActiveAccount eq 'tNO'` };
|
||||
|
||||
/** Plan de cuentas (`ChartOfAccounts`): solo campos estándar de SAP. */
|
||||
export const FINANZAS_SCREENS: readonly MasterScreen[] = [
|
||||
{
|
||||
path: '/finanzas/plan-de-cuentas',
|
||||
resource: 'ChartOfAccounts',
|
||||
keyField: 'Code',
|
||||
singular: 'Cuenta',
|
||||
createLabel: 'Nueva cuenta',
|
||||
orderby: 'Code',
|
||||
extraFields: ['ActiveAccount'],
|
||||
columns: [
|
||||
{ key: 'Code', title: 'Código', width: '170px', mono: true, searchable: true },
|
||||
{ key: 'Name', title: 'Nombre', searchable: true },
|
||||
{ key: 'AccountLevel', title: 'Nivel', type: 'number', align: 'left', width: '70px' },
|
||||
{
|
||||
key: 'AccountType',
|
||||
title: 'Tipo',
|
||||
width: '100px',
|
||||
format: (r) => ACCOUNT_TYPES.find((t) => t.value === r['AccountType'])?.label ?? String(r['AccountType'] ?? ''),
|
||||
},
|
||||
{ key: 'Balance', title: 'Saldo', type: 'number', decimals: 2, width: '140px' },
|
||||
{
|
||||
key: 'ActiveAccount',
|
||||
title: 'Clase',
|
||||
width: '120px',
|
||||
format: (r) => (r['ActiveAccount'] === 'tYES' ? 'De movimiento' : 'De título'),
|
||||
tone: (r) => (r['ActiveAccount'] === 'tYES' ? 'ok' : 'muted'),
|
||||
},
|
||||
],
|
||||
filters: [postableFilter, { type: 'select', key: 'AccountType', label: 'Tipo', options: ACCOUNT_TYPES }],
|
||||
layout: {
|
||||
header: {
|
||||
left: [
|
||||
field({ type: 'text', key: 'Code', label: 'Código', immutable: true, required: true, missing: 'el código' }),
|
||||
field({ type: 'text', key: 'Name', label: 'Nombre', required: true, missing: 'el nombre' }),
|
||||
],
|
||||
right: [field({ type: 'checkbox', key: 'ActiveAccount', label: 'Cuenta de movimiento', immutable: true })],
|
||||
},
|
||||
tabs: [
|
||||
{
|
||||
id: 'general',
|
||||
label: 'General',
|
||||
left: [
|
||||
field({ type: 'lookup', key: 'FatherAccountKey', label: 'Cuenta padre', lookup: TITLE_ACCOUNTS, immutable: true }),
|
||||
field({ type: 'select', key: 'AccountType', label: 'Tipo de cuenta', options: ACCOUNT_TYPES }),
|
||||
field({ type: 'lookup', key: 'AcctCurrency', label: 'Moneda', lookup: MASTER_LOOKUPS.currency }),
|
||||
],
|
||||
right: [
|
||||
field({ type: 'checkbox', key: 'CashAccount', label: 'Cuenta de caja' }),
|
||||
field({ type: 'number', key: 'AccountLevel', label: 'Nivel', readonly: true }),
|
||||
field({ type: 'number', key: 'Balance', label: 'Saldo', decimals: 2, readonly: true }),
|
||||
],
|
||||
},
|
||||
],
|
||||
newHeader: () => ({ ActiveAccount: 'tYES', AccountType: 'at_Other', CashAccount: 'tNO' }),
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -108,8 +108,8 @@ export class MasterField {
|
||||
});
|
||||
protected readonly ro = computed(() => {
|
||||
const f = this.field();
|
||||
if (f.type === 'text') return f.readonly || (f.immutable && this.saved());
|
||||
return 'readonly' in f ? f.readonly : false;
|
||||
if ('immutable' in f && f.immutable && this.saved()) return true;
|
||||
return 'readonly' in f ? !!f.readonly : false;
|
||||
});
|
||||
protected readonly lookup = computed(() => {
|
||||
const f = this.field();
|
||||
|
||||
@@ -20,7 +20,7 @@ export const masterRequiredOf = (layout: MasterLayout): { key: string; label: st
|
||||
|
||||
/** Campos que no se pueden cambiar una vez creado el registro (el código). */
|
||||
export const masterImmutableOf = (layout: MasterLayout): ReadonlySet<string> =>
|
||||
new Set(masterFieldsOf(layout).flatMap((f) => (f.type === 'text' && f.immutable ? [f.key] : [])));
|
||||
new Set(masterFieldsOf(layout).flatMap((f) => ('immutable' in f && f.immutable ? [f.key] : [])));
|
||||
|
||||
/** Valores editables de un registro de SAP (los vacíos quedan como texto vacío). */
|
||||
export function pickMaster(doc: DocRecord | undefined, keys: readonly string[], blank: DocRecord): DocRecord {
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
* configuración completa (`master-screens.ts`) se carga en diferido al abrir una. Un test comprueba que
|
||||
* coincide con `MASTER_SCREENS`.
|
||||
*/
|
||||
export const MASTER_PATHS: readonly string[] = ['/socios/clientes', '/socios/proveedores', '/inventario/articulos'];
|
||||
export const MASTER_PATHS: readonly string[] = ['/socios/clientes', '/socios/proveedores', '/inventario/articulos', '/finanzas/plan-de-cuentas'];
|
||||
@@ -1,3 +1,5 @@
|
||||
import { FINANZAS_EXTENSIONS } from './finanzas/finanzas.extensions';
|
||||
import { FINANZAS_SCREENS } from './finanzas/finanzas.screens';
|
||||
import { ARTICULOS_SCREENS } from './articulos/articulos.screens';
|
||||
import { ARTICULOS_EXTENSIONS } from './articulos/articulos.extensions';
|
||||
import { type MasterExtensions, applyMasterExtensions } from './master-extensions';
|
||||
@@ -6,7 +8,7 @@ import { SOCIOS_SCREENS } from './socios-negocio/socios.screens';
|
||||
import type { MasterScreen } from './master-types';
|
||||
|
||||
/** Las pantallas de maestros del proyecto base, agrupadas por módulo (una carpeta cada uno). */
|
||||
export const BASE_MASTER_SCREENS: readonly MasterScreen[] = [...SOCIOS_SCREENS, ...ARTICULOS_SCREENS];
|
||||
export const BASE_MASTER_SCREENS: readonly MasterScreen[] = [...SOCIOS_SCREENS, ...ARTICULOS_SCREENS, ...FINANZAS_SCREENS];
|
||||
|
||||
/** Cada módulo solo puede extender sus propias pantallas: una ruta ajena es un error, no un descuido silencioso. */
|
||||
function ownedBy(screens: readonly MasterScreen[], extensions: MasterExtensions): MasterExtensions {
|
||||
@@ -20,6 +22,7 @@ function ownedBy(screens: readonly MasterScreen[], extensions: MasterExtensions)
|
||||
export const MASTER_EXTENSIONS: MasterExtensions = {
|
||||
...ownedBy(SOCIOS_SCREENS, SOCIOS_EXTENSIONS),
|
||||
...ownedBy(ARTICULOS_SCREENS, ARTICULOS_EXTENSIONS),
|
||||
...ownedBy(FINANZAS_SCREENS, FINANZAS_EXTENSIONS),
|
||||
};
|
||||
|
||||
/** Las pantallas con lo que cada cliente agrega (en `main`, igual al base). */
|
||||
|
||||
Reference in new issue
Block a user