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:
2026-10-11 12:17:23 +00:00
parent ee8a844d96
commit a916753ebe
38 changed files with 4201 additions and 30 deletions

View File

@@ -21,6 +21,7 @@ from odoo.addons.skeen_citas.clinic_settings import (
set_settings as set_clinic_settings,
get_impuestos,
get_recursos,
get_compras,
)
from odoo.addons.skeen_citas.glosario import (
GLOSARIO_KEYS as GLOSARIO_KEY_SET,
@@ -660,9 +661,16 @@ class SkeenFrontendController(http.Controller):
# Permisos por acción (catálogo + por rol)
# ============================================================
def _perm_catalogo(self):
# Con `compras.activo=false` el grupo de permisos de Compras no se
# muestra en el editor de roles: apagar el módulo no debe cambiar
# ninguna pantalla existente.
try:
ocultar = set() if get_compras(request.env).get('activo') else {'compras'}
except Exception:
ocultar = set()
return [{'grupo': g, 'label': label,
'permisos': [{'key': k, 'label': l} for k, l in perms]}
for g, label, perms in PERMISSION_GROUPS]
for g, label, perms in PERMISSION_GROUPS if g not in ocultar]
@http.route('/skeen/frontend/v1/permisos', type='http', auth='none',
methods=['GET', 'OPTIONS'], csrf=False)
@@ -8014,6 +8022,10 @@ class SkeenFrontendController(http.Controller):
'qty_min': it.qty_min,
'cost': it.cost,
'price': it.price,
'costo_promedio': getattr(it, 'costo_promedio', 0.0),
'ultimo_costo': getattr(it, 'ultimo_costo', 0.0),
'margen': getattr(it, 'margen', 0.0),
'margen_pct': getattr(it, 'margen_pct', 0.0),
'inventory_value': it.inventory_value,
'expiry_date': it.expiry_date.strftime('%Y-%m-%d') if it.expiry_date else None,
'last_count_date': it.last_count_date.strftime('%Y-%m-%d') if it.last_count_date else None,
@@ -8036,6 +8048,9 @@ class SkeenFrontendController(http.Controller):
'date': m.date.strftime('%Y-%m-%d %H:%M') if m.date else None,
'reference': m.reference or '',
'notes': m.notes or '',
'lote': getattr(m, 'lote', '') or '',
'fecha_caducidad': m.fecha_caducidad.strftime('%Y-%m-%d')
if getattr(m, 'fecha_caducidad', None) else None,
}
@http.route('/skeen/frontend/v1/inventory/items', type='http', auth='none', methods=['GET', 'OPTIONS'], csrf=False)
@@ -8114,12 +8129,13 @@ class SkeenFrontendController(http.Controller):
data = _parse_json_body()
allowed = ['name', 'kind', 'sku', 'category', 'unit', 'qty_optimal', 'qty_min',
'cost', 'price', 'expiry_date', 'notes', 'active', 'genera_monedero',
'tax_rate', 'tax_use_default']
'tax_rate', 'tax_use_default', 'costo_promedio', 'ultimo_costo']
vals = {}
for k in allowed:
if k in data:
vals[k] = data[k]
for f in ('qty_optimal', 'qty_min', 'cost', 'price', 'tax_rate'):
for f in ('qty_optimal', 'qty_min', 'cost', 'price', 'tax_rate',
'costo_promedio', 'ultimo_costo'):
if f in vals:
vals[f] = float(vals[f] or 0)
if 'tax_use_default' in vals:

View File

@@ -106,6 +106,16 @@ PERMISSION_GROUPS = [
('membresias.editar', 'Editar planes y membresías'),
('membresias.cancelar', 'Congelar y cancelar membresías'),
]),
('compras', 'Compras y proveedores', [
('compras.ver', 'Ver compras, cuentas por pagar y margen'),
('compras.crear', 'Crear órdenes de compra'),
('compras.recibir', 'Recibir mercancía (afecta stock y costo)'),
('compras.pagar', 'Registrar pagos a proveedores'),
('compras.editar', 'Editar órdenes de compra'),
('compras.cancelar', 'Cancelar órdenes de compra'),
('proveedores.ver', 'Ver proveedores'),
('proveedores.editar', 'Administrar proveedores'),
]),
('config', 'Usuarios y configuración', [
('config.usuarios', 'Administrar usuarios'),
('config.roles', 'Administrar roles y menús'),
@@ -136,6 +146,8 @@ READ_PERM_KEYS = (
'notificaciones.ver',
'formularios.ver',
'membresias.ver',
'compras.ver',
'proveedores.ver',
)
# ---------------------------------------------------------------------------
@@ -234,6 +246,17 @@ DEFAULT_PERM_ROLES = {
'membresias.cobrar': ('admin', 'recepcion', 'gerencia'),
'membresias.editar': AG,
'membresias.cancelar': AG,
# Compras y proveedores. Ver es de todos; crear/recibir/pagar es de
# recepción/gerencia/admin (igual que inventario y membresías); editar,
# cancelar y administrar proveedores es de admin/gerencia (tocan dinero).
'compras.ver': ALL,
'compras.crear': ('admin', 'recepcion', 'gerencia'),
'compras.recibir': ('admin', 'recepcion', 'gerencia'),
'compras.pagar': ('admin', 'recepcion', 'gerencia'),
'compras.editar': AG,
'compras.cancelar': AG,
'proveedores.ver': ALL,
'proveedores.editar': AG,
# Usuarios y configuración
'config.usuarios': AG,
'config.roles': AG,

View File

@@ -169,6 +169,9 @@ DEFAULT_MENU_ROLES = {
'/pagos': ['admin', 'recepcion', 'gerencia'],
'/monedero': ['admin', 'recepcion', 'lectura', 'gerencia'],
'/inventario': ['admin', 'recepcion', 'lectura', 'gerencia'],
# Compras: apagado por defecto (compras.activo=false). La sección solo se
# pinta cuando la instancia la enciende; aquí queda su visibilidad por rol.
'/compras': ['admin', 'recepcion', 'lectura', 'gerencia'],
'/cortes': ['admin', 'recepcion', 'gerencia'],
'/reportes': ['admin', 'recepcion', 'lectura', 'gerencia'],
'/reportes?tab=adeudos': ['admin', 'recepcion', 'lectura', 'gerencia'],
@@ -179,6 +182,8 @@ DEFAULT_MENU_ROLES = {
'/reportes?tab=vendedores': ['admin', 'recepcion', 'lectura', 'gerencia'],
'/reportes?tab=concentrado': ['admin', 'recepcion', 'lectura', 'gerencia'],
'/reportes?tab=recomendaciones': ['admin', 'recepcion', 'lectura', 'gerencia'],
'/reportes?tab=compras': ['admin', 'recepcion', 'lectura', 'gerencia'],
'/reportes?tab=margen': ['admin', 'recepcion', 'lectura', 'gerencia'],
'/reportes?tab=exportar': ['admin', 'recepcion', 'lectura', 'gerencia'],
'/configuracion': ['admin', 'gerencia'],
'/cumpleanos': ['admin', 'recepcion', 'gerencia', 'marketing'],