Browse Source

Remove old hr_efficiency directory to prepare for subtree

root 5 months ago
parent
commit
1ff377a85d

+ 0 - 292
hr_efficiency/README.md

@@ -1,292 +0,0 @@
-# HR Efficiency Module
-
-## Descripción
-
-El módulo `hr_efficiency` permite llevar un registro de la eficiencia de los empleados comparando las horas planeadas vs las horas realmente trabajadas. Proporciona análisis mensuales detallados que incluyen:
-
-- **Horas disponibles** (considerando vacaciones y tiempo libre)
-- **Horas planeadas** (desde el módulo de planeación)
-- **Horas planeadas en proyectos facturables**
-- **Horas registradas en proyectos facturables**
-- **Horas planeadas en proyectos no facturables**
-- **Horas registradas en proyectos no facturables**
-
-## Características
-
-### 🔄 **Cálculo Automático**
-- Ejecución semanal automática para los últimos 3 meses y próximos 6 meses
-- Cron job configurado para ejecutarse cada semana
-- Actualización automática de registros existentes
-
-### 🛠️ **Cálculo Manual**
-- Wizard para ejecución manual del cálculo
-- Selección de período personalizado
-- Filtrado por empleados específicos
-- Resultados detallados del proceso
-
-### 📊 **Reportes y Análisis**
-- Vista de lista con indicadores visuales de eficiencia
-- Reportes pivot y gráficos para análisis
-- **Indicadores Configurables**: Sistema flexible de indicadores con fórmulas y ponderaciones
-- Filtros por indicadores (Good Planning, Good Time Tracking, High Overall Efficiency)
-- Filtros por tipo de proyecto (facturable, no facturable)
-- Filtros de meses dinámicos (últimos 2, actual, próximos 2)
-- Agrupación por empleado, mes, departamento
-
-### 📈 **Seguimiento Histórico Inteligente**
-- Nuevos registros se crean **solo cuando hay cambios** en los datos
-- Registros anteriores se archivan automáticamente (campo `active = False`)
-- Campo `calculation_date` para rastrear cuándo se realizó cada cálculo
-- Permite ver la evolución de la eficiencia a lo largo del tiempo
-- Solo los registros más recientes están activos para evitar duplicados
-
-### 🔗 **Integración Completa**
-- **Planning**: Extrae horas planeadas de `planning.slot`
-- **Timesheets**: Obtiene horas reales de `account.analytic.line`
-- **Projects**: Distingue entre proyectos facturables y no facturables
-- **Holidays**: Considera vacaciones y tiempo libre en el cálculo de disponibilidad
-
-### ⚙️ **Indicadores Configurables**
-- **Indicador 1 - Planeación**: Al menos 90% del tiempo disponible debe estar planeado
-- **Indicador 2 - Tiempo Registrado**: Al menos 90% del tiempo planeado debe estar registrado
-- **Fórmulas Personalizables**: Cada indicador puede usar fórmulas con variables de eficiencia
-- **Ponderaciones**: Cada indicador tiene un peso en el cálculo de eficiencia general
-- **Umbrales de Color**: Configuración de colores para diferentes niveles de rendimiento
-- **Gestión desde Planning**: Configuración disponible en Planning → Settings → Efficiency Indicators
-
-## Instalación
-
-1. Copiar el módulo a `/extra-addons/custom/hr_efficiency`
-2. Actualizar la lista de módulos en Odoo
-3. Instalar el módulo "HR Efficiency"
-
-## Uso
-
-### Acceso al Módulo
-
-El módulo se puede acceder desde:
-- **Menú HR**: Employees → Efficiency
-- **Menú Planning**: Planning → Reporting → Efficiency
-
-### Cálculo Manual
-
-1. Ir a **Employees → Efficiency → Calculate Efficiency**
-2. Configurar el período (formato: YYYY-MM)
-3. Seleccionar empleados (opcional, por defecto todos)
-4. Ejecutar el cálculo
-5. Revisar resultados y acceder a los registros
-
-### Configuración de Indicadores
-
-#### Acceso a la Configuración
-1. Ir a **Planning → Settings → Efficiency Indicators**
-2. Aquí puedes gestionar todos los indicadores de eficiencia
-
-#### Indicadores por Defecto
-- **Planning Efficiency**: `(planned_hours / available_hours) * 100`
-  - Objetivo: 90%
-  - Peso: 50%
-  - Mide qué tan bien se planea el tiempo disponible
-
-- **Time Tracking Efficiency**: `((actual_billable_hours + actual_non_billable_hours) / planned_hours) * 100`
-  - Objetivo: 90%
-  - Peso: 50%
-  - Mide qué tan bien se registra el tiempo planeado
-
-#### Variables Disponibles en Fórmulas
-- `available_hours`: Horas disponibles
-- `planned_hours`: Horas planeadas totales
-- `planned_billable_hours`: Horas planeadas en proyectos facturables
-- `planned_non_billable_hours`: Horas planeadas en proyectos no facturables
-- `actual_billable_hours`: Horas registradas en proyectos facturables
-- `actual_non_billable_hours`: Horas registradas en proyectos no facturables
-
-### Visualización de Datos
-
-#### Vista de Lista
-- Muestra todos los registros de eficiencia
-- Indicadores visuales:
-  - 🟢 Verde: Eficiencia ≥ 90%
-  - 🟡 Amarillo: Eficiencia 70-89%
-  - 🔴 Rojo: Eficiencia < 70%
-
-#### Filtros Disponibles
-- **Filtros de Indicadores**:
-  - Good Planning (≥ 90%)
-  - Good Time Tracking (≥ 90%)
-  - High Overall Efficiency (≥ 90%)
-
-- **Filtros de Tipo de Proyecto**:
-  - Proyectos Facturables
-  - Proyectos No Facturables
-
-- **Filtros de Fecha** (similar a ventas):
-  - **Meses**: abril, mayo, junio, julio, agosto (por defecto)
-  - **Trimestres**: Trimestre 1, 2, 3, 4
-  - **Años**: 2023, 2024, 2025
-
-- **Filtros de Estado**:
-  - Archivado
-  - No Archivado
-
-#### Reportes
-- **Efficiency Analysis**: Reportes pivot y gráficos
-- Agrupación por mes, empleado, departamento
-- Métricas de eficiencia y utilización
-
-## Estructura del Módulo
-
-```
-hr_efficiency/
-├── __init__.py
-├── __manifest__.py
-├── models/
-│   ├── __init__.py
-│   └── hr_efficiency.py
-├── wizard/
-│   ├── __init__.py
-│   ├── hr_efficiency_calculation_wizard.py
-│   └── hr_efficiency_calculation_wizard_views.xml
-├── views/
-│   ├── hr_efficiency_views.xml
-│   └── planning_views.xml
-├── report/
-│   ├── __init__.py
-│   ├── hr_efficiency_report.py
-│   └── hr_efficiency_report_views.xml
-├── security/
-│   └── ir.model.access.csv
-├── data/
-│   └── hr_efficiency_cron.xml
-└── README.md
-```
-
-## Modelos
-
-### hr.efficiency
-Modelo principal que almacena los registros de eficiencia mensual por empleado.
-
-**Campos principales:**
-- `month_year`: Mes y año (formato: YYYY-MM)
-- `employee_id`: Empleado
-- `calculation_date`: Fecha y hora cuando se realizó el cálculo (para seguimiento histórico)
-- `available_hours`: Horas disponibles
-- `planned_hours`: Horas planeadas totales
-- `planned_billable_hours`: Horas planeadas en proyectos facturables
-- `planned_non_billable_hours`: Horas planeadas en proyectos no facturables
-- `actual_billable_hours`: Horas reales en proyectos facturables
-- `actual_non_billable_hours`: Horas reales en proyectos no facturables
-- `efficiency_rate`: Porcentaje de eficiencia general
-- `billable_efficiency_rate`: Porcentaje de eficiencia en proyectos facturables
-
-### hr.efficiency.calculation.wizard
-Wizard para ejecutar cálculos manuales de eficiencia.
-
-**Comportamiento de Registros:**
-- **Creación inteligente**: Nuevos registros se crean solo cuando hay cambios en los datos
-- **Archivo automático**: Registros anteriores se archivan (campo `active = False`) antes de crear nuevos
-- **Seguimiento histórico**: Permite ver cómo evolucionan los datos de eficiencia a lo largo del tiempo
-- **Sin duplicados activos**: Solo el registro más reciente está activo para cada empleado y mes
-- **Timestamp de cálculo**: Cada registro incluye la fecha y hora exacta cuando se realizó el cálculo
-Wizard para cálculo manual de eficiencia.
-
-### hr.efficiency.report
-Modelo de reporte para análisis y visualización de datos.
-
-## Cálculos
-
-### Horas Disponibles
-```python
-# Horas totales del calendario del empleado
-total_work_hours = employee._list_work_time_per_day(start_date, end_date)
-
-# Restar horas de vacaciones aprobadas
-time_off_hours = get_approved_leaves_hours(employee, start_date, end_date)
-
-available_hours = total_work_hours - time_off_hours
-```
-
-### Horas Planeadas
-```python
-# Obtener slots de planning del empleado
-planning_slots = env['planning.slot'].search([
-    ('employee_id', '=', employee.id),
-    ('start_datetime', '>=', start_datetime),
-    ('end_datetime', '<=', end_datetime),
-    ('state', 'in', ['draft', 'published'])
-])
-
-# Separar por tipo de proyecto
-for slot in planning_slots:
-    if slot.project_id.allow_billable:
-        planned_billable += slot.allocated_hours
-    else:
-        planned_non_billable += slot.allocated_hours
-```
-
-### Horas Reales
-```python
-# Obtener timesheets del empleado (excluyendo vacaciones)
-timesheets = env['account.analytic.line'].search([
-    ('employee_id', '=', employee.id),
-    ('date', '>=', start_date),
-    ('date', '<=', end_date),
-    ('project_id', '!=', False),
-    ('holiday_id', '=', False)  # Excluir timesheets de vacaciones
-])
-
-# Separar por tipo de proyecto
-for timesheet in timesheets:
-    if timesheet.project_id.allow_billable:
-        actual_billable += timesheet.unit_amount
-    else:
-        actual_non_billable += timesheet.unit_amount
-```
-
-## Configuración
-
-### Cron Job
-El módulo incluye un cron job configurado para ejecutarse semanalmente:
-- **Nombre**: HR Efficiency: Calculate Efficiency (Weekly)
-- **Frecuencia**: Semanal
-- **Prioridad**: 10
-- **Método**: `_cron_calculate_efficiency()`
-
-### Permisos
-- **Usuarios HR**: Lectura, escritura y creación de registros
-- **Managers HR**: Acceso completo incluyendo eliminación
-- **Reportes**: Solo lectura para análisis
-
-## Dependencias
-
-- `hr`: Gestión de empleados
-- `hr_timesheet`: Timesheets
-- `hr_holidays`: Gestión de vacaciones
-- `project`: Gestión de proyectos
-- `planning`: Planeación de recursos
-- `project_timesheet_holidays`: Integración de vacaciones con timesheets
-
-## Notas Técnicas
-
-### Consideraciones de Rendimiento
-- El cálculo se ejecuta por empleado y mes para optimizar el rendimiento
-- Los registros existentes se actualizan en lugar de crear duplicados
-- El cron job está configurado para ejecutarse en horarios de baja actividad
-
-### Integración con Vacaciones
-- El módulo considera automáticamente las vacaciones aprobadas
-- Excluye timesheets generados automáticamente por vacaciones
-- Calcula la disponibilidad real considerando el calendario del empleado
-
-### Proyectos Facturables
-- Utiliza el campo `allow_billable` de `project.project`
-- Distingue entre proyectos facturables y no facturables
-- Permite análisis separado de eficiencia por tipo de proyecto
-
-## Soporte
-
-Para reportar problemas o solicitar mejoras, contactar al equipo de desarrollo.
-
-## Licencia
-
-LGPL-3

+ 0 - 6
hr_efficiency/__init__.py

