Una vez elegida OpenERP como la herramienta que mejor se adapta a nuestras necesidades, con frecuencia nos encontraremos con la necesidad de adaptar alguno de los módulos disponibles a los requisitos específicos de nuestra empresa.
Haciendo un resumen rápido de lo que tenemos que tener en cuenta antes de elegir qué herramienta se adecua mejor a nuestra empresa y que “verdaderamente” necesitamos para cumplir todos los procesos de nuestro negocio ( esta ultima frase por la que hemos pasado casi de puntillas, quizás sea la fundamental o una de ellas para llevar a cabo una instalación de la herramienta con éxito y sin demasiados quebraderos de cabeza ), tenemos que tener asimilado que los objetivos de cualquier ERP son :
- Optimización de los procesos empresariales.
- Acceso a la información.
- Posibilidad de compartir información entre todos los componentes de la organización.
- Eliminación de datos y operaciones innecesarias de reingeniería
Además, un ERP como tiene que ser configurable y modular. Una vez elegida OpenERP como la herramienta que mejor se adapta a nuestras necesidades, con frecuencia nos encontraremos con la necesidad de adaptar alguno de los módulos disponibles a los requisitos específicos de nuestra empresa.
Con todo ello, deberemos probar la herramienta para tener claros las siguientes ideas fundamentales:
- Qué módulos necesitaremos y cuáles no ( y por tanto no deberemos de instalarlos)
- Realizar o pedir sólo los desarrollos de los módulos que verdaderamente necesitemos. En este apartado tenemos que hacernos a la idea que en muchos casos deberemos adaptar los procesos a la manera-estructura de OpenERP y no a la inversa, porque se trata de “configurar”-«customizar» la herramienta y no “crear” una nueva.
Al hilo de lo escrito, comentaremos el desarrollo de un modulo para gestionar vacaciones a petición de las necesidades de un cliente.
El módulo que desarrollamos fue una extensión al módulo vacaciones “hr_holidays” cuyo autor es OPENERP S.A. , que gestiona las vacaciones de los empleados del sistema. Este modulo basa su comportamiento en la asignación de días de ausencia aprobados por los responsables del departamento de RRHH y que los empleados van gastando en forma de “crédito”, es decir, por cada tipo de ausencia el empleado tiene derecho a un numero determinados de días que se podrá ir incrementando o disminuyendo en función del caso. Aún con la pobre definición de la funcionalidad que acabamos de hacer del módulo, refleja de una manera bastante cercana a como se comporta realmente ya que deja muchas lagunas para una mínima gestión de cualquier departamento de RRHH.
En una empresa pequeña de no mas de 5 empleados podría bastar el módulo para el personal pero a partir de un número mayor ya el gestionar vacaciones con dicho módulo no sería eficiente debido a:
- No existen calendarios laborales ni festivos, en el momento que dos empleados o mas que puedan pertenecer a lugares de trabajo distinto (con diferente calendario laboral) sería un problema.
- La gestión de las vacaciones en el cambio de año tampoco existe y corregir de año en año las vacaciones de cada empleado si éste es alto, tambien conllevaria un tiempo ineficiente.
- En relación con el primer punto sólo existe la posibilidad de contar los días como naturales, dejando atrás la posibilidad que en dos puntos de trabajos distintos se cuentes de una manera u otra (O incluso como hemos desarrollado nosotros, el conteo natural o laboral va asociado al tipo de ausencia y dependiendo de ella, contara dias naturales o laborales).
A parte de esta situación inicial fue básico a la hora de hacer el desarrollo (y en verdad también para cualquier otro) saber manejar bien las fechas en python, concretamente la clase time y datetime y el comportamiento de sus dos metodos mas utlizados : strftime() y strptime(). También para la problemática del conteo de los días hábiles hicimos uso del método isoweekday() de la clase datetime para saber que dia es de la semana dada una fecha..
Con esta problemática, el punto de partida de nuestro desarrollo es el siguiente; creamos dos nuevas identidades calendario_laboral y festivo. Calendario_laboral tendrá una relación one2many con hr_employee y también tendrá una relación many2many con la entidad festivo. Con ello podremos crear múltiples calendarios laborales que podrán poseer los festivos correspondientes, cada año sólo deberemos meter los festivos una vez para luego asociarlos a calendarios sucesivos. A su vez un mismo calendario podré asociarlo también a los empleados que correspondan, así podremos contear las vacaciones en función del calendario que tenga el empleado asignado.
En una primera versión el conteo de los días hábiles o no, lo asociamos a los calendarios laborales pero luego decidimos asociárselo al tipo de ausencia de manera que crearemos un nuevo campo check “dias_habiles” que si esta marcado descontará los días hábiles que hay en el intervalo de tiempo que ha pedido las vacaciones el empleado. En el mismo objeto, crearemos también un checkbox “lanzar_fin” que será el que controle si en el wizard que crearemos para gestionar el cambio de año y la compensación de vacaciones, se creen o no las nuevas asiganaciones de vacaciones para los empleados.
def dias_habiles(self, date_from, date_to):
res = 0
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
date_from = datetime.datetime.strptime(date_from, DATETIME_FORMAT)
date_to = datetime.datetime.strptime(date_to, DATETIME_FORMAT)
date_from = datetime.datetime.date(date_from)
date_to = datetime.datetime.date(date_to)
while( date_from <= date_to):
if(datetime.datetime.isoweekday(date_from)== 7 or datetime.datetime.isoweekday(date_from)== 6 ):
res = res + 1
date_from = date_from + relativedelta(days=+1)
return res
Y con esto contaremos las vacaciones con el siguiente método:
def _get_number_of_days(self, cr, uid, id, date_from, date_to, employee_id):
"""Returns a float equals to the timedelta between two dates given as string."""
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
from_dt = datetime.datetime.strptime(date_from, DATETIME_FORMAT)
to_dt = datetime.datetime.strptime(date_to, DATETIME_FORMAT)
timedelta = to_dt - from_dt
diff_day = timedelta.days
calendario_obj = self.pool.get("hr.holidays.calendario")
id_cal = calendario_obj.search(cr, uid,[('employee_ids', '=',employee_id)])
if id_cal:
calendario = calendario_obj.browse(cr, uid,[id_cal[0]])[0]
val_intervalo = []
for festivos in calendario.festivos:
val_intervalo.append(datetime.datetime.date(datetime.datetime.strptime(festivos.fecha,"%Y-%m-%d")))
d1 = datetime.datetime.date(from_dt)
d2 = datetime.datetime.date(to_dt)
for elem in val_intervalo:
if elem >= d1 and elem <= d2:
diff_day = round(diff_day) - 1
hol = self.pool.get("hr.holidays.status")
hol_obj = hol.browse(cr, uid, id)
if hol_obj.id != False:
if hol_obj.laborales == True:
res = self.dias_habiles(date_from, date_to)
diff_day = diff_day - res
return diff_day
La idea del asistente de fin de año es regularizar las vacaciones de manera correcta de un año para otro. Para ello una vez que se hubiesen creado las vacaciones de la empresa con sus días correspondientes, si marcamos “lanzar_fin” el asistente creado volverá a reponer los días de disfrute hasta los marcados de nuevo. Resaltar en este apartado, que la única problemática que se nos planteó fue con las Vacaciones, ya que podrían darse 3 casos (supongamos que nuestra empresa tiene derecho a 30 días naturales)
- Un empleado gasta 30 días —————-> se le asignaran de nuevo 30
- Un empleado gasta 27 días —————-> se le debe asignar 33
- Un empleado gasta 34 días —————-> se le debe asignar 26
En un primer acercamiento, se planteó el método de manera que en función de las vacaciones disfrutadas del año anterior se le creaba una nueva asignación con ese número de días. Pero esto solo será válido para el nuevo cambio de año y no en sucesivos, ya que el sistema de esta manera no puede diferenciar si un empleado gasta más o menos vacaciones porque le corresponde (de años anteriores) o porque los ha adelantado, y no actuará en consecuencia. Por tanto el sistema siempre asociará 30 días ( o los correspondientes) y controlará un nuevo tipo de ausencia creada por el modulo, “compensación de vacaciones” de manera que si alguien quiere gastar días de más del año que viene se le deberá asociar a este tipo de ausencia y en consecuencia creará una asignación negativa en los 30 días de vacaciones ( y por tanto se le descontarán las vacaciones anticipadas)
def regulariza_vacaciones(self,cr,uid,ids,context):
mes_actual=datetime.datetime.today().strftime("%m")
dia_actual=datetime.datetime.today().strftime("%d")
anio_actual=datetime.date.today().strftime("%Y")
anio_anterior = str(int(anio_actual) - 1)
# Year change can only be executed first 31 days in January
if int(mes_actual) == 10 and int(dia_actual)<31:
holidays = self.pool.get('hr.holidays')
holidays_obj = self.pool.get('hr.holidays.status')
holidays_ids = holidays_obj.search(cr, uid,[('lanzar_fin','=',True)])
category_obj = self.pool.get('hr.employee.category')
categoria = category_obj.search(cr,uid,[('name','=','Todos')])
obj_emp = self.pool.get("hr.employee")
emp_ids = obj_emp.search(cr, uid, [('category_ids',"=" , categoria[0])])
for hol in holidays_obj.browse(cr, uid, holidays_ids):
for emp in emp_ids:
resto = holidays._get_remaining_days_by_holidays(cr, uid, emp, hol.id, anio_anterior)
if resto < 0:
resto = -resto
# if resto > hol.ndias:
# exc = resto - hol.ndias
# resto = hol.ndias - exc
if hol.name == "Vacaciones":
resto = hol.ndias
if hol.name == "Compensacion Vacaciones":
resto = -resto
hol.name == "Vacaciones"
hol.id = holidays_obj.search(cr, uid,[('name','=',"Vacaciones")])[0]
values = {
'name' : hol.name,
'holiday_type' : 'employee',
'employee_id': emp,
'type':'add',
'holiday_status_id': hol.id,
'state' : 'validate',
'number_of_days_temp': resto,
}
if(resto != 0):
holidays.create(cr, uid, values)
else:
raise osv.except_osv(_('Warning'),
_('La regularizacion de vacaciones en el cambio de año solo se puede realizar en el mes de Enero'))
return {}
cambio_anio_wizard()
Ya casi para acabar, deberemos crear las vistas correspondientes para dibujar los menús de la aplicación y colocarlos en los sitios que nos interesen; en nuestro caso “colgaremos” todo desde el menu de configuración de vacaciones, aprovechando el filtrado que trae por defecto el sistema que solo permite acceso a este menú al grupo “HR Manager”.
Para acabar este punto y todo el post, un pequeño apunte en cuanto a filtrados y permisos; el modulo por defecto como vemos a continuación trae los derechos de accesos configurados en el csv correspondiente en la carpeta security, en el que daremos acceso a las nuevas entidades creadas según grupos. Esta tarea en el desarrollo de cualquier modulo podemos encontrar varias veces que “no esta hecha”, ¿ que quiero decir con estas comillas? Que no es que no este hecha, es mas, considero que si no es un desarrollo muy concreto para un determinado proyecto debería de dejarse “a gusto del consumidor” que gestione permisos y accesos a sus necesidades ( una de las esencias del ERP).
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_calendario_manager","hr.holidays.calendario manager","model_hr_holidays_calendario","base.group_hr_manager",1,1,1,1
"access_festivos_manager","hr.holidays.festivos manager","model_hr_holidays_festivos","base.group_hr_manager",1,1,1,1
"access_calendario_user","hr.holidays.calendario user","model_hr_holidays_calendario","base.group_user",1,0,0,0
"access_festivos_user","hr.holidays.festivos user","model_hr_holidays_festivos","base.group_user",1,0,0,0
Para quien quiera echarle un vistazo mas detenido o hacer uso del modulo que hemos comentado, está disponible para la comunidad en launchpad.