You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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也会触发键错误或属性不存在的报错。

修复方案

  1. 兼容list_leaves的两种返回结构,判断返回值类型,字典类型则遍历所有值(即当日区间列表),列表类型则直接遍历。
  2. 遍历区间时增加类型校验,跳过不符合三元组结构的无效条目。
  3. 区分个人请假和公共假期两种场景:个人请假走原有逻辑关联假期类型,公共假期单独归类统计,避免读取不存在的holiday_id。
  4. 增加边界判断,避免区间元数据缺失时触发键错误。

修复后的核心代码段(仅修改请假计算部分,其余逻辑保持不变):

# 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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 22:42:31