Преглед изворни кода

Squashed 'hr_efficiency/' changes from ffdd53e..23b2f59

23b2f59 Actualizar HR Efficiency: Eliminar campos de agregación (_agg)
94f4b3c feat: Major update to hr_efficiency module - Added new fields (wage, utilization_rate, overhead, precio_por_hora) - Extended hr.employee and res.company models - Added profitability indicators - Updated formulas and views - Enhanced calculation logic
4c6fe8d fix: Add post-install hook to preserve dynamic indicators after module reinstall
dc60c91 feat: Implement standard Odoo date filter like Sales module
26da5fa fix: Corregir cálculo de horas planificadas para slots que cruzan meses - Usar lógica de Odoo Planning nativo para calcular solo horas del período específico - Evitar contar horas completas de slots que cruzan meses
9f7f6e5 fix: Use Odoo Planning's exact logic for allocated hours calculation
128fb7a fix: Simplify planned hours calculation to match Odoo Planning behavior
4e74612 fix: Filter planned hours to only count working days like Odoo Planning
7907628 fix: Use flexible date range for planning slots to capture cross-month slots
80122ae fix: Remove state restriction from planning slots query
0d4b30e feat: Include dynamic indicators in new record creation criteria
ee596eb fix: Include indicators with zero value in Overall Efficiency calculation
f93fb9f feat: Add hr_efficiency module with latest updates
1ff377a Remove old hr_efficiency directory to prepare for subtree
cbe7b89 Merge commit 'c4020d66a3f3eea08746325571e0d51ab15d5888' as 'hr_efficiency'
c4020d6 Squashed 'hr_efficiency/' content from commit ffdd53e
4c4f7a5 update
a54216c project_semaphore update
35b606f project semaphore
855d7e7 project semaphore add
9954fb5 remove project_semaphore
2cd0b17 restore delete semaphore
f4592ee remove project_semaphore file
9449e5d project_semaphore master
f5ce391 nuevo modulo project_semaphore
de44555 sale_template_contract update
8ef44cc bloquear cambio de plantilla mensual si ya hay ordenes
a3f9a87 template no mensual copia proyecto
55a2a1e Update ir_attachment.py
e3b6720 upd Descarga masiva
3cf1f20 sale_template_contract updates
a5bc338 sale_template_contract updates
1d5712f parametros para registro de horas en proyectos y ordenes de venta
ed61dcb Actualizar project_task.py
4aedcd5 upd SAT
9c39021 Update account_edi_format.py
728e124 Update project_task.py
d7810be upd Proyecto y subtareas
e9cbda1 Update project_task.xml
97aafb7 Update project_task.xml
9d4ab04 Update project_task.xml
d3b10f1 add Planeacion de proyecto
e8790bf Update __manifest__.py
2a4e58c se agrega app whatsapp web con repo en git
034ebe4 Delete .gitmodules
3fdc544 Delete whatsapp_web
ce5efaf upd Facturas
59aeabe Update account_payment.py
045672a gitmodule update
282af2a gitmodules update
d10e009 whatsapp_web add
b6d3125 remove .gitmodules
7b2a91e Eliminar submódulo whatsapp_web
65390eb ww updates
6761987 se agrega submódulo whatsapp web
2dbe6b3 Update __manifest__.py
b0eb049 Update planning_slot.py
6745016 Update __manifest__.py
30a80aa upd
d66370a Update account_payment.py
8eaa468 logs en planning_slot.py para identificar errores en horas
8d5f7ef logs en planning_slot.py para identificar errores en horas
97b6a4a se agrega gitignore y logs en planning_slot.py para identificar errores en horas
3299143 fix con zona horaria para calculo de dias
0adfc7c mejoras en calculo de dias disponibles en el mes para el recurso al modificar slot
e80dd0a mejoras en calculo de dias disponibles en el mes para el recurso al modificar slot
3f66104 fix para contemplar hora al inicio y fin del mes
f00a20a aplicacion m22_planning agrega columna real_working_days_count al modelo planning.slot y basado en el codigo de _compute_allocated_hours pone como fecha inicial el 1er día del mes de la fecha inicial del slot y fecha final el ultimo dia del mes de la fecha inicial
373ef4e Merge pull request #30 from M22TechConsulting/tc_import_taxes
88b15d6 [ADD] custom_import_layout: add taxes in tc
2b0c4eb Merge pull request #28 from M22TechConsulting/remove_taxes_prod
274d4f4 [ADD] remove taxes
f453fcc Merge pull request #26 from M22TechConsulting/add_account_import
e61c0e7 [ADD] account import lines
cf06e88 Merge pull request #23 from M22TechConsulting/import_layaout_prod
c8cf91a [ADD] custom_import_layout: add module
2132301 Merge pull request #21 from M22TechConsulting/upd_change_vat_in_partner
39dad4b [UPD] upd module
acacbd2 Merge pull request #18 from M22TechConsulting/validate_rate_prod
1b1d3ef [UPD] custom_report_invoice
5f19cd0 Merge pull request #16 from M22TechConsulting/fix_rate
9b2efef [FIX] cutom_report_invoice: fix error in rate
aa1fcfe Merge pull request #15 from M22TechConsulting/change_rate
dfa3aac [ADD] custom_report_invoice: add exchange rate in prod
cc1e577 Merge pull request #13 from M22TechConsulting/tc2
e059461 [ADD] change_vat_in_partner: add partner in complement
1480014 Merge pull request #12 from M22TechConsulting/account_report_prod
a217367 [ADD] custom_report_invoice: add module
8b50d91 [ADD] change_vat_in_partner: change addres native
5c04be2 Merge pull request #9 from M22TechConsulting/tc_change_vat_in_partner
850acdb [ADD] change_vat_in_partner: add module in prod
39be5d0 Add files via upload
a0031e3 Update __manifest__.py
58c5a86 Add files via upload
4c59a6a Add files via upload
a0be044 Add files via upload
6bd5dc4 Initial commit
REVERT: ffdd53e Initial commit: HR Efficiency module with dynamic fields and indicators
REVERT: f4bea10 Initial commit

git-subtree-dir: hr_efficiency
git-subtree-split: 23b2f59bd4411609fb0945f55918e8604a4a01c4
root пре 5 месеци
родитељ
комит
12cb86c91e

+ 13 - 2
README.md

@@ -98,8 +98,16 @@ El módulo se puede acceder desde:
 - `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
+- `expected_hours_to_date`: Horas esperadas hasta la fecha actual
+- `wage`: Salario bruto del empleado al momento del cálculo
+- `utilization_rate`: Tasa de utilización del empleado al momento del cálculo
+- `overhead`: Overhead de la compañía al momento del cálculo
 - `actual_billable_hours`: Horas registradas en proyectos facturables
 - `actual_non_billable_hours`: Horas registradas en proyectos no facturables
+- `expected_hours_to_date`: Horas esperadas hasta la fecha actual
+- `wage`: Salario bruto del empleado según su contrato al momento del cálculo
 
 ### Visualización de Datos
 
@@ -176,8 +184,11 @@ Modelo principal que almacena los registros de eficiencia mensual por empleado.
 - `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
+- `wage`: Salario bruto del empleado según su contrato al momento del cálculo
+- `currency_id`: Moneda del salario
+- `utilization_rate`: Tasa de utilización del empleado al momento del cálculo
+- `overhead`: Overhead de la compañía al momento del cálculo
+- `overall_efficiency`: Porcentaje de eficiencia general basado en indicadores configurados
 
 ### hr.efficiency.calculation.wizard
 Wizard para ejecutar cálculos manuales de eficiencia.

+ 9 - 1
__manifest__.py

@@ -33,22 +33,30 @@ Features:
         'planning',
         'project_timesheet_holidays',
     ],
-                                        'data': [
+    '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',
+        'data/hr_efficiency_dynamic_filters.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/hr_employee_views.xml',
+        'views/res_company_views.xml',
         'views/planning_views.xml',
         'report/hr_efficiency_report_views.xml',
     ],
+    'assets': {
+        'web.assets_backend': [
+            'hr_efficiency/static/src/css/hr_efficiency.css',
+        ],
+    },
     'demo': [],
     'installable': True,
     'auto_install': False,

+ 50 - 0
data/hr_efficiency_dynamic_filters.xml

@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+    <data noupdate="1">
+        <!-- Dynamic Month Filters -->
+        <record id="filter_two_months_ago" model="ir.filters">
+            <field name="name">Mes 06</field>
+            <field name="model_id" ref="model_hr_efficiency"/>
+            <field name="domain">[('month_year', '=', (context_today() - relativedelta(months=2)).strftime('%Y-%m'))]</field>
+            <field name="context">{}</field>
+            <field name="is_default" eval="False"/>
+            <field name="active" eval="True"/>
+        </record>
+
+        <record id="filter_last_month" model="ir.filters">
+            <field name="name">Mes 07</field>
+            <field name="model_id" ref="model_hr_efficiency"/>
+            <field name="domain">[('month_year', '=', (context_today() - relativedelta(months=1)).strftime('%Y-%m'))]</field>
+            <field name="context">{}</field>
+            <field name="is_default" eval="False"/>
+            <field name="active" eval="True"/>
+        </record>
+
+        <record id="filter_current_month" model="ir.filters">
+            <field name="name">Mes 08 &lt;&lt; actual</field>
+            <field name="model_id" ref="model_hr_efficiency"/>
+            <field name="domain">[('month_year', '=', context_today().strftime('%Y-%m'))]</field>
+            <field name="context">{}</field>
+            <field name="is_default" eval="True"/>
+            <field name="active" eval="True"/>
+        </record>
+
+        <record id="filter_next_month" model="ir.filters">
+            <field name="name">Mes 09</field>
+            <field name="model_id" ref="model_hr_efficiency"/>
+            <field name="domain">[('month_year', '=', (context_today() + relativedelta(months=1)).strftime('%Y-%m'))]</field>
+            <field name="context">{}</field>
+            <field name="is_default" eval="False"/>
+            <field name="active" eval="True"/>
+        </record>
+
+        <record id="filter_two_months_ahead" model="ir.filters">
+            <field name="name">Mes 10</field>
+            <field name="model_id" ref="model_hr_efficiency"/>
+            <field name="domain">[('month_year', '=', (context_today() + relativedelta(months=2)).strftime('%Y-%m'))]</field>
+            <field name="context">{}</field>
+            <field name="is_default" eval="False"/>
+            <field name="active" eval="True"/>
+        </record>
+    </data>
+</odoo>

