Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions ina_mp_production_fichar/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg
:target: https://opensource.org/licenses/AGPL-3.0
:alt: License: AGPL-3

=========================
Ina MP Production Fichar
=========================

Gestión de fichajes de fábrica: entrada/salida de empleados e imputación de
su tiempo y cantidades producidas a órdenes de fabricación, reparaciones,
trabajos de láser o tiempo improductivo.

El fichaje real de presencia se apoya en el modelo core ``hr.attendance`` en
lugar de un modelo propio de presencia.

Cómo funciona
==============

Modelos
-------

* ``hr.employee`` (extendido): hace de "perfil" de fichaje — una fila fija
por empleado con su orden actual (``tipo``, y según el tipo
``workorder_id``, ``repara_id``, ``laser_id`` o ``improductivo``), su
puesto y su estado de presencia (``proximo_movi``: si le toca fichar
entrada o salida).
* ``hr.attendance`` (core, sin campos añadidos por este módulo): el log real
de entradas y salidas. Cada fichaje de "Entrada - Salida" crea o cierra un
registro aquí.
* ``hr.puesto.fichar``: un puesto de trabajo físico (centro de trabajo,
ubicación de consumo, ubicación de desechos) asociado a un usuario de
Odoo. Pensado para que cada terminal/ordenador de fábrica tenga su propio
usuario fijo, de forma que el sistema sepa en qué puesto está sin
preguntar.
* ``hr.fichar.historico``: histórico de líneas de trabajo por empleado (tipo
de orden, fechas, cantidades aceptadas/rechazadas, puesto), de solo
consulta.
* ``wiz.fichar.orden``: wizard que se abre al fichar entrada sin ninguna
orden activa, o al pulsar "Nueva Orden" / "Comunicar Cantidad".

Flujo de fichaje
-----------------

1. El empleado ficha **Entrada - Salida** desde el tablero del mismo nombre
(una fila por empleado).
2. Si no tiene ninguna orden activa y ficha en modo "Fabrica", se abre
automáticamente el wizard **Fichar Orden**: ahí elige en qué va a
trabajar (Fabricación, Reparación, Láser o Improductivo) y en qué
**Puesto** está (por defecto se sugiere el del usuario de Odoo actual,
pero se puede cambiar cada vez).
3. Al confirmar ("Crear Orden") se abre un registro de tiempo — según el
tipo, en ``mrp.workcenter.productivity``, ``mrp.repair.tiempos``,
``order.olaser.tiempos`` o ``mrp.improductivo`` — y una línea nueva en el
histórico.
4. Mientras trabaja, puede pulsar **Comunicar Cantidad** para informar de
piezas aceptadas/rechazadas sin dejar la orden: cierra el tramo de
tiempo/histórico actual y abre uno nuevo para seguir imputando en la
misma orden.
5. **Parar Orden** cierra el tiempo/histórico actual sin comunicar ninguna
cantidad y deja al empleado sin orden asignada, reabriendo el wizard para
que elija la siguiente tarea.
6. Al fichar salida se cierra la asistencia (``hr.attendance``) y, si tenía
una orden activa, ésta queda marcada como interrumpida (sin comunicar
cantidad).

Finalizar formalmente una orden de trabajo (dejarla en estado "Hecho") se
sigue haciendo desde la pantalla nativa de Fabricación de Odoo, no desde
este módulo — el fichaje solo imputa tiempo y cantidades parciales.

Menús (Fichar)
---------------

* **Entrada - Salida**: tablero de fichaje, una fila por empleado con su
estado actual.
* **Alta en Fichajes**: lista editable para asignar el código de barras a
los empleados que fichan.
* **Puesto para Fichar**: alta y configuración de los puestos de trabajo
físicos.

Otras acciones desde la ficha de fichaje
-------------------------------------------

* **Desechos**: abre el formulario de scrap con la orden y el puesto ya
precargados.
* **Material de la Orden**: lista del material a consumir de la orden de
fabricación activa, con acceso rápido a existencias.
* **Histórico**: pestaña con todas las líneas de trabajo del empleado. Se
pueden borrar si hay un error al comunicar una cantidad — al borrar, se
revierte automáticamente lo que esa línea había sumado a la orden de
trabajo.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues
<https://github.com/avanzosc/custom-addons/issues>`_.

Credits
=======

Contributors
------------

