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
-
-
-
-
+
+
+
+
+
+
+
+
-
+
+
diff --git a/yuthon_hr_attendance/views/yuthon_overtime_line_views.xml b/yuthon_hr_attendance/views/yuthon_overtime_line_views.xml
index e5274afcc..62c771eb7 100644
--- a/yuthon_hr_attendance/views/yuthon_overtime_line_views.xml
+++ b/yuthon_hr_attendance/views/yuthon_overtime_line_views.xml
@@ -1,5 +1,29 @@
+
+ yuthon.overtime.line.tree
+ yuthon.overtime.line
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
yuthon.overtime.line.tree
yuthon.overtime.line
@@ -12,17 +36,13 @@
-
-
+
+
+
-
- 加班合计数报表
- yuthon.overtime.line
- tree
-
\ No newline at end of file
diff --git a/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml b/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml
index 8c5fbf16c..63ea04d8d 100644
--- a/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml
+++ b/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml
@@ -651,11 +651,6 @@
-