Odoo CE HR Payroll工资单请假天数计算报错排查
使用HR Payroll Community模块。
工资单模块请假计算逻辑修复
需求为在工资单计算逻辑中集成考勤数据统计功能,导入「Public Holidays」模块的公共假期数据后触发运行报错,以下是排查过程和修复方案。
原有问题代码
@api.model def get_worked_day_lines(self, contracts, date_from, date_to): """ @param contract: Browse record of contracts @return: returns a list of dict containing the input that should be applied for the given contract between date_from and date_to """ res = [] # fill only if the contract as a working schedule linked for contract in contracts.filtered(lambda contract: contract.resource_calendar_id): day_from = datetime.combine(fields.Date.from_string(date_from), datetime_time.min) day_to = datetime.combine(fields.Date.from_string(date_to), datetime_time.max) # compute leave days leaves = {} day_leave_intervals = contract.employee_id.list_leaves(day_from, day_to, calendar=contract.resource_calendar_id) for day_intervals in day_leave_intervals: for interval in day_intervals: holiday = interval[2]['leaves'].holiday_id current_leave_struct = leaves.setdefault(holiday.holiday_status_id, { 'name': holiday.holiday_status_id.name, 'sequence': 5, 'code': holiday.holiday_status_id.name, 'number_of_days': 0.0, 'number_of_hours': 0.0, 'contract_id': contract.id, }) leave_time = (interval[1] - interval[0]).seconds / 3600 current_leave_struct['number_of_hours'] += leave_time work_hours = contract.employee_id.get_day_work_hours_count(interval[0].date(), calendar=contract.resource_calendar_id) if work_hours: current_leave_struct['number_of_days'] += leave_time / work_hours # compute worked days work_data = contract.employee_id.get_work_days_data(day_from, day_to, calendar=contract.resource_calendar_id) attendances = { 'name': _("Normal Working Days paid at 100%"), 'sequence': 1, 'code': 'WORK100', 'number_of_days': work_data['days'], 'number_of_hours': work_data['hours'], 'contract_id': contract.id, } res.append(attendances) attendance_lines = self.env['hr.attendance'].search([('employee_id', '=', contract.employee_id.id), ('check_in', '>=', date_from), ('check_out', '<=', date_to)]) if attendance_lines: total_hours = sum(attendance_lines.mapped('worked_hours')) or 0.00 attendance_hours = { 'name': _("Attendence Hours"), 'sequence': 1, 'code': 'ATTNHOURS', 'number_of_days': 0.00, 'number_of_hours': total_hours, 'contract_id': contract.id, } res.append(attendance_hours) res.extend(leaves.values()) return res
报错信息
models/hr_payslip.py", line 50, in get_worked_day_lines holiday = interval[2]['leaves'].holiday_id Exception The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/opt/odoo15/odoo/odoo/http.py", line 644, in _handle_exception return super(JsonRequest, self)._handle_exception(exception) File "/opt/odoo15/odoo/odoo/http.py", line 302, in _handle_exception raise exception.with_traceback(None) from new_cause TypeError: 'datetime.date' object is not subscriptable
排查思路
- 报错定位在取
interval[2]的位置,提示datetime.date类型对象不能用下标取值,说明当前遍历到的interval不是代码预期的「(开始时间、结束时间、元数据字典)」三元组结构,而是日期类型对象。 - 根因是
list_leaves方法的返回结构存在差异:未配置公共假期时,方法返回区间列表;导入公共假期后,方法返回字典结构,键为datetime.date类型的日期,值为当天对应的请假/假期区间列表。原有代码直接遍历返回值,拿到的是字典的键(日期对象),后续对日期对象做下标取值自然触发报错。 - 额外隐藏问题:公共假期属于全局日历配置,没有关联的个人请假单(
holiday_id),即使修复遍历逻辑,直接取interval[2]['leaves'].holiday_id也会触发键错误或属性不存在的报错。
修复方案
- 兼容
list_leaves的两种返回结构,判断返回值类型,字典类型则遍历所有值(即当日区间列表),列表类型则直接遍历。 - 遍历区间时增加类型校验,跳过不符合三元组结构的无效条目。
- 区分个人请假和公共假期两种场景:个人请假走原有逻辑关联假期类型,公共假期单独归类统计,避免读取不存在的
holiday_id。 - 增加边界判断,避免区间元数据缺失时触发键错误。
修复后的核心代码段(仅修改请假计算部分,其余逻辑保持不变):
# compute leave days leaves = {} public_holiday_struct = None day_leave_intervals = contract.employee_id.list_leaves(day_from, day_to, calendar=contract.resource_calendar_id) # 兼容字典/列表两种返回结构 if isinstance(day_leave_intervals, dict): interval_groups = day_leave_intervals.values() else: interval_groups = day_leave_intervals for day_intervals in interval_groups: for interval in day_intervals: # 跳过不符合三元组结构的无效条目 if not isinstance(interval, (list, tuple)) or len(interval) < 3 or not isinstance(interval[2], dict): continue # 处理公共假期(无关联holiday_id的场景) if 'leaves' not in interval[2] or not hasattr(interval[2]['leaves'], 'holiday_id') or not interval[2]['leaves'].holiday_id: if not public_holiday_struct: public_holiday_struct = { 'name': _("Public Holidays"), 'sequence': 2, 'code': 'PUBHOL', 'number_of_days': 0.0, 'number_of_hours': 0.0, 'contract_id': contract.id, } leave_time = (interval[1] - interval[0]).seconds / 3600 public_holiday_struct['number_of_hours'] += leave_time work_hours = contract.employee_id.get_day_work_hours_count(interval[0].date(), calendar=contract.resource_calendar_id) if work_hours: public_holiday_struct['number_of_days'] += leave_time / work_hours continue # 原有个人请假计算逻辑 holiday = interval[2]['leaves'].holiday_id current_leave_struct = leaves.setdefault(holiday.holiday_status_id, { 'name': holiday.holiday_status_id.name, 'sequence': 5, 'code': holiday.holiday_status_id.name, 'number_of_days': 0.0, 'number_of_hours': 0.0, 'contract_id': contract.id, }) leave_time = (interval[1] - interval[0]).seconds / 3600 current_leave_struct['number_of_hours'] += leave_time work_hours = contract.employee_id.get_day_work_hours_count(interval[0].date(), calendar=contract.resource_calendar_id) if work_hours: current_leave_struct['number_of_days'] += leave_time / work_hours # 把公共假期统计项加入结果 if public_holiday_struct: res.append(public_holiday_struct)
验证要点
- 导入公共假期后重新计算工资单,确认不再触发类型报错。
- 核对公共假期、个人请假、正常出勤、打卡考勤的天数/小时统计值和实际规则一致。
- 验证无公共假期的计薪周期,工资单计算逻辑不受修复影响。
内容的提问来源于stack exchange,提问作者Bill S.
相关产品推荐
相关产品推荐

