From bcc673cd090ff90086cf1bb14c3d16417fbd7916 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: Tue, 27 Jan 2026 18:47:35 +0800 Subject: [PATCH 1/2] =?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 --- .../models/yuthon_vacation_reports.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/yuthon_hr_attendance/models/yuthon_vacation_reports.py b/yuthon_hr_attendance/models/yuthon_vacation_reports.py index e8ff74ff1..358a317b6 100644 --- a/yuthon_hr_attendance/models/yuthon_vacation_reports.py +++ b/yuthon_hr_attendance/models/yuthon_vacation_reports.py @@ -1032,10 +1032,7 @@ class YuthonVacationReports(models.Model): base_month = int(base_line.select_month) base_date = date(base_year, base_month, 1) self.env['yuthon.overtime.line'].search([]).unlink() - # 上上个月第一天(用于select_month赋值) - last_month_first = (base_date - relativedelta(months=2)).replace(day=1) - # 上上个月最后一天(时间筛选的截止点) - current_month_last = (base_date - relativedelta(months=1)).replace(day=1) - timedelta(days=1) + current_month_last = base_date - timedelta(days=1) current_month_last_dt = datetime.combine(current_month_last, datetime.min.time()) dec_2025_end = datetime(2025, 12, 31, 23, 59, 59) if self.env.user.company_id.name == '集团本部': @@ -1077,8 +1074,8 @@ class YuthonVacationReports(models.Model): 'carry_forward_quantity': 0.0, 'carry_forward_date': line.carry_forward_date, 'remaining_quantity': 0.0, - 'select_month': str(last_month_first.month), - 'select_year': str(last_month_first.year) + 'select_month': str(current_month_last.month), + 'select_year': str(current_month_last.year) } summary = employee_summary[emp_id] @@ -1103,7 +1100,6 @@ class YuthonVacationReports(models.Model): base_month = int(base_line.select_month) base_date = date(base_year, base_month, 1) self.env['yuthon.overtime.line'].search([]).unlink() - next_month_first = (base_date + relativedelta(months=1)).replace(day=1) next_month_last = (base_date + relativedelta(months=2)).replace(day=1) - timedelta(days=1) next_month_last_dt = datetime.combine(next_month_last, datetime.min.time()) dec_2025_end = datetime(2025, 12, 31, 23, 59, 59) @@ -1144,8 +1140,8 @@ class YuthonVacationReports(models.Model): 'public_holidays': 0.0, 'carry_forward_quantity': 0.0, 'carry_forward_date': line.carry_forward_date, - 'select_month': str(next_month_first.month), - 'select_year': str(next_month_first.year) + 'select_month': str(next_month_last.month), + 'select_year': str(next_month_last.year) } summary = employee_summary[emp_id] From 1e5a8ced6b19766d716cfab6d6ee83ce1707ac0f 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: Wed, 28 Jan 2026 00:01:44 +0800 Subject: [PATCH 2/2] =?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 --- .../models/vacation_overtime_active.py | 60 +++++++- .../models/yuthon_overtime_line.py | 135 +++++++++++++++++- .../models/yuthon_vacation_reports.py | 35 +++-- .../static/src/js/button_vacation_confirm.js | 5 +- .../views/vacation_overtime_active_views.xml | 15 +- .../views/yuthon_overtime_line_views.xml | 34 ++++- .../views/yuthon_vacation_reports_views.xml | 5 - 7 files changed, 246 insertions(+), 43 deletions(-) diff --git a/yuthon_hr_attendance/models/vacation_overtime_active.py b/yuthon_hr_attendance/models/vacation_overtime_active.py index e9bed3cad..5cc1df31e 100644 --- a/yuthon_hr_attendance/models/vacation_overtime_active.py +++ b/yuthon_hr_attendance/models/vacation_overtime_active.py @@ -13,12 +13,58 @@ class VacationOvertimeActive(models.Model): _name = 'vacation.overtime.active' _description = '加班情况归档' - employee_id = fields.Many2one('hr.employee', string='员工') - department_id = fields.Many2one('hr.department', related='employee_id.department_id', string="部门") - company_id = fields.Many2one('res.company', related='employee_id.company_id', string="公司") - workflow_record_id = fields.Many2one('soong.workflow.record', string='流程') - overtime_name = fields.Char(string="标题") - carry_forward_date = fields.Date(string="结转日期") + employee_id = fields.Many2one('hr.employee', string="员工") + select_month = fields.Selection([('1', '一月'), ('2', '二月'), ('3', '三月'), ('4', '四月'), + ('5', '五月'), ('6', '六月'), ('7', '七月'), ('8', '八月'), + ('9', '九月'), ('10', '十月'), ('11', '十一月'), + ('12', '十二月')], string='月份', + default=lambda self: str(datetime.now().month)) + select_year = fields.Selection(selection=[(str(i), str(i)) for i in range(2024, 2060)], string='年份', + default=lambda self: str(datetime.now().year)) + last_balance = fields.Float(string="上年结余") + valid_date = fields.Date(string="有效至") + workday = fields.Float(string='工作日') + public_holidays = fields.Float(string='公休日') + vacation_sum = fields.Float(string='合计数') carry_forward_quantity = fields.Float(string="结转时长") - carry_result = fields.Selection([('success', '成功'), ('failed', '失败')], string="结转结果") + carry_forward_date = fields.Date(string="结转日期") remaining_quantity = fields.Float(string="剩余数") + + def open_record(self): + self.ensure_one() + current_year = int(self.select_year) + current_month = int(self.select_month) + cutoff_date = date(current_year, current_month, 1) - timedelta(days=1) + cutoff_dt = datetime.combine(cutoff_date, datetime.min.time()) + dec_2025_end = datetime(2025, 12, 31, 23, 59, 59) + domain = [ + ('start_time', '<', cutoff_dt), + ('remaining_quantity', '!=', 0.0), + ('employee_id', '=', self.employee_id.id) + ] + if self.employee_id.company_id.name == '集团本部': + domain = [ + ('employee_id', '=', self.employee_id.id), + '|', + ('carry_forward_date', '=', False), + ('carry_forward_date', '<', cutoff_dt), + '|', + ('is_initial_number', '=', True), + '&', ('is_initial_number', '=', False), + '&', ('update_type', '=', 'add'), + '&', ('workflow_record_id', '!=', False), + '&', ('start_time', '>=', dec_2025_end), + '&', ('vacation_id', '!=', False), + ('start_time', '<', cutoff_dt) + ] + + tree_id = self.env.ref("yuthon_hr_attendance.view_yuthon_vacation_overtime_tree").id + return { + 'views': [[tree_id, 'tree']], + 'name': f'加班明细', + 'type': 'ir.actions.act_window', + 'res_model': 'vacation.over.line', + 'view_mode': 'tree,form', + 'domain': domain, + 'target': 'current' + } diff --git a/yuthon_hr_attendance/models/yuthon_overtime_line.py b/yuthon_hr_attendance/models/yuthon_overtime_line.py index 3912158e6..576a12237 100644 --- a/yuthon_hr_attendance/models/yuthon_overtime_line.py +++ b/yuthon_hr_attendance/models/yuthon_overtime_line.py @@ -21,22 +21,23 @@ class YuthonOvertimeLine(models.Model): valid_date = fields.Date(string="有效至") workday = fields.Float(string='工作日') public_holidays = fields.Float(string='公休日') - vacation_sum = fields.Float(string='合计数', compute='_compute_vacation_sum') + vacation_sum = fields.Float(string='合计数', compute='_compute_vacation_sum', store=True) carry_forward_quantity = fields.Float(string="结转时长") carry_forward_date = fields.Date(string="结转日期") + archive_state = fields.Selection([('to_do', '待归档'), ('done', '已归档')], default='to_do', string='状态') remaining_quantity = fields.Float(string="剩余数", compute='_compute_vacation_sum') @api.depends('workday', 'public_holidays', 'last_balance', 'carry_forward_quantity') def _compute_vacation_sum(self): for rec in self: rec.vacation_sum = rec.public_holidays + rec.workday + rec.last_balance - rec.remaining_quantity = rec.vacation_sum - rec.carry_forward_quantity + rec.remaining_quantity = max((rec.vacation_sum - rec.carry_forward_quantity), 0) def open_record(self): self.ensure_one() current_year = int(self.select_year) current_month = int(self.select_month) - cutoff_date = date(current_year, current_month, 1) + relativedelta(months=1) - timedelta(days=1) + cutoff_date = date(current_year, current_month, 1) - timedelta(days=1) cutoff_dt = datetime.combine(cutoff_date, datetime.min.time()) dec_2025_end = datetime(2025, 12, 31, 23, 59, 59) domain = [ @@ -47,7 +48,9 @@ class YuthonOvertimeLine(models.Model): if self.employee_id.company_id.name == '集团本部': domain = [ ('employee_id', '=', self.employee_id.id), - ('remaining_quantity', '!=', 0.0), + '|', + ('carry_forward_date', '=', False), + ('carry_forward_date', '<', cutoff_dt), '|', ('is_initial_number', '=', True), '&', ('is_initial_number', '=', False), @@ -67,4 +70,126 @@ class YuthonOvertimeLine(models.Model): 'view_mode': 'tree,form', 'domain': domain, 'target': 'current' - } \ No newline at end of file + } + + def carryover_active(self): + overtime_ids = self.env["yuthon.overtime.line"].browse( + self._context.get('active_ids', self._context.get('active_id')) + ) + for overtime_id in overtime_ids: + self.env["vacation.overtime.active"].create({ + 'employee_id': overtime_id.employee_id.id, + 'select_month': overtime_id.select_month, + 'select_year': overtime_id.select_year, + 'last_balance': overtime_id.last_balance, + 'valid_date': overtime_id.valid_date, + 'workday': overtime_id.workday, + 'public_holidays': overtime_id.public_holidays, + 'vacation_sum': overtime_id.vacation_sum, + 'carry_forward_quantity': overtime_id.carry_forward_quantity, + 'carry_forward_date': overtime_id.carry_forward_date, + 'remaining_quantity': overtime_id.remaining_quantity, + }) + overtime_id.archive_state = 'done' + remaining_to_allocate = overtime_id.carry_forward_quantity + current_year = int(overtime_id.select_year) + current_month = int(overtime_id.select_month) + cutoff_date = date(current_year, current_month, 1) - timedelta(days=1) + cutoff_dt = datetime.combine(cutoff_date, datetime.min.time()) + dec_2025_end = datetime(2025, 12, 31, 23, 59, 59) + domain = [ + ('start_time', '<', cutoff_dt), + ('remaining_quantity', '!=', 0.0), + ('employee_id', '=', overtime_id.employee_id.id) + ] + + if overtime_id.employee_id.company_id.name == '集团本部': + domain = [ + ('employee_id', '=', overtime_id.employee_id.id), + ('remaining_quantity', '!=', 0.0), + '|', + ('is_initial_number', '=', True), + '&', ('is_initial_number', '=', False), + '&', ('update_type', '=', 'add'), + '&', ('workflow_record_id', '!=', False), + '&', ('start_time', '>=', dec_2025_end), + '&', ('vacation_id', '!=', False), + ('start_time', '<', cutoff_dt) + ] + over_lines = self.env['vacation.over.line'].search(domain, order='start_time ASC') + # 4. 按顺序分配结转时长 + for rec in over_lines: + if remaining_to_allocate <= 0: + break # 分配完毕,退出循环 + + # 计算当前记录可分配的最大时长(不超过剩余时长) + max_allocation = rec.duration - rec.carry_forward_quantity + if max_allocation <= 0: + continue # 该记录已无剩余可分配时长 + + if rec.is_initial_number and rec.last_balance > 1e-6: + need_deduct_total = min(remaining_to_allocate, rec.last_balance) + remaining_deduct = need_deduct_total + + # 1. 优先扣减 vacation_id.last_workday + if rec.vacation_id and rec.vacation_id.last_workday > 1e-6: + deduct_workday = min(remaining_deduct, rec.vacation_id.last_workday) + rec.vacation_id.last_workday -= deduct_workday + remaining_deduct -= deduct_workday # 剩余待扣减金额减少 + rec.vacation_id.write({'last_workday': rec.vacation_id.last_workday}) + + # 2.扣减 vacation_id.last_holiday + if remaining_deduct > 1e-6 and rec.vacation_id and rec.vacation_id.last_holiday > 1e-6: + deduct_holiday = min(remaining_deduct, rec.vacation_id.last_holiday) + rec.vacation_id.last_holiday -= deduct_holiday + remaining_deduct -= deduct_holiday + rec.vacation_id.write({'last_holiday': rec.vacation_id.last_holiday}) + + # 确定本次分配的时长 + if remaining_to_allocate >= max_allocation: + # 足够分配完当前记录的剩余时长 + allocate_amount = max_allocation + new_state = 'completed' + remaining_to_allocate -= allocate_amount + else: + # 只能分配部分时长 + allocate_amount = remaining_to_allocate + new_state = 'partial' + remaining_to_allocate = 0 + + rec.write({ + 'carry_forward_quantity': rec.carry_forward_quantity + allocate_amount, + 'carry_forward_state': new_state, + 'carry_forward_date': overtime_id.carry_forward_date + }) + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'type': 'success', + 'message': '归档成功', + 'sticky': False, + } + } + + @api.model + def carryover_details(self): + domain = [('vacation_sum', '>', 40), + ('remaining_quantity', '!=', 0.0) + ] + overtime_lines = self.env['yuthon.overtime.line'].search(domain) + for overtime in overtime_lines: + overtime.carry_forward_date = fields.Date.today() + overtime.carry_forward_quantity += overtime.remaining_quantity - 40 + tree_id = self.env.ref("yuthon_hr_attendance.view_yuthon_overtime_line_tree1").id + return { + "name": _("结转明细"), + "type": "ir.actions.act_window", + "views": [(tree_id, "tree")], + "res_model": "yuthon.overtime.line", + "target": "current", + "domain": domain + } + + diff --git a/yuthon_hr_attendance/models/yuthon_vacation_reports.py b/yuthon_hr_attendance/models/yuthon_vacation_reports.py index 358a317b6..34ca1af86 100644 --- a/yuthon_hr_attendance/models/yuthon_vacation_reports.py +++ b/yuthon_hr_attendance/models/yuthon_vacation_reports.py @@ -1032,12 +1032,15 @@ class YuthonVacationReports(models.Model): base_month = int(base_line.select_month) base_date = date(base_year, base_month, 1) self.env['yuthon.overtime.line'].search([]).unlink() - current_month_last = base_date - timedelta(days=1) + current_date = base_date - timedelta(days=1) + current_month_last = base_date - relativedelta(months=1) - timedelta(days=1) current_month_last_dt = datetime.combine(current_month_last, datetime.min.time()) dec_2025_end = datetime(2025, 12, 31, 23, 59, 59) if self.env.user.company_id.name == '集团本部': domain = [ - ('remaining_quantity', '!=', 0.0), + '|', + ('carry_forward_date', '=', False), + ('carry_forward_date', '<', current_month_last_dt), '|', ('is_initial_number', '=', True), '&', ('is_initial_number', '=', False), @@ -1054,7 +1057,8 @@ class YuthonVacationReports(models.Model): ('remaining_quantity', '!=', 0.0), ('start_time', '<', current_month_last_dt) ] - over_lines = self.env['vacation.over.line'].search(domain) + all_records = self.env['vacation.over.line'].search(domain) + over_lines = all_records.filtered(lambda r: abs(r.remaining_quantity) > 1e-6) if not over_lines: return @@ -1074,8 +1078,8 @@ class YuthonVacationReports(models.Model): 'carry_forward_quantity': 0.0, 'carry_forward_date': line.carry_forward_date, 'remaining_quantity': 0.0, - 'select_month': str(current_month_last.month), - 'select_year': str(current_month_last.year) + 'select_month': str(current_date.month), + 'select_year': str(current_date.year) } summary = employee_summary[emp_id] @@ -1100,12 +1104,15 @@ class YuthonVacationReports(models.Model): base_month = int(base_line.select_month) base_date = date(base_year, base_month, 1) self.env['yuthon.overtime.line'].search([]).unlink() - next_month_last = (base_date + relativedelta(months=2)).replace(day=1) - timedelta(days=1) + current_date = (base_date + relativedelta(months=2)).replace(day=1) - timedelta(days=1) + next_month_last = (base_date + relativedelta(months=1)).replace(day=1) - timedelta(days=1) next_month_last_dt = datetime.combine(next_month_last, datetime.min.time()) dec_2025_end = datetime(2025, 12, 31, 23, 59, 59) if self.env.user.company_id.name == '集团本部': domain = [ - ('remaining_quantity', '!=', 0.0), + '|', + ('carry_forward_date', '=', False), + ('carry_forward_date', '<', next_month_last_dt), '|', ('is_initial_number', '=', True), '&', ('is_initial_number', '=', False), @@ -1122,7 +1129,8 @@ class YuthonVacationReports(models.Model): ('remaining_quantity', '!=', 0.0), ('start_time', '<', next_month_last_dt) ] - over_lines = self.env['vacation.over.line'].search(domain) + all_records = self.env['vacation.over.line'].search(domain) + over_lines = all_records.filtered(lambda r: abs(r.remaining_quantity) > 1e-6) if not over_lines: return @@ -1140,8 +1148,8 @@ class YuthonVacationReports(models.Model): 'public_holidays': 0.0, 'carry_forward_quantity': 0.0, 'carry_forward_date': line.carry_forward_date, - 'select_month': str(next_month_last.month), - 'select_year': str(next_month_last.year) + 'select_month': str(current_date.month), + 'select_year': str(current_date.year) } summary = employee_summary[emp_id] @@ -1173,7 +1181,9 @@ class YuthonVacationReports(models.Model): # 原有domain(先筛选出基础数据) if self.env.user.company_id.name == '集团本部': domain = [ - ('remaining_quantity', '!=', 0.0), + '|', + ('carry_forward_date', '=', False), + ('carry_forward_date', '<', current_month_first_dt), '|', ('is_initial_number', '=', True), '&', ('is_initial_number', '=', False), @@ -1191,7 +1201,8 @@ class YuthonVacationReports(models.Model): ('start_time', '<', current_month_first_dt) ] - over_lines = self.env['vacation.over.line'].search(domain) + all_records = self.env['vacation.over.line'].search(domain) + over_lines = all_records.filtered(lambda r: abs(r.remaining_quantity) > 1e-6) if not over_lines: return diff --git a/yuthon_hr_attendance/static/src/js/button_vacation_confirm.js b/yuthon_hr_attendance/static/src/js/button_vacation_confirm.js index 45b5533fe..0183e88f7 100644 --- a/yuthon_hr_attendance/static/src/js/button_vacation_confirm.js +++ b/yuthon_hr_attendance/static/src/js/button_vacation_confirm.js @@ -8,8 +8,9 @@ export class CustomAttendanceVacationConfirmController extends ListController { super.setup(); } - async getAction(lock_type) { - await this.orm.call('vacation.over.line', 'confirm', []); + async getAction() { + const res = await this.orm.call('yuthon.overtime.line', 'carryover_details', []); + this.actionService.doAction(res) } async getNextAction(lock_type) { await this.orm.call('yuthon.vacation.reports', 'next_month', []); diff --git a/yuthon_hr_attendance/views/vacation_overtime_active_views.xml b/yuthon_hr_attendance/views/vacation_overtime_active_views.xml index b736954bc..57608beeb 100644 --- a/yuthon_hr_attendance/views/vacation_overtime_active_views.xml +++ b/yuthon_hr_attendance/views/vacation_overtime_active_views.xml @@ -5,13 +5,18 @@ vacation.overtime.active - - - - + + + + + + + + - + +