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