Initial commit: SKEEN Derma Experts - Sistema Integral de Gestión Clínica
- Frontend React (SKEEN Brand) con Vite, TypeScript, Tailwind - Frontend Homenest (versión alternativa) - Módulos Odoo 17 custom (citas, pacientes, monedero, pagos, ventas, inventario, whatsapp) - WACRM fork (Next.js 16 + Supabase) - Hermes + Bridge + Skills (Qwen3.6 via Nan Builders) - Scripts de migración y operación - Documentación extensiva en docs/
This commit is contained in:
2
odoo-addons/skeen_inventario/__init__.py
Normal file
2
odoo-addons/skeen_inventario/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import models
|
||||
17
odoo-addons/skeen_inventario/__manifest__.py
Normal file
17
odoo-addons/skeen_inventario/__manifest__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'SKEEN Inventario y Consumibles',
|
||||
'version': '1.0.0',
|
||||
'category': 'Healthcare',
|
||||
'summary': 'Inventario de productos y consumibles con niveles y movimientos',
|
||||
'author': 'Consultoria Alcaraz Salazar, S.A.S.',
|
||||
'website': 'https://skeen.mx',
|
||||
'depends': ['base'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'auto_install': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
2
odoo-addons/skeen_inventario/models/__init__.py
Normal file
2
odoo-addons/skeen_inventario/models/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import inventario
|
||||
98
odoo-addons/skeen_inventario/models/inventario.py
Normal file
98
odoo-addons/skeen_inventario/models/inventario.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class SkeenInventarioItem(models.Model):
|
||||
_name = 'skeen.inventario.item'
|
||||
_description = 'Item de Inventario / Consumible SKEEN'
|
||||
_order = 'name asc'
|
||||
|
||||
name = fields.Char(string='Nombre', required=True, index=True)
|
||||
kind = fields.Selection([
|
||||
('producto', 'Producto'),
|
||||
('consumible', 'Consumible'),
|
||||
], string='Tipo', required=True, default='producto', index=True)
|
||||
sku = fields.Char(string='SKU / Código', index=True)
|
||||
category = fields.Char(string='Línea / Categoría')
|
||||
unit = fields.Char(string='Unidad', default='pieza')
|
||||
|
||||
qty = fields.Float(string='Existencia', default=0.0)
|
||||
qty_optimal = fields.Float(string='Nivel óptimo', default=0.0)
|
||||
qty_min = fields.Float(string='Stock mínimo', default=0.0)
|
||||
|
||||
cost = fields.Float(string='Costo unitario', default=0.0)
|
||||
inventory_value = fields.Float(string='Valor inventario', compute='_compute_value', store=True)
|
||||
|
||||
expiry_date = fields.Date(string='Caducidad')
|
||||
last_count_date = fields.Date(string='Último conteo físico')
|
||||
active = fields.Boolean(string='Activo', default=True)
|
||||
notes = fields.Text(string='Notas')
|
||||
|
||||
move_ids = fields.One2many('skeen.inventario.move', 'item_id', string='Movimientos')
|
||||
stock_level = fields.Selection([
|
||||
('out', 'Sin existencias'),
|
||||
('critical', 'Crítico'),
|
||||
('low', 'Bajo'),
|
||||
('optimal', 'Óptimo'),
|
||||
], string='Nivel', compute='_compute_level', store=True)
|
||||
|
||||
@api.depends('qty', 'cost')
|
||||
def _compute_value(self):
|
||||
for rec in self:
|
||||
rec.inventory_value = (rec.qty or 0.0) * (rec.cost or 0.0)
|
||||
|
||||
@api.depends('qty', 'qty_min', 'qty_optimal')
|
||||
def _compute_level(self):
|
||||
for rec in self:
|
||||
q = rec.qty or 0.0
|
||||
if q <= 0:
|
||||
rec.stock_level = 'out'
|
||||
elif rec.qty_min and q <= rec.qty_min:
|
||||
rec.stock_level = 'critical'
|
||||
elif rec.qty_optimal and q < rec.qty_optimal:
|
||||
rec.stock_level = 'low'
|
||||
else:
|
||||
rec.stock_level = 'optimal'
|
||||
|
||||
|
||||
class SkeenInventarioMove(models.Model):
|
||||
_name = 'skeen.inventario.move'
|
||||
_description = 'Movimiento de Inventario SKEEN'
|
||||
_order = 'date desc, id desc'
|
||||
|
||||
item_id = fields.Many2one('skeen.inventario.item', string='Item', required=True, ondelete='cascade', index=True)
|
||||
type = fields.Selection([
|
||||
('compra', 'Compra (entrada)'),
|
||||
('venta', 'Venta (salida)'),
|
||||
('baja', 'Baja (merma)'),
|
||||
('ajuste', 'Ajuste (conteo físico)'),
|
||||
], string='Tipo', required=True)
|
||||
qty = fields.Float(string='Cantidad', required=True, default=0.0)
|
||||
before_qty = fields.Float(string='Existencia anterior', readonly=True)
|
||||
after_qty = fields.Float(string='Existencia resultante', readonly=True)
|
||||
date = fields.Datetime(string='Fecha', default=fields.Datetime.now)
|
||||
reference = fields.Char(string='Referencia')
|
||||
notes = fields.Char(string='Notas')
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
moves = super().create(vals_list)
|
||||
for m in moves:
|
||||
item = m.item_id
|
||||
if not item:
|
||||
continue
|
||||
before = item.qty or 0.0
|
||||
q = m.qty or 0.0
|
||||
if m.type == 'compra':
|
||||
after = before + q
|
||||
elif m.type in ('venta', 'baja'):
|
||||
after = before - q
|
||||
elif m.type == 'ajuste':
|
||||
after = q # ajuste fija la existencia al valor del conteo
|
||||
else:
|
||||
after = before
|
||||
after = max(0.0, after)
|
||||
item.write({'qty': after, 'last_count_date': fields.Date.today() if m.type == 'ajuste' else item.last_count_date})
|
||||
m.write({'before_qty': before, 'after_qty': after})
|
||||
return moves
|
||||
@@ -0,0 +1,3 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_skeen_inventario_item_user,Acceso a Items de Inventario SKEEN,model_skeen_inventario_item,base.group_user,1,1,1,1
|
||||
access_skeen_inventario_move_user,Acceso a Movimientos de Inventario SKEEN,model_skeen_inventario_move,base.group_user,1,1,1,1
|
||||
|
Reference in New Issue
Block a user