* INAEL, jag
* Ana Juaristi <ajuaristio@gmail.com>
* Lucía Echeverría <luciaecheverria@avanzosc.es>
2 changes: 2 additions & 0 deletions ina_mp_production_fichar/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import wizard
30 changes: 30 additions & 0 deletions ina_mp_production_fichar/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright 2026 Inael
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
"name": "Ina MP Production Fichar",
"version": "18.0.1.0.0",
"category": "Inael MP",
"license": "AGPL-3",
"author": "INAEL, jag, AvanzOSC",
"website": "https://github.com/avanzosc/custom-addons",
"depends": [
"mrp",
"hr_attendance",
"stock",
"ina_mc_permisos",
"ina_mc_product_campos",
"ina_mp_mrp_repara",
"ina_mp_production_olaser",
"mrp_productivity_employee",
],
"data": [
"security/ir.model.access.csv",
"views/hr_puesto_fichar_views.xml",
"views/hr_employee_views.xml",
"views/hr_employee_fichar_views.xml",
"views/stock_scrap_views.xml",
"views/stock_move_views.xml",
"wizard/wiz_fichar_orden.xml",
],
"installable": True,
}
120 changes: 120 additions & 0 deletions ina_mp_production_fichar/migrations/18.0.1.0.0/post-migrate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Copyright 2026 Inael
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

import logging

_logger = logging.getLogger(__name__)


def _table_exists(cr, table_name):
cr.execute(
"SELECT 1 FROM information_schema.tables WHERE table_name = %s",
(table_name,),
)
return bool(cr.fetchone())


def _column_exists(cr, table_name, column_name):
cr.execute(
"SELECT 1 FROM information_schema.columns "
"WHERE table_name = %s AND column_name = %s",
(table_name, column_name),
)
return bool(cr.fetchone())


def _migrate_perfil(cr):
"""hr_employee_fichar -> columnas nuevas de hr_employee."""
if not _table_exists(cr, "hr_employee_fichar"):
_logger.info(
"ina_mp_production_fichar: no existe hr_employee_fichar, nada "
"que migrar del perfil."
)
return

candidate_columns = [
"tipo",
"workorder_id",
"repara_id",
"laser_id",
"improductivo",
"puesto_id",
"entrada_ultima",
"salida_ultima",
]
columns = [
c
for c in candidate_columns
if _column_exists(cr, "hr_employee_fichar", c)
and _column_exists(cr, "hr_employee", c)
]
if not columns:
_logger.warning(
"ina_mp_production_fichar: hr_employee_fichar existe pero no "
"hay columnas migrables en comun con hr_employee."
)
return

# Los nombres de columna no se pueden pasar como parametro %s de
# psycopg2 (eso solo vale para valores, no para identificadores). "columns"
# sale de una lista blanca fija (candidate_columns) filtrada por
# _column_exists, no de entrada de usuario, asi que es seguro.
set_clause = ", ".join(f"{c} = hef.{c}" for c in columns)
query = f"""
UPDATE hr_employee he
SET {set_clause}
FROM hr_employee_fichar hef
WHERE hef.employee_id = he.id
"""
cr.execute(query) # pylint: disable=sql-injection
_logger.info(
"ina_mp_production_fichar: %s empleados actualizados con su perfil "
"de fichar antiguo (%s)",
cr.rowcount,
", ".join(columns),
)


def _migrate_historico(cr):
"""hr_fichar_historico.fichar_id (perfil) -> employee_id (empleado)."""
if not _table_exists(cr, "hr_fichar_historico"):
return
if not _column_exists(cr, "hr_fichar_historico", "fichar_id"):
_logger.info(
"ina_mp_production_fichar: hr_fichar_historico ya no tiene "
"fichar_id, nada que repuntar."
)
return
if not _table_exists(cr, "hr_employee_fichar"):
_logger.warning(
"ina_mp_production_fichar: hr_fichar_historico.fichar_id existe "
"pero hr_employee_fichar no. No se puede repuntar a "
"employee_id automaticamente."
)
return

cr.execute(
"""
UPDATE hr_fichar_historico hfh
SET employee_id = hef.employee_id
FROM hr_employee_fichar hef
WHERE hfh.fichar_id = hef.id
AND hfh.employee_id IS NULL
"""
)
_logger.info(
"ina_mp_production_fichar: %s lineas de historico repuntadas de "
"fichar_id a employee_id",
cr.rowcount,
)


def migrate(cr, version):
_migrate_perfil(cr)
_migrate_historico(cr)
_logger.info(
"ina_mp_production_fichar: migracion %s completada. Las tablas/"
"columnas antiguas no se borran aqui explicitamente; Odoo las "
"limpiara solo al terminar de cargar los modulos.",
version,
)
7 changes: 7 additions & 0 deletions ina_mp_production_fichar/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from . import hr_puesto_fichar
from . import hr_employee
from . import mrp_improductivo
from . import mrp_workorder
from . import mrp_workcenter_productivity
from . import stock_scrap
from . import stock_move
Loading
Loading