+ 155 - 19
data/hr_efficiency_indicators.xml

@@ -1,30 +1,166 @@
 <?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>
+
+        <!-- ================================================== -->
+        <!-- Indicadores de Capacidad (Capacity KPIs)           -->
+        <!-- ================================================== -->
+
+        <record id="indicator_occupancy_rate" model="hr.efficiency.indicator">
+            <field name="name">Occupancy Rate</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>
+            <field name="indicator_type">percentage</field>
+            <field name="formula">(total_actual_hours / available_hours) if available_hours > 0 else 0</field>
+            <field name="target_percentage">95.0</field>
+            <field name="weight">0.0</field>
+            <field name="description">Tasa de Ocupación: Mide qué tan "ocupado" está el equipo en general, considerando horas facturables y no facturables. (Total Horas Registradas / Horas Disponibles)</field>
+            <field name="color_threshold_green">85.0</field>
+            <field name="color_threshold_yellow">75.0</field>
+            <field name="color_threshold_red">60.0</field>
         </record>
 
-        <!-- Default Time Tracking Indicator -->
-        <record id="indicator_time_tracking" model="hr.efficiency.indicator">
-            <field name="name">Time Tracking Efficiency</field>
+        <record id="indicator_utilization_rate" model="hr.efficiency.indicator">
+            <field name="name">Utilization Rate</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>
+            <field name="indicator_type">percentage</field>
+            <field name="formula">(actual_billable_hours / available_hours) if available_hours > 0 else 0</field>
+            <field name="target_percentage">80.0</field>
+            <field name="weight">0.0</field>
+            <field name="description">Tasa de Utilización: Mide qué tan "productivo" (generando ingresos) está el equipo. (Horas Facturables Registradas / Horas Disponibles)</field>
+            <field name="color_threshold_green">85.0</field>
+            <field name="color_threshold_yellow">75.0</field>
+            <field name="color_threshold_red">60.0</field>
+        </record>
+
+        <!-- ================================================== -->
+        <!-- Indicadores de Eficiencia (Efficiency KPIs)        -->
+        <!-- ================================================== -->
+
+        <record id="indicator_billability_rate" model="hr.efficiency.indicator">
+            <field name="name">Billability Rate</field>
+            <field name="sequence">30</field>
+            <field name="active">True</field>
+            <field name="indicator_type">percentage</field>
+            <field name="formula">(actual_billable_hours / total_actual_hours) if total_actual_hours > 0 else 0</field>
+            <field name="target_percentage">85.0</field>
+            <field name="weight">0.0</field>
+            <field name="description">Tasa de Facturabilidad: De todo el tiempo trabajado, ¿qué porcentaje fue facturable? (Horas Facturables Registradas / Total Horas Registradas)</field>
+            <field name="color_threshold_green">85.0</field>
+            <field name="color_threshold_yellow">75.0</field>
+            <field name="color_threshold_red">65.0</field>
         </record>
+
+        <!-- ================================================== -->
+        <!-- Indicadores de Planificación (Planning KPIs)       -->
+        <!-- ================================================== -->
+
+        <record id="indicator_planned_utilization" model="hr.efficiency.indicator">
+            <field name="name">Planned Utilization</field>
+            <field name="sequence">40</field>
+            <field name="active">True</field>
+            <field name="indicator_type">percentage</field>
+            <field name="formula">(planned_billable_hours / available_hours) if available_hours > 0 else 0</field>
+            <field name="target_percentage">80.0</field>
+            <field name="weight">0.0</field>
+            <field name="description">Utilización Planeada: ¿Cuál era el objetivo de utilización para el equipo? Permite comparar meta vs. realidad. (Horas Facturables Planeadas / Horas Disponibles)</field>
+            <field name="color_threshold_green">85.0</field>
+            <field name="color_threshold_yellow">75.0</field>
+            <field name="color_threshold_red">60.0</field>
+        </record>
+
+        <record id="indicator_planning_coverage" model="hr.efficiency.indicator">
+            <field name="name">Planning Coverage</field>
+            <field name="sequence">50</field>
+            <field name="active">True</field>
+            <field name="indicator_type">percentage</field>
+            <field name="formula">(planned_hours / available_hours) if available_hours > 0 else 0</field>
+            <field name="target_percentage">95.0</field>
+            <field name="weight">0.0</field>
+            <field name="description">Cobertura de Planificación: Mide qué porcentaje del tiempo disponible ha sido planificado, sin importar si es facturable o no. (Total Horas Planeadas / Horas Disponibles)</field>
+            <field name="color_threshold_green">85.0</field>
+            <field name="color_threshold_yellow">75.0</field>
+            <field name="color_threshold_red">65.0</field>
+        </record>
+
+        <record id="indicator_estimation_accuracy" model="hr.efficiency.indicator">
+            <field name="name">Estimation Accuracy Plan Adherence</field>
+            <field name="sequence">60</field>
+            <field name="active">True</field>
+            <field name="indicator_type">percentage</field>
+            <field name="formula">(total_actual_hours / planned_hours) if planned_hours > 0 else 0</field>
+            <field name="target_percentage">100.0</field>
+            <field name="weight">0.0</field>
+            <field name="description">Precisión de la Estimación: ¿Qué tan acertada fue la planificación general vs. la realidad? Un valor cercano a 100% es ideal. (Total Horas Registradas / Total Horas Planeadas)</field>
+            <field name="color_threshold_green">85.0</field>
+            <field name="color_threshold_yellow">75.0</field>
+            <field name="color_threshold_red">60.0</field>
+        </record>
+
+        <record id="indicator_billable_plan_compliance" model="hr.efficiency.indicator">
+            <field name="name">Billable Plan Compliance</field>
+            <field name="sequence">70</field>
+            <field name="active">True</field>
+            <field name="indicator_type">percentage</field>
+            <field name="formula">(actual_billable_hours / planned_billable_hours) if planned_billable_hours > 0 else 0</field>
+            <field name="target_percentage">100.0</field>
+            <field name="weight">0.0</field>
+            <field name="description">Cumplimiento del Plan Facturable: ¿Se cumplió con el objetivo específico de horas facturables? (Horas Facturables Registradas / Horas Facturables Planeadas)</field>
+            <field name="color_threshold_green">85.0</field>
+            <field name="color_threshold_yellow">75.0</field>
+            <field name="color_threshold_red">65.0</field>
+        </record>
+        
+        <!-- ================================================== -->
+        <!-- Indicadores de Rentabilidad (Profitability KPIs)   -->
+        <!-- ================================================== -->
+
+        <record id="indicator_break_even_hours" model="hr.efficiency.indicator">
+            <field name="name">Break-Even Hours Needed</field>
+            <field name="sequence">80</field>
+            <field name="active">True</field>
+            <field name="indicator_type">hours</field>
+            <!-- FORMULA RESTAURADA: Calcula el costo productivo basado en la tasa de utilización. -->
+            <field name="formula">(wage * (utilization_rate / 100) * (1 + (overhead / 100))) / precio_por_hora if precio_por_hora > 0 else 0</field>
+            <field name="target_percentage">0.0</field>
+            <field name="weight">0.0</field>
+            <field name="description">Horas de Punto de Equilibrio: ¿Cuántas horas facturables se necesitan para cubrir el costo productivo (costo ponderado por utilización)? El resultado es un número de horas.</field>
+            <!-- Note: Thresholds for 'hours' type are less direct than for percentages. -->
+            <field name="color_threshold_green">85.0</field>
+            <field name="color_threshold_yellow">75.0</field>
+            <field name="color_threshold_red">65.0</field>
+        </record>
+
+        <record id="indicator_planned_profitability_coverage" model="hr.efficiency.indicator">
+            <field name="name">Planned Profitability Coverage</field>
+            <field name="sequence">90</field>
+            <field name="active">True</field>
+            <field name="indicator_type">percentage</field>
+            <!-- FORMULA AJUSTADA: Compara las horas planeadas contra las horas de punto de equilibrio (costo productivo). -->
+            <field name="formula">(planned_billable_hours / ((wage * (utilization_rate / 100) * (1 + (overhead / 100))) / precio_por_hora)) if wage > 0 and precio_por_hora > 0 else 0</field>
+            <field name="target_percentage">100.0</field>
+            <field name="weight">0.0</field>
+            <field name="description">Cobertura de Rentabilidad Planeada: Mide si las horas facturables planeadas son suficientes para alcanzar el punto de equilibrio. Más de 100% indica un plan rentable. (Horas Facturables Planeadas / Horas de Punto de Equilibrio)</field>
+            <field name="color_threshold_green">100.0</field>
+            <field name="color_threshold_yellow">85.0</field>
+            <field name="color_threshold_red">70.0</field>
+        </record>
+
+        <record id="indicator_actual_profitability_achievement" model="hr.efficiency.indicator">
+            <field name="name">Actual Profitability Achievement</field>
+            <field name="sequence">100</field>
+            <field name="active">True</field>
+            <field name="indicator_type">percentage</field>
+            <!-- FORMULA AJUSTADA: Compara las horas reales contra las horas de punto de equilibrio (costo productivo). -->
+            <field name="formula">(actual_billable_hours / ((wage * (utilization_rate / 100) * (1 + (overhead / 100))) / precio_por_hora)) if wage > 0 and precio_por_hora > 0 else 0</field>
+            <field name="target_percentage">100.0</field>
+            <field name="weight">0.0</field>
+            <field name="description">Logro de Rentabilidad Real: Mide el progreso real hacia el punto de equilibrio basado en las horas facturables registradas. Más de 100% indica que ya se ha alcanzado la rentabilidad. (Horas Facturables Registradas / Horas de Punto de Equilibrio)</field>
+            <field name="color_threshold_green">100.0</field>
+            <field name="color_threshold_yellow">85.0</field>
+            <field name="color_threshold_red">70.0</field>
+        </record>
+
     </data>
-</odoo>
+</odoo>

+ 2 - 4
data/hr_efficiency_post_init.xml

@@ -1,9 +1,7 @@
 <?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"/>
+        <!-- Post-install hook to ensure dynamic fields are created -->
+        <function model="hr.efficiency" name="_post_init_hook"/>
     </data>
 </odoo>

+ 2 - 0
models/__init__.py