@@ -1,6 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from . import models
-from . import wizard
-from . import report

+ 0 - 57
hr_efficiency/__manifest__.py

@@ -1,57 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-{
-    'name': 'HR Efficiency',
-    'version': '1.0',
-    'category': 'Human Resources',
-    'summary': 'Employee Efficiency Tracking',
-    'description': """
-Employee Efficiency Tracking
-============================
-
-This module tracks employee efficiency by comparing planned hours vs actual hours worked.
-It provides monthly reports showing:
-- Available hours (considering holidays and time off)
-- Planned hours (from planning module)
-- Planned hours on billable projects
-- Actual hours worked on billable projects
-- Planned hours on non-billable projects
-- Actual hours worked on non-billable projects
-
-Features:
-- Automatic weekly calculation for last 3 months and next 6 months
-- Manual calculation wizard
-- Integration with Planning, Timesheets, Projects and Holidays
-- Efficiency reports in Planning application
-    """,
-    'depends': [
-        'hr',
-        'hr_timesheet',
-        'hr_holidays',
-        'project',
-        'planning',
-        'project_timesheet_holidays',
-    ],
-                                        'data': [
-        'security/ir.model.access.csv',
-        'data/hr_efficiency_cron.xml',
-        'data/hr_efficiency_filters.xml',
-        'data/hr_efficiency_indicators.xml',
-        'data/hr_efficiency_init.xml',
-        'data/hr_efficiency_post_init.xml',
-        'wizard/hr_efficiency_calculation_wizard_views.xml',
-        'wizard/hr_efficiency_field_visibility_wizard_views.xml',
-        'views/hr_efficiency_views.xml',
-        'views/hr_efficiency_indicator_views.xml',
-        'views/hr_efficiency_dynamic_field_views.xml',
-        'views/hr_efficiency_dynamic_views.xml',
-        'views/planning_views.xml',
-        'report/hr_efficiency_report_views.xml',
-    ],
-    'demo': [],
-    'installable': True,
-    'auto_install': False,
-    'application': False,
-    'license': 'LGPL-3',
-}

+ 0 - 16
hr_efficiency/data/hr_efficiency_cron.xml

@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
-    <data noupdate="1">
-        <record id="ir_cron_hr_efficiency_calculation" model="ir.cron">
-            <field name="name">HR Efficiency: Calculate Efficiency (Weekly)</field>
-            <field name="model_id" ref="model_hr_efficiency"/>
-            <field name="state">code</field>
-            <field name="code">model._cron_calculate_efficiency()</field>
-            <field name="interval_number">1</field>
-            <field name="interval_type">weeks</field>
-            <field name="active" eval="True"/>
-            <field name="priority">10</field>
-            <field name="nextcall" eval="(DateTime.now() + timedelta(days=7)).strftime('%Y-%m-%d %H:%M:%S')"/>
-        </record>
-    </data>
-</odoo>

+ 0 - 97
hr_efficiency/data/hr_efficiency_filters.xml

@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
-    <data noupdate="1">
-        <!-- Filtros dinámicos para la vista de búsqueda -->
-        
-        <!-- Filtro para proyectos facturables -->
-        <record id="filter_billable_projects" model="ir.filters">
-            <field name="name">Proyectos Facturables</field>
-            <field name="model_id" ref="model_hr_efficiency"/>
-            <field name="domain">[('planned_billable_hours', '>', 0)]</field>
-            <field name="context">{}</field>
-            <field name="is_default" eval="False"/>
-            <field name="active" eval="True"/>
-        </record>
-        
-        <!-- Filtro para proyectos no facturables -->
-        <record id="filter_non_billable_projects" model="ir.filters">
-            <field name="name">Proyectos No Facturables</field>
-            <field name="model_id" ref="model_hr_efficiency"/>
-            <field name="domain">[('planned_non_billable_hours', '>', 0)]</field>
-            <field name="context">{}</field>
-            <field name="is_default" eval="False"/>
-            <field name="active" eval="True"/>
-        </record>
-        
-        <!-- Filtro para mes actual (por defecto) -->
-        <record id="filter_current_month" model="ir.filters">
-            <field name="name">Mes Actual</field>
-            <field name="model_id" ref="model_hr_efficiency"/>
-            <field name="domain">[('month_year', '=', '2024-08')]</field>
-            <field name="context">{}</field>
-            <field name="is_default" eval="True"/>
-            <field name="active" eval="True"/>
-        </record>
-        
-        <!-- Filtro para últimos 2 meses -->
-        <record id="filter_last_2_months" model="ir.filters">
-            <field name="name">Últimos 2 Meses</field>
-            <field name="model_id" ref="model_hr_efficiency"/>
-            <field name="domain">[('month_year', 'in', ['2024-06', '2024-07'])]</field>
-            <field name="context">{}</field>
-            <field name="is_default" eval="False"/>
-            <field name="active" eval="True"/>
-        </record>
-        
-        <!-- Filtro para próximos 2 meses -->
-        <record id="filter_next_2_months" model="ir.filters">
-            <field name="name">Próximos 2 Meses</field>
-            <field name="model_id" ref="model_hr_efficiency"/>
-            <field name="domain">[('month_year', 'in', ['2024-09', '2024-10'])]</field>
-            <field name="context">{}</field>
-            <field name="is_default" eval="False"/>
-            <field name="active" eval="True"/>
-        </record>
-        
-        <!-- Filtro para junio 2024 -->
-        <record id="filter_june_2024" model="ir.filters">
-            <field name="name">Junio 2024</field>
-            <field name="model_id" ref="model_hr_efficiency"/>
-            <field name="domain">[('month_year', '=', '2024-06')]</field>
-            <field name="context">{}</field>
-            <field name="is_default" eval="False"/>
-            <field name="active" eval="True"/>
-        </record>
-        
-        <!-- Filtro para julio 2024 -->
-        <record id="filter_july_2024" model="ir.filters">
-            <field name="name">Julio 2024</field>
-            <field name="model_id" ref="model_hr_efficiency"/>
-            <field name="domain">[('month_year', '=', '2024-07')]</field>
-            <field name="context">{}</field>
-            <field name="is_default" eval="False"/>
-            <field name="active" eval="True"/>
-        </record>
-        
-        <!-- Filtro para septiembre 2024 -->
-        <record id="filter_september_2024" model="ir.filters">
-            <field name="name">Septiembre 2024</field>
-            <field name="model_id" ref="model_hr_efficiency"/>
-            <field name="domain">[('month_year', '=', '2024-09')]</field>
-            <field name="context">{}</field>
-            <field name="is_default" eval="False"/>
-            <field name="active" eval="True"/>
-        </record>
-        
-        <!-- Filtro para octubre 2024 -->
-        <record id="filter_october_2024" model="ir.filters">
-            <field name="name">Octubre 2024</field>
-            <field name="model_id" ref="model_hr_efficiency"/>
-            <field name="domain">[('month_year', '=', '2024-10')]</field>
-            <field name="context">{}</field>
-            <field name="is_default" eval="False"/>
-            <field name="active" eval="True"/>
-        </record>
-        
-    </data>
-</odoo>

+ 0 - 30
hr_efficiency/data/hr_efficiency_indicators.xml

@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
-    <data noupdate="1">
-        <!-- Default Planning Indicator -->
-        <record id="indicator_planning" model="hr.efficiency.indicator">
-            <field name="name">Planning Efficiency</field>
-            <field name="sequence">10</field>
-            <field name="active">True</field>
-            <field name="formula">(planned_hours / available_hours) * 100 if available_hours > 0 else 0</field>
-            <field name="target_percentage">90.0</field>
-            <field name="weight">50.0</field>
-            <field name="description">Measures how well the employee's time is planned compared to available hours. Target: at least 90% of available time should be planned.</field>
-            <field name="color_threshold_green">90.0</field>
-            <field name="color_threshold_yellow">70.0</field>
-        </record>
-
-        <!-- Default Time Tracking Indicator -->
-        <record id="indicator_time_tracking" model="hr.efficiency.indicator">
-            <field name="name">Time Tracking Efficiency</field>
-            <field name="sequence">20</field>
-            <field name="active">True</field>
-            <field name="formula">((actual_billable_hours + actual_non_billable_hours) / planned_hours) * 100 if planned_hours > 0 else 0</field>
-            <field name="target_percentage">90.0</field>
-            <field name="weight">50.0</field>
-            <field name="description">Measures how well the employee tracks their time compared to planned hours. Target: at least 90% of planned time should be tracked.</field>
-            <field name="color_threshold_green">90.0</field>
-            <field name="color_threshold_yellow">70.0</field>
-        </record>
-    </data>
-</odoo>

+ 0 - 7
hr_efficiency/data/hr_efficiency_init.xml

@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
-    <data noupdate="1">
-        <!-- Initialize dynamic fields and views -->
-        <function model="hr.efficiency" name="_init_dynamic_system"/>
-    </data>
-</odoo>

+ 0 - 9
hr_efficiency/data/hr_efficiency_post_init.xml

@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
-    <data noupdate="1">
-        <!-- Post-installation: Ensure dynamic fields are created and views are updated -->
-        <function model="hr.efficiency" name="_init_dynamic_system"/>
-        <!-- Update views with dynamic fields -->
-        <function model="hr.efficiency" name="_update_views_with_dynamic_fields"/>
-    </data>
-</odoo>

+ 0 - 6
hr_efficiency/models/__init__.py

@@ -1,6 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from . import hr_efficiency
-from . import hr_efficiency_indicator
-from . import hr_efficiency_dynamic_field

+ 0 - 559
hr_efficiency/models/hr_efficiency.py

