From df19c6f30f7675d292bdebfbab1eafe4d124cd41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Fri, 13 Mar 2026 11:24:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_holiday/models/inherit_hr_leave.py | 81 +++++++++-------- yuthon_hr_attendance/__manifest__.py | 1 + yuthon_hr_attendance/models/__init__.py | 1 + .../models/overtime_carryover.py | 26 ++++++ .../models/vacation_overtime_active.py | 86 ++++++++++++++++--- .../models/yuthon_vacation_reports.py | 63 +++++++++++--- .../security/ir.model.access.csv | 4 +- .../views/overtime_carryover_views.xml | 23 +++++ .../views/yuthon_vacation_reports_views.xml | 3 +- yuthon_work_brand/views/hr_menus.xml | 6 ++ 10 files changed, 228 insertions(+), 66 deletions(-) create mode 100644 yuthon_hr_attendance/models/overtime_carryover.py create mode 100644 yuthon_hr_attendance/views/overtime_carryover_views.xml diff --git a/yuthon_holiday/models/inherit_hr_leave.py b/yuthon_holiday/models/inherit_hr_leave.py index 3e807fa8c..52d435412 100644 --- a/yuthon_holiday/models/inherit_hr_leave.py +++ b/yuthon_holiday/models/inherit_hr_leave.py @@ -474,6 +474,7 @@ class HolidaysRequestBatch(models.Model): def _get_day_leaves(self, leave_type_name): workflow_id = self.env['soong.workflow.record'].search([('code', '=', self.code)], limit=1).id return [{ + 'leave_id': leave.id, 'start': fields.Date.from_string(leave.request_date_from), 'end': fields.Date.from_string(leave.request_date_to), 'type_id': leave.holiday_status_id, @@ -492,6 +493,7 @@ class HolidaysRequestBatch(models.Model): hours_match = re.search(r'(\d+(?:\.\d+)?)', line.number_of_hours_text or '') hours = float(hours_match.group(1)) if hours_match else 0 hourly_records.append({ + 'leave_id': line.id, 'date': fields.Date.from_string(line.request_date_from), 'type_id': line.holiday_status_id, 'workflow_id': workflow_id, @@ -532,6 +534,7 @@ class HolidaysRequestBatch(models.Model): :param leave_type_name: 假期类型名称 """ employee_id = self.users_id.employee_ids.id if self.users_id.employee_ids else False + leave_id = leave_data.get('leave_id', False) if 'date' in leave_data: leave_start = leave_data['date'] leave_end = leave_data['date'] @@ -604,7 +607,7 @@ class HolidaysRequestBatch(models.Model): ) # 4. 只有当两个报表中都没有该记录时,才创建新记录 if not existing_line_in_vacation_report and not existing_line_in_vacation_report1: - report_id = self.env['vacation.over.line'].create({ + over_id = self.env['vacation.over.line'].create({ 'vacation_id': actual_vacation_report.id, 'employee_id': employee_id, 'logging_data': '666', @@ -618,45 +621,15 @@ class HolidaysRequestBatch(models.Model): 'is_void': False, 'text': f"请假审批" }) - report_id.update_line() - report_id._compute_working_overtime() - - if self.env.user.company_id.name == '集团本部': - domain = [ - ('employee_id', '=', employee_id), - "|", - ('is_initial_number', '=', True), - '&', ('is_initial_number', '=', False), - '&', ('update_type', '=', 'add'), - '&', ('workflow_record_id', '!=', False), - '&', ('start_time', '>=', datetime(2025, 12, 31, 23, 59, 59)), - '&', ('vacation_id', '!=', False), - ('employee_id.is_annual_salary', '!=', True) - ] - else: - domain = [ - ('workflow_record_id', '!=', False), - ('update_type', '=', 'add'), - ('remaining_quantity', '!=', 0.0), - ] - overtime_records = self.env['vacation.over.line'].search(domain, order='start_time ASC') - remaining_deduct = hours - for record in overtime_records: - if remaining_deduct <= 0: - break - current_remaining = record.remaining_quantity # 剩余数 - deduct_amount = min(remaining_deduct, current_remaining) - if remaining_deduct < current_remaining: - carry_state = 'partial' - else: - carry_state = 'completed' - record.write({ - 'carry_forward_date': fields.Date.to_string(period_start), - 'carry_forward_quantity': record.carry_forward_quantity + deduct_amount, - 'carry_forward_state': carry_state, - 'logging_data': '113', - }) - remaining_deduct -= deduct_amount + actual_vacation_report.update_line() + actual_vacation_report._compute_working_overtime() + if leave_id and over_id: + self._create_overtime_carryover( + over_line=over_id, + deduct_amount=interval_hours, + carry_date=period_start, + leave_id=leave_id + ) def _create_annual_leave_line(self, period_start, leave_data, hours, period): """ @@ -736,7 +709,7 @@ class HolidaysRequestBatch(models.Model): limit=1 ) if not existing_line_in_vacation_report and not existing_line_in_vacation_report1: - report_id = self.env['vacation.leave.line'].create({ + self.env['vacation.leave.line'].create({ 'vacation_id': actual_vacation_report.id, 'employee_id': employee_id, 'workflow_record_id': leave_data['workflow_id'], @@ -747,7 +720,7 @@ class HolidaysRequestBatch(models.Model): 'allocation_type': 'vacation', 'text': f"请假审批" }) - report_id.update_line() + actual_vacation_report.update_line() def _create_other_leave_line(self, period_start, leave_data, days, period, leave_type_name): """ @@ -823,6 +796,30 @@ class HolidaysRequestBatch(models.Model): 'text': f"请假审批" }) + def _create_overtime_carryover(self, over_line, deduct_amount, carry_date, leave_id): + """ + 封装创建加班结转明细的逻辑 + """ + # 1. 防重复校验:同一假单+同一假期行,只创建一条结转明细 + if leave_id and over_line: + existing_carryover = self.env['overtime.carryover'].search([ + ('leave_id', '=', leave_id), + ('over_line_id', '=', over_line.id), + ('carry_forward_quantity', '=', deduct_amount) + ], limit=1) + if existing_carryover: + return + + carry_forward_date = fields.Date.to_date(carry_date) if carry_date else fields.Date.today() + self.env['overtime.carryover'].create({ + 'over_line_id': over_line.id, + 'leave_id': leave_id, + 'carry_forward_date': carry_forward_date, + 'carry_forward_quantity': deduct_amount, + 'carryover_type': 'leave', + 'text': '', + }) + def update_attendance(self, employee_id, start_date, end_date, code): """ 更新考勤 diff --git a/yuthon_hr_attendance/__manifest__.py b/yuthon_hr_attendance/__manifest__.py index 0bd3dd7ec..f9c517d3b 100644 --- a/yuthon_hr_attendance/__manifest__.py +++ b/yuthon_hr_attendance/__manifest__.py @@ -72,6 +72,7 @@ 'views/all_attendance_menu.xml', 'views/overtime_active_line_views.xml', 'views/vacation_meal_active_views.xml', + 'views/overtime_carryover_views.xml', 'views/overtime_approval_inherit_header.xml', 'wizard/hr_attendance_month_wizard_views.xml', 'wizard/yuthon_vacation_import_wizard_views.xml', diff --git a/yuthon_hr_attendance/models/__init__.py b/yuthon_hr_attendance/models/__init__.py index 444e9c856..c6979f47f 100644 --- a/yuthon_hr_attendance/models/__init__.py +++ b/yuthon_hr_attendance/models/__init__.py @@ -40,6 +40,7 @@ from . import yuthon_overtime_line from . import overtime_active_line from . import vacation_meal_active from . import duty_adjustment_schedule +from . import overtime_carryover # from odoo.tools import SQL # import odoo # odoo.models.READ_GROUP_AGGREGATE.update({ diff --git a/yuthon_hr_attendance/models/overtime_carryover.py b/yuthon_hr_attendance/models/overtime_carryover.py new file mode 100644 index 000000000..d5979efd6 --- /dev/null +++ b/yuthon_hr_attendance/models/overtime_carryover.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError + + +class OvertimeCarryover(models.Model): + _name = 'overtime.carryover' + _description = '加班结转明细' + + over_line_id = fields.Many2one('vacation.over.line', string='加班记录') + carry_forward_date = fields.Date(string="结转日期") + carry_forward_quantity = fields.Float(string="结转时长") + carryover_type = fields.Selection([('fee', '加班费结算'), ('leave', '假单结算')], string='加班补偿方式', + default='leave') + text = fields.Text(string='备注') + leave_id = fields.Many2one('hr.leave', string='假单') + + + +class OvertimeCarryoverLine(models.Model): + _name = 'overtime.carryover.line' + +class OvertimeUserLine(models.Model): + _name = 'overtime.carryover.wizard.line' + + diff --git a/yuthon_hr_attendance/models/vacation_overtime_active.py b/yuthon_hr_attendance/models/vacation_overtime_active.py index 6934c4ac6..8629b9866 100644 --- a/yuthon_hr_attendance/models/vacation_overtime_active.py +++ b/yuthon_hr_attendance/models/vacation_overtime_active.py @@ -31,22 +31,88 @@ class VacationOvertimeActive(models.Model): remaining_quantity = fields.Float(string="剩余数") select_revoke = fields.Selection([('to_do', '待撤销'), ('done', '已撤销')], string='状态') - def update_vacation(self): + """ + 结转时长处理逻辑: + 1. 优先匹配 is_initial_number=True 的假期行记录 + 2. 不足时匹配指定条件的 is_initial_number=False 记录(按start_time升序) + 3. 更新结转状态、剩余时长 + 填充对应记录的carry_forward_quantity/date + 4. 为每条修改的记录创建加班结转明细(overtime.carryover + overtime.carryover.line) + """ + dec_2025_end = fields.Datetime.to_datetime('2025-12-31 23:59:59') active_ids = self.env["vacation.overtime.active"].browse( self._context.get('active_ids', self._context.get('active_id', [])) ) for record in active_ids: - overtime_lines = self.env['vacation.over.line'].search([ - ('employee_id', '=', record.employee_id.id), - ('overtime_methods', '=', 'fee') - ]) - overtime_lines.write({ - 'workday': record.carry_forward_quantity, - 'update_date': record.carry_forward_date, - 'logging_data': 'qq', - }) + remaining_to_carry = record.carry_forward_quantity + initial_lines = self.env['vacation.over.line'].search([ + ('is_initial_number', '=', True), + ('employee_id', '=', record.employee_id.id) + ]) + # ========== 步骤1:处理初始记录 + 创建结转明细 ========== + for initial_line in initial_lines: + if remaining_to_carry <= 0: + break + line_duration = initial_line.duration or 0.0 + actual_carry = min(line_duration, remaining_to_carry) + + # 1. 更新初始记录的结转字段 + initial_line.carry_forward_date = record.carry_forward_date + initial_line.carry_forward_quantity = actual_carry + + if line_duration >= remaining_to_carry: + remaining_to_carry = 0.0 + initial_line.carry_forward_state = 'partial' + else: + remaining_to_carry -= actual_carry + initial_line.carry_forward_state = 'completed' + + # 核心新增:为当前初始记录创建加班结转明细 + self._create_carryover_line(initial_line, record) + + # ========== 步骤2:处理非初始记录 + 创建结转明细 ========== + if remaining_to_carry > 0: + non_initial_lines = self.env['vacation.over.line'].search([ + ('remaining_quantity', '!=', 0.0), + ('employee_id', '=', record.employee_id.id), + ('is_initial_number', '=', False), + ('update_type', '=', 'add'), + ('workflow_record_id', '!=', False), + ('start_time', '>=', dec_2025_end), + ('vacation_id', '!=', False) + ], order='start_time ASC') + for non_line in non_initial_lines: + if remaining_to_carry <= 0: + break + line_remaining = non_line.remaining_quantity or 0.0 + actual_carry = min(line_remaining, remaining_to_carry) + + # 1. 更新非初始记录的结转字段 + non_line.carry_forward_date = record.carry_forward_date + non_line.carry_forward_quantity = actual_carry + + if line_remaining >= remaining_to_carry: + non_line.remaining_quantity -= actual_carry + remaining_to_carry = 0.0 + non_line.carry_forward_state = 'partial' + else: + remaining_to_carry -= actual_carry + non_line.remaining_quantity = 0.0 + non_line.carry_forward_state = 'completed' + + # 核心新增:为当前非初始记录创建加班结转明细 + self._create_carryover_line(non_line, record) + + def _create_carryover_line(self, over_line, active_record): + self.env['overtime.carryover'].create({ + 'over_line_id': over_line.id, + 'carry_forward_date': active_record.carry_forward_date, + 'carry_forward_quantity': over_line.carry_forward_quantity, + 'carryover_type': 'fee', + '' + 'text': '', + }) def all_unlink(self): active_ids = self.env["vacation.overtime.active"].browse( diff --git a/yuthon_hr_attendance/models/yuthon_vacation_reports.py b/yuthon_hr_attendance/models/yuthon_vacation_reports.py index de1df46ab..68c0f2ee6 100644 --- a/yuthon_hr_attendance/models/yuthon_vacation_reports.py +++ b/yuthon_hr_attendance/models/yuthon_vacation_reports.py @@ -306,8 +306,7 @@ class YuthonVacationReports(models.Model): for rec in over_ids: inherit_lines = rec.overtime_ids.filtered(lambda l: l.text == '上月额度继承') if inherit_lines: - rec.is_initial_number = True - + inherit_lines.is_initial_number = True def all_update_line(self): records = self.env["yuthon.vacation.reports"].browse( @@ -2103,7 +2102,6 @@ class VacationOverline(models.Model): _name = 'vacation.over.line' _description = '加班明细' - active = fields.Boolean('Active', default=True, tracking=True) vacation_id = fields.Many2one('yuthon.vacation.reports', string='关联假期额度记录') employee_id = fields.Many2one('hr.employee', string='员工', compute='_compute_employee_id', store=True) @@ -2134,18 +2132,49 @@ class VacationOverline(models.Model): workflow_id = fields.Many2one('soong.workflow', compute="_compute_overtime", string='审批类型', store=True) start_time = fields.Datetime(string="开始时间", compute="_compute_overtime", store=True) end_time = fields.Datetime(string="结束时间", compute="_compute_overtime", store=True) - carry_forward_date = fields.Date(string="结转日期") duration = fields.Float(string="时长", compute="_compute_overtime", store=True) - carry_forward_quantity = fields.Float(string="结转时长") - validity = fields.Char(string="有效期") - carry_result = fields.Selection([('success', '成功'), ('failed', '失败')], string="结转结果") - is_filing = fields.Boolean(string="归档") - carry_forward_state = fields.Selection([('pending', '待结转'),('completed', '已结转'), ('partial', '部分结转') - ],string='结转状态', - default='pending', - ) remaining_quantity = fields.Float(string='剩余数', compute="_compute_last_balance") logging_data = fields.Char(string="日志日志日志") + validity = fields.Char(string="有效期") + is_filing = fields.Boolean(string="归档") + + carry_result = fields.Selection([('success', '成功'), ('failed', '失败')], string="结转结果", default='success') + carry_forward_date = fields.Date(string="结转日期", compute='_compute_carry_forward_info', store=True) + carry_forward_quantity = fields.Float(string="结转时长", compute='_compute_carry_forward_info', store=True) + carry_forward_state = fields.Selection([ + ('pending', '待结转'), + ('completed', '已结转'), + ('partial', '部分结转') + ], string='结转状态', compute='_compute_carry_forward_info', store=True, default='pending') + carryover_ids = fields.One2many('overtime.carryover', 'over_line_id', string='关联结转明细') + + @api.depends('carryover_ids.carry_forward_quantity', 'carryover_ids.carry_forward_date', 'remaining_quantity') + def _compute_carry_forward_info(self): + """ + 从overtime.carryover明细行自动计算: + 1. 结转日期:取最新的结转明细日期 + 2. 结转时长:汇总所有关联明细的结转时长 + 3. 结转状态:按剩余时长+结转时长计算(复用原有逻辑) + """ + for rec in self: + rec.carry_forward_date = False + rec.carry_forward_quantity = 0.0 + rec.carry_forward_state = 'pending' + if rec.carryover_ids: + # 结转时长:汇总所有明细的结转时长 + total_carry = sum(rec.carryover_ids.mapped('carry_forward_quantity')) + rec.carry_forward_quantity = total_carry + # 结转日期:取最新的结转明细日期(无则取最早) + carry_dates = rec.carryover_ids.mapped('carry_forward_date') + if carry_dates: + rec.carry_forward_date = max(carry_dates) + current_remaining = rec.remaining_quantity or 0.0 + if total_carry <= 0: + rec.carry_forward_state = 'pending' + elif total_carry >= current_remaining: + rec.carry_forward_state = 'completed' + else: + rec.carry_forward_state = 'partial' def open_record1(self): tree_id = self.env.ref("yuthon_hr_attendance.view_yuthon_vacation_overtime_tree").id @@ -2160,6 +2189,16 @@ class VacationOverline(models.Model): ], } + def open_overtime_carryover(self): + return { + "type": "ir.actions.act_window", + "name": "结转明细", + "res_model": "overtime.carryover", + "view_mode": "tree", + "target": "new", + 'domain': [('over_line_id', '=', self.id)], + } + @api.model def confirm(self): # 1. 构建筛选域 diff --git a/yuthon_hr_attendance/security/ir.model.access.csv b/yuthon_hr_attendance/security/ir.model.access.csv index b553f270a..1673f675b 100644 --- a/yuthon_hr_attendance/security/ir.model.access.csv +++ b/yuthon_hr_attendance/security/ir.model.access.csv @@ -96,4 +96,6 @@ access_export_date_wizard,export.date.wizard,model_export_date_wizard,base.group access_vacation_meal_active,vacation.meal.active,model_vacation_meal_active,base.group_user,1,1,1,1 access_yuthon_vacation_import_wizard,yuthon.vacation.import.wizard,model_yuthon_vacation_import_wizard,base.group_user,1,1,1,1 access_overtime_change_wizard,overtime.change.wizard,model_overtime_change_wizard,base.group_user,1,1,1,1 -access_duty_adjustment_schedule,duty.adjustment.schedule,model_duty_adjustment_schedule,base.group_user,1,1,1,1 \ No newline at end of file +access_duty_adjustment_schedule,duty.adjustment.schedule,model_duty_adjustment_schedule,base.group_user,1,1,1,1 +access_overtime_carryover,overtime.carryover,model_overtime_carryover,base.group_user,1,1,1,1 +access_overtime_carryover_line,overtime.carryover.line,model_overtime_carryover_line,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/yuthon_hr_attendance/views/overtime_carryover_views.xml b/yuthon_hr_attendance/views/overtime_carryover_views.xml new file mode 100644 index 000000000..320d526a2 --- /dev/null +++ b/yuthon_hr_attendance/views/overtime_carryover_views.xml @@ -0,0 +1,23 @@ + + + + overtime.carryover.tree + overtime.carryover + + + + + + + + + + + + + 结转明细 + overtime.carryover + tree + + + diff --git a/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml b/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml index 133ad0484..854713d4b 100644 --- a/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml +++ b/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml @@ -694,7 +694,7 @@ vacation.over.line.overtime.tree vacation.over.line - + @@ -711,6 +711,7 @@ +