@@ -4,3 +4,5 @@
 from . import hr_efficiency
 from . import hr_efficiency_indicator
 from . import hr_efficiency_dynamic_field
+from . import hr_employee
+from . import res_company

+ 766 - 95
models/hr_efficiency.py

@@ -1,12 +1,16 @@
 # -*- coding: utf-8 -*-
 # Part of Odoo. See LICENSE file for full copyright and licensing details.
 
+import logging
+import pytz
 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
 
+_logger = logging.getLogger(__name__)
+
 
 class HrEfficiency(models.Model):
     _name = 'hr.efficiency'
@@ -15,17 +19,30 @@ class HrEfficiency(models.Model):
     _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)
+    # Basic fields
+    name = fields.Char('Name', compute='_compute_display_name', store=True)
+    employee_id = fields.Many2one('hr.employee', 'Employee', required=True, domain=[('employee_type', '=', 'employee')])
+    month_year = fields.Char('Month Year', required=True, help="Format: YYYY-MM (e.g., 2024-08)")
+    date = fields.Date('Date', compute='_compute_date', store=True, help="Date field for standard Odoo date filters")
+    company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company)
+    active = fields.Boolean('Active', default=True)
     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")
+    # Available hours (what employee should work)
+    available_hours = fields.Float('Available Hours', digits=(10, 2), help="Total hours employee should work in the month")
+    
+    # Employee contract information
+    wage = fields.Float('Gross Salary', digits=(10, 2), aggregator='sum', help="Employee's gross salary from their contract at the time of calculation")
+    currency_id = fields.Many2one('res.currency', 'Currency', help="Currency for the wage field")
     
-    # Planned hours
-    planned_hours = fields.Float('Planned Hours', digits=(10, 2), help="Total hours planned in planning module")
+    # Employee utilization and company overhead (stored at calculation time)
+    utilization_rate = fields.Float('Utilization Rate (%)', digits=(5, 2), default=100.0, aggregator='avg', help="Employee's utilization rate at the time of calculation")
+    overhead = fields.Float('Company Overhead (%)', digits=(5, 2), default=40.0, aggregator='avg', help="Company's overhead percentage at the time of calculation")
+    expected_profitability = fields.Float('Expected Profitability (%)', digits=(5, 2), default=30.0, aggregator='avg', help="Company's expected profitability percentage at the time of calculation")
+    efficiency_factor = fields.Float('Efficiency Factor (%)', digits=(5, 2), default=85.0, aggregator='avg', help="Company's efficiency factor percentage at the time of calculation")
+    
+    # Planned hours (what was planned)
+    planned_hours = fields.Float('Planned Hours', digits=(10, 2), help="Total hours planned for the month")
     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")
     
@@ -33,27 +50,139 @@ class HrEfficiency(models.Model):
     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)
+    # Calculated fields (stored for performance)
+    total_actual_hours = fields.Float('Total Actual Hours', digits=(10, 2), help="Total actual hours (billable + non-billable)")
+    expected_hours_to_date = fields.Float('Expected Hours to Date', digits=(10, 2), help='Hours that should be registered based on planning until current date')
+    
+    # Precio por Hora (stored field)
+    precio_por_hora = fields.Float('Precio por Hora', digits=(10, 2), help='Precio que cobramos al cliente por hora (hourly_cost + overhead + rentabilidad_esperada)', aggregator='avg')
     
     # 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')
+    # Overall efficiency (always present) - Now stored fields calculated on save
+    overall_efficiency = fields.Float('Overall Efficiency (%)', digits=(5, 2), help='Overall efficiency based on configured indicators')
+    overall_efficiency_display = fields.Char('Overall Efficiency Display', help='Overall efficiency formatted for display with badge widget')
     
     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):
+    @api.depends('month_year')
+    def _compute_date(self):
+        """
+        Compute date field from month_year for standard Odoo date filters
+        """
         for record in self:
-            record.total_actual_hours = record.actual_billable_hours + record.actual_non_billable_hours
+            if record.month_year:
+                try:
+                    year, month = record.month_year.split('-')
+                    record.date = date(int(year), int(month), 1)
+                except (ValueError, AttributeError):
+                    record.date = False
+            else:
+                record.date = False
+
+
+
+
+
+    def _calculate_expected_hours_to_date(self, record):
+        """
+        Calculate expected hours to date based on planned hours and working days
+        """
+        if not record.month_year or not record.planned_hours or not record.employee_id.contract_id:
+            return 0.0
+            
+        try:
+            # Parse month_year (format: YYYY-MM)
+            year, month = record.month_year.split('-')
+            start_date = date(int(year), int(month), 1)
+            end_date = (start_date + relativedelta(months=1)) - relativedelta(days=1)
+            
+            # Get current date
+            current_date = date.today()
+            
+            # If current date is outside the month, use end of month
+            if current_date > end_date:
+                calculation_date = end_date
+            elif current_date < start_date:
+                calculation_date = start_date
+            else:
+                calculation_date = current_date
+            
+            # Calculate working days in the month
+            total_working_days = self._count_working_days(start_date, end_date, record.employee_id)
+            
+            # Calculate working days until current date
+            working_days_until_date = self._count_working_days(start_date, calculation_date, record.employee_id)
+            
+            if total_working_days > 0:
+                # Calculate expected hours based on planned hours (what was planned to work)
+                expected_hours = (record.planned_hours / total_working_days) * working_days_until_date
+                
+                # Ensure we don't exceed planned hours for the month
+                expected_hours = min(expected_hours, record.planned_hours)
+                
+                return float_round(expected_hours, 2)
+            else:
+                return 0.0
+                
+        except (ValueError, AttributeError) as e:
+            return 0.0
 
-    @api.depends('available_hours', 'planned_hours', 'total_actual_hours')
-    def _compute_indicators(self):
+    def _calculate_precio_por_hora(self, record):
+        """
+        Calculate precio por hora: hourly_cost * (1 + overhead/100) * (1 + expected_profitability/100)
+        """
+        try:
+            # Get hourly_cost from employee
+            employee = getattr(record, 'employee_id', None)
+            if not employee:
+                return 0.0
+            
+            hourly_cost = getattr(employee, 'hourly_cost', 0.0) or 0.0
+            overhead = getattr(record, 'overhead', 40.0) or 40.0
+            expected_profitability = getattr(record, 'expected_profitability', 30.0) or 30.0
+            
+            if hourly_cost > 0:
+                # Apply overhead and expected profitability margin
+                precio_por_hora = hourly_cost * (1 + (overhead / 100)) * (1 + (expected_profitability / 100))
+                return float_round(precio_por_hora, 2)
+            else:
+                return 0.0
+                
+        except (ValueError, AttributeError, ZeroDivisionError) as e:
+            return 0.0
+
+
+    def _calculate_all_indicators(self):
+        """
+        Calculate all indicators and overall efficiency for stored fields
+        This method is called on create/write instead of using computed fields
+        """
+        # Prepare values to update without triggering write recursively
+        values_to_update = {}
+        
+        # Pre-fetch necessary fields to avoid CacheMiss during iteration
+        fields_to_load = [
+            'available_hours', 'planned_hours', 'planned_billable_hours',
+            'planned_non_billable_hours', 'actual_billable_hours',
+            'actual_non_billable_hours', 'total_actual_hours',
+            'expected_hours_to_date', 'wage', 'utilization_rate', 'overhead',
+            'precio_por_hora', 'employee_id', 'company_id', 'month_year', 'active',
+        ]
+        
+        # Load fields for all records in 'self' with error handling
+        try:
+            self.read(fields_to_load)
+        except Exception as e:
+            import logging
+            _logger = logging.getLogger(__name__)
+            _logger.warning(f"Error loading fields: {e}")
+            # Continue with empty cache if needed
+        
         for record in self:
             # Get all manual fields for this model
             manual_fields = self.env['ir.model.fields'].search([
@@ -62,48 +191,163 @@ class HrEfficiency(models.Model):
                 ('ttype', '=', 'float')
             ])
             
-            # Prepare efficiency data
+            # Prepare efficiency data with safe field access
+            efficiency_data = {
+                'available_hours': getattr(record, 'available_hours', 0.0) or 0.0,
+                'planned_hours': getattr(record, 'planned_hours', 0.0) or 0.0,
+                'planned_billable_hours': getattr(record, 'planned_billable_hours', 0.0) or 0.0,
+                'planned_non_billable_hours': getattr(record, 'planned_non_billable_hours', 0.0) or 0.0,
+                'actual_billable_hours': getattr(record, 'actual_billable_hours', 0.0) or 0.0,
+                'actual_non_billable_hours': getattr(record, 'actual_non_billable_hours', 0.0) or 0.0,
+                'total_actual_hours': getattr(record, 'total_actual_hours', 0.0) or 0.0,
+                'expected_hours_to_date': getattr(record, 'expected_hours_to_date', 0.0) or 0.0,
+                'wage': getattr(record, 'wage', 0.0) or 0.0,
+                'utilization_rate': getattr(record, 'utilization_rate', 100.0) or 100.0,
+                'overhead': getattr(record, 'overhead', 40.0) or 40.0,
+                'precio_por_hora': getattr(record, 'precio_por_hora', 0.0) or 0.0,
+            }
+            
+            # STEP 1: Calculate total_actual_hours FIRST (needed for indicators)
+            try:
+                total_actual_hours = getattr(record, 'actual_billable_hours', 0.0) + getattr(record, 'actual_non_billable_hours', 0.0)
+            except Exception as e:
+                import logging
+                _logger = logging.getLogger(__name__)
+                _logger.error(f"Error calculating total_actual_hours for record {record.id}: {e}")
+                total_actual_hours = 0.0
+            record_values = {'total_actual_hours': float_round(total_actual_hours, 2)}
+            
+            # STEP 2: Calculate expected_hours_to_date
+            expected_hours = self._calculate_expected_hours_to_date(record)
+            record_values['expected_hours_to_date'] = expected_hours
+            
+            # STEP 3: Calculate precio_por_hora (needed for profitability indicators)
+            precio_por_hora = self._calculate_precio_por_hora(record)
+            record_values['precio_por_hora'] = precio_por_hora
+            
+            # STEP 4: Update efficiency_data with ALL calculated base fields
+            efficiency_data.update({
+                'total_actual_hours': total_actual_hours,
+                'expected_hours_to_date': expected_hours,
+                'precio_por_hora': precio_por_hora,
+            })
+            
+            # STEP 5: Calculate all indicators dynamically (in sequence order)
+            active_indicators = self.env['hr.efficiency.indicator'].search([('active', '=', True)], order='sequence')
+            
+            for indicator in active_indicators:
+                field_name = self._get_indicator_field_name(indicator.name)
+                
+                # Check if the field exists in the record
+                if field_name in record._fields:
+                    # Calculate indicator value using the indicator formula
+                    indicator_value = indicator.evaluate_formula(efficiency_data)
+                    
+                    # Store the value to update later
+                    record_values[field_name] = float_round(indicator_value, 2)
+            
+            # Calculate overall efficiency
+            overall_efficiency = self._calculate_overall_efficiency(record)
+            record_values['overall_efficiency'] = overall_efficiency
+            
+            # Calculate overall efficiency display
+            if overall_efficiency == 0:
+                record_values['overall_efficiency_display'] = '0.00'
+            else:
+                record_values['overall_efficiency_display'] = f"{overall_efficiency:.2f}"
+            
+            # Store values for this record
+            values_to_update[record.id] = record_values
+        
+        # Update all records at once to avoid recursion
+        for record_id, values in values_to_update.items():
+            record = self.browse(record_id)
+            # Use direct SQL update to avoid triggering write method
+            if values:
+                record.env.cr.execute(
+                    "UPDATE hr_efficiency SET " + 
+                    ", ".join([f"{key} = %s" for key in values.keys()]) + 
+                    " WHERE id = %s",
+                    list(values.values()) + [record_id]
+                )
+        
+
+    
+
+    
+    def _update_stored_manual_fields(self):
+        """Update stored manual fields with computed values"""
+        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'),
+                ('store', '=', True),
+            ], order='id')
+            
+            # Prepare efficiency data with safe field access
             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,