@@ -1,559 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from datetime import datetime, date
-from dateutil.relativedelta import relativedelta
-from odoo import api, fields, models, _
-from odoo.exceptions import UserError
-from odoo.tools import float_round
-
-
-class HrEfficiency(models.Model):
-    _name = 'hr.efficiency'
-    _description = 'Employee Efficiency'
-    _order = 'month_year desc, employee_id'
-    _rec_name = 'display_name'
-    _active_name = 'active'
-
-    active = fields.Boolean('Active', default=True, help='Technical field to archive old records')
-    month_year = fields.Char('Month Year', required=True, index=True, help="Format: YYYY-MM")
-    employee_id = fields.Many2one('hr.employee', 'Employee', required=True, index=True)
-    company_id = fields.Many2one('res.company', 'Company', required=True, default=lambda self: self.env.company)
-    calculation_date = fields.Datetime('Calculation Date', default=fields.Datetime.now, help='When this calculation was performed')
-    
-    # Available hours (considering holidays and time off)
-    available_hours = fields.Float('Available Hours', digits=(10, 2), help="Total available hours considering holidays and time off")
-    
-    # Planned hours
-    planned_hours = fields.Float('Planned Hours', digits=(10, 2), help="Total hours planned in planning module")
-    planned_billable_hours = fields.Float('Planned Billable Hours', digits=(10, 2), help="Hours planned on billable projects")
-    planned_non_billable_hours = fields.Float('Planned Non-Billable Hours', digits=(10, 2), help="Hours planned on non-billable projects")
-    
-    # Actual hours
-    actual_billable_hours = fields.Float('Actual Billable Hours', digits=(10, 2), help="Hours actually worked on billable projects")
-    actual_non_billable_hours = fields.Float('Actual Non-Billable Hours', digits=(10, 2), help="Hours actually worked on non-billable projects")
-    
-    # Computed fields
-    total_actual_hours = fields.Float('Total Actual Hours', compute='_compute_total_actual_hours', store=True)
-    
-    # Dynamic indicator fields (will be created automatically)
-    # These fields are managed dynamically based on hr.efficiency.indicator records
-    
-    # Overall efficiency (always present)
-    overall_efficiency = fields.Float('Overall Efficiency (%)', compute='_compute_indicators', store=True, help='Overall efficiency based on configured indicators')
-    
-    display_name = fields.Char('Display Name', compute='_compute_display_name', store=True)
-    
-    # Note: Removed unique constraint to allow historical tracking
-    # Multiple records can exist for the same employee and month
-
-    @api.depends('actual_billable_hours', 'actual_non_billable_hours')
-    def _compute_total_actual_hours(self):
-        for record in self:
-            record.total_actual_hours = record.actual_billable_hours + record.actual_non_billable_hours
-
-    @api.depends('available_hours', 'planned_hours', 'total_actual_hours')
-    def _compute_indicators(self):
-        for record in self:
-            # Get all manual fields for this model
-            manual_fields = self.env['ir.model.fields'].search([
-                ('model', '=', 'hr.efficiency'),
-                ('state', '=', 'manual'),
-                ('ttype', '=', 'float')
-            ])
-            
-            # Prepare efficiency data
-            efficiency_data = {
-                'available_hours': record.available_hours,
-                'planned_hours': record.planned_hours,
-                'planned_billable_hours': record.planned_billable_hours,
-                'planned_non_billable_hours': record.planned_non_billable_hours,
-                'actual_billable_hours': record.actual_billable_hours,
-                'actual_non_billable_hours': record.actual_non_billable_hours,
-            }
-            
-            # Calculate all indicators dynamically
-            for field in manual_fields:
-                # Find the corresponding indicator
-                indicator = self.env['hr.efficiency.indicator'].search([
-                    ('name', 'ilike', field.field_description)
-                ], limit=1)
-                
-                if indicator:
-                    # Calculate indicator value using the indicator formula
-                    indicator_value = indicator.evaluate_formula(efficiency_data)
-                    
-                    # Set the value on the record
-                    if hasattr(record, field.name):
-                        setattr(record, field.name, float_round(indicator_value, 2))
-            
-            # Overall efficiency based on configured indicators
-            record.overall_efficiency = self._calculate_overall_efficiency(record)
-    
-    def _get_indicator_field_name(self, indicator_name):
-        """
-        Convert indicator name to valid field name
-        """
-        # Remove special characters and convert to lowercase
-        field_name = indicator_name.lower()
-        field_name = field_name.replace(' ', '_').replace('-', '_').replace('(', '').replace(')', '')
-        field_name = field_name.replace('í', 'i').replace('á', 'a').replace('é', 'e').replace('ó', 'o').replace('ú', 'u')
-        field_name = field_name.replace('ñ', 'n')
-        
-        # Ensure it starts with x_ for manual fields
-        if not field_name.startswith('x_'):
-            field_name = 'x_' + field_name
-            
-        return field_name
-    
-
-    
-    @api.model
-    def _create_default_dynamic_fields(self):
-        """
-        Create default dynamic field records for existing indicators
-        """
-        # Get all active indicators
-        indicators = self.env['hr.efficiency.indicator'].search([('active', '=', True)], order='sequence')
-        
-        for indicator in indicators:
-            # Check if field already exists in ir.model.fields
-            field_name = indicator.name.lower().replace(' ', '_')
-            field_name = field_name.replace('í', 'i').replace('á', 'a').replace('é', 'e').replace('ó', 'o').replace('ú', 'u')
-            field_name = field_name.replace('ñ', 'n')
-            
-            # Ensure it starts with x_ for manual fields
-            if not field_name.startswith('x_'):
-                field_name = 'x_' + field_name
-            
-            existing_field = self.env['ir.model.fields'].search([
-                ('model', '=', 'hr.efficiency'),
-                ('name', '=', field_name)
-            ], limit=1)
-            
-            if not existing_field:
-                # Create field in ir.model.fields (like Studio does)
-                self.env['ir.model.fields'].create({
-                    'name': field_name,
-                    'model': 'hr.efficiency',
-                    'model_id': self.env['ir.model'].search([('model', '=', 'hr.efficiency')], limit=1).id,
-                    'ttype': 'float',
-                    'field_description': indicator.name,
-                    'state': 'manual',  # This is the key - it makes it a custom field
-                    'store': True,
-                    'compute': '_compute_indicators',
-                })
-
-
-    
-    def _calculate_overall_efficiency(self, record):
-        """
-        Calculate overall efficiency based on configured indicators
-        """
-        indicators = self.env['hr.efficiency.indicator'].search([('active', '=', True)], order='sequence')
-        
-        if not indicators:
-            # Default calculation if no indicators configured
-            return 0.0
-        
-        total_weight = 0
-        weighted_sum = 0
-        
-        efficiency_data = {
-            'available_hours': record.available_hours,
-            'planned_hours': record.planned_hours,
-            'planned_billable_hours': record.planned_billable_hours,
-            'planned_non_billable_hours': record.planned_non_billable_hours,
-            'actual_billable_hours': record.actual_billable_hours,
-            'actual_non_billable_hours': record.actual_non_billable_hours,
-        }
-        
-        for indicator in indicators:
-            if indicator.weight > 0:
-                indicator_value = indicator.evaluate_formula(efficiency_data)
-                weighted_sum += indicator_value * indicator.weight
-                total_weight += indicator.weight
-        
-        if total_weight > 0:
-            return float_round(weighted_sum / total_weight, 2)
-        else:
-            return 0.0
-
-    @api.depends('employee_id', 'month_year')
-    def _compute_display_name(self):
-        for record in self:
-            if record.employee_id and record.month_year:
-                record.display_name = f"{record.employee_id.name} - {record.month_year}"
-            else:
-                record.display_name = "New Efficiency Record"
-
-    @api.model
-    def _calculate_employee_efficiency(self, employee, month_year):
-        """
-        Calculate efficiency for a specific employee and month
-        """
-        # Parse month_year (format: YYYY-MM)
-        try:
-            year, month = month_year.split('-')
-            start_date = date(int(year), int(month), 1)
-            end_date = (start_date + relativedelta(months=1)) - relativedelta(days=1)
-        except ValueError:
-            raise UserError(_("Invalid month_year format. Expected: YYYY-MM"))
-
-        # Calculate available hours (considering holidays and time off)
-        available_hours = self._calculate_available_hours(employee, start_date, end_date)
-        
-        # Calculate planned hours
-        planned_hours, planned_billable_hours, planned_non_billable_hours = self._calculate_planned_hours(employee, start_date, end_date)
-        
-        # Calculate actual hours
-        actual_billable_hours, actual_non_billable_hours = self._calculate_actual_hours(employee, start_date, end_date)
-        
-        return {
-            'month_year': month_year,
-            'employee_id': employee.id,
-            'company_id': employee.company_id.id,
-            'available_hours': available_hours,
-            'planned_hours': planned_hours,
-            'planned_billable_hours': planned_billable_hours,
-            'planned_non_billable_hours': planned_non_billable_hours,
-            'actual_billable_hours': actual_billable_hours,
-            'actual_non_billable_hours': actual_non_billable_hours,
-        }
-
-    def _calculate_available_hours(self, employee, start_date, end_date):
-        """
-        Calculate available hours considering holidays and time off
-        """
-        if not employee.resource_calendar_id:
-            return 0.0
-        
-        # Convert dates to datetime for the method
-        start_datetime = datetime.combine(start_date, datetime.min.time())
-        end_datetime = datetime.combine(end_date, datetime.max.time())
-        
-        # Get working hours from calendar
-        work_hours_data = employee._list_work_time_per_day(start_datetime, end_datetime)
-        total_work_hours = sum(hours for _, hours in work_hours_data[employee.id])
-        
-        # Subtract hours from approved time off
-        time_off_hours = self._get_time_off_hours(employee, start_date, end_date)
-        
-        return max(0.0, total_work_hours - time_off_hours)
-
-    def _get_time_off_hours(self, employee, start_date, end_date):
-        """
-        Get hours from approved time off requests
-        """
-        # Get approved time off requests
-        leaves = self.env['hr.leave'].search([
-            ('employee_id', '=', employee.id),
-            ('state', '=', 'validate'),
-            ('date_from', '<=', end_date),
-            ('date_to', '>=', start_date),
-        ])
-        
-        total_hours = 0.0
-        for leave in leaves:
-            # Calculate overlap with the month
-            overlap_start = max(leave.date_from.date(), start_date)
-            overlap_end = min(leave.date_to.date(), end_date)
-            
-            if overlap_start <= overlap_end:
-                # Get hours for the overlap period
-                overlap_start_dt = datetime.combine(overlap_start, datetime.min.time())
-                overlap_end_dt = datetime.combine(overlap_end, datetime.max.time())
-                work_hours_data = employee._list_work_time_per_day(overlap_start_dt, overlap_end_dt)
-                total_hours += sum(hours for _, hours in work_hours_data[employee.id])
-        
-        return total_hours
-
-    def _calculate_planned_hours(self, employee, start_date, end_date):
-        """
-        Calculate planned hours from planning module
-        """
-        # Get planning slots for the employee in the date range
-        planning_slots = self.env['planning.slot'].search([
-            ('employee_id', '=', employee.id),
-            ('start_datetime', '>=', datetime.combine(start_date, datetime.min.time())),
-            ('end_datetime', '<=', datetime.combine(end_date, datetime.max.time())),
-            ('state', 'in', ['draft', 'published']),
-        ])
-        
-        total_planned = 0.0
-        total_billable = 0.0
-        total_non_billable = 0.0
-        
-        for slot in planning_slots:
-            hours = slot.allocated_hours or 0.0
-            
-            # Check if the slot is linked to a billable project
-            if slot.project_id and slot.project_id.allow_billable:
-                total_billable += hours
-            else:
-                total_non_billable += hours
-            
-            total_planned += hours
-        
-        return total_planned, total_billable, total_non_billable
-
-    def _calculate_actual_hours(self, employee, start_date, end_date):
-        """
-        Calculate actual hours from timesheets
-        """
-        # Get timesheets for the employee in the date range (excluding time off)
-        timesheets = self.env['account.analytic.line'].search([
-            ('employee_id', '=', employee.id),
-            ('date', '>=', start_date),
-            ('date', '<=', end_date),
-            ('project_id', '!=', False),  # Only project timesheets
-            ('holiday_id', '=', False),   # Exclude time off timesheets
-        ])
-        
-        total_billable = 0.0
-        total_non_billable = 0.0
-        
-        for timesheet in timesheets:
-            hours = timesheet.unit_amount or 0.0
-            
-            # Additional filter: exclude time off tasks
-            if timesheet.task_id and timesheet.task_id.name and 'time off' in timesheet.task_id.name.lower():
-                continue  # Skip time off tasks
-            
-            # Additional filter: exclude time off from name
-            if timesheet.name and 'tiempo personal' in timesheet.name.lower():
-                continue  # Skip personal time entries
-            
-            # Check if the project is billable
-            if timesheet.project_id and timesheet.project_id.allow_billable:
-                total_billable += hours
-            else:
-                total_non_billable += hours
-        
-        return total_billable, total_non_billable
-
-    @api.model
-    def calculate_efficiency_for_period(self, start_month=None, end_month=None):
-        """
-        Calculate efficiency for all employees for a given period
-        """
-        if not start_month:
-            # Default: last 3 months and next 6 months
-            current_date = date.today()
-            start_month = (current_date - relativedelta(months=3)).strftime('%Y-%m')
-            end_month = (current_date + relativedelta(months=6)).strftime('%Y-%m')
-        
-        # Generate list of months
-        months = self._generate_month_list(start_month, end_month)
-        
-        # Get all active employees
-        employees = self.env['hr.employee'].search([('active', '=', True)])
-        
-        created_records = []
-        
-        for employee in employees:
-            for month in months:
-                # Calculate efficiency data
-                efficiency_data = self._calculate_employee_efficiency(employee, month)
-                
-                # Check if there are changes compared to the latest record
-                latest_record = self.search([
-                    ('employee_id', '=', employee.id),
-                    ('month_year', '=', month),
-                    ('company_id', '=', employee.company_id.id),
-                ], order='calculation_date desc', limit=1)
-                
-                has_changes = False
-                if latest_record:
-                    # Compare current data with latest record
-                    fields_to_compare = [
-                        'available_hours', 'planned_hours', 'planned_billable_hours',
-                        'planned_non_billable_hours', 'actual_billable_hours',
-                        'actual_non_billable_hours'
-                    ]
-                    
-                    for field in fields_to_compare:
-                        if abs(efficiency_data[field] - latest_record[field]) > 0.01:  # Tolerance for floating point
-                            has_changes = True
-                            break
-                else:
-                    # No previous record exists, so this is a change
-                    has_changes = True
-                
-                # Only create new record if there are changes
-                if has_changes:
-                    # Archive existing records for this employee and month
-                    existing_records = self.search([
-                        ('employee_id', '=', employee.id),
-                        ('month_year', '=', month),
-                        ('company_id', '=', employee.company_id.id),
-                    ])
-                    if existing_records:
-                        existing_records.write({'active': False})
-                    
-                    # Create new record
-                    new_record = self.create(efficiency_data)
-                    created_records.append(new_record)
-        
-        return {
-            'created': len(created_records),
-            'updated': 0,  # No longer updating existing records
-            'total_processed': len(created_records),
-        }
-    
-    @api.model
-    def _init_dynamic_system(self):
-        """
-        Initialize dynamic fields and views when module is installed
-        """
-        import logging
-        _logger = logging.getLogger(__name__)
-        
-        try:
-            _logger.info("Starting dynamic system initialization...")
-            
-            # Step 1: Create dynamic field records for existing indicators
-            self._create_default_dynamic_fields()
-            _logger.info("Default dynamic fields created successfully")
-            
-            # Step 2: Dynamic fields are created via hr.efficiency.dynamic.field model
-            _logger.info("Dynamic fields creation handled by hr.efficiency.dynamic.field model")
-            
-            # Step 3: Update views
-            self._update_views_with_dynamic_fields()
-            _logger.info("Views updated successfully")
-            
-            # Step 4: Force recompute of existing records
-            records = self.search([])
-            if records:
-                records._invalidate_cache()
-                _logger.info(f"Invalidated cache for {len(records)} records")
-            
-            _logger.info("Dynamic system initialization completed successfully")
-            
-        except Exception as e:
-            _logger.error(f"Error during dynamic system initialization: {str(e)}")
-            raise
-    
-    @api.model
-    def _update_views_with_dynamic_fields(self):
-        """
-        Update inherited views to include dynamic fields after module is loaded
-        """
-        import logging
-        _logger = logging.getLogger(__name__)
-        
-        try:
-            # Get all manual fields for this model (like Studio does)
-            manual_fields = self.env['ir.model.fields'].search([
-                ('model', '=', 'hr.efficiency'),
-                ('state', '=', 'manual'),
-                ('ttype', '=', 'float')
-            ], order='id')
-            
-            _logger.info(f"Found {len(manual_fields)} manual fields to add to views")
-            
-            # Build dynamic fields XML for list view
-            dynamic_fields_xml = ''
-            for field in manual_fields:
-                field_xml = f'<field name="{field.name}" widget="percentage" optional="hide"'
-                
-                # Add decorations based on indicator thresholds
-                indicator = self.env['hr.efficiency.indicator'].search([
-                    ('name', 'ilike', field.field_description)
-                ], limit=1)
-                
-                if indicator:
-                    if indicator.color_threshold_green:
-                        field_xml += f' decoration-success="{field.name} &gt;= {indicator.color_threshold_green}"'
-                    if indicator.color_threshold_yellow:
-                        field_xml += f' decoration-warning="{field.name} &gt;= {indicator.color_threshold_yellow} and {field.name} &lt; {indicator.color_threshold_green}"'
-                    field_xml += f' decoration-danger="{field.name} &lt; {indicator.color_threshold_yellow}"'
-                
-                field_xml += '/>'
-                dynamic_fields_xml += field_xml
-            
-            # Update inherited list view
-            inherited_list_view = self.env.ref('hr_efficiency.view_hr_efficiency_list_inherited', raise_if_not_found=False)
-            if inherited_list_view:
-                arch = inherited_list_view.arch
-                comment = '<!-- Dynamic indicator fields will be added here -->'
-                if comment in arch:
-                    arch = arch.replace(comment, dynamic_fields_xml)
-                    inherited_list_view.write({'arch': arch})
-                    _logger.info(f"Updated inherited list view with {len(manual_fields)} dynamic fields")
-                else:
-                    _logger.warning("Comment not found in inherited list view")
-            
-            # Build dynamic fields XML for form view
-            form_dynamic_fields_xml = ''
-            for field in manual_fields:
-                field_xml = f'<field name="{field.name}" widget="percentage"/>'
-                form_dynamic_fields_xml += field_xml
-            
-            # Update inherited form view
-            inherited_form_view = self.env.ref('hr_efficiency.view_hr_efficiency_form_inherited', raise_if_not_found=False)
-            if inherited_form_view:
-                arch = inherited_form_view.arch
-                comment = '<!-- Dynamic indicator fields will be added here -->'
-                if comment in arch:
-                    arch = arch.replace(comment, form_dynamic_fields_xml)
-                    inherited_form_view.write({'arch': arch})
-                    _logger.info(f"Updated inherited form view with dynamic fields")
-                else:
-                    _logger.warning("Comment not found in inherited form view")
-                    
-        except Exception as e:
-            _logger.error(f"Error updating views with dynamic fields: {str(e)}")
-            raise
-
-    def _generate_month_list(self, start_month, end_month):
-        """
-        Generate list of months between start_month and end_month (inclusive)
-        """
-        months = []
-        current = datetime.strptime(start_month, '%Y-%m')
-        end = datetime.strptime(end_month, '%Y-%m')
-        
-        while current <= end:
-            months.append(current.strftime('%Y-%m'))
-            current = current + relativedelta(months=1)
-        
-        return months
-
-    @api.model
-    def _cron_calculate_efficiency(self):
-        """
-        Cron job to automatically calculate efficiency
-        """
-        self.calculate_efficiency_for_period()
-
-    @api.model
-    def _get_month_filter_options(self):
-        """
-        Get dynamic month filter options for search view
-        Returns a list of tuples (month_year, display_name) for the last 2, current, and next 2 months
-        """
-        current_date = date.today()
-        months = []
-        
-        # Last 2 months
-        for i in range(2, 0, -1):
-            month_date = current_date - relativedelta(months=i)
-            month_year = month_date.strftime('%Y-%m')
-            month_name = month_date.strftime('%B %Y')  # e.g., "August 2024"
-            months.append((month_year, month_name))
-        
-        # Current month
-        current_month_year = current_date.strftime('%Y-%m')
-        current_month_name = current_date.strftime('%B %Y')
-        months.append((current_month_year, current_month_name))
-        
-        # Next 2 months
-        for i in range(1, 3):
-            month_date = current_date + relativedelta(months=i)
-            month_year = month_date.strftime('%Y-%m')
-            month_name = month_date.strftime('%B %Y')
-            months.append((month_year, month_name))
-        
-        return months

