fase 3.2: compras y proveedores (modulo skeen_compras: proveedores, ordenes de compra con recepcion total/parcial, costo promedio ponderado con lote y caducidad, pagos a proveedor, cuentas por pagar con antiguedad, reportes de compras y margen por articulo); apagado por defecto
This commit is contained in:
@@ -666,6 +666,91 @@ def get_recursos(env):
|
||||
"""Bloque de recursos vigente (copia). Fuente única para modelos y API."""
|
||||
return normalizar_recursos(get_settings(env).get('recursos'))
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Compras y proveedores.
|
||||
#
|
||||
# Bloque anidado del mismo JSON de ajustes, igual que `impuestos`. La regla de
|
||||
# oro: con `activo=False` (default) NADA cambia en la instancia — la sección
|
||||
# Compras no aparece en el menú, el inventario y el POS se ven idénticos, no
|
||||
# corre ningún cron y las rutas del módulo responden 403. Por eso el default es
|
||||
# apagado.
|
||||
#
|
||||
# activo: enciende TODO el módulo de compras
|
||||
# actualizar_costo_promedio: al recibir, recalcula el costo promedio ponderado
|
||||
# permitir_recepcion_parcial: permite recibir menos de lo pedido y recibir después
|
||||
# requiere_autorizacion: las órdenes nacen en borrador y exigen enviarse
|
||||
# dias_credito_default: días de crédito por defecto de un proveedor nuevo
|
||||
# margen_minimo_alerta: margen (%) bajo el cual el reporte avisa
|
||||
# permitir_compras_sin_orden: permite registrar una recepción sin orden de compra
|
||||
# ---------------------------------------------------------------------------
|
||||
COMPRAS_DEFAULT = {
|
||||
'activo': False,
|
||||
'actualizar_costo_promedio': True,
|
||||
'permitir_recepcion_parcial': True,
|
||||
'requiere_autorizacion': False,
|
||||
'dias_credito_default': 0,
|
||||
'margen_minimo_alerta': 0,
|
||||
'permitir_compras_sin_orden': False,
|
||||
}
|
||||
|
||||
COMPRAS_DIAS_CREDITO_MAX = 365
|
||||
COMPRAS_MARGEN_MAX = 1000 # % (permite margen negativo / muy alto sin tope real)
|
||||
|
||||
|
||||
def normalizar_compras(raw):
|
||||
"""Bloque `compras` completo, con defaults. Tolerante: nunca lanza."""
|
||||
out = dict(COMPRAS_DEFAULT)
|
||||
if not isinstance(raw, dict):
|
||||
return out
|
||||
for k in ('activo', 'actualizar_costo_promedio', 'permitir_recepcion_parcial',
|
||||
'requiere_autorizacion', 'permitir_compras_sin_orden'):
|
||||
out[k] = _bool(raw.get(k), COMPRAS_DEFAULT[k])
|
||||
out['dias_credito_default'] = min(max(_int(
|
||||
raw.get('dias_credito_default'), COMPRAS_DEFAULT['dias_credito_default']),
|
||||
0), COMPRAS_DIAS_CREDITO_MAX)
|
||||
out['margen_minimo_alerta'] = min(max(_num(
|
||||
raw.get('margen_minimo_alerta'), COMPRAS_DEFAULT['margen_minimo_alerta']),
|
||||
-COMPRAS_MARGEN_MAX), COMPRAS_MARGEN_MAX)
|
||||
return out
|
||||
|
||||
|
||||
def validar_compras(raw):
|
||||
"""Valida el bloque `compras` de un PUT. Lanza ValueError si algo no cuadra
|
||||
(se traduce a 400). Devuelve SOLO las claves presentes y normalizadas para
|
||||
que el merge anidado no reinicie lo que el PUT no tocó."""
|
||||
if raw is None:
|
||||
return None
|
||||
if not isinstance(raw, dict):
|
||||
raise ValueError('El bloque de compras debe ser un objeto')
|
||||
out = {}
|
||||
for k in ('activo', 'actualizar_costo_promedio', 'permitir_recepcion_parcial',
|
||||
'requiere_autorizacion', 'permitir_compras_sin_orden'):
|
||||
if k in raw:
|
||||
if not isinstance(raw[k], bool):
|
||||
raise ValueError('El campo %s debe ser verdadero o falso' % k)
|
||||
out[k] = raw[k]
|
||||
if 'dias_credito_default' in raw:
|
||||
if isinstance(raw['dias_credito_default'], bool) or \
|
||||
not isinstance(raw['dias_credito_default'], int):
|
||||
raise ValueError('Los días de crédito deben ser un número entero')
|
||||
if not (0 <= raw['dias_credito_default'] <= COMPRAS_DIAS_CREDITO_MAX):
|
||||
raise ValueError('Los días de crédito deben estar entre 0 y %d'
|
||||
% COMPRAS_DIAS_CREDITO_MAX)
|
||||
out['dias_credito_default'] = raw['dias_credito_default']
|
||||
if 'margen_minimo_alerta' in raw:
|
||||
n = _num(raw['margen_minimo_alerta'], None)
|
||||
if n is None:
|
||||
raise ValueError('El margen mínimo de alerta debe ser un número')
|
||||
if not (-COMPRAS_MARGEN_MAX <= n <= COMPRAS_MARGEN_MAX):
|
||||
raise ValueError('El margen mínimo de alerta está fuera de rango')
|
||||
out['margen_minimo_alerta'] = n
|
||||
return out
|
||||
|
||||
|
||||
def get_compras(env):
|
||||
"""Bloque de compras vigente (copia). Fuente única para modelos y API."""
|
||||
return normalizar_compras(get_settings(env).get('compras'))
|
||||
|
||||
# Horas de agenda de respaldo: el comportamiento de siempre (8:00-19:00).
|
||||
AGENDA_START_DEFAULT = 8.0
|
||||
AGENDA_END_DEFAULT = 19.0
|
||||
@@ -710,6 +795,7 @@ def get_settings(env):
|
||||
'notificaciones': normalizar_notificaciones(None),
|
||||
'membresias': normalizar_membresias(None),
|
||||
'recursos': dict(RECURSOS_DEFAULT),
|
||||
'compras': dict(COMPRAS_DEFAULT),
|
||||
}
|
||||
raw = env['ir.config_parameter'].sudo().get_param(SETTINGS_KEY)
|
||||
if raw:
|
||||
@@ -726,6 +812,7 @@ def get_settings(env):
|
||||
out['notificaciones'] = normalizar_notificaciones(guardado.get('notificaciones'))
|
||||
out['membresias'] = normalizar_membresias(guardado.get('membresias'))
|
||||
out['recursos'] = normalizar_recursos(guardado.get('recursos'))
|
||||
out['compras'] = normalizar_compras(guardado.get('compras'))
|
||||
return out
|
||||
|
||||
|
||||
@@ -758,6 +845,11 @@ def set_settings(env, vals):
|
||||
bloque = validar_recursos(vals['recursos'])
|
||||
if bloque is not None:
|
||||
actual['recursos'] = {**actual['recursos'], **bloque}
|
||||
if 'compras' in vals:
|
||||
bloque = validar_compras(vals['compras'])
|
||||
if bloque is not None:
|
||||
# Merge anidado: un PUT parcial (p. ej. solo activo) no borra el resto.
|
||||
actual['compras'] = {**actual['compras'], **bloque}
|
||||
env['ir.config_parameter'].sudo().set_param(
|
||||
SETTINGS_KEY, json.dumps(actual, ensure_ascii=False))
|
||||
return get_settings(env)
|
||||
|
||||
Reference in New Issue
Block a user