+                'available_hours': record.available_hours or 0.0,
+                'planned_hours': record.planned_hours or 0.0,
+                'planned_billable_hours': record.planned_billable_hours or 0.0,
+                'planned_non_billable_hours': record.planned_non_billable_hours or 0.0,
+                'actual_billable_hours': record.actual_billable_hours or 0.0,
+                'actual_non_billable_hours': record.actual_non_billable_hours or 0.0,
+                'total_actual_hours': record.total_actual_hours or 0.0,
+                'expected_hours_to_date': record.expected_hours_to_date or 0.0,
+                'wage': record.wage or 0.0,
+                'utilization_rate': record.utilization_rate or 100.0,
+                'overhead': record.overhead or 40.0,
             }
             
-            # Calculate all indicators dynamically
+            # Calculate and update stored manual fields
             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:
+                if indicator and field.name in record._fields:
                     # 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)
+                    # Update the stored field value
+                    record[field.name] = float_round(indicator_value, 2)
+    
+    @api.model
+    def _recompute_all_indicators(self):
+        """Recompute all indicator fields for all records"""
+        records = self.search([])
+        if records:
+            records._calculate_all_indicators()
     
     def _get_indicator_field_name(self, indicator_name):
         """
         Convert indicator name to valid field name
         """
+        import re
+        
         # 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')
+        
+        # Replace spaces, hyphens, and other special characters with underscores
+        field_name = re.sub(r'[^a-z0-9_]', '_', field_name)
+        
+        # Remove multiple consecutive underscores
+        field_name = re.sub(r'_+', '_', field_name)
+        
+        # Remove leading and trailing underscores
+        field_name = field_name.strip('_')
         
         # Ensure it starts with x_ for manual fields
         if not field_name.startswith('x_'):
             field_name = 'x_' + field_name
             
+        # Ensure it doesn't exceed 63 characters (PostgreSQL limit)
+        if len(field_name) > 63:
+            field_name = field_name[:63]
+            
         return field_name
     
 
@@ -118,13 +362,7 @@ class HrEfficiency(models.Model):
         
         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