+ 0 - 145
hr_efficiency/models/hr_efficiency_dynamic_field.py

@@ -1,145 +0,0 @@
-from odoo import models, fields, api, _
-from odoo.exceptions import ValidationError
-
-
-class HrEfficiencyDynamicField(models.Model):
-    _name = 'hr.efficiency.dynamic.field'
-    _description = 'Dynamic Field Configuration'
-    _order = 'sequence, name'
-
-    name = fields.Char('Field Name', required=True, help='Technical field name (e.g., planning_efficiency)')
-    label = fields.Char('Field Label', required=True, help='Display name (e.g., Planning Efficiency)')
-    indicator_id = fields.Many2one('hr.efficiency.indicator', 'Related Indicator', required=True, ondelete='cascade')
-    
-    # Display settings
-    sequence = fields.Integer('Sequence', default=10, help='Order in which fields appear')
-    active = fields.Boolean('Active', default=True, help='Whether this field is visible')
-    
-    # View settings
-    show_in_list = fields.Boolean('Show in List View', default=True)
-    show_in_form = fields.Boolean('Show in Form View', default=True)
-    show_in_search = fields.Boolean('Show in Search View', default=False)
-    
-    # Formatting
-    widget = fields.Selection([
-        ('percentage', 'Percentage'),
-        ('float_time', 'Float Time'),
-        ('number', 'Number'),
-        ('text', 'Text'),
-    ], string='Widget', default='percentage', help='How to display the field')
-    
-    # Styling
-    decoration_success = fields.Float('Success Threshold', help='Value above which to show green')
-    decoration_warning = fields.Float('Warning Threshold', help='Value above which to show yellow')
-    decoration_danger = fields.Float('Danger Threshold', help='Value below which to show red')
-    
-    # Computed fields
-    field_technical_name = fields.Char('Technical Field Name', compute='_compute_technical_name', store=True)
-    
-    @api.depends('name')
-    def _compute_technical_name(self):
-        for record in self:
-            if record.name:
-                # Convert to valid field name
-                field_name = record.name.lower()
-                field_name = field_name.replace(' ', '_').replace('-', '_').replace('(', '').replace(')', '')
-                field_name = field_name.replace('í', 'i').replace('á', 'a').replace('é', 'e').replace('ó', 'o').replace('ú', 'u')
-                field_name = field_name.replace('ñ', 'n')
-                
-                # Ensure it starts with a letter
-                if not field_name[0].isalpha():
-                    field_name = 'indicator_' + field_name
-                
-                record.field_technical_name = field_name
-    
-    @api.constrains('name')
-    def _check_name_unique(self):
-        for record in self:
-            if self.search_count([('name', '=', record.name), ('id', '!=', record.id)]) > 0:
-                raise ValidationError(_('Field name must be unique'))
-    
-    @api.model_create_multi
-    def create(self, vals_list):
-        """Create dynamic fields and update model"""
-        records = super().create(vals_list)
-        self._update_model_fields()
-        return records
-    
-    def write(self, vals):
-        """Update dynamic field and update model"""
-        result = super().write(vals)
-        self._update_model_fields()
-        return result
-    
-    def unlink(self):
-        """Delete dynamic fields and update model"""
-        result = super().unlink()
-        self._update_model_fields()
-        return result
-    
-    @api.model
-    def _update_model_fields(self):
-        """Update the hr.efficiency model with dynamic fields"""
-        efficiency_model = self.env['hr.efficiency']
-        
-        # Get all active dynamic fields
-        dynamic_fields = self.search([('active', '=', True)], order='sequence')
-        
-        # Clear existing dynamic fields from model (only those starting with 'indicator_')
-        for field_name in list(efficiency_model._fields.keys()):
-            if field_name.startswith('indicator_'):
-                if hasattr(efficiency_model.__class__, field_name):
-                    try:
-                        delattr(efficiency_model.__class__, field_name)
-                    except AttributeError:
-                        pass  # Field might not exist, ignore
-        
-        # Add new dynamic fields
-        for dynamic_field in dynamic_fields:
-            field_name = dynamic_field.field_technical_name
-            
-
-            
-            # Create field with appropriate widget
-            field_kwargs = {
-                'string': dynamic_field.label,
-                'compute': '_compute_indicators',
-                'store': True,
-                'help': f'Dynamic indicator: {dynamic_field.indicator_id.name}',
-            }
-            
-            # Add widget if specified
-            if dynamic_field.widget:
-                field_kwargs['widget'] = dynamic_field.widget
-            
-            # Create the field
-            field = fields.Float(**field_kwargs)
-            setattr(efficiency_model.__class__, field_name, field)
-    
-    def action_toggle_visibility(self):
-        """Toggle field visibility"""
-        for record in self:
-            record.active = not record.active
-        self._update_model_fields()
-    
-    def action_move_up(self):
-        """Move field up in sequence"""
-        for record in self:
-            prev_record = self.search([
-                ('sequence', '<', record.sequence),
-                ('active', '=', True)
-            ], order='sequence desc', limit=1)
-            
-            if prev_record:
-                record.sequence, prev_record.sequence = prev_record.sequence, record.sequence
-    
-    def action_move_down(self):
-        """Move field down in sequence"""
-        for record in self:
-            next_record = self.search([
-                ('sequence', '>', record.sequence),
-                ('active', '=', True)
-            ], order='sequence', limit=1)
-            
-            if next_record:
-                record.sequence, next_record.sequence = next_record.sequence, record.sequence

