fase 2.2: impuestos configurables por instancia (tasa por servicio/articulo, calculo por linea, desglose en POS/tickets/estado de cuenta, reporte de impuestos, permiso ventas.impuestos) con defaults apagados
This commit is contained in:
54
frontend/src/lib/clinic-settings.ts
Normal file
54
frontend/src/lib/clinic-settings.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { odooApi, type ClinicSettings, CLINIC_SETTINGS_DEFAULT, IMPUESTOS_DEFAULT } from '../services/odoo';
|
||||
|
||||
// Ajustes de la clínica compartidos por la sesión (nombre, moneda e impuestos).
|
||||
// Se leen una sola vez del servidor y se avisa a quien los use cuando cambian.
|
||||
// Es la fuente que decide si los impuestos están activos en cada pantalla.
|
||||
|
||||
const normalizar = (settings?: Partial<ClinicSettings> | null): ClinicSettings => ({
|
||||
...CLINIC_SETTINGS_DEFAULT,
|
||||
...(settings || {}),
|
||||
impuestos: { ...IMPUESTOS_DEFAULT, ...(settings?.impuestos || {}) },
|
||||
});
|
||||
|
||||
let actual: ClinicSettings = normalizar(null);
|
||||
let peticion: Promise<ClinicSettings> | null = null;
|
||||
const oyentes = new Set<(s: ClinicSettings) => void>();
|
||||
|
||||
const publicar = (s: ClinicSettings) => {
|
||||
actual = s;
|
||||
oyentes.forEach((f) => f(s));
|
||||
};
|
||||
|
||||
export const cargarClinicSettings = (): Promise<ClinicSettings> => {
|
||||
if (peticion) return peticion;
|
||||
peticion = odooApi.getClinicSettings()
|
||||
.then((res) => {
|
||||
if (res.status === 'success' && res.settings) publicar(normalizar(res.settings));
|
||||
return actual;
|
||||
})
|
||||
.catch(() => actual)
|
||||
.finally(() => { peticion = null; });
|
||||
return peticion;
|
||||
};
|
||||
|
||||
/** Fuerza la recarga (tras guardar en Configuración). */
|
||||
export const recargarClinicSettings = (): Promise<ClinicSettings> => {
|
||||
peticion = null;
|
||||
return cargarClinicSettings();
|
||||
};
|
||||
|
||||
/** Registra los ajustes ya cargados/guardados sin volver a pedirlos. */
|
||||
export const publicarClinicSettings = (settings: Partial<ClinicSettings>) =>
|
||||
publicar(normalizar(settings));
|
||||
|
||||
export const useClinicSettings = (): ClinicSettings => {
|
||||
const [s, setS] = useState(actual);
|
||||
useEffect(() => {
|
||||
const f = (v: ClinicSettings) => setS(v);
|
||||
oyentes.add(f);
|
||||
cargarClinicSettings().then(setS);
|
||||
return () => { oyentes.delete(f); };
|
||||
}, []);
|
||||
return s;
|
||||
};
|
||||
@@ -9,7 +9,7 @@ export const PERM_KEYS: string[] = [
|
||||
'pacientes.ver', 'pacientes.expediente', 'pacientes.crear', 'pacientes.editar',
|
||||
'pacientes.eliminar', 'pacientes.exportar', 'pacientes.saldos',
|
||||
'visitas.ver', 'visitas.crear', 'visitas.editar', 'visitas.eliminar', 'visitas.archivos',
|
||||
'ventas.vender', 'ventas.cobrar', 'ventas.descuentos', 'ventas.cancelar',
|
||||
'ventas.vender', 'ventas.cobrar', 'ventas.descuentos', 'ventas.impuestos', 'ventas.cancelar',
|
||||
'ventas.devolver', 'ventas.reimprimir',
|
||||
'inventario.ver', 'inventario.ajustar', 'inventario.mover', 'inventario.costos',
|
||||
'servicios.ver', 'servicios.editar',
|
||||
@@ -46,6 +46,7 @@ const DEFAULT_PERM_ROLES: Record<string, string[]> = {
|
||||
'visitas.ver': ALL, 'visitas.crear': W, 'visitas.editar': W,
|
||||
'visitas.eliminar': W, 'visitas.archivos': W,
|
||||
'ventas.vender': MONEY, 'ventas.cobrar': WN, 'ventas.descuentos': MONEY,
|
||||
'ventas.impuestos': MONEY,
|
||||
'ventas.cancelar': AG, 'ventas.devolver': MONEY, 'ventas.reimprimir': ALL,
|
||||
'inventario.ver': ALL, 'inventario.ajustar': WN, 'inventario.mover': WN,
|
||||
'inventario.costos': WN,
|
||||
|
||||
@@ -68,6 +68,7 @@ export const FALLBACK_CONFIG: TicketConfig = {
|
||||
subtotal: true,
|
||||
descuento: true,
|
||||
puntos_canjeados: true,
|
||||
impuesto: true,
|
||||
total: true,
|
||||
total_usd: true,
|
||||
metodo_pago: true,
|
||||
|
||||
Reference in New Issue
Block a user