+            field_name = self._get_indicator_field_name(indicator.name)
             
             existing_field = self.env['ir.model.fields'].search([
                 ('model', '=', 'hr.efficiency'),
@@ -156,28 +394,46 @@ class HrEfficiency(models.Model):
             # Default calculation if no indicators configured
             return 0.0
         
+        # Check if there's any data to calculate
+        if (record.available_hours == 0 and 
+            record.planned_hours == 0 and 
+            record.actual_billable_hours == 0 and 
+            record.actual_non_billable_hours == 0):
+            return 0.0
+        
         total_weight = 0
         weighted_sum = 0
+        valid_indicators = 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,
+            'available_hours': getattr(record, 'available_hours', 0.0) or 0.0,
+            'planned_hours': getattr(record, 'planned_hours', 0.0) or 0.0,
+            'planned_billable_hours': getattr(record, 'planned_billable_hours', 0.0) or 0.0,
+            'planned_non_billable_hours': getattr(record, 'planned_non_billable_hours', 0.0) or 0.0,
+            'actual_billable_hours': getattr(record, 'actual_billable_hours', 0.0) or 0.0,
+            'actual_non_billable_hours': getattr(record, 'actual_non_billable_hours', 0.0) or 0.0,
+            'total_actual_hours': getattr(record, 'total_actual_hours', 0.0) or 0.0,
+            'expected_hours_to_date': getattr(record, 'expected_hours_to_date', 0.0) or 0.0,
+            'wage': getattr(record, 'wage', 0.0) or 0.0,
+            'utilization_rate': getattr(record, 'utilization_rate', 100.0) or 100.0,
+            'overhead': getattr(record, 'overhead', 40.0) or 40.0,
         }
         
         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
+                # Count indicators with valid values (including 0 when it's a valid result)
+                if indicator_value is not None:
+                    weighted_sum += indicator_value * indicator.weight
+                    total_weight += indicator.weight
+                    valid_indicators += 1
         
-        if total_weight > 0:
-            return float_round(weighted_sum / total_weight, 2)
-        else:
+        # If no valid indicators or no total weight, return 0
+        if total_weight <= 0 or valid_indicators == 0:
             return 0.0
+        
+        # Multiply by 100 to show as percentage
+        return float_round((weighted_sum / total_weight) * 100, 2)
 
     @api.depends('employee_id', 'month_year')
     def _compute_display_name(self):
@@ -209,6 +465,20 @@ class HrEfficiency(models.Model):
         # Calculate actual hours
         actual_billable_hours, actual_non_billable_hours = self._calculate_actual_hours(employee, start_date, end_date)
         
+        # Calculate wage and currency from employee's contract
+        # Use current date for wage calculation (when the record is being created/calculated)
+        wage_date = date.today()
+        wage, currency_id = self._get_employee_wage_and_currency(employee, wage_date)
+        
+        # Get employee's utilization rate and company's overhead at calculation time
+        utilization_rate = employee.utilization_rate or 100.0
+        overhead = employee.company_id.overhead or 40.0
+        expected_profitability = employee.company_id.expected_profitability or 30.0
+        efficiency_factor = employee.company_id.efficiency_factor or 85.0
+        
+        # Apply utilization_rate to actual_billable_hours to reflect real billable capacity
+        adjusted_actual_billable_hours = actual_billable_hours * (utilization_rate / 100)
+        
         return {
             'month_year': month_year,
             'employee_id': employee.id,
@@ -217,10 +487,56 @@ class HrEfficiency(models.Model):
             '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_billable_hours': adjusted_actual_billable_hours,
             'actual_non_billable_hours': actual_non_billable_hours,
+            'wage': wage,
+            'currency_id': currency_id,
+            'utilization_rate': utilization_rate,
+            'overhead': overhead,
+            'expected_profitability': expected_profitability,
+            'efficiency_factor': efficiency_factor,
         }
 
+    @api.model
+    def _get_employee_wage_and_currency(self, employee, target_date):
+        """
+        Get employee's wage and currency from their contract for a specific date
+        Always take the contract that is active on the target date
+        If no active contract on that date, return 0 with company currency
+        """
+        if not employee:
+            return 0.0, self.env.company.currency_id.id
+        
+        # Get all contracts for the employee
+        contracts = self.env['hr.contract'].search([
+            ('employee_id', '=', employee.id),
+            ('state', '=', 'open')
+        ], order='date_start desc')
+        
+        if not contracts:
+            return 0.0, self.env.company.currency_id.id
+        
+        # Find the contract that is active on the target date
+        for contract in contracts:
+            contract_start = contract.date_start
+            
+            # Check if contract is active on target date
+            if contract.date_end:
+                # Contract has end date
+                if contract_start <= target_date <= contract.date_end:
+                    wage = contract.wage or 0.0
+                    currency_id = contract.currency_id.id if contract.currency_id else self.env.company.currency_id.id
+                    return wage, currency_id
+            else:
+                # Contract has no end date, it's active for any date after start
+                if contract_start <= target_date:
+                    wage = contract.wage or 0.0
+                    currency_id = contract.currency_id.id if contract.currency_id else self.env.company.currency_id.id
+                    return wage, currency_id
+        
+        # If no contract is active on the target date, return defaults
+        return 0.0, self.env.company.currency_id.id
+
     def _calculate_available_hours(self, employee, start_date, end_date):
         """
         Calculate available hours considering holidays and time off
@@ -272,20 +588,43 @@ class HrEfficiency(models.Model):
         """
         Calculate planned hours from planning module
         """
-        # Get planning slots for the employee in the date range
+        # Get planning slots for the employee that overlap with the date range
+        # This is the same logic as Odoo Planning's Gantt chart
+        start_datetime = datetime.combine(start_date, datetime.min.time())
+        end_datetime = datetime.combine(end_date, datetime.max.time())
+        
         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']),
+            ('start_datetime', '<=', end_datetime),
+            ('end_datetime', '>=', start_datetime),
+            # Removed state restriction to include all planning slots regardless of state
         ])
         
         total_planned = 0.0
         total_billable = 0.0
         total_non_billable = 0.0
         
+        # Get working intervals for the resource and company calendar
+        # This is the same approach as Odoo Planning's Gantt chart
+        start_utc = pytz.utc.localize(start_datetime)
+        end_utc = pytz.utc.localize(end_datetime)
+        
+        if employee.resource_id:
+            resource_work_intervals, calendar_work_intervals = employee.resource_id._get_valid_work_intervals(
+                start_utc, end_utc, calendars=employee.company_id.resource_calendar_id
+            )
+        else:
+            # Fallback to company calendar if no resource
+            calendar_work_intervals = {employee.company_id.resource_calendar_id.id: []}
+            resource_work_intervals = {}
+        
         for slot in planning_slots:
-            hours = slot.allocated_hours or 0.0
+            # Use the same logic as Odoo Planning's Gantt chart
+            # Calculate duration only within the specified period
+            hours = slot._get_duration_over_period(
+                start_utc, end_utc,
+                resource_work_intervals, calendar_work_intervals, has_allocated_hours=False
+            )
             
             # Check if the slot is linked to a billable project
             if slot.project_id and slot.project_id.allow_billable:
@@ -346,8 +685,11 @@ class HrEfficiency(models.Model):
         # 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)])
+        # Get all active employees of type 'employee'
+        employees = self.env['hr.employee'].search([
+            ('active', '=', True),
+            ('employee_type', '=', 'employee')
+        ])
         
         created_records = []
         
@@ -372,10 +714,34 @@ class HrEfficiency(models.Model):
                         'actual_non_billable_hours'
                     ]
                     
+                    # Check basic fields
                     for field in fields_to_compare:
                         if abs(efficiency_data[field] - latest_record[field]) > 0.01:  # Tolerance for floating point
                             has_changes = True
                             break
+                    
+                    # If no changes in basic fields, check dynamic indicators
+                    if not has_changes:
+                        # Get all active indicators
+                        active_indicators = self.env['hr.efficiency.indicator'].search([('active', '=', True)], order='sequence')
+                        
+                        for indicator in active_indicators:
+                            field_name = self._get_indicator_field_name(indicator.name)
+                            
+                            # Calculate current indicator value
+                            current_value = indicator.evaluate_formula(efficiency_data)
+                            
+                            # Get previous indicator value from record
+                            previous_value = getattr(latest_record, field_name, None)
+                            
+                            # Compare values with tolerance
+                            if (current_value is not None and previous_value is not None and 
+                                abs(current_value - previous_value) > 0.01):
+                                has_changes = True
+                                break
+                            elif current_value != previous_value:  # Handle None vs value cases
+                                has_changes = True
+                                break
                 else:
                     # No previous record exists, so this is a change
                     has_changes = True
@@ -395,6 +761,12 @@ class HrEfficiency(models.Model):
                     new_record = self.create(efficiency_data)
                     created_records.append(new_record)
         
+        # Calculate indicators for all newly created records
+        if created_records:
+            # Convert list to recordset
+            created_recordset = self.browse([record.id for record in created_records])
+            created_recordset._calculate_all_indicators()
+        
         return {
             'created': len(created_records),
             'updated': 0,  # No longer updating existing records
@@ -435,6 +807,78 @@ class HrEfficiency(models.Model):
             _logger.error(f"Error during dynamic system initialization: {str(e)}")
             raise
     
+    @api.model
+    def _post_init_hook(self):
+        """
+        Post-install hook to ensure dynamic fields are created for existing indicators
+        """
+        import logging
+        _logger = logging.getLogger(__name__)
+        
+        try:
+            _logger.info("Running post-install hook for hr_efficiency module")
+            
+            # Ensure all active indicators have manual fields
+            active_indicators = self.env['hr.efficiency.indicator'].search([('active', '=', True)], order='sequence')
+            for indicator in active_indicators:
+                self.env['hr.efficiency.indicator']._create_dynamic_field(indicator)
+            
+            # Update views with dynamic fields
+            self._update_views_with_dynamic_fields()
+            
+            # Apply default values to existing records
+            self._apply_default_values_to_existing_records()
+            
+            _logger.info(f"Post-install hook completed. Processed {len(active_indicators)} indicators")
+            
+        except Exception as e:
+            _logger.error(f"Error in post-install hook: {str(e)}")
+            raise
+
+
+    @api.model
+    def create(self, vals_list):
+        """
+        Override create to calculate indicators when records are created
+        """
+        records = super().create(vals_list)
+        
+        # Calculate indicators for newly created records
+        if records:
+            records._calculate_all_indicators()
+        
+        return records
+    
+    def write(self, vals):
+        """
+        Override write to recalculate indicators when records are updated
+        """
+        result = super().write(vals)
+        
+        # Recalculate indicators for updated records
+        self._calculate_all_indicators()
+        
+        return result
+
+    @api.model
+    def _register_hook(self):
+        """
+        Called when the registry is loaded. 
+        Update views with dynamic fields on every module load/restart.
+        """
+        super()._register_hook()
+        try:
+
+            # Update views with current dynamic fields on every module load
+            self._update_views_with_dynamic_fields()
+        except Exception as e:
+            # Log error but don't prevent module loading
+            import logging
+            _logger = logging.getLogger(__name__)
+            _logger.warning(f"Could not update dynamic views on module load: {str(e)}")
+
+
+
     @api.model
     def _update_views_with_dynamic_fields(self):
         """
@@ -444,64 +888,161 @@ class HrEfficiency(models.Model):
         _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')
+            # Get active indicators ordered by sequence (consistent with other parts)
+            active_indicators = self.env['hr.efficiency.indicator'].search([('active', '=', True)], order='sequence')
             
-            _logger.info(f"Found {len(manual_fields)} manual fields to add to views")
+            fields_to_display = []
+            
+            for indicator in active_indicators:
+                field_name = self._get_indicator_field_name(indicator.name)
+                
+                # Check if this indicator has a manual field
+                manual_field = self.env['ir.model.fields'].search([
+                    ('model', '=', 'hr.efficiency'),
+                    ('state', '=', 'manual'),
+                    ('ttype', '=', 'float'),
+                    ('name', '=', field_name),
+                ], limit=1)
+                
+                if manual_field:
+                    # Indicator with manual field
+                    fields_to_display.append({
+                        'name': field_name,
+                        'field_description': indicator.name,
+                        'indicator': indicator
+                    })
+                else:
+                    # Create manual field for this indicator
+                    _logger.info(f"Creating manual field for indicator '{indicator.name}'")
+                    self.env['hr.efficiency.indicator']._create_dynamic_field(indicator)
+                    # Add to display list after creation
+                    fields_to_display.append({
+                        'name': field_name,
+                        'field_description': indicator.name,
+                        'indicator': indicator
+                    })
+            
+            _logger.info(f"Found {len(fields_to_display)} 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"'
+            for field_info in fields_to_display:
+                # Determine widget based on indicator type
+                indicator = field_info['indicator']
+                widget_map = {
+                    'percentage': 'percentage',  # Changed back to 'percentage' to show % symbol
+                    'hours': 'float_time',
+                    'currency': 'monetary',
+                    'number': 'float'
+                }
+                widget = widget_map.get(indicator.indicator_type, 'float') if indicator else 'float'
                 
-                # Add decorations based on indicator thresholds
-                indicator = self.env['hr.efficiency.indicator'].search([
-                    ('name', 'ilike', field.field_description)
-                ], limit=1)
+                field_xml = f'<field name="{field_info["name"]}" widget="{widget}" optional="show"'
                 
+                # Add field name as string (tooltip will be shown automatically from field description)
                 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 += f' string="{indicator.name}"'
+                
+                # Add decorations based on indicator thresholds
+                if indicator:
+                    # Use dynamic thresholds from indicator configuration
+                    green_threshold = indicator.color_threshold_green / 100.0
+                    yellow_threshold = indicator.color_threshold_yellow / 100.0
+                    
+                    field_xml += f' decoration-success="{field_info["name"]} &gt;= {green_threshold}"'
+                    field_xml += f' decoration-warning="{field_info["name"]} &gt;= {yellow_threshold} and {field_info["name"]} &lt; {green_threshold}"'
+                    field_xml += f' decoration-danger="{field_info["name"]} &lt; {yellow_threshold}"'
+                    
+                    # Add priority background color using CSS classes
+                    if hasattr(indicator, 'priority') and indicator.priority != 'none':
+                        if indicator.priority == 'low':
+                            field_xml += f' class="priority-low-bg"'
+                        elif indicator.priority == 'medium':
+                            field_xml += f' class="priority-medium-bg"'
+                        elif indicator.priority == 'high':
+                            field_xml += f' class="priority-high-bg"'
                 
                 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")
+                if dynamic_fields_xml:
+                    new_arch = f"""
+<xpath expr=\"//field[@name='expected_hours_to_date']\" position=\"after\">{dynamic_fields_xml}</xpath>
+"""
                 else:
-                    _logger.warning("Comment not found in inherited list view")
+                    # If no dynamic fields, remove any existing dynamic fields from the view
+                    new_arch = """
+<xpath expr=\"//field[@name='expected_hours_to_date']\" position=\"after\">
+    <!-- No dynamic fields to display -->
+</xpath>
+"""
+                inherited_list_view.write({'arch': new_arch})
+                _logger.info(f"Updated inherited list view with {len(fields_to_display)} dynamic fields")
             
             # 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"/>'
+            for field_info in fields_to_display:
+                # Determine widget based on indicator type
+                indicator = field_info['indicator']
+                widget_map = {
+                    'percentage': 'badge',
+                    'hours': 'float_time',
+                    'currency': 'monetary',
+                    'number': 'float'
+                }
+                widget = widget_map.get(indicator.indicator_type, 'badge') if indicator else 'badge'
+                
+                field_xml = f'<field name="{field_info["name"]}" widget="{widget}"'
+                
+                # Add help text with indicator description (valid in form views)
+                if indicator and indicator.description:
+                    # Escape quotes in description for XML
+                    help_text = indicator.description.replace('"', '&quot;')
+                    field_xml += f' help="{help_text}"'
+                
+                # Add decorations based on indicator thresholds
+                if indicator:
+                    # Use dynamic thresholds from indicator configuration
+                    green_threshold = indicator.color_threshold_green / 100.0
+                    yellow_threshold = indicator.color_threshold_yellow / 100.0
+                    
+                    field_xml += f' decoration-success="{field_info["name"]} &gt;= {green_threshold}"'
+                    field_xml += f' decoration-warning="{field_info["name"]} &gt;= {yellow_threshold} and {field_info["name"]} &lt; {green_threshold}"'
+                    field_xml += f' decoration-danger="{field_info["name"]} &lt; {yellow_threshold}"'
+                    
+                    # Add priority background color using CSS classes
+                    if hasattr(indicator, 'priority') and indicator.priority != 'none':
+                        if indicator.priority == 'low':
+                            field_xml += f' class="priority-low-bg"'
+                        elif indicator.priority == 'medium':
+                            field_xml += f' class="priority-medium-bg"'
+                        elif indicator.priority == 'high':
+                            field_xml += f' class="priority-high-bg"'
+                
+                field_xml += '/>'
                 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")
+                if form_dynamic_fields_xml:
+                    new_form_arch = f"""
+<xpath expr=\"//field[@name='overall_efficiency']\" position=\"before\">{form_dynamic_fields_xml}</xpath>
+"""
                 else:
-                    _logger.warning("Comment not found in inherited form view")
+                    # If no dynamic fields, remove any existing dynamic fields from the view
+                    new_form_arch = """
+<xpath expr=\"//field[@name='overall_efficiency']\" position=\"before\">
+    <!-- No dynamic fields to display -->
+</xpath>
+"""
+                inherited_form_view.write({'arch': new_form_arch})
+                _logger.info("Updated inherited form view with dynamic fields")
                     
         except Exception as e:
             _logger.error(f"Error updating views with dynamic fields: {str(e)}")
@@ -512,6 +1053,13 @@ class HrEfficiency(models.Model):
         Generate list of months between start_month and end_month (inclusive)
         """
         months = []
+        
+        # Convert date objects to datetime if needed
+        if isinstance(start_month, date):
+            start_month = start_month.strftime('%Y-%m')
+        if isinstance(end_month, date):
+            end_month = end_month.strftime('%Y-%m')
+            
         current = datetime.strptime(start_month, '%Y-%m')
         end = datetime.strptime(end_month, '%Y-%m')
         
@@ -527,6 +1075,31 @@ class HrEfficiency(models.Model):
         Cron job to automatically calculate efficiency
         """
         self.calculate_efficiency_for_period()
+        self._update_dynamic_filter_labels()
+
+    @api.model
+    def _update_dynamic_filter_labels(self):
+        """
+        Update dynamic filter labels based on current date
+        """
+        labels = self._get_dynamic_month_labels()
+        
+        # Update filter labels
+        filter_mapping = {
+            'filter_two_months_ago': labels['two_months_ago'],
+            'filter_last_month': labels['last_month'],
+            'filter_current_month': labels['current_month'],
+            'filter_next_month': labels['next_month'],
+            'filter_two_months_ahead': labels['two_months_ahead'],
+        }
+        
+        for filter_name, label in filter_mapping.items():
+            try:
+                filter_record = self.env.ref(f'hr_efficiency.{filter_name}', raise_if_not_found=False)
+                if filter_record:
+                    filter_record.write({'name': label})
+            except Exception as e:
+                _logger.warning(f"Could not update filter {filter_name}: {str(e)}")
 
     @api.model
     def _get_month_filter_options(self):
@@ -557,3 +1130,101 @@ class HrEfficiency(models.Model):
             months.append((month_year, month_name))
         
         return months
+
+    @api.model
+    def _get_dynamic_month_labels(self):
+        """
+        Get dynamic month labels for filters
+        Returns a dictionary with month labels like "Mes 1", "Mes 2", "Mes 3 << actual", etc.
+        """
+        current_date = date.today()
+        labels = {}
+        
+        # 2 months ago
+        month_2_ago = current_date - relativedelta(months=2)
+        labels['two_months_ago'] = f"Mes {month_2_ago.strftime('%m')}"
+        
+        # 1 month ago
+        month_1_ago = current_date - relativedelta(months=1)
+        labels['last_month'] = f"Mes {month_1_ago.strftime('%m')}"
+        
+        # Current month
+        labels['current_month'] = f"Mes {current_date.strftime('%m')} << actual"
+        
+        # Next month
+        month_1_ahead = current_date + relativedelta(months=1)
+        labels['next_month'] = f"Mes {month_1_ahead.strftime('%m')}"
+        
+        # 2 months ahead
+        month_2_ahead = current_date + relativedelta(months=2)
+        labels['two_months_ahead'] = f"Mes {month_2_ahead.strftime('%m')}"
+        
+        return labels
+
+    def _count_working_days(self, start_date, end_date, employee=None):
+        """
+        Count working days between two dates considering employee calendar
+        """
+        working_days = 0
+        current_date = start_date
+        
+        while current_date <= end_date:
+            # Check if it's a working day according to employee calendar
+            if self._is_working_day(current_date, employee):
+                working_days += 1
+            current_date += relativedelta(days=1)
+        
+        return working_days
+
+    def _is_working_day(self, check_date, employee=None):
+        """
+        Check if a date is a working day considering employee calendar
+        """
+        if not employee or not employee.resource_calendar_id:
+            # Fallback to basic weekday check
+            return check_date.weekday() < 5
+        
+        try:
+            # Convert date to datetime for calendar check
+            check_datetime = datetime.combine(check_date, datetime.min.time())
+            
+            # Check if the day is a working day according to employee calendar
+            working_hours = employee.resource_calendar_id._list_work_time_per_day(
+                check_datetime, check_datetime, compute_leaves=True
+            )
+            
+            # If there are working hours, it's a working day
+            return bool(working_hours)
+            
+        except Exception:
+            # Fallback to basic weekday check if there's any error
+            return check_date.weekday() < 5
+
+    @api.model
+    def _apply_default_values_to_existing_records(self):
+        """Apply default values to existing employee and company records"""
+        import logging
+        _logger = logging.getLogger(__name__)
+        
+        try:
+            # Update employees with utilization_rate = 100.0 if not set
+            employees_to_update = self.env['hr.employee'].search([
+                ('utilization_rate', '=', 0.0)
+            ])
+            if employees_to_update:
+                employees_to_update.write({'utilization_rate': 100.0})
+                _logger.info(f"Updated {len(employees_to_update)} employees with default utilization_rate = 100.0%")
+            
+            # Update companies with overhead = 40.0 if not set
+            companies_to_update = self.env['res.company'].search([
+                ('overhead', '=', 0.0)
+            ])
+            if companies_to_update:
+                companies_to_update.write({'overhead': 40.0})
+                _logger.info(f"Updated {len(companies_to_update)} companies with default overhead = 40.0%")
+                
+            _logger.info("Default values applied successfully to existing records")
+            
+        except Exception as e:
+            _logger.error(f"Error applying default values to existing records: {str(e)}")
+            # Don't raise the exception to avoid breaking the installation

+ 2 - 0
models/hr_efficiency_dynamic_field.py

@@ -26,12 +26,14 @@ class HrEfficiencyDynamicField(models.Model):
         ('float_time', 'Float Time'),
         ('number', 'Number'),
         ('text', 'Text'),
+        ('monetary', 'Monetary'),
     ], 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')
+    priority_background_color = fields.Char('Priority Background Color', help='Color de fondo de la columna basado en la prioridad del indicador')
     
     # Computed fields
     field_technical_name = fields.Char('Technical Field Name', compute='_compute_technical_name', store=True)

+ 155 - 7
models/hr_efficiency_indicator.py

@@ -1,6 +1,9 @@
+import logging
 from odoo import models, fields, api, _
 from odoo.exceptions import ValidationError
 
+_logger = logging.getLogger(__name__)
+
 
 class HrEfficiencyIndicator(models.Model):
     _name = 'hr.efficiency.indicator'
@@ -11,6 +14,15 @@ class HrEfficiencyIndicator(models.Model):
     sequence = fields.Integer('Sequence', default=10)
     active = fields.Boolean('Active', default=True)
     
+    # Indicator type for widget selection
+    indicator_type = fields.Selection([
+        ('percentage', 'Percentage'),
+        ('hours', 'Hours'),
+        ('currency', 'Currency'),
+        ('number', 'Number')
+    ], string='Indicator Type', default='percentage', required=True, 
+       help='Type of indicator that determines how it will be displayed in views')
+    
     # Formula configuration
     formula = fields.Text('Formula', required=True, help="""
     Use the following variables in your formula:
@@ -30,10 +42,35 @@ class HrEfficiencyIndicator(models.Model):
     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')
     
+    # Priority for column background color
+    priority = fields.Selection([
+        ('none', 'Sin Prioridad'),
+        ('low', 'Baja (Verde)'),
+        ('medium', 'Media (Amarillo)'),
+        ('high', 'Alta (Rojo)')
+    ], string='Prioridad', default='none', help='Prioridad del indicador que determina el color de fondo de la columna')
+    
     # 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')
+    color_threshold_red = fields.Float('Red Threshold', default=50.0, help='Percentage above which to show red (below this shows danger)')
+    
+    # Computed field for priority background color
+    priority_color = fields.Char('Priority Color', compute='_compute_priority_color', store=True, help='Color de fondo basado en la prioridad')
+    
+    @api.depends('priority')
+    def _compute_priority_color(self):
+        """Compute background color based on priority"""
+        for record in self:
+            if record.priority == 'low':
+                record.priority_color = '#d4edda'  # Verde claro
+            elif record.priority == 'medium':
+                record.priority_color = '#fff3cd'  # Amarillo claro
+            elif record.priority == 'high':
+                record.priority_color = '#f8d7da'  # Rojo claro
+            else:
+                record.priority_color = ''  # Sin color (transparente)
     
     @api.constrains('weight')
     def _check_weight(self):
@@ -61,24 +98,41 @@ class HrEfficiencyIndicator(models.Model):
         result = super().write(vals)
         # Update dynamic field for this indicator
         for record in self:
-            self._update_dynamic_field(record)
+            self._update_dynamic_field(record, vals)
         return result
     
     def unlink(self):
         """Delete indicator and delete dynamic field"""
-        # Delete associated dynamic fields
+        # FIRST: Mark indicators as inactive to exclude them from views
+        self.write({'active': False})
+        
+        # SECOND: Update views to remove field references BEFORE deleting fields
+        self.env['hr.efficiency']._update_views_with_dynamic_fields()
+        
+        # THEN: Delete associated dynamic fields and manual fields
         for record in self:
             dynamic_field = self.env['hr.efficiency.dynamic.field'].search([
                 ('indicator_id', '=', record.id)
             ])
             if dynamic_field:
                 dynamic_field.unlink()
+            # Also remove the corresponding manual field in ir.model.fields
+            efficiency_model = 'hr.efficiency'
+            technical_name = self.env[efficiency_model]._get_indicator_field_name(record.name)
+            imf = self.env['ir.model.fields'].search([
+                ('model', '=', efficiency_model),
+                ('name', '=', technical_name),
+                ('state', '=', 'manual'),
+            ], limit=1)
+            if imf:
+                imf.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
+        # Create dynamic field record for all indicators
         existing_field = self.env['hr.efficiency.dynamic.field'].search([
             ('indicator_id', '=', indicator.id)
         ])
@@ -93,6 +147,15 @@ class HrEfficiencyIndicator(models.Model):
             if not field_name[0].isalpha():
                 field_name = 'indicator_' + field_name
             
+            # Determine widget based on indicator type
+            widget_map = {
+                'percentage': 'percentage',
+                'hours': 'float_time',
+                'currency': 'monetary',
+                'number': 'float'
+            }
+            widget = widget_map.get(indicator.indicator_type, 'percentage')
+            
             self.env['hr.efficiency.dynamic.field'].create({
                 'name': field_name,
                 'label': indicator.name,
@@ -102,25 +165,102 @@ class HrEfficiencyIndicator(models.Model):
                 'show_in_list': True,
                 'show_in_form': True,
                 'show_in_search': False,
-                'widget': 'percentage',
+                'widget': widget,
                 'decoration_success': indicator.color_threshold_green,
                 'decoration_warning': indicator.color_threshold_yellow,
-                'decoration_danger': 0.0,
+                'decoration_danger': indicator.color_threshold_red,
+                'priority_background_color': indicator.priority_color,
+            })
+
+        # Ensure an ir.model.fields manual field exists (Studio-like) for ALL indicators
+        efficiency_model = 'hr.efficiency'
+        model_rec = self.env['ir.model']._get(efficiency_model)
+        # Compute the technical field name like Studio (x_ prefix)
+        technical_name = self.env[efficiency_model]._get_indicator_field_name(indicator.name)
+        imf = self.env['ir.model.fields'].search([
+            ('model', '=', efficiency_model),
+            ('name', '=', technical_name),
+        ], limit=1)
+        if not imf:
+            self.env['ir.model.fields'].with_context(studio=True).create({
+                'name': technical_name,
+                'model': efficiency_model,
+                'model_id': model_rec.id,
+                'ttype': 'float',
+                'field_description': indicator.name,
+                'help': indicator.description or indicator.name,
+                'state': 'manual',
+                'store': True,
+                'compute': False,
             })
+        else:
+            # Keep label and help in sync
+            update_vals = {}
+            if imf.field_description != indicator.name:
+                update_vals['field_description'] = indicator.name
+            if imf.help != (indicator.description or indicator.name):
+                update_vals['help'] = indicator.description or indicator.name
+            if update_vals:
+                imf.write(update_vals)
+
+        # Update views to include the new field
+        self.env['hr.efficiency']._update_views_with_dynamic_fields()
+        
+        # Recompute all indicators to populate the new stored field
+        records = self.env['hr.efficiency'].search([])
+        if records:
+            records._calculate_all_indicators()
     
-    def _update_dynamic_field(self, indicator):
+    def _update_dynamic_field(self, indicator, vals=None):
         """Update the dynamic field for the indicator"""
         dynamic_field = self.env['hr.efficiency.dynamic.field'].search([
             ('indicator_id', '=', indicator.id)
         ])
         
         if dynamic_field:
+            # Determine widget based on indicator type
+            widget_map = {
+                'percentage': 'percentage',
+                'hours': 'float_time',
+                'currency': 'monetary',
+                'number': 'float'
+            }
+            widget = widget_map.get(indicator.indicator_type, 'percentage')
+            
             dynamic_field.write({
                 'label': indicator.name,
                 'active': indicator.active,
+                'widget': widget,
                 'decoration_success': indicator.color_threshold_green,
                 'decoration_warning': indicator.color_threshold_yellow,
+                'decoration_danger': indicator.color_threshold_red,
+                'priority_background_color': indicator.priority_color,
             })
+        # Sync corresponding ir.model.fields label and refresh views
+        efficiency_model = 'hr.efficiency'
+        technical_name = self.env[efficiency_model]._get_indicator_field_name(indicator.name)
+        imf = self.env['ir.model.fields'].search([
+            ('model', '=', efficiency_model),
+            ('name', '=', technical_name),
+        ], limit=1)
+        if imf:
+            update_vals = {}
+            if imf.field_description != indicator.name:
+                update_vals['field_description'] = indicator.name
+            if imf.help != (indicator.description or indicator.name):
+                update_vals['help'] = indicator.description or indicator.name
+            if update_vals:
+                imf.write(update_vals)
+        # Update views when active status, sequence, or bulk operations change
+        if 'active' in vals or 'sequence' in vals or len(self) > 1:
+            try:
+                self.env['hr.efficiency']._update_views_with_dynamic_fields()
+            except Exception as e:
+                # Log the error but don't let it rollback the transaction
+                import logging
+                _logger = logging.getLogger(__name__)
+                _logger.error(f"Error updating views after indicator change: {e}")
+                # Continue with the transaction even if view update fails
     
     def evaluate_formula(self, efficiency_data):
         """
@@ -135,6 +275,12 @@ class HrEfficiencyIndicator(models.Model):
                 '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),
+                'total_actual_hours': efficiency_data.get('total_actual_hours', 0),
+                'expected_hours_to_date': efficiency_data.get('expected_hours_to_date', 0),
+                'wage': efficiency_data.get('wage', 0),
+                'utilization_rate': efficiency_data.get('utilization_rate', 100.0),
+                'overhead': efficiency_data.get('overhead', 40.0),
+                'precio_por_hora': efficiency_data.get('precio_por_hora', 0),
             }
             
             # Add math functions for safety
@@ -161,5 +307,7 @@ class HrEfficiencyIndicator(models.Model):
             return 'text-success'
         elif percentage >= self.color_threshold_yellow:
             return 'text-warning'
-        else:
+        elif percentage >= self.color_threshold_red:
             return 'text-danger'
+        else:
+            return 'text-danger'  # Below red threshold - critical

+ 15 - 0
models/hr_employee.py

@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class HrEmployee(models.Model):
+    _inherit = 'hr.employee'
+
+    utilization_rate = fields.Float(
+        'Utilization Rate (%)', 
+        default=100.0,
+        help="Se refiere al porcentaje de tiempo que un empleado dedica a trabajo facturable o productivo directo",
+        groups="hr.group_hr_user"
+    )

+ 29 - 0
models/res_company.py

@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class ResCompany(models.Model):
+    _inherit = 'res.company'
+
+    overhead = fields.Float(
+        'Overhead (%)', 
+        default=40.0,
+        help="Porcentaje de overhead de la compañía",
+        groups="base.group_system"
+    )
+    
+    expected_profitability = fields.Float(
+        'Rentabilidad esperada (%)',
+        default=30.0,
+        help="Porcentaje de rentabilidad esperada de la compañía",
+        groups="base.group_system"
+    )
+    
+    efficiency_factor = fields.Float(
+        'Factor de Eficiencia (%)',
+        default=85.0,
+        help="Porcentaje de horas disponibles que se espera que el empleado trabaje efectivamente",
+        groups="base.group_system"
+    )

+ 32 - 0
recompute_expected_hours.py

@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+"""
+Script to force recompute expected_hours_to_date field for existing records
+"""
+
+import odoo
+from odoo import api, fields, models
+from odoo.tools import config
+
+def main():
+    # Initialize Odoo
+    odoo.cli.server.main()
+    
+    # Get environment
+    env = api.Environment(cr, uid, {})
+    
+    # Get all hr.efficiency records
+    efficiency_records = env['hr.efficiency'].search([('active', '=', True)])
+    
+    print(f"Found {len(efficiency_records)} efficiency records to update")
+    
+    # Force recompute of expected_hours_to_date field
+    for record in efficiency_records:
+        record._compute_expected_hours_to_date()
+        print(f"Updated record {record.id}: {record.employee_id.name} - {record.month_year} - Expected: {record.expected_hours_to_date}")
+    
+    # Commit changes
+    env.cr.commit()
+    print("All records updated successfully!")
+
+if __name__ == "__main__":
+    main()

+ 29 - 0
recompute_script.py

@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+import sys
+import os
+
+# Add Odoo path
+sys.path.append('/var/odoo/stg2.mcteam.run/src')
+
+import odoo
+from odoo import api, SUPERUSER_ID
+from odoo.tools import config
+
+# Initialize Odoo
+odoo.cli.server.main()
+
+# Get environment
+env = api.Environment(cr, SUPERUSER_ID, {})
+
+# Get all hr.efficiency records
+records = env['hr.efficiency'].search([('active', '=', True)])
+print(f"Found {len(records)} records to update")
+
+# Force recompute
+for record in records:
+    record._compute_expected_hours_to_date()
+    print(f"Updated {record.employee_id.name} - {record.month_year}: {record.expected_hours_to_date}")
+
+# Commit changes
+env.cr.commit()
+print("Done!")

+ 1 - 1
report/hr_efficiency_report.py

@@ -11,7 +11,7 @@ class HrEfficiencyReport(models.Model):
     _auto = False
 
     month_year = fields.Char('Month Year', readonly=True)
-    employee_id = fields.Many2one('hr.employee', 'Employee', readonly=True)
+    employee_id = fields.Many2one('hr.employee', 'Employee', readonly=True, domain=[('employee_type', '=', 'employee')])
     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)