+ 0 - 165
hr_efficiency/models/hr_efficiency_indicator.py

@@ -1,165 +0,0 @@
-from odoo import models, fields, api, _
-from odoo.exceptions import ValidationError
-
-
-class HrEfficiencyIndicator(models.Model):
-    _name = 'hr.efficiency.indicator'
-    _description = 'Efficiency Indicator Configuration'
-    _order = 'sequence, name'
-
-    name = fields.Char('Indicator Name', required=True)
-    sequence = fields.Integer('Sequence', default=10)
-    active = fields.Boolean('Active', default=True)
-    
-    # Formula configuration
-    formula = fields.Text('Formula', required=True, help="""
-    Use the following variables in your formula:
-    - available_hours: Available hours for the employee
-    - planned_hours: Total planned hours
-    - planned_billable_hours: Planned hours on billable projects
-    - planned_non_billable_hours: Planned hours on non-billable projects
-    - actual_billable_hours: Actual hours worked on billable projects
-    - actual_non_billable_hours: Actual hours worked on non-billable projects
-    
-    Examples:
-    - (planned_hours / available_hours) * 100  # Planning efficiency
-    - ((actual_billable_hours + actual_non_billable_hours) / planned_hours) * 100  # Time tracking efficiency
-    """)
-    
-    # Target and thresholds
-    target_percentage = fields.Float('Target %', default=90.0, help='Target percentage for this indicator')
-    weight = fields.Float('Weight %', default=50.0, help='Weight of this indicator in the overall efficiency calculation')
-    
-    # Display settings
-    description = fields.Text('Description', help='Description of what this indicator measures')
-    color_threshold_green = fields.Float('Green Threshold', default=90.0, help='Percentage above which to show green')
-    color_threshold_yellow = fields.Float('Yellow Threshold', default=70.0, help='Percentage above which to show yellow')
-    
-    @api.constrains('weight')
-    def _check_weight(self):
-        for record in self:
-            if record.weight < 0 or record.weight > 100:
-                raise ValidationError(_('Weight must be between 0 and 100'))
-    
-    @api.constrains('target_percentage')
-    def _check_target_percentage(self):
-        for record in self:
-            if record.target_percentage < 0 or record.target_percentage > 100:
-                raise ValidationError(_('Target percentage must be between 0 and 100'))
-    
-    @api.model_create_multi
-    def create(self, vals_list):
-        """Create indicators and create dynamic fields"""
-        records = super().create(vals_list)
-        # Create dynamic fields for all indicators
-        for record in records:
-            self._create_dynamic_field(record)
-        return records
-    
-    def write(self, vals):
-        """Update indicator and update dynamic field"""
-        result = super().write(vals)
-        # Update dynamic field for this indicator
-        for record in self:
-            self._update_dynamic_field(record)
-        return result
-    
-    def unlink(self):
-        """Delete indicator and delete dynamic field"""
-        # Delete associated dynamic fields
-        for record in self:
-            dynamic_field = self.env['hr.efficiency.dynamic.field'].search([
-                ('indicator_id', '=', record.id)
-            ])
-            if dynamic_field:
-                dynamic_field.unlink()
-        result = super().unlink()
-        return result
-    
-    def _create_dynamic_field(self, indicator):
-        """Create a dynamic field for the indicator"""
-        # Check if dynamic field already exists
-        existing_field = self.env['hr.efficiency.dynamic.field'].search([
-            ('indicator_id', '=', indicator.id)
-        ])
-        
-        if not existing_field:
-            # Create dynamic field
-            field_name = indicator.name.lower().replace(' ', '_').replace('-', '_')
-            field_name = field_name.replace('í', 'i').replace('á', 'a').replace('é', 'e').replace('ó', 'o').replace('ú', 'u')
-            field_name = field_name.replace('ñ', 'n')
-            
-            # Ensure it starts with a letter
-            if not field_name[0].isalpha():
-                field_name = 'indicator_' + field_name
-            
-            self.env['hr.efficiency.dynamic.field'].create({
-                'name': field_name,
-                'label': indicator.name,
-                'indicator_id': indicator.id,
-                'sequence': indicator.sequence,
-                'active': indicator.active,
-                'show_in_list': True,
-                'show_in_form': True,
-                'show_in_search': False,
-                'widget': 'percentage',
-                'decoration_success': indicator.color_threshold_green,
-                'decoration_warning': indicator.color_threshold_yellow,
-                'decoration_danger': 0.0,
-            })
-    
-    def _update_dynamic_field(self, indicator):
-        """Update the dynamic field for the indicator"""
-        dynamic_field = self.env['hr.efficiency.dynamic.field'].search([
-            ('indicator_id', '=', indicator.id)
-        ])
-        
-        if dynamic_field:
-            dynamic_field.write({
-                'label': indicator.name,
-                'active': indicator.active,
-                'decoration_success': indicator.color_threshold_green,
-                'decoration_warning': indicator.color_threshold_yellow,
-            })
-    
-    def evaluate_formula(self, efficiency_data):
-        """
-        Evaluate the formula using the provided efficiency data
-        """
-        try:
-            # Create a safe environment for formula evaluation
-            safe_dict = {
-                'available_hours': efficiency_data.get('available_hours', 0),
-                'planned_hours': efficiency_data.get('planned_hours', 0),
-                'planned_billable_hours': efficiency_data.get('planned_billable_hours', 0),
-                'planned_non_billable_hours': efficiency_data.get('planned_non_billable_hours', 0),
-                'actual_billable_hours': efficiency_data.get('actual_billable_hours', 0),
-                'actual_non_billable_hours': efficiency_data.get('actual_non_billable_hours', 0),
-            }
-            
-            # Add math functions for safety
-            import math
-            safe_dict.update({
-                'abs': abs,
-                'min': min,
-                'max': max,
-                'round': round,
-            })
-            
-            # Evaluate the formula
-            result = eval(self.formula, {"__builtins__": {}}, safe_dict)
-            return result
-        except Exception as e:
-            _logger.error(f"Error evaluating formula for indicator {self.name}: {e}")
-            return 0.0
-    
-    def get_color_class(self, percentage):
-        """
-        Return the CSS color class based on the percentage
-        """
-        if percentage >= self.color_threshold_green:
-            return 'text-success'
-        elif percentage >= self.color_threshold_yellow:
-            return 'text-warning'
-        else:
-            return 'text-danger'

+ 0 - 4
hr_efficiency/report/__init__.py

@@ -1,4 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from . import hr_efficiency_report

+ 0 - 89
hr_efficiency/report/hr_efficiency_report.py

@@ -1,89 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import api, fields, models
-from odoo.tools.sql import drop_view_if_exists, SQL
-
-
-class HrEfficiencyReport(models.Model):
-    _name = "hr.efficiency.report"
-    _description = "HR Efficiency Analysis Report"
-    _auto = False
-
-    month_year = fields.Char('Month Year', readonly=True)
-    employee_id = fields.Many2one('hr.employee', 'Employee', readonly=True)
-    company_id = fields.Many2one('res.company', 'Company', readonly=True)
-    department_id = fields.Many2one('hr.department', 'Department', readonly=True)
-    job_title = fields.Char('Job Title', readonly=True)
-    
-    # Hours
-    available_hours = fields.Float('Available Hours', readonly=True)
-    planned_hours = fields.Float('Planned Hours', readonly=True)
-    planned_billable_hours = fields.Float('Planned Billable Hours', readonly=True)
-    planned_non_billable_hours = fields.Float('Planned Non-Billable Hours', readonly=True)
-    actual_billable_hours = fields.Float('Actual Billable Hours', readonly=True)
-    actual_non_billable_hours = fields.Float('Actual Non-Billable Hours', readonly=True)
-    total_actual_hours = fields.Float('Total Actual Hours', readonly=True)
-    
-    # Efficiency rates
-    efficiency_rate = fields.Float('Efficiency Rate (%)', readonly=True)
-    billable_efficiency_rate = fields.Float('Billable Efficiency Rate (%)', readonly=True)
-    
-    # Computed fields for analysis
-    utilization_rate = fields.Float('Utilization Rate (%)', readonly=True, help="Percentage of available hours that were planned")
-    billable_utilization_rate = fields.Float('Billable Utilization Rate (%)', readonly=True, help="Percentage of available hours that were planned on billable projects")
-
-    @property
-    def _table_query(self):
-        return """
-            SELECT 
-                e.id,
-                e.month_year,
-                e.employee_id,
-                e.company_id,
-                emp.department_id,
-                emp.job_title,
-                e.available_hours,
-                e.planned_hours,
-                e.planned_billable_hours,
-                e.planned_non_billable_hours,
-                e.actual_billable_hours,
-                e.actual_non_billable_hours,
-                e.total_actual_hours,
-                e.efficiency_rate,
-                e.billable_efficiency_rate,
-                CASE 
-                    WHEN e.available_hours > 0 THEN (e.planned_hours / e.available_hours) * 100
-                    ELSE 0 
-                END AS utilization_rate,
-                CASE 
-                    WHEN e.available_hours > 0 THEN (e.planned_billable_hours / e.available_hours) * 100
-                    ELSE 0 
-                END AS billable_utilization_rate
-            FROM hr_efficiency e
-            LEFT JOIN hr_employee emp ON e.employee_id = emp.id
-        """
-
-    @api.model
-    def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
-        """
-        Override read_group to handle computed fields
-        """
-        res = super().read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
-        
-        # Recalculate computed fields for grouped data
-        for group in res:
-            if '__domain' in group:
-                records = self.search(group['__domain'])
-                if records:
-                    # Calculate averages for efficiency rates
-                    if 'efficiency_rate' in fields:
-                        group['efficiency_rate'] = sum(r.efficiency_rate for r in records) / len(records)
-                    if 'billable_efficiency_rate' in fields:
-                        group['billable_efficiency_rate'] = sum(r.billable_efficiency_rate for r in records) / len(records)
-                    if 'utilization_rate' in fields:
-                        group['utilization_rate'] = sum(r.utilization_rate for r in records) / len(records)
-                    if 'billable_utilization_rate' in fields:
-                        group['billable_utilization_rate'] = sum(r.billable_utilization_rate for r in records) / len(records)
-        
-        return res

+ 0 - 90
hr_efficiency/report/hr_efficiency_report_views.xml

