hr_efficiency_calculation_wizard.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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', 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. ])
  41. if not employees:
  42. raise UserError(_("No employees found to process."))
  43. # Calculate efficiency
  44. efficiency_model = self.env['hr.efficiency']
  45. # Generate list of months
  46. months = self._generate_month_list(self.start_month, self.end_month)
  47. created_records = []
  48. for employee in employees:
  49. for month in months:
  50. # Calculate efficiency data
  51. efficiency_data = efficiency_model._calculate_employee_efficiency(employee, month)
  52. efficiency_data['company_id'] = self.company_id.id
  53. # Check if there are changes compared to the latest record
  54. latest_record = efficiency_model.search([
  55. ('employee_id', '=', employee.id),
  56. ('month_year', '=', month),
  57. ('company_id', '=', self.company_id.id),
  58. ], order='calculation_date desc', limit=1)
  59. has_changes = False
  60. if latest_record:
  61. # Compare current data with latest record
  62. fields_to_compare = [
  63. 'available_hours', 'planned_hours', 'planned_billable_hours',
  64. 'planned_non_billable_hours', 'actual_billable_hours',
  65. 'actual_non_billable_hours'
  66. ]
  67. for field in fields_to_compare:
  68. if abs(efficiency_data[field] - latest_record[field]) > 0.01: # Tolerance for floating point
  69. has_changes = True
  70. break
  71. else:
  72. # No previous record exists, so this is a change
  73. has_changes = True
  74. # Only create new record if there are changes
  75. if has_changes:
  76. # Archive existing records for this employee and month
  77. existing_records = efficiency_model.search([
  78. ('employee_id', '=', employee.id),
  79. ('month_year', '=', month),
  80. ('company_id', '=', self.company_id.id),
  81. ])
  82. if existing_records:
  83. existing_records.write({'active': False})
  84. # Create new record
  85. new_record = efficiency_model.create(efficiency_data)
  86. created_records.append(new_record)
  87. # Update wizard with results
  88. self.write({
  89. 'created_count': len(created_records),
  90. 'updated_count': 0, # No longer updating existing records
  91. 'total_count': len(created_records),
  92. 'result_message': self._generate_result_message(created_records, employees, months),
  93. })
  94. return {
  95. 'type': 'ir.actions.act_window',
  96. 'res_model': 'hr.efficiency.calculation.wizard',
  97. 'res_id': self.id,
  98. 'view_mode': 'form',
  99. 'target': 'new',
  100. }
  101. def _validate_input(self):
  102. """
  103. Validate wizard input
  104. """
  105. # Validate month format
  106. try:
  107. datetime.strptime(self.start_month, '%Y-%m')
  108. datetime.strptime(self.end_month, '%Y-%m')
  109. except ValueError:
  110. raise UserError(_("Invalid month format. Please use YYYY-MM format (e.g., 2024-01)"))
  111. # Validate date range
  112. start_date = datetime.strptime(self.start_month, '%Y-%m')
  113. end_date = datetime.strptime(self.end_month, '%Y-%m')
  114. if start_date > end_date:
  115. raise UserError(_("Start month cannot be after end month."))
  116. # Limit range to reasonable period (e.g., 2 years)
  117. if (end_date - start_date).days > 730:
  118. raise UserError(_("Date range cannot exceed 2 years."))
  119. def _generate_month_list(self, start_month, end_month):
  120. """
  121. Generate list of months between start_month and end_month (inclusive)
  122. """
  123. months = []
  124. current = datetime.strptime(start_month, '%Y-%m')
  125. end = datetime.strptime(end_month, '%Y-%m')
  126. while current <= end:
  127. months.append(current.strftime('%Y-%m'))
  128. current = current + relativedelta(months=1)
  129. return months
  130. def _generate_result_message(self, created_records, employees, months):
  131. """
  132. Generate result message for the wizard
  133. """
  134. message = _("Efficiency calculation completed successfully!\n\n")
  135. message += _("Period: %s to %s\n") % (self.start_month, self.end_month)
  136. message += _("Employees processed: %d\n") % len(employees)
  137. message += _("Months processed: %d\n\n") % len(months)
  138. message += _("Results:\n")
  139. message += _("- New records created: %d\n") % len(created_records)
  140. message += _("- Total records processed: %d\n") % len(created_records)
  141. message += _("\nNote: New records are only created when there are changes. Old records are archived.")
  142. return message
  143. def action_view_results(self):
  144. """
  145. Open the efficiency records view
  146. """
  147. return {
  148. 'type': 'ir.actions.act_window',
  149. 'name': _('Efficiency Records'),
  150. 'res_model': 'hr.efficiency',
  151. 'view_mode': 'list,form',
  152. 'domain': [
  153. ('company_id', '=', self.company_id.id),
  154. ('month_year', '>=', self.start_month),
  155. ('month_year', '<=', self.end_month),
  156. ],
  157. 'context': {
  158. 'default_company_id': self.company_id.id,
  159. },
  160. }