+ 3 - 3
report/hr_efficiency_report_views.xml

@@ -79,12 +79,12 @@
             </field>
         </record>
 
-        <!-- Menu -->
-        <menuitem id="menu_hr_efficiency_report"
+        <!-- Menu - REMOVED: Now only in Planning -->
+        <!-- <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"/>
+                  groups="hr.group_hr_user"/> -->
     </data>
 </odoo>

+ 42 - 0
static/src/css/hr_efficiency.css

@@ -0,0 +1,42 @@
+/* HR Efficiency - Priority Background Colors */
+
+/* Prioridad Alta (Rojo) - Aplicado al TD completo usando :has() */
+.o_list_view .o_data_row .o_data_cell:has(.priority-high-bg) {
+    background-color: #ffebee !important;
+}
+
+/* Prioridad Media (Amarillo) - Aplicado al TD completo usando :has() */
+.o_list_view .o_data_row .o_data_cell:has(.priority-medium-bg) {
+    background-color: #fff8e1 !important;
+}
+
+/* Prioridad Baja (Verde) - Aplicado al TD completo usando :has() */
+.o_list_view .o_data_row .o_data_cell:has(.priority-low-bg) {
+    background-color: #e8f5e8 !important;
+}
+
+/* Aplicar también al encabezado de la columna */
+.o_list_view .o_list_table thead th.priority-high-bg {
+    background-color: #ffebee !important;
+}
+
+.o_list_view .o_list_table thead th.priority-medium-bg {
+    background-color: #fff8e1 !important;
+}
+
+.o_list_view .o_list_table thead th.priority-low-bg {
+    background-color: #e8f5e8 !important;
+}
+
+/* Hover effects para mejor visibilidad */
+.o_list_view .o_data_row:hover .o_data_cell:has(.priority-high-bg) {
+    background-color: #ffcdd2 !important;
+}
+
+.o_list_view .o_data_row:hover .o_data_cell:has(.priority-medium-bg) {
+    background-color: #ffeaa7 !important;
+}
+
+.o_list_view .o_data_row:hover .o_data_cell:has(.priority-low-bg) {
+    background-color: #c8e6c9 !important;
+}