@@ -1,90 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
-    <data>
-        <!-- Pivot View -->
-        <record id="view_hr_efficiency_report_pivot" model="ir.ui.view">
-            <field name="name">hr.efficiency.report.pivot</field>
-            <field name="model">hr.efficiency.report</field>
-            <field name="arch" type="xml">
-                <pivot string="Efficiency Analysis" sample="1">
-                    <field name="month_year" interval="month" type="row"/>
-                    <field name="employee_id" type="row"/>
-                    <field name="efficiency_rate" type="measure" widget="percentage"/>
-                    <field name="billable_efficiency_rate" type="measure" widget="percentage"/>
-                    <field name="utilization_rate" type="measure" widget="percentage"/>
-                    <field name="billable_utilization_rate" type="measure" widget="percentage"/>
-                    <field name="available_hours" type="measure" widget="float_time"/>
-                    <field name="planned_hours" type="measure" widget="float_time"/>
-                    <field name="total_actual_hours" type="measure" widget="float_time"/>
-                </pivot>
-            </field>
-        </record>
-
-        <!-- Graph View -->
-        <record id="view_hr_efficiency_report_graph" model="ir.ui.view">
-            <field name="name">hr.efficiency.report.graph</field>
-            <field name="model">hr.efficiency.report</field>
-            <field name="arch" type="xml">
-                <graph string="Efficiency Analysis" type="bar" sample="1">
-                    <field name="month_year" interval="month"/>
-                    <field name="employee_id"/>
-                    <field name="efficiency_rate" type="measure"/>
-                    <field name="billable_efficiency_rate" type="measure"/>
-                </graph>
-            </field>
-        </record>
-
-        <!-- Search View -->
-        <record id="view_hr_efficiency_report_search" model="ir.ui.view">
-            <field name="name">hr.efficiency.report.search</field>
-            <field name="model">hr.efficiency.report</field>
-            <field name="arch" type="xml">
-                <search string="Search Efficiency Report">
-                    <field name="employee_id"/>
-                    <field name="month_year"/>
-                    <field name="department_id"/>
-                    <field name="company_id" groups="base.group_multi_company"/>
-                    <filter string="High Efficiency" name="high_efficiency" domain="[('efficiency_rate', '>=', 90)]"/>
-                    <filter string="Medium Efficiency" name="medium_efficiency" domain="[('efficiency_rate', '>=', 70), ('efficiency_rate', '&lt;', 90)]"/>
-                    <filter string="Low Efficiency" name="low_efficiency" domain="[('efficiency_rate', '&lt;', 70)]"/>
-                    <filter string="High Billable Efficiency" name="high_billable_efficiency" domain="[('billable_efficiency_rate', '>=', 90)]"/>
-                    <group expand="0" string="Group By">
-                        <filter string="Employee" name="group_employee" context="{'group_by': 'employee_id'}"/>
-                        <filter string="Month" name="group_month" context="{'group_by': 'month_year'}"/>
-                        <filter string="Department" name="group_department" context="{'group_by': 'department_id'}"/>
-                        <filter string="Company" name="group_company" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
-                    </group>
-                </search>
-            </field>
-        </record>
-
-        <!-- Action -->
-        <record id="action_hr_efficiency_report" model="ir.actions.act_window">
-            <field name="name">Efficiency Analysis</field>
-            <field name="res_model">hr.efficiency.report</field>
-            <field name="view_mode">pivot,graph</field>
-            <field name="search_view_id" ref="view_hr_efficiency_report_search"/>
-            <field name="context">{
-                'pivot_row_groupby': ['month_year:month'],
-                'pivot_measures': ['efficiency_rate', 'billable_efficiency_rate', 'utilization_rate'],
-                'graph_groupbys': ['month_year:month', 'employee_id'],
-                'group_by': [],
-            }</field>
-            <field name="help" type="html">
-                <p class="o_view_nocontent_empty_folder">
-                    No efficiency data found!
-                </p><p>
-                    Generate efficiency data by running the calculation wizard or wait for the automatic weekly calculation.
-                </p>
-            </field>
-        </record>
-
-        <!-- Menu -->
-        <menuitem id="menu_hr_efficiency_report"
-                  name="Efficiency Analysis"
-                  parent="hr.menu_hr_root"
-                  action="action_hr_efficiency_report"
-                  sequence="52"
-                  groups="hr.group_hr_user"/>
-    </data>
-</odoo>

+ 0 - 13
hr_efficiency/security/ir.model.access.csv

@@ -1,13 +0,0 @@
-id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_hr_efficiency_user,hr.efficiency.user,model_hr_efficiency,hr.group_hr_user,1,1,1,0
-access_hr_efficiency_manager,hr.efficiency.manager,model_hr_efficiency,hr.group_hr_manager,1,1,1,1
-access_hr_efficiency_calculation_wizard_user,hr.efficiency.calculation.wizard.user,model_hr_efficiency_calculation_wizard,hr.group_hr_user,1,1,1,0
-access_hr_efficiency_calculation_wizard_manager,hr.efficiency.calculation.wizard.manager,model_hr_efficiency_calculation_wizard,hr.group_hr_manager,1,1,1,1
-access_hr_efficiency_report_user,hr.efficiency.report.user,model_hr_efficiency_report,hr.group_hr_user,1,0,0,0
-access_hr_efficiency_report_manager,hr.efficiency.report.manager,model_hr_efficiency_report,hr.group_hr_manager,1,0,0,0
-access_hr_efficiency_indicator_user,hr.efficiency.indicator.user,model_hr_efficiency_indicator,hr.group_hr_user,1,1,1,0
-access_hr_efficiency_indicator_manager,hr.efficiency.indicator.manager,model_hr_efficiency_indicator,hr.group_hr_manager,1,1,1,1
-access_hr_efficiency_field_visibility_wizard_user,hr.efficiency.field.visibility.wizard.user,model_hr_efficiency_field_visibility_wizard,hr.group_hr_user,1,0,0,0
-access_hr_efficiency_field_visibility_wizard_manager,hr.efficiency.field.visibility.wizard.manager,model_hr_efficiency_field_visibility_wizard,hr.group_hr_manager,1,1,1,1
-access_hr_efficiency_dynamic_field_user,hr.efficiency.dynamic.field.user,model_hr_efficiency_dynamic_field,hr.group_hr_user,1,1,1,0
-access_hr_efficiency_dynamic_field_manager,hr.efficiency.dynamic.field.manager,model_hr_efficiency_dynamic_field,hr.group_hr_manager,1,1,1,1

+ 0 - 116
hr_efficiency/views/hr_efficiency_dynamic_field_views.xml

@@ -1,116 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
-    <data>
-        <!-- List View -->
-        <record id="view_hr_efficiency_dynamic_field_list" model="ir.ui.view">
-            <field name="name">hr.efficiency.dynamic.field.list</field>
-            <field name="model">hr.efficiency.dynamic.field</field>
-            <field name="arch" type="xml">
-                <list string="Dynamic Fields">
-                    <field name="sequence" widget="handle"/>
-                    <field name="name"/>
-                    <field name="label"/>
-                    <field name="indicator_id"/>
-                    <field name="active"/>
-                    <field name="show_in_list"/>
-                    <field name="show_in_form"/>
-                    <field name="widget"/>
-                    <field name="decoration_success"/>
-                    <field name="decoration_warning"/>
-                    <field name="decoration_danger"/>
-                </list>
-            </field>
-        </record>
-
-        <!-- Form View -->
-        <record id="view_hr_efficiency_dynamic_field_form" model="ir.ui.view">
-            <field name="name">hr.efficiency.dynamic.field.form</field>
-            <field name="model">hr.efficiency.dynamic.field</field>
-            <field name="arch" type="xml">
-                <form string="Dynamic Field Configuration">
-                    <header>
-                        <button name="action_toggle_visibility" string="Toggle Visibility" type="object" class="btn-secondary"/>
-                        <button name="action_move_up" string="Move Up" type="object" class="btn-secondary"/>
-                        <button name="action_move_down" string="Move Down" type="object" class="btn-secondary"/>
-                    </header>
-                    <sheet>
-                        <div class="oe_title">
-                            <h1>
-                                <field name="label" placeholder="Field Label"/>
-                            </h1>
-                        </div>
-                        <group>
-                            <group>
-                                <field name="name"/>
-                                <field name="indicator_id"/>
-                                <field name="sequence"/>
-                                <field name="active"/>
-                            </group>
-                            <group>
-                                <field name="widget"/>
-                                <field name="show_in_list"/>
-                                <field name="show_in_form"/>
-                                <field name="show_in_search"/>
-                            </group>
-                        </group>
-                        <notebook>
-                            <page string="Styling" name="styling">
-                                <group>
-                                    <field name="decoration_success"/>
-                                    <field name="decoration_warning"/>
-                                    <field name="decoration_danger"/>
-                                </group>
-                            </page>
-                            <page string="Technical Info" name="technical">
-                                <group>
-                                    <field name="field_technical_name" readonly="1"/>
-                                </group>
-                            </page>
-                        </notebook>
-                    </sheet>
-                </form>
-            </field>
-        </record>
-
-        <!-- Search View -->
-        <record id="view_hr_efficiency_dynamic_field_search" model="ir.ui.view">
-            <field name="name">hr.efficiency.dynamic.field.search</field>
-            <field name="model">hr.efficiency.dynamic.field</field>
-            <field name="arch" type="xml">
-                <search string="Search Dynamic Fields">
-                    <field name="name"/>
-                    <field name="label"/>
-                    <field name="indicator_id"/>
-                    <filter string="Active" name="active" domain="[('active', '=', True)]"/>
-                    <filter string="Inactive" name="inactive" domain="[('active', '=', False)]"/>
-                    <separator/>
-                    <filter string="Show in List" name="show_in_list" domain="[('show_in_list', '=', True)]"/>
-                    <filter string="Show in Form" name="show_in_form" domain="[('show_in_form', '=', True)]"/>
-                </search>
-            </field>
-        </record>
-
-        <!-- Action -->
-        <record id="action_hr_efficiency_dynamic_field" model="ir.actions.act_window">
-            <field name="name">Dynamic Fields</field>
-            <field name="res_model">hr.efficiency.dynamic.field</field>
-            <field name="view_mode">list,form</field>
-            <field name="context">{}</field>
-            <field name="help" type="html">
-                <p class="o_view_nocontent_smiling_face">
-                    Create your first dynamic field!
-                </p>
-                <p>
-                    Dynamic fields allow you to customize how indicators are displayed in the efficiency views.
-                </p>
-            </field>
-        </record>
-
-        <!-- Menu Item -->
-        <menuitem id="menu_hr_efficiency_dynamic_field"
-                  name="Dynamic Fields"
-                  parent="planning.planning_menu_settings"
-                  action="action_hr_efficiency_dynamic_field"
-                  sequence="15"/>
-    </data>
-</odoo>

+ 0 - 45
hr_efficiency/views/hr_efficiency_dynamic_views.xml

@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
-    <data>
-        <!-- Vista heredada para la lista con campos dinámicos -->
-        <record id="view_hr_efficiency_list_inherited" model="ir.ui.view">
-            <field name="name">hr.efficiency.list.inherited</field>
-            <field name="model">hr.efficiency</field>
-            <field name="inherit_id" ref="hr_efficiency.view_hr_efficiency_tree"/>
-            <field name="arch" type="xml">
-                <xpath expr="//field[@name='total_actual_hours']" position="after">
-                    <!-- Dynamic indicator fields will be added here -->
-                </xpath>
-            </field>
-        </record>
-
-        <!-- Vista heredada para el formulario con campos dinámicos -->
-        <record id="view_hr_efficiency_form_inherited" model="ir.ui.view">
-            <field name="name">hr.efficiency.form.inherited</field>
-            <field name="model">hr.efficiency</field>
-            <field name="inherit_id" ref="hr_efficiency.view_hr_efficiency_form"/>
-            <field name="arch" type="xml">
-                <xpath expr="//field[@name='overall_efficiency']" position="before">
-                    <!-- Dynamic indicator fields will be added here -->
-                </xpath>
-            </field>
-        </record>
-
-        <!-- Actualizar la acción para usar la vista heredada -->
-        <record id="action_hr_efficiency" model="ir.actions.act_window">
-            <field name="name">Employee Efficiency</field>
-            <field name="res_model">hr.efficiency</field>
-            <field name="view_mode">list,form</field>
-            <field name="view_id" ref="view_hr_efficiency_list_inherited"/>
-            <field name="context">{'search_default_current_month': 1}</field>
-            <field name="help" type="html">
-                <p class="o_view_nocontent_smiling_face">
-                    No efficiency records found!
-                </p>
-                <p>
-                    Click on "Calculate Efficiency" to generate efficiency records for employees.
-                </p>
-            </field>
-        </record>
-    </data>
-</odoo>

+ 0 - 85
hr_efficiency/views/hr_efficiency_indicator_views.xml

