Răsfoiți Sursa

🔧 Fix: Resolver conflicto y actualizar hr_efficiency con sincronización corregida

- Resuelto conflicto de merge en hr_efficiency_indicator.py
- Actualizado con la versión que corrige la sincronización de fórmulas
- Las fórmulas de rentabilidad ahora usan wage_overhead correctamente
- Sistema de sincronización robusto implementado
root 5 luni în urmă
părinte
comite
d68c9cf87d
1 a modificat fișierele cu 146 adăugiri și 1 ștergeri
  1. 146 1
      hr_efficiency/models/hr_efficiency_indicator.py

+ 146 - 1
hr_efficiency/models/hr_efficiency_indicator.py

@@ -339,6 +339,122 @@ class HrEfficiencyIndicator(models.Model):
             }
         }
     
+    @api.model
+    def _load_indicator_from_xml(self, indicator_name):
+        """
+        Cargar datos del indicador desde el XML
+        """
+        try:
+            # Mapeo de indicadores con sus datos del XML
+            xml_indicators = {
+                'Planning Coverage': {
+                    'formula': '(planned_hours / available_hours) if available_hours > 0 else 0',
+                    'sequence': 10,
+                    '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)',
+                    'target_percentage': 95.0,
+                    'weight': 0.0,
+                    'color_threshold_green': 85.0,
+                    'color_threshold_yellow': 75.0,
+                    'color_threshold_red': 65.0,
+                },
+                'Planned Utilization': {
+                    'formula': '(planned_billable_hours / available_hours) if available_hours > 0 else 0',
+                    'sequence': 20,
+                    '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)',
+                    'target_percentage': 80.0,
+                    'weight': 0.0,
+                    'color_threshold_green': 85.0,
+                    'color_threshold_yellow': 75.0,
+                    'color_threshold_red': 60.0,
+                },
+                'Break-Even Hours Needed': {
+                    'formula': '(wage_overhead * (utilization_rate / 100)) / precio_por_hora if precio_por_hora > 0 else 0',
+                    'sequence': 30,
+                    '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.',
+                    'target_percentage': 0.0,
+                    'weight': 0.0,
+                    'color_threshold_green': 85.0,
+                    'color_threshold_yellow': 75.0,
+                    'color_threshold_red': 65.0,
+                },
+                'Planned Profitability Coverage': {
+                    'formula': '(planned_billable_hours / ((wage_overhead * (utilization_rate / 100)) / precio_por_hora)) if wage_overhead > 0 and precio_por_hora > 0 else 0',
+                    'sequence': 40,
+                    '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)',
+                    'target_percentage': 100.0,
+                    'weight': 0.0,
+                    'color_threshold_green': 100.0,
+                    'color_threshold_yellow': 85.0,
+                    'color_threshold_red': 70.0,
+                },
+                'Estimation Accuracy Plan Adherence': {
+                    'formula': '(total_actual_hours / planned_hours) if planned_hours > 0 else 0',
+                    'sequence': 50,
+                    '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)',
+                    'target_percentage': 100.0,
+                    'weight': 0.0,
+                    'color_threshold_green': 85.0,
+                    'color_threshold_yellow': 75.0,
+                    'color_threshold_red': 60.0,
+                },
+                'Billable Plan Compliance': {
+                    'formula': '(actual_billable_hours / planned_billable_hours) if planned_billable_hours > 0 else 0',
+                    'sequence': 60,
+                    'description': 'Cumplimiento del Plan Facturable: ¿Se cumplió con el objetivo específico de horas facturables? (Horas Facturables Registradas / Horas Facturables Planeadas)',
+                    'target_percentage': 100.0,
+                    'weight': 0.0,
+                    'color_threshold_green': 85.0,
+                    'color_threshold_yellow': 75.0,
+                    'color_threshold_red': 65.0,
+                },
+                'Occupancy Rate': {
+                    'formula': '(total_actual_hours / available_hours) if available_hours > 0 else 0',
+                    'sequence': 70,
+                    '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)',
+                    'target_percentage': 95.0,
+                    'weight': 0.0,
+                    'color_threshold_green': 85.0,
+                    'color_threshold_yellow': 75.0,
+                    'color_threshold_red': 60.0,
+                },
+                'Utilization Rate': {
+                    'formula': '(actual_billable_hours / available_hours) if available_hours > 0 else 0',
+                    'sequence': 80,
+                    'description': 'Tasa de Utilización: Mide qué tan "productivo" (generando ingresos) está el equipo. (Horas Facturables Registradas / Horas Disponibles)',
+                    'target_percentage': 80.0,
+                    'weight': 0.0,
+                    'color_threshold_green': 85.0,
+                    'color_threshold_yellow': 75.0,
+                    'color_threshold_red': 60.0,
+                },
+                'Billability Rate': {
+                    'formula': '(actual_billable_hours / total_actual_hours) if total_actual_hours > 0 else 0',
+                    'sequence': 90,
+                    'description': 'Tasa de Facturabilidad: De todo el tiempo trabajado, ¿qué porcentaje fue facturable? (Horas Facturables Registradas / Total Horas Registradas)',
+                    'target_percentage': 85.0,
+                    'weight': 0.0,
+                    'color_threshold_green': 85.0,
+                    'color_threshold_yellow': 75.0,
+                    'color_threshold_red': 65.0,
+                },
+                'Actual Profitability Achievement': {
+                    'formula': '(actual_billable_hours / ((wage_overhead * (utilization_rate / 100)) / precio_por_hora)) if wage_overhead > 0 and precio_por_hora > 0 else 0',
+                    'sequence': 100,
+                    '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)',
+                    'target_percentage': 100.0,
+                    'weight': 0.0,
+                    'color_threshold_green': 100.0,
+                    'color_threshold_yellow': 85.0,
+                    'color_threshold_red': 70.0,
+                },
+            }
+            
+            return xml_indicators.get(indicator_name, {})
+            
+        except Exception as e:
+            _logger.error(f"Error cargando indicador {indicator_name} desde XML: {e}")
+            return {}
+
     @api.model
     def sync_indicators_from_xml(self):
         """
@@ -362,7 +478,36 @@ class HrEfficiencyIndicator(models.Model):
             
             for indicator in active_indicators:
                 try:
-                    # Verificar si el campo dinámico existe
+                    # PASO 1: Actualizar el indicador desde XML (fórmulas, secuencias, etc.)
+                    # Cargar el indicador desde XML para obtener la versión correcta
+                    xml_indicator = self._load_indicator_from_xml(indicator.name)
+                    if xml_indicator:
+                        # Actualizar indicador con datos del XML
+                        update_vals = {}
+                        if indicator.formula != xml_indicator.get('formula'):
+                            update_vals['formula'] = xml_indicator.get('formula')
+                        if indicator.sequence != xml_indicator.get('sequence'):
+                            update_vals['sequence'] = xml_indicator.get('sequence')
+                        if indicator.description != xml_indicator.get('description'):
+                            update_vals['description'] = xml_indicator.get('description')
+                        if indicator.target_percentage != xml_indicator.get('target_percentage'):
+                            update_vals['target_percentage'] = xml_indicator.get('target_percentage')
+                        if indicator.weight != xml_indicator.get('weight'):
+                            update_vals['weight'] = xml_indicator.get('weight')
+                        if indicator.color_threshold_green != xml_indicator.get('color_threshold_green'):
+                            update_vals['color_threshold_green'] = xml_indicator.get('color_threshold_green')
+                        if indicator.color_threshold_yellow != xml_indicator.get('color_threshold_yellow'):
+                            update_vals['color_threshold_yellow'] = xml_indicator.get('color_threshold_yellow')
+                        if indicator.color_threshold_red != xml_indicator.get('color_threshold_red'):
+                            update_vals['color_threshold_red'] = xml_indicator.get('color_threshold_red')
+                        
+                        if update_vals:
+                            indicator.write(update_vals)
+                            _logger.info(f"✅ Actualizado indicador: {indicator.name}")
+                            if 'formula' in update_vals:
+                                _logger.info(f"   Nueva fórmula: {update_vals['formula'][:50]}...")
+                    
+                    # PASO 2: Verificar si el campo dinámico existe
                     field_name = self.env['hr.efficiency']._get_indicator_field_name(indicator.name)
                     
                     # Buscar el campo manual existente