+ 2 - 17
views/hr_efficiency_dynamic_views.xml

@@ -7,7 +7,7 @@
             <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">
+                <xpath expr="//field[@name='expected_hours_to_date']" position="after">
                     <!-- Dynamic indicator fields will be added here -->
                 </xpath>
             </field>
@@ -25,21 +25,6 @@
             </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>

+ 12 - 1
views/hr_efficiency_indicator_views.xml

@@ -8,11 +8,14 @@
             <list string="Efficiency Indicators">
                 <field name="sequence" widget="handle"/>
                 <field name="name"/>
+                <field name="indicator_type"/>
                 <field name="formula"/>
                 <field name="target_percentage"/>
                 <field name="weight"/>
                 <field name="color_threshold_green"/>
                 <field name="color_threshold_yellow"/>
+                <field name="color_threshold_red"/>
+                <field name="priority"/>
                 <field name="active"/>
             </list>
         </field>
@@ -29,6 +32,7 @@
                         <group>
                             <field name="name"/>
                             <field name="sequence"/>
+                            <field name="indicator_type"/>
                             <field name="active"/>
                         </group>
                         <group>
@@ -36,6 +40,8 @@
                             <field name="weight"/>
                             <field name="color_threshold_green"/>
                             <field name="color_threshold_yellow"/>
