from odoo import models, fields, api, _ from odoo.exceptions import UserError, ValidationError import datetime class HrPayslipRun(models.Model): _inherit = 'hr.payslip.run' @api.model def get_consolidated_attendance_data(self, payslip_run_id): """ Returns consolidated attendance and leave data for all employees in the payslip run """ # Get all payslips in this batch payslips = self.env['hr.payslip'].search([('payslip_run_id', '=', payslip_run_id)]) if not payslips: return [] result = [] for slip in payslips: employee = slip.employee_id contract = slip.contract_id # Get attendance data attendance_days = self._get_attendance_days(slip) worked_days = self._get_worked_days(slip) leave_days = self._get_leave_days(slip) lop_days = self._get_lop_days(slip) # Get leave balances leave_balances = self._get_leave_balances(employee,slip.date_from,slip.date_to) leave_taken = self._get_leave_taken(slip) result.append({ 'id': slip.id, 'employee_id': (employee.id, employee.name), 'employee_code': employee.employee_id or '', 'department_id': (employee.department_id.id, employee.department_id.name) if employee.department_id else False, 'total_days': (slip.date_to - slip.date_from).days + 1, 'worked_days': worked_days, 'attendance_days': attendance_days, 'leave_days': leave_days, 'lop_days': lop_days, 'doj':employee.doj, 'birthday':employee.birthday, 'bank': employee.bank_account_id.display_name if employee.bank_account_id else '-', 'sick_leave_balance': leave_balances.get('LEAVE110', 0), 'casual_leave_balance': leave_balances.get('LEAVE120', 0), 'privilege_leave_balance': leave_balances.get('LEAVE100', 0), 'sick_leave_taken': leave_taken.get('sick', 0), 'casual_leave_taken': leave_taken.get('casual', 0), 'privilege_leave_taken': leave_taken.get('privilege', 0), 'state': slip.state, 'lines':self.get_payslip_lines_data(slip), }) return result @api.model def sub_columns(self,payslip_run_id): payslips = self.env['hr.payslip'].search([('payslip_run_id', '=', payslip_run_id)]) names = payslips.line_ids.filtered(lambda x:x.amount != 0) code_name_dict = {line.code+line.name.replace(" ", "_")if line.code in ['REIMBURSEMENT','DEDUCTION'] else line.code : line.name for line in names} columns = [] for code, name in code_name_dict.items(): columns.append({ 'title': name, 'dataIndx': code, 'width': 150, 'editable': False, 'summary': {'type': "sum_"}, }) return columns def save_consolidated_attendance_data(self, payslip_run_id, data): """ Saves the edited attendance and leave data from the grid """ self.ensure_one() for item in data: slip = self.env['hr.payslip'].browse(item['id']) if slip.state != 'draft': raise UserError(_("Cannot edit payslip in %s state") % slip.state) # Update LOP days if 'lop_days' in item: self._update_lop_days(slip, float(item['lop_days'])) # Update leave days taken leave_updates = {} if 'sick_leave_taken' in item: leave_updates['sick'] = float(item['sick_leave_taken']) if 'casual_leave_taken' in item: leave_updates['casual'] = float(item['casual_leave_taken']) if 'privilege_leave_taken' in item: leave_updates['privilege'] = float(item['privilege_leave_taken']) if leave_updates: self._update_leave_taken(slip, leave_updates) return True def recalculate_lop_days(self, payslip_run_id): """ Recalculates LOP days for all payslips in the batch based on attendance """ self.ensure_one() payslips = self.env['hr.payslip'].search([ ('payslip_run_id', '=', payslip_run_id), ('state', '=', 'draft') ]) for slip in payslips: attendance_days = self._get_attendance_days(slip) expected_days = (slip.date_to - slip.date_from).days + 1 lop_days = expected_days - attendance_days self._update_lop_days(slip, lop_days) return True def validate_all_attendance_data(self, payslip_run_id): """ Marks all payslips in the batch as validated """ self.ensure_one() payslips = self.env['hr.payslip'].search([ ('payslip_run_id', '=', payslip_run_id), ('state', '=', 'draft') ]) if not payslips: raise UserError(_("No draft payslips found in this batch")) payslips.write({'state': 'verify'}) return True # Helper methods def _get_attendance_days(self, payslip): """ Returns number of days employee was present (based on attendance records) """ attendance_records = self.env['hr.attendance'].search([ ('employee_id', '=', payslip.employee_id.id), ('check_in', '>=', payslip.date_from), ('check_in', '<=', payslip.date_to) ]) # Group by day unique_days = set() for att in attendance_records: unique_days.add(att.check_in.date()) return len(unique_days) def _get_worked_days(self, payslip): """ Returns number of working days (excluding weekends and holidays) """ return payslip._get_worked_days_line_number_of_days('WORK100') # Assuming WORK100 is your work code def get_payslip_lines_data(self, payslip_id): list = [] for line in payslip_id.line_ids: list.append({ 'name': line.name, 'code': line.code + line.name.replace(" ", "_")if line.code in ['REIMBURSEMENT','DEDUCTION'] else line.code, 'category_id': line.category_id.name if line.category_id else False, 'amount': line.amount, 'quantity': line.quantity, 'rate': line.rate }) return list def _get_leave_days(self, payslip): """ Returns total leave days taken in this period """ leave_lines = payslip.worked_days_line_ids.filtered( lambda l: l.code in ['LEAVE110', 'LEAVE90', 'LEAVE100', 'LEAVE120'] # Your leave codes ) return sum(leave_lines.mapped('number_of_days')) def _get_lop_days(self, payslip): """ Returns LOP days from payslip """ lop_line = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'LEAVE90') return lop_line.number_of_days if lop_line else 0 def _get_leave_taken(self, payslip): """ Returns leave days taken in this payslip period """ leave_lines = payslip.worked_days_line_ids return { 'sick': sum(leave_lines.filtered(lambda l: l.code == 'LEAVE110').mapped('number_of_days')), 'casual': sum(leave_lines.filtered(lambda l: l.code == 'LEAVE120').mapped('number_of_days')), 'privilege': sum(leave_lines.filtered(lambda l: l.code == 'LEAVE100').mapped('number_of_days')), } def _update_lop_days(self, payslip, days): """ Updates LOP days in the payslip """ WorkedDays = self.env['hr.payslip.worked_days'] lop_line = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'LOP') if lop_line: if days > 0: lop_line.write({'number_of_days': days}) else: lop_line.unlink() elif days > 0: WorkedDays.create({ 'payslip_id': payslip.id, 'name': _('Loss of Pay'), 'code': 'LOP', 'number_of_days': days, 'number_of_hours': days * 8, # Assuming 8-hour work day 'contract_id': payslip.contract_id.id, }) def _get_leave_balances(self, employee, date_from, date_to): Leave = self.env['hr.leave'] Allocation = self.env['hr.leave.allocation'] leave_types = self.env['hr.leave.type'].search([]) balances = {} for leave_type in leave_types: # Approved allocations within or before payslip period allocations = Allocation.search([ ('employee_id', '=', employee.id), ('state', '=', 'validate'), ('holiday_status_id', '=', leave_type.id), ('date_from', '<=', str(date_to)), # Allocation should be active during payslip ]) allocated = sum(alloc.number_of_days for alloc in allocations) # Approved leaves within the payslip period leaves = Leave.search([ ('employee_id', '=', employee.id), ('state', '=', 'validate'), ('holiday_status_id', '=', leave_type.id), ('request_date_to', '<=', str(date_to)) ]) used = sum(leave.number_of_days for leave in leaves) # Key: leave code or fallback to name code = leave_type.work_entry_type_id.code balances[code] = allocated - used return balances def _update_leave_taken(self, payslip, leave_data): """ Updates leave days taken in the payslip """ WorkedDays = self.env['hr.payslip.worked_days'] for leave_type, days in leave_data.items(): code = leave_type.upper() line = payslip.worked_days_line_ids.filtered(lambda l: l.code == code) if line: if days > 0: line.write({'number_of_days': days}) else: line.unlink() elif days > 0: WorkedDays.create({ 'payslip_id': payslip.id, 'name': _(leave_type.capitalize() + ' Leave'), 'code': code, 'number_of_days': days, 'number_of_hours': days * 8, # Assuming 8-hour work day 'contract_id': payslip.contract_id.id, }) class HrPayslip(models.Model): _inherit = 'hr.payslip' def get_payslip_lines_data(self, payslip_id): payslip = self.browse(payslip_id) return [{ 'name': line.name, 'code': line.code, 'category_id': (line.category_id.id, line.category_id.name), 'amount': line.amount, 'quantity': line.quantity, 'rate': line.rate } for line in payslip.line_ids]