@@ -1,85 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
-    <!-- Tree View -->
-    <record id="view_hr_efficiency_indicator_tree" model="ir.ui.view">
-        <field name="name">hr.efficiency.indicator.tree</field>
-        <field name="model">hr.efficiency.indicator</field>
-        <field name="arch" type="xml">
-            <list string="Efficiency Indicators">
-                <field name="sequence" widget="handle"/>
-                <field name="name"/>
-                <field name="formula"/>
-                <field name="target_percentage"/>
-                <field name="weight"/>
-                <field name="color_threshold_green"/>
-                <field name="color_threshold_yellow"/>
-                <field name="active"/>
-            </list>
-        </field>
-    </record>
-
-    <!-- Form View -->
-    <record id="view_hr_efficiency_indicator_form" model="ir.ui.view">
-        <field name="name">hr.efficiency.indicator.form</field>
-        <field name="model">hr.efficiency.indicator</field>
-        <field name="arch" type="xml">
-            <form string="Efficiency Indicator">
-                <sheet>
-                    <group>
-                        <group>
-                            <field name="name"/>
-                            <field name="sequence"/>
-                            <field name="active"/>
-                        </group>
-                        <group>
-                            <field name="target_percentage"/>
-                            <field name="weight"/>
-                            <field name="color_threshold_green"/>
-                            <field name="color_threshold_yellow"/>
-                        </group>
-                    </group>
-                    <group>
-                        <field name="description"/>
-                    </group>
-                    <group>
-                        <field name="formula" widget="ace" options="{'mode': 'python'}"/>
-                    </group>
-                </sheet>
-            </form>
-        </field>
-    </record>
-
-    <!-- Search View -->
-    <record id="view_hr_efficiency_indicator_search" model="ir.ui.view">
-        <field name="name">hr.efficiency.indicator.search</field>
-        <field name="model">hr.efficiency.indicator</field>
-        <field name="arch" type="xml">
-            <search string="Search Efficiency Indicators">
-                <field name="name"/>
-                <filter string="Active" name="active" domain="[('active', '=', True)]"/>
-                <filter string="Inactive" name="inactive" domain="[('active', '=', False)]"/>
-            </search>
-        </field>
-    </record>
-
-    <!-- Action -->
-    <record id="action_hr_efficiency_indicator" model="ir.actions.act_window">
-        <field name="name">Efficiency Indicators</field>
-        <field name="res_model">hr.efficiency.indicator</field>
-                        <field name="view_mode">list,form</field>
-        <field name="help" type="html">
-            <p class="o_view_nocontent_empty_folder">
-                No efficiency indicators configured!
-            </p><p>
-                Create efficiency indicators to define how efficiency is calculated.
-            </p>
-        </field>
-    </record>
-
-    <!-- Menu Item -->
-    <menuitem id="menu_hr_efficiency_indicator"
-              name="Efficiency Indicators"
-              parent="planning.planning_menu_settings_config"
-              action="action_hr_efficiency_indicator"
-              sequence="20"/>
-</odoo>

+ 0 - 155
hr_efficiency/views/hr_efficiency_views.xml

@@ -1,155 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
-    <data>
-        <!-- Tree View -->
-        <record id="view_hr_efficiency_tree" model="ir.ui.view">
-            <field name="name">hr.efficiency.tree</field>
-            <field name="model">hr.efficiency</field>
-            <field name="arch" type="xml">
-                <list string="Employee Efficiency" decoration-success="overall_efficiency >= 90" decoration-warning="overall_efficiency >= 70 and overall_efficiency &lt; 90" decoration-danger="overall_efficiency &lt; 70">
-                    <field name="month_year"/>
-                    <field name="employee_id"/>
-                    <field name="company_id" groups="base.group_multi_company"/>
-                    <field name="calculation_date"/>
-                    <field name="available_hours" sum="Total Available"/>
-                    <field name="planned_hours" sum="Total Planned"/>
-                    <field name="planned_billable_hours" sum="Total Planned Billable"/>
-                    <field name="planned_non_billable_hours" sum="Total Planned Non-Billable"/>
-                    <field name="actual_billable_hours" sum="Total Actual Billable"/>
-                    <field name="actual_non_billable_hours" sum="Total Actual Non-Billable"/>
-                    <field name="total_actual_hours" sum="Total Actual"/>
-                    <field name="overall_efficiency"/>
-                </list>
-            </field>
-        </record>
-
-        <!-- Form View -->
-        <record id="view_hr_efficiency_form" model="ir.ui.view">
-            <field name="name">hr.efficiency.form</field>
-            <field name="model">hr.efficiency</field>
-            <field name="arch" type="xml">
-                <form string="Employee Efficiency">
-                    <sheet>
-                        <div class="oe_title">
-                            <h1>
-                                <field name="display_name" readonly="1"/>
-                            </h1>
-                        </div>
-                        <group>
-                            <group>
-                                <field name="month_year"/>
-                                <field name="employee_id"/>
-                                <field name="company_id" groups="base.group_multi_company"/>
-                                <field name="calculation_date"/>
-                            </group>
-                            <group>
-                                <!-- Dynamic indicator fields will be added here -->
-                                <field name="overall_efficiency"/>
-                            </group>
-                        </group>
-                        <notebook>
-                            <page string="Available Hours" name="available">
-                                <group>
-                                    <field name="available_hours" widget="float_time"/>
-                                </group>
-                            </page>
-                            <page string="Planned Hours" name="planned">
-                                <group>
-                                    <field name="planned_hours" widget="float_time"/>
-                                    <field name="planned_billable_hours" widget="float_time"/>
-                                    <field name="planned_non_billable_hours" widget="float_time"/>
-                                </group>
-                            </page>
-                            <page string="Actual Hours" name="actual">
-                                <group>
-                                    <field name="actual_billable_hours" widget="float_time"/>
-                                    <field name="actual_non_billable_hours" widget="float_time"/>
-                                    <field name="total_actual_hours" widget="float_time"/>
-                                </group>
-                            </page>
-                        </notebook>
-                    </sheet>
-                </form>
-            </field>
-        </record>
-
-        <!-- Search View -->
-        <record id="view_hr_efficiency_search" model="ir.ui.view">
-            <field name="name">hr.efficiency.search</field>
-            <field name="model">hr.efficiency</field>
-            <field name="arch" type="xml">
-                <search string="Search Efficiency">
-                    <field name="employee_id"/>
-                    <field name="month_year"/>
-                    <field name="company_id" groups="base.group_multi_company"/>
-                    
-                                            <!-- Filtros de indicadores -->
-                        <filter string="High Overall Efficiency" name="high_overall_efficiency" domain="[('overall_efficiency', '>=', 90)]"/>
-                    
-                    <!-- Filtros de tipo de proyecto -->
-                    <separator/>
-                    <filter string="Proyectos Facturables" name="billable_projects" domain="[('planned_billable_hours', '>', 0)]"/>
-                    <filter string="Proyectos No Facturables" name="non_billable_projects" domain="[('planned_non_billable_hours', '>', 0)]"/>
-                    
-                                            <!-- Filtros de fecha -->
-                        <separator/>
-                        <filter string="junio" name="june_2025" domain="[('month_year', '=', '2025-06')]"/>
-                        <filter string="julio" name="july_2025" domain="[('month_year', '=', '2025-07')]"/>
-                        <filter string="agosto" name="august_2025" domain="[('month_year', '=', '2025-08')]"/>
-                        <filter string="septiembre" name="september_2025" domain="[('month_year', '=', '2025-09')]"/>
-                        <filter string="octubre" name="october_2025" domain="[('month_year', '=', '2025-10')]"/>
-                        
-                        <!-- Filtros de trimestres -->
-                        <separator/>
-                        <filter string="Trimestre 1" name="q1_2025" domain="[('month_year', 'in', ['2025-01', '2025-02', '2025-03'])]"/>
-                        <filter string="Trimestre 2" name="q2_2025" domain="[('month_year', 'in', ['2025-04', '2025-05', '2025-06'])]"/>
-                        <filter string="Trimestre 3" name="q3_2025" domain="[('month_year', 'in', ['2025-07', '2025-08', '2025-09'])]"/>
-                        <filter string="Trimestre 4" name="q4_2025" domain="[('month_year', 'in', ['2025-10', '2025-11', '2025-12'])]"/>
-                        
-                        <!-- Filtros de años -->
-                        <separator/>
-                        <filter string="2023" name="year_2023" domain="[('month_year', 'like', '2023-')]"/>
-                        <filter string="2024" name="year_2024" domain="[('month_year', 'like', '2024-')]"/>
-                        <filter string="2025" name="year_2025" domain="[('month_year', 'like', '2025-')]"/>
-                    
-                    <!-- Filtro de archivado -->
-                    <separator/>
-                    <filter string="Archivado" name="archived" domain="[('active', '=', False)]"/>
-                    <filter string="No Archivado" name="not_archived" domain="[('active', '=', True)]"/>
-                    
-                    <group expand="0" string="Group By">
-                        <filter string="Employee" name="group_employee" context="{'group_by': 'employee_id'}"/>
-                        <filter string="Month" name="group_month" context="{'group_by': 'month_year'}"/>
-                        <filter string="Company" name="group_company" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
-                    </group>
-                </search>
-            </field>
-        </record>
-
-        <!-- Action -->
-        <record id="action_hr_efficiency" model="ir.actions.act_window">
-            <field name="name">Employee Efficiency</field>
-            <field name="res_model">hr.efficiency</field>
-            <field name="view_mode">list,form</field>
-            <field name="search_view_id" ref="view_hr_efficiency_search"/>
-            <field name="context">{'search_default_august_2025': 1}</field>
-            <field name="help" type="html">
-                <p class="o_view_nocontent_empty_folder">
-                    No efficiency records found!
-                </p><p>
-                    Create efficiency records by running the calculation wizard or wait for the automatic weekly calculation.
-                </p>
-            </field>
-        </record>
-        
-
-
-        <!-- Menu -->
-        <menuitem id="menu_hr_efficiency"
-                  name="Efficiency"
-                  parent="hr.menu_hr_root"
-                  action="action_hr_efficiency"
-                  sequence="50"
-                  groups="hr.group_hr_user"/>
-    </data>
-</odoo>

+ 0 - 12
hr_efficiency/views/planning_views.xml

@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
-    <data>
-        <!-- Add Efficiency menu to Planning Reporting -->
-                            <menuitem id="planning_menu_efficiency"
-                              name="Efficiency"
-                              parent="planning.planning_menu_reporting"
-                              action="hr_efficiency.action_hr_efficiency"
-                              sequence="20"
-                              groups="planning.group_planning_user"/>
-    </data>
-</odoo>

+ 0 - 5
hr_efficiency/wizard/__init__.py

@@ -1,5 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from . import hr_efficiency_calculation_wizard
-from . import hr_efficiency_field_visibility_wizard

+ 0 - 190
hr_efficiency/wizard/hr_efficiency_calculation_wizard.py