+                            <field name="color_threshold_red"/>
+                            <field name="priority"/>
                         </group>
                     </group>
                     <group>
@@ -56,8 +62,13 @@
         <field name="arch" type="xml">
             <search string="Search Efficiency Indicators">
                 <field name="name"/>
+                <field name="indicator_type"/>
                 <filter string="Active" name="active" domain="[('active', '=', True)]"/>
                 <filter string="Inactive" name="inactive" domain="[('active', '=', False)]"/>
+                <filter string="Percentage" name="percentage" domain="[('indicator_type', '=', 'percentage')]"/>
+                <filter string="Hours" name="hours" domain="[('indicator_type', '=', 'hours')]"/>
+                <filter string="Currency" name="currency" domain="[('indicator_type', '=', 'currency')]"/>
+                <filter string="Number" name="number" domain="[('indicator_type', '=', 'number')]"/>
             </search>
         </field>
     </record>
@@ -79,7 +90,7 @@
     <!-- Menu Item -->
     <menuitem id="menu_hr_efficiency_indicator"
               name="Efficiency Indicators"
-              parent="planning.planning_menu_settings_config"
+              parent="planning.planning_menu_settings"
               action="action_hr_efficiency_indicator"
               sequence="20"/>
 </odoo>

+ 43 - 39
views/hr_efficiency_views.xml

@@ -6,19 +6,29 @@
             <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">
+                <list string="Employee Efficiency">
                     <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"/>
+                    <field name="wage" sum="Total Gross Salary" optional="show"/>
+                    <field name="utilization_rate" avg="Average Utilization" optional="show"/>
+                    <field name="overhead" avg="Average Overhead" optional="show"/>
+                    <field name="expected_profitability" avg="Average Expected Profitability" optional="show"/>
+                    <field name="efficiency_factor" avg="Average Efficiency Factor" optional="show"/>
+                    <field name="precio_por_hora" avg="Average Precio por Hora" optional="show"/>
+                    <field name="company_id" groups="base.group_multi_company" optional="hide"/>
+                    <field name="calculation_date" optional="hide"/>
+                    <field name="available_hours" sum="Total Available" optional="show"/>
+                    <field name="planned_hours" sum="Total Planned" optional="show"/>
+                    <field name="planned_billable_hours" sum="Total Planned Billable" optional="hide"/>
+                    <field name="planned_non_billable_hours" sum="Total Planned Non-Billable" optional="hide"/>
+                    <field name="actual_billable_hours" sum="Total Actual Billable" optional="show"/>
+                    <field name="actual_non_billable_hours" sum="Total Actual Non-Billable" optional="show"/>
+                    <field name="total_actual_hours" sum="Total Actual" optional="show"/>
+                    <field name="expected_hours_to_date" sum="Total Expected" optional="hide"/>
+                    <field name="overall_efficiency_display" widget="badge"
+                           decoration-danger="overall_efficiency_display == '0.00' or overall_efficiency &lt; 80"
+                           decoration-success="overall_efficiency >= 90"
+                           decoration-warning="overall_efficiency >= 80 and overall_efficiency &lt; 90"/>
                 </list>
             </field>
         </record>
@@ -39,12 +49,21 @@
                             <group>
                                 <field name="month_year"/>
                                 <field name="employee_id"/>
+                                <field name="wage"/>
+                                <field name="utilization_rate"/>
+                                <field name="overhead"/>
+                                <field name="expected_profitability"/>
+                                <field name="efficiency_factor"/>
+                                <field name="precio_por_hora"/>
                                 <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"/>
+                                <field name="overall_efficiency" widget="badge" 
+                                       decoration-danger="overall_efficiency_display == '0.00' or overall_efficiency &lt; 80"
+                                       decoration-success="overall_efficiency >= 90"
+                                       decoration-warning="overall_efficiency >= 80 and overall_efficiency &lt; 90"/>
                             </group>
                         </group>
                         <notebook>
@@ -65,6 +84,7 @@
                                     <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"/>
+                                    <field name="expected_hours_to_date" widget="float_time"/>
                                 </group>
                             </page>
                         </notebook>
@@ -83,34 +103,17 @@
                     <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 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 fecha como ventas -->
+                    <separator/>
+                    <filter name="filter_date" date="date" default_period="month" start_month="-2" end_month="2"/>
                     
                     <!-- Filtro de archivado -->
                     <separator/>
@@ -119,7 +122,7 @@
                     
                     <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="Month" name="group_month" context="{'group_by': 'date:month'}"/>
                         <filter string="Company" name="group_company" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
                     </group>
                 </search>
@@ -131,8 +134,9 @@
             <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_tree"/>
             <field name="search_view_id" ref="view_hr_efficiency_search"/>
-            <field name="context">{'search_default_august_2025': 1}</field>
+            <field name="context">{'search_default_filter_date': 1}</field>
             <field name="help" type="html">
                 <p class="o_view_nocontent_empty_folder">
                     No efficiency records found!
@@ -144,12 +148,12 @@
         
 
 
-        <!-- Menu -->
-        <menuitem id="menu_hr_efficiency"
+        <!-- Menu - REMOVED: Now only in Planning -->
+        <!-- <menuitem id="menu_hr_efficiency"
                   name="Efficiency"
                   parent="hr.menu_hr_root"
                   action="action_hr_efficiency"
                   sequence="50"
-                  groups="hr.group_hr_user"/>
+                  groups="hr.group_hr_user"/> -->
     </data>
 </odoo>

+ 20 - 0
views/hr_employee_views.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+    <data>
+        <!-- Inherit employee form to add utilization rate field after hourly_cost -->
+        <record id="view_employee_form_utilization_rate" model="ir.ui.view">
+            <field name="name">hr.employee.form.utilization.rate</field>
+            <field name="model">hr.employee</field>
+            <field name="inherit_id" ref="hr_hourly_cost.view_employee_form"/>
+            <field name="priority" eval="50"/>
+            <field name="arch" type="xml">
+                <group name="application_group" position="inside">
+                    <label for="utilization_rate"/>
+                    <div name="utilization_rate">
+                        <field name="utilization_rate" class="oe_inline"/>
+                    </div>
+                </group>
+            </field>
+        </record>
+    </data>
+</odoo>

+ 19 - 0
views/res_company_views.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+    <data>
+        <!-- Inherit company form to add overhead field after currency_id -->
+        <record id="view_company_form_overhead" model="ir.ui.view">
+            <field name="name">res.company.form.overhead</field>
+            <field name="model">res.company</field>
+            <field name="inherit_id" ref="base.view_company_form"/>
+            <field name="priority" eval="50"/>
+            <field name="arch" type="xml">
+                <field name="currency_id" position="after">
+                    <field name="overhead" groups="base.group_system"/>
+                    <field name="expected_profitability" groups="base.group_system"/>
+                    <field name="efficiency_factor" groups="base.group_system"/>
+                </field>
+            </field>
+        </record>
+    </data>
+</odoo>

+ 3 - 2
wizard/hr_efficiency_calculation_wizard.py

@@ -13,7 +13,7 @@ class HrEfficiencyCalculationWizard(models.TransientModel):
 
     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")
+    employee_ids = fields.Many2many('hr.employee', string='Employees', domain=[('employee_type', '=', 'employee')], 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
@@ -44,7 +44,8 @@ class HrEfficiencyCalculationWizard(models.TransientModel):
         else:
             employees = self.env['hr.employee'].search([
                 ('active', '=', True),
-                ('company_id', '=', self.company_id.id)
+                ('company_id', '=', self.company_id.id),
+                ('employee_type', '=', 'employee')
             ])
         
         if not employees:

+ 3 - 3
wizard/hr_efficiency_calculation_wizard_views.xml

@@ -71,12 +71,12 @@
             </field>
         </record>
 
-        <!-- Menu -->
-        <menuitem id="menu_hr_efficiency_calculation"
+        <!-- Menu - REMOVED: Now only in Planning -->
+        <!-- <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"/>
+                  groups="hr.group_hr_manager"/> -->
     </data>
 </odoo>