hr_efficiency_calculation_wizard.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. from datetime import date, datetime
  4. from dateutil.relativedelta import relativedelta
  5. from odoo import api, fields, models, _
  6. from odoo.exceptions import UserError
  7. class HrEfficiencyCalculationWizard(models.TransientModel):
  8. _name = 'hr.efficiency.calculation.wizard'
  9. _description = 'HR Efficiency Calculation Wizard'
  10. start_month = fields.Char('Start Month', required=True, help="Format: YYYY-MM")
  11. end_month = fields.Char('End Month', required=True, help="Format: YYYY-MM")
  12. employee_ids = fields.Many2many('hr.employee', string='Employees', domain=[('employee_type', '=', 'employee')], help="Leave empty to calculate for all active employees")
  13. company_id = fields.Many2one('res.company', 'Company', required=True, default=lambda self: self.env.company)
  14. # Results
  15. result_message = fields.Text('Result', readonly=True)
  16. created_count = fields.Integer('Created Records', readonly=True)
  17. updated_count = fields.Integer('Updated Records', readonly=True)
  18. total_count = fields.Integer('Total Processed', readonly=True)
  19. @api.model
  20. def default_get(self, fields_list):
  21. res = super().default_get(fields_list)
  22. # Set default period: last 3 months and next 6 months
  23. current_date = date.today()
  24. res['start_month'] = (current_date - relativedelta(months=3)).strftime('%Y-%m')
  25. res['end_month'] = (current_date + relativedelta(months=6)).strftime('%Y-%m')
  26. return res
  27. def action_calculate_efficiency(self):
  28. """
  29. Calculate efficiency for the specified period and employees
  30. """
  31. # Validate input
  32. self._validate_input()
  33. # Get employees to process
  34. if self.employee_ids:
  35. employees = self.employee_ids
  36. else:
  37. employees = self.env['hr.employee'].search([
  38. ('active', '=', True),
  39. ('company_id', '=', self.company_id.id),
  40. ('employee_type', '=', 'employee')
  41. ])
  42. if not employees:
  43. raise UserError(_("No employees found to process."))
  44. # Calculate efficiency
  45. efficiency_model = self.env['hr.efficiency']
  46. # Generate list of months
  47. months = self._generate_month_list(self.start_month, self.end_month)
  48. created_records = []
  49. for employee in employees:
  50. for month in months:
  51. # Calculate efficiency data
  52. efficiency_data = efficiency_model._calculate_employee_efficiency(employee, month)
  53. efficiency_data['company_id'] = self.company_id.id
  54. # Check if there are changes compared to the latest record
  55. latest_record = efficiency_model.search([
  56. ('employee_id', '=', employee.id),
  57. ('month_year', '=', month),
  58. ('company_id', '=', self.company_id.id),
  59. ], order='calculation_date desc', limit=1)
  60. has_changes = False
  61. if latest_record:
  62. # Compare current data with latest record
  63. fields_to_compare = [
  64. 'available_hours', 'planned_hours', 'planned_billable_hours',
  65. 'planned_non_billable_hours', 'actual_billable_hours',
  66. 'actual_non_billable_hours'
  67. ]
  68. for field in fields_to_compare:
  69. if abs(efficiency_data[field] - latest_record[field]) > 0.01: # Tolerance for floating point
  70. has_changes = True
  71. break
  72. else:
  73. # No previous record exists, so this is a change
  74. has_changes = True
  75. # Only create new record if there are changes
  76. if has_changes:
  77. # Archive existing records for this employee and month
  78. existing_records = efficiency_model.search([
  79. ('employee_id', '=', employee.id),
  80. ('month_year', '=', month),
  81. ('company_id', '=', self.company_id.id),
  82. ])
  83. if existing_records:
  84. existing_records.write({'active': False})
  85. # Create new record
  86. new_record = efficiency_model.create(efficiency_data)
  87. created_records.append(new_record)
  88. # Update wizard with results
  89. self.write({
  90. 'created_count': len(created_records),
  91. 'updated_count': 0, # No longer updating existing records
  92. 'total_count': len(created_records),
  93. 'result_message': self._generate_result_message(created_records, employees, months),
  94. })
  95. return {
  96. 'type': 'ir.actions.act_window',
  97. 'res_model': 'hr.efficiency.calculation.wizard',
  98. 'res_id': self.id,
  99. 'view_mode': 'form',
  100. 'target': 'new',
  101. }
  102. def _validate_input(self):
  103. """
  104. Validate wizard input
  105. """
  106. # Validate month format
  107. try:
  108. datetime.strptime(self.start_month, '%Y-%m')
  109. datetime.strptime(self.end_month, '%Y-%m')
  110. except ValueError:
  111. raise UserError(_("Invalid month format. Please use YYYY-MM format (e.g., 2024-01)"))
  112. # Validate date range
  113. start_date = datetime.strptime(self.start_month, '%Y-%m')
  114. end_date = datetime.strptime(self.end_month, '%Y-%m')
  115. if start_date > end_date:
  116. raise UserError(_("Start month cannot be after end month."))
  117. # Limit range to reasonable period (e.g., 2 years)
  118. if (end_date - start_date).days > 730:
  119. raise UserError(_("Date range cannot exceed 2 years."))
  120. def _generate_month_list(self, start_month, end_month):
  121. """
  122. Generate list of months between start_month and end_month (inclusive)
  123. """
  124. months = []
  125. current = datetime.strptime(start_month, '%Y-%m')
  126. end = datetime.strptime(end_month, '%Y-%m')
  127. while current <= end:
  128. months.append(current.strftime('%Y-%m'))
  129. current = current + relativedelta(months=1)
  130. return months
  131. def _generate_result_message(self, created_records, employees, months):
  132. """
  133. Generate result message for the wizard
  134. """
  135. message = _("Efficiency calculation completed successfully!\n\n")
  136. message += _("Period: %s to %s\n") % (self.start_month, self.end_month)
  137. message += _("Employees processed: %d\n") % len(employees)
  138. message += _("Months processed: %d\n\n") % len(months)
  139. message += _("Results:\n")
  140. message += _("- New records created: %d\n") % len(created_records)
  141. message += _("- Total records processed: %d\n") % len(created_records)
  142. message += _("\nNote: New records are only created when there are changes. Old records are archived.")
  143. return message
  144. def action_view_results(self):
  145. """
  146. Open the efficiency records view
  147. """
  148. return {
  149. 'type': 'ir.actions.act_window',
  150. 'name': _('Efficiency Records'),
  151. 'res_model': 'hr.efficiency',
  152. 'view_mode': 'list,form',
  153. 'domain': [
  154. ('company_id', '=', self.company_id.id),
  155. ('month_year', '>=', self.start_month),
  156. ('month_year', '<=', self.end_month),
  157. ],
  158. 'context': {
  159. 'default_company_id': self.company_id.id,
  160. },
  161. }