@@ -1,190 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from datetime import date, datetime
-from dateutil.relativedelta import relativedelta
-from odoo import api, fields, models, _
-from odoo.exceptions import UserError
-
-
-class HrEfficiencyCalculationWizard(models.TransientModel):
-    _name = 'hr.efficiency.calculation.wizard'
-    _description = 'HR Efficiency Calculation Wizard'
-
-    start_month = fields.Char('Start Month', required=True, help="Format: YYYY-MM")
-    end_month = fields.Char('End Month', required=True, help="Format: YYYY-MM")
-    employee_ids = fields.Many2many('hr.employee', string='Employees', help="Leave empty to calculate for all active employees")
-    company_id = fields.Many2one('res.company', 'Company', required=True, default=lambda self: self.env.company)
-    
-    # Results
-    result_message = fields.Text('Result', readonly=True)
-    created_count = fields.Integer('Created Records', readonly=True)
-    updated_count = fields.Integer('Updated Records', readonly=True)
-    total_count = fields.Integer('Total Processed', readonly=True)
-
-    @api.model
-    def default_get(self, fields_list):
-        res = super().default_get(fields_list)
-        # Set default period: last 3 months and next 6 months
-        current_date = date.today()
-        res['start_month'] = (current_date - relativedelta(months=3)).strftime('%Y-%m')
-        res['end_month'] = (current_date + relativedelta(months=6)).strftime('%Y-%m')
-        return res
-
-    def action_calculate_efficiency(self):
-        """
-        Calculate efficiency for the specified period and employees
-        """
-        # Validate input
-        self._validate_input()
-        
-        # Get employees to process
-        if self.employee_ids:
-            employees = self.employee_ids
-        else:
-            employees = self.env['hr.employee'].search([
-                ('active', '=', True),
-                ('company_id', '=', self.company_id.id)
-            ])
-        
-        if not employees:
-            raise UserError(_("No employees found to process."))
-        
-        # Calculate efficiency
-        efficiency_model = self.env['hr.efficiency']
-        
-        # Generate list of months
-        months = self._generate_month_list(self.start_month, self.end_month)
-        
-        created_records = []
-        
-        for employee in employees:
-            for month in months:
-                # Calculate efficiency data
-                efficiency_data = efficiency_model._calculate_employee_efficiency(employee, month)
-                efficiency_data['company_id'] = self.company_id.id
-                
-                # Check if there are changes compared to the latest record
-                latest_record = efficiency_model.search([
-                    ('employee_id', '=', employee.id),
-                    ('month_year', '=', month),
-                    ('company_id', '=', self.company_id.id),
-                ], order='calculation_date desc', limit=1)
-                
-                has_changes = False
-                if latest_record:
-                    # Compare current data with latest record
-                    fields_to_compare = [
-                        'available_hours', 'planned_hours', 'planned_billable_hours',
-                        'planned_non_billable_hours', 'actual_billable_hours',
-                        'actual_non_billable_hours'
-                    ]
-                    
-                    for field in fields_to_compare:
-                        if abs(efficiency_data[field] - latest_record[field]) > 0.01:  # Tolerance for floating point
-                            has_changes = True
-                            break
-                else:
-                    # No previous record exists, so this is a change
-                    has_changes = True
-                
-                # Only create new record if there are changes
-                if has_changes:
-                    # Archive existing records for this employee and month
-                    existing_records = efficiency_model.search([
-                        ('employee_id', '=', employee.id),
-                        ('month_year', '=', month),
-                        ('company_id', '=', self.company_id.id),
-                    ])
-                    if existing_records:
-                        existing_records.write({'active': False})
-                    
-                    # Create new record
-                    new_record = efficiency_model.create(efficiency_data)
-                    created_records.append(new_record)
-        
-        # Update wizard with results
-        self.write({
-            'created_count': len(created_records),
-            'updated_count': 0,  # No longer updating existing records
-            'total_count': len(created_records),
-            'result_message': self._generate_result_message(created_records, employees, months),
-        })
-        
-        return {
-            'type': 'ir.actions.act_window',
-            'res_model': 'hr.efficiency.calculation.wizard',
-            'res_id': self.id,
-            'view_mode': 'form',
-            'target': 'new',
-        }
-
-    def _validate_input(self):
-        """
-        Validate wizard input
-        """
-        # Validate month format
-        try:
-            datetime.strptime(self.start_month, '%Y-%m')
-            datetime.strptime(self.end_month, '%Y-%m')
-        except ValueError:
-            raise UserError(_("Invalid month format. Please use YYYY-MM format (e.g., 2024-01)"))
-        
-        # Validate date range
-        start_date = datetime.strptime(self.start_month, '%Y-%m')
-        end_date = datetime.strptime(self.end_month, '%Y-%m')
-        
-        if start_date > end_date:
-            raise UserError(_("Start month cannot be after end month."))
-        
-        # Limit range to reasonable period (e.g., 2 years)
-        if (end_date - start_date).days > 730:
-            raise UserError(_("Date range cannot exceed 2 years."))
-
-    def _generate_month_list(self, start_month, end_month):
-        """
-        Generate list of months between start_month and end_month (inclusive)
-        """
-        months = []
-        current = datetime.strptime(start_month, '%Y-%m')
-        end = datetime.strptime(end_month, '%Y-%m')
-        
-        while current <= end:
-            months.append(current.strftime('%Y-%m'))
-            current = current + relativedelta(months=1)
-        
-        return months
-
-    def _generate_result_message(self, created_records, employees, months):
-        """
-        Generate result message for the wizard
-        """
-        message = _("Efficiency calculation completed successfully!\n\n")
-        message += _("Period: %s to %s\n") % (self.start_month, self.end_month)
-        message += _("Employees processed: %d\n") % len(employees)
-        message += _("Months processed: %d\n\n") % len(months)
-        message += _("Results:\n")
-        message += _("- New records created: %d\n") % len(created_records)
-        message += _("- Total records processed: %d\n") % len(created_records)
-        message += _("\nNote: New records are only created when there are changes. Old records are archived.")
-        
-        return message
-
-    def action_view_results(self):
-        """
-        Open the efficiency records view
-        """
-        return {
-            'type': 'ir.actions.act_window',
-            'name': _('Efficiency Records'),
-            'res_model': 'hr.efficiency',
-            'view_mode': 'list,form',
-            'domain': [
-                ('company_id', '=', self.company_id.id),
-                ('month_year', '>=', self.start_month),
-                ('month_year', '<=', self.end_month),
-            ],
-            'context': {
-                'default_company_id': self.company_id.id,
-            },
-        }

+ 0 - 82
hr_efficiency/wizard/hr_efficiency_calculation_wizard_views.xml

@@ -1,82 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
-    <data>
-        <!-- Wizard Form View -->
-        <record id="view_hr_efficiency_calculation_wizard_form" model="ir.ui.view">
-            <field name="name">hr.efficiency.calculation.wizard.form</field>
-            <field name="model">hr.efficiency.calculation.wizard</field>
-            <field name="arch" type="xml">
-                <form string="Calculate Employee Efficiency">
-                    <sheet>
-                        <div class="oe_title">
-                            <h1>Calculate Employee Efficiency</h1>
-                        </div>
-                        <group>
-                            <group>
-                                <field name="start_month" placeholder="YYYY-MM"/>
-                                <field name="end_month" placeholder="YYYY-MM"/>
-                            </group>
-                            <group>
-                                <field name="company_id" groups="base.group_multi_company"/>
-                                <field name="employee_ids" widget="many2many_tags"/>
-                            </group>
-                        </group>
-                        <group invisible="not result_message">
-                            <field name="result_message" readonly="1" nolabel="1"/>
-                        </group>
-                        <group invisible="total_count == 0">
-                            <group>
-                                <field name="created_count" readonly="1"/>
-                                <field name="updated_count" readonly="1"/>
-                            </group>
-                            <group>
-                                <field name="total_count" readonly="1"/>
-                            </group>
-                        </group>
-                    </sheet>
-                    <footer>
-                        <button name="action_calculate_efficiency" 
-                                string="Calculate Efficiency" 
-                                type="object" 
-                                class="btn-primary"
-                                invisible="result_message"/>
-                        <button name="action_view_results" 
-                                string="View Results" 
-                                type="object" 
-                                class="btn-secondary"
-                                invisible="total_count == 0"/>
-                        <button string="Close" class="btn-secondary" special="cancel"/>
-                    </footer>
-                </form>
-            </field>
-        </record>
-
-        <!-- Action -->
-        <record id="action_hr_efficiency_calculation_wizard" model="ir.actions.act_window">
-            <field name="name">Calculate Efficiency</field>
-            <field name="res_model">hr.efficiency.calculation.wizard</field>
-            <field name="view_mode">form</field>
-            <field name="target">new</field>
-            <field name="help" type="html">
-                <p>
-                    This wizard will calculate efficiency for all employees for the specified period.
-                    The calculation includes:
-                </p>
-                <ul>
-                    <li>Available hours (considering holidays and time off)</li>
-                    <li>Planned hours from the planning module</li>
-                    <li>Actual hours from timesheets</li>
-                    <li>Separation between billable and non-billable projects</li>
-                </ul>
-            </field>
-        </record>
-
-        <!-- Menu -->
-        <menuitem id="menu_hr_efficiency_calculation"
-                  name="Calculate Efficiency"
-                  parent="hr.menu_hr_root"
-                  action="action_hr_efficiency_calculation_wizard"
-                  sequence="51"
-                  groups="hr.group_hr_manager"/>
-    </data>
-</odoo>

+ 0 - 66
hr_efficiency/wizard/hr_efficiency_field_visibility_wizard.py

@@ -1,66 +0,0 @@
-from odoo import models, fields, api, _
-from odoo.exceptions import UserError
-
-
-class HrEfficiencyFieldVisibilityWizard(models.TransientModel):
-    _name = 'hr.efficiency.field.visibility.wizard'
-    _description = 'Efficiency Field Visibility Wizard'
-    _table = 'hr_efficiency_field_vis_wizard'
-
-    # Dynamic fields for each indicator
-    indicator_ids = fields.Many2many('hr.efficiency.indicator', 'hr_efficiency_field_vis_wizard_rel', 'wizard_id', 'indicator_id', string='Indicators to Show')
-    
-    @api.model
-    def default_get(self, fields_list):
-        """Get default values - show all active indicators"""
-        res = super().default_get(fields_list)
-        active_indicators = self.env['hr.efficiency.indicator'].search([('active', '=', True)])
-        res['indicator_ids'] = [(6, 0, active_indicators.ids)]
-        return res
-    
-    def action_apply_visibility(self):
-        """Apply the selected field visibility"""
-        # Get all active indicators
-        all_indicators = self.env['hr.efficiency.indicator'].search([('active', '=', True)])
-        
-        # Get selected indicators
-        selected_indicators = self.indicator_ids
-        
-        # Update the hr.efficiency model to only show selected indicators
-        efficiency_model = self.env['hr.efficiency']
-        
-        # Clear all dynamic fields first
-        for indicator in all_indicators:
-            field_name = efficiency_model._get_indicator_field_name(indicator.name)
-            if hasattr(efficiency_model, field_name):
-                delattr(efficiency_model.__class__, field_name)
-        
-        # Create fields only for selected indicators
-        for indicator in selected_indicators:
-            field_name = efficiency_model._get_indicator_field_name(indicator.name)
-            field = fields.Float(
-                indicator.name + ' (%)',
-                compute='_compute_indicators',
-                store=True,
-                help=f'Indicator: {indicator.description or indicator.name}'
-            )
-            setattr(efficiency_model.__class__, field_name, field)
-        
-        # Update views with dynamic fields
-        efficiency_model._update_views_with_dynamic_fields()
-        
-        # Force recomputation
-        records = efficiency_model.search([])
-        if records:
-            records._invalidate_cache()
-        
-        return {
-            'type': 'ir.actions.client',
-            'tag': 'display_notification',
-            'params': {
-                'title': _('Success'),
-                'message': _('Field visibility updated successfully.'),
-                'type': 'success',
-                'sticky': False,
-            }
-        }

+ 0 - 39
hr_efficiency/wizard/hr_efficiency_field_visibility_wizard_views.xml

@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
-    <data>
-        <!-- Field Visibility Wizard Form View -->
-        <record id="view_hr_efficiency_field_visibility_wizard_form" model="ir.ui.view">
-            <field name="name">hr.efficiency.field.visibility.wizard.form</field>
-            <field name="model">hr.efficiency.field.visibility.wizard</field>
-            <field name="arch" type="xml">
-                <form string="Field Visibility Settings">
-                    <sheet>
-                        <group>
-                            <field name="indicator_ids" widget="many2many_tags" options="{'no_create': True}"/>
-                        </group>
-                    </sheet>
-                    <footer>
-                        <button name="action_apply_visibility" string="Apply" type="object" class="btn-primary"/>
-                        <button string="Cancel" class="btn-secondary" special="cancel"/>
-                    </footer>
-                </form>
-            </field>
-        </record>
-
-        <!-- Field Visibility Wizard Action -->
-        <record id="action_hr_efficiency_field_visibility_wizard" model="ir.actions.act_window">
-            <field name="name">Field Visibility</field>
-            <field name="res_model">hr.efficiency.field.visibility.wizard</field>
-            <field name="view_mode">form</field>
-            <field name="target">new</field>
-            <field name="context">{}</field>
-        </record>
-
-        <!-- Menu Item for Field Visibility -->
-        <menuitem id="menu_hr_efficiency_field_visibility"
-                  name="Field Visibility"
-                  parent="planning.planning_menu_settings"
-                  action="action_hr_efficiency_field_visibility_wizard"
-                  sequence="20"/>
-    </data>
-</odoo>