From f82c96c3cadbd1d191ee059ecd670098a125d407 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: Thu, 22 Jan 2026 17:55:58 +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_hr_attendance/__manifest__.py | 2 +
.../models/overtime_approval.py | 11 ++-
.../models/yuthon_holiday_statement.py | 1 +
.../models/yuthon_vacation_reports.py | 68 ++++++++++++++++-
.../static/src/js/button_vacation_confirm.js | 23 ++++++
.../static/src/js/button_vacation_put.js | 54 ++++++++++---
.../src/xml/button_vacation_confirm.xml | 10 +++
.../views/yuthon_holiday_statement.xml | 15 +++-
.../views/yuthon_vacation_reports_views.xml | 76 +++++++++++++++----
yuthon_work_brand/views/hr_menus.xml | 29 +++++++
10 files changed, 257 insertions(+), 32 deletions(-)
create mode 100644 yuthon_hr_attendance/static/src/js/button_vacation_confirm.js
create mode 100644 yuthon_hr_attendance/static/src/xml/button_vacation_confirm.xml
diff --git a/yuthon_hr_attendance/__manifest__.py b/yuthon_hr_attendance/__manifest__.py
index 9afb7c4d6..092754072 100644
--- a/yuthon_hr_attendance/__manifest__.py
+++ b/yuthon_hr_attendance/__manifest__.py
@@ -95,6 +95,8 @@
'yuthon_hr_attendance/static/src/xml/button_detail_wechat.xml',
'yuthon_hr_attendance/static/src/components/*/*.xml',
'yuthon_hr_attendance/static/src/css/*.css',
+ 'yuthon_hr_attendance/static/src/js/button_vacation_confirm.js',
+ 'yuthon_hr_attendance/static/src/xml/button_vacation_confirm.xml',
'yuthon_hr_attendance/static/src/js/planning_gantt_renderer.js',
'yuthon_hr_attendance/static/src/xml/planning_gantt_renderer.xml',
'yuthon_hr_attendance/static/src/js/button_vacation_put.js',
diff --git a/yuthon_hr_attendance/models/overtime_approval.py b/yuthon_hr_attendance/models/overtime_approval.py
index f31fd0a63..179b35065 100644
--- a/yuthon_hr_attendance/models/overtime_approval.py
+++ b/yuthon_hr_attendance/models/overtime_approval.py
@@ -219,9 +219,14 @@ class OvertimeApprovals(models.Model):
if company_id:
company = self.env['res.company'].browse(company_id)
if company.name == '天河物管':
- if vals.get('duration'):
- if (vals['duration']) % 1 != 0.5:
- raise ValidationError("时长不对!请重新编辑")
+ duration = vals.get('duration')
+ if duration is None:
+ raise ValidationError("时长不能为空!请填写有效的加班时长")
+ if duration < 1:
+ raise ValidationError(f"时长不能小于1!当前填写的是{duration},请重新编辑")
+ if (duration * 2) % 1 != 0:
+ raise ValidationError(
+ f"时长格式错误!请填写1、1.5、2、2.5...等以0.5递增的数值,当前填写的是{duration}")
return super(OvertimeApprovals, self).create(vals_list)
@api.onchange('is_issues')
diff --git a/yuthon_hr_attendance/models/yuthon_holiday_statement.py b/yuthon_hr_attendance/models/yuthon_holiday_statement.py
index c8965af07..4841522a4 100644
--- a/yuthon_hr_attendance/models/yuthon_holiday_statement.py
+++ b/yuthon_hr_attendance/models/yuthon_holiday_statement.py
@@ -12,6 +12,7 @@ class InheritHrLeave(models.Model):
caption = fields.Char(string="标题", compute="_compute_application_reason")
employee_id = fields.Many2one('hr.employee', string="发起人")
leave_stats = fields.Char(string="请假统计", compute="_compute_leave_stats")
+ is_filing = fields.Boolean(string="归档")
application_reason = fields.Char(string="申请原因", compute="_compute_application_reason")
@api.depends('batch_id', 'hour_batch_id')
diff --git a/yuthon_hr_attendance/models/yuthon_vacation_reports.py b/yuthon_hr_attendance/models/yuthon_vacation_reports.py
index 48eee60e9..e14f9dcfa 100644
--- a/yuthon_hr_attendance/models/yuthon_vacation_reports.py
+++ b/yuthon_hr_attendance/models/yuthon_vacation_reports.py
@@ -85,7 +85,7 @@ class YuthonVacationReports(models.Model):
attendance_message_log_ids = fields.One2many('attendance.message.log', 'vacation_id')
admin_confirms = fields.Boolean(string="是否管理员确认")
-
+ is_filing = fields.Boolean(string="归档")
vacation_state = fields.Selection(
selection=[
('not_sent', "未发送"),
@@ -986,7 +986,7 @@ class VacationOverline(models.Model):
carry_forward_quantity = fields.Float(string="结转时长")
validity = fields.Char(string="有效期")
carry_result = fields.Selection([('success', '成功'), ('failed', '失败')], string="结转结果")
- is_active = fields.Boolean(string="归档")
+ is_filing = fields.Boolean(string="归档")
carry_forward_state = fields.Selection([('pending', '待结转'),('completed', '已结转'), ('partial', '部分结转')
],string='结转状态',
default='pending',
@@ -994,7 +994,28 @@ class VacationOverline(models.Model):
remaining_quantity = fields.Float(string='剩余数', compute="_compute_remaining_quantity", store=True)
def confirm(self):
- pass
+ domain = [
+ '|',
+ ['is_initial_number', '=', True],
+ '&',
+ ['is_initial_number', '=', False],
+ '&',
+ ['update_type', '=', 'add'],
+ '&',
+ ['workflow_record_id', '!=', False],
+ '!',
+ '&',
+ ['start_time', '>=', '2025-12-01 00:00:00'],
+ ['start_time', '<', '2026-01-01 00:00:00']
+ ]
+ filtered_records = self.search(domain)
+ if not filtered_records:
+ return
+ records_to_update = filtered_records.filtered(
+ lambda r: fields.float_compare(r.total, r.carry_forward_quantity) == 0
+ )
+ if records_to_update:
+ records_to_update.write({'is_filing': True})
def update_line(self):
over_ids = self.env["vacation.over.line"].browse(
@@ -1181,9 +1202,15 @@ class VacationMealLine(models.Model):
_description = '误餐明细'
vacation_id = fields.Many2one('yuthon.vacation.reports', string='关联假期额度记录')
- employee_id = fields.Many2one('hr.employee', string='员工')
+ is_filing = fields.Boolean(string="归档")
+ employee_id = fields.Many2one('hr.employee', string='员工', compute='_compute_employee_id', store=True)
+ department_id = fields.Many2one('hr.department', related='employee_id.department_id', string="部门", store=True)
+ company_id = fields.Many2one('res.company', related='employee_id.company_id', string="公司", store=True)
workflow_record_id = fields.Many2one('soong.workflow.record', string='流程')
+ vacation_date = fields.Date(related='vacation_id.vacation_date', string='误餐日期')
update_type= fields.Selection([('add', '增加'), ('reduce', '减少')], string='更新类型')
+ meal_name = fields.Char(string="标题", compute="_compute_overtime", store=True)
+ start_date = fields.Date(string="日期", compute="_compute_overtime", store=True)
update_date = fields.Date(string='更新时间')
update_lines = fields.Float(string='更新额度')
is_soon = fields.Boolean(string="流程修改")
@@ -1194,6 +1221,39 @@ class VacationMealLine(models.Model):
text = fields.Text(string='备注')
is_confirm = fields.Boolean(string="是否确认")
+ def update_line(self):
+ meal_ids = self.env["vacation.meal.line"].browse(
+ self._context.get('active_ids', self._context.get('active_id')))
+ for rec in meal_ids:
+ rec._compute_employee_id()
+ rec._compute_overtime()
+
+ @api.depends('workflow_record_id')
+ def _compute_overtime(self):
+ for rec in self:
+ rec.start_date = False
+ res_model = rec.workflow_record_id.res_model
+ res_id = rec.workflow_record_id.res_id
+ if res_model and res_id and res_model in self.env:
+ if res_model == 'overtime.approval':
+ overtime_id = rec.env[res_model].search([('id', '=', res_id)], limit=1)
+ if overtime_id:
+ rec.start_date = overtime_id.start_time.date() or ''
+ else:
+ go_approval_id = rec.env[res_model].search([('id', '=', res_id)], limit=1)
+ if go_approval_id:
+ rec.start_date = go_approval_id.date_start.date() or ''
+ rec.meal_name = rec.workflow_record_id.name or ''
+
+
+ @api.depends('vacation_id')
+ def _compute_employee_id(self):
+ for rec in self:
+ if rec.vacation_id:
+ rec.employee_id = rec.vacation_id.employee_id.id
+ elif not rec.employee_id:
+ rec.employee_id = False
+
def action_void(self):
self.is_void = True
diff --git a/yuthon_hr_attendance/static/src/js/button_vacation_confirm.js b/yuthon_hr_attendance/static/src/js/button_vacation_confirm.js
new file mode 100644
index 000000000..42d44f38c
--- /dev/null
+++ b/yuthon_hr_attendance/static/src/js/button_vacation_confirm.js
@@ -0,0 +1,23 @@
+/** @odoo-module */
+import { ListController } from "@web/views/list/list_controller";
+import { registry } from '@web/core/registry';
+import { listView } from '@web/views/list/list_view';
+
+export class CustomAttendanceVacationConfirmController extends ListController {
+ setup() {
+ super.setup();
+ }
+ async openConfirm() {
+ await this.model.orm.call(
+ 'vacation.over.line',
+ 'confirm',
+ []
+ );
+ }
+}
+
+registry.category("views").add("custom_vacation_confirm_button", {
+ ...listView,
+ Controller: CustomAttendanceVacationConfirmController,
+ buttonTemplate: "vacation_confirm_module.ListView.Buttons",
+});
\ No newline at end of file
diff --git a/yuthon_hr_attendance/static/src/js/button_vacation_put.js b/yuthon_hr_attendance/static/src/js/button_vacation_put.js
index 965dbcbb8..9d1cfd6e1 100644
--- a/yuthon_hr_attendance/static/src/js/button_vacation_put.js
+++ b/yuthon_hr_attendance/static/src/js/button_vacation_put.js
@@ -9,6 +9,19 @@ export class CustomAttendanceVacationPutController extends ListController {
}
openHrLeaveWizard() {
+ const now = new Date();
+ const lastMonthYear = now.getMonth() === 0 ? now.getFullYear() - 1 : now.getFullYear();
+ const lastMonth = now.getMonth() === 0 ? 11 : now.getMonth() - 1;
+ const lastMonthFirstDay = new Date(lastMonthYear, lastMonth, 1);
+ const currentMonthFirstDay = new Date(now.getFullYear(), now.getMonth(), 1);
+ const formatDate = (date) => {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+ return `${year}-${month}-${day} 00:00:00`;
+ };
+ const lastMonthStart = formatDate(lastMonthFirstDay);
+ const lastMonthEnd = formatDate(currentMonthFirstDay);
this.actionService.doAction({
type: 'ir.actions.act_window',
res_model: 'hr.leave',
@@ -18,6 +31,11 @@ export class CustomAttendanceVacationPutController extends ListController {
views: [[false, 'tree']],
target: 'new', // 弹窗形式打开
context: {'tree_view_ref': "yuthon_hr_attendance.view_hr_leave_statement_tree"},
+ domain: [
+ '&',
+ ['date_from', '>=', lastMonthStart],
+ ['date_from', '<', lastMonthEnd]
+ ],
});
}
@@ -33,17 +51,14 @@ export class CustomAttendanceVacationPutController extends ListController {
context: {'tree_view_ref': "yuthon_hr_attendance.view_yuthon_vacation_overtime_briefly_tree"},
domain: [
'|',
- // 条件1:is_initial_number=true → 无视时间,直接显示
['is_initial_number', '=', true],
- // 条件组2:is_initial_number=false + add + workflow_record_id≠空 + 非12月
- '&', // 且:作用于「条件2」和「运算符&的结果」
+ '&',
['is_initial_number', '=', false],
- '&', // 且:作用于「条件3」和「运算符&的结果」
+ '&',
['update_type', '=', 'add'],
- '&', // 且:作用于「条件4」和「运算符!的结果」
+ '&',
['workflow_record_id', '!=', false],
- // 时间过滤:只作用于第二类记录
- '!', // 取反:不是2025年12月
+ '!',
'&',
['start_time', '>=', '2025-12-01 00:00:00'],
['start_time', '<', '2026-01-01 00:00:00']
@@ -52,16 +67,35 @@ export class CustomAttendanceVacationPutController extends ListController {
}
openYuthonVacationWizard() {
+ const now = new Date();
+ const lastMonthYear = now.getMonth() === 0 ? now.getFullYear() - 1 : now.getFullYear();
+ const lastMonth = now.getMonth() === 0 ? 11 : now.getMonth() - 1;
+ const lastMonthFirstDay = new Date(lastMonthYear, lastMonth, 1);
+ const currentMonthFirstDay = new Date(now.getFullYear(), now.getMonth(), 1);
+ const formatDate = (date) => {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+ return `${year}-${month}-${day} 00:00:00`;
+ };
+ const lastMonthStart = formatDate(lastMonthFirstDay);
+ const lastMonthEnd = formatDate(currentMonthFirstDay);
this.actionService.doAction({
type: 'ir.actions.act_window',
- res_model: 'yuthon.vacation.reports',
+ res_model: 'vacation.meal.line',
name: '误餐情况',
view_mode: 'tree',
view_type: 'form',
views: [[false, 'tree']],
target: 'new',
- context: {'tree_view_ref': "yuthon_hr_attendance.view_yuthon_vacation_meal_tree"},
- domain: [['meal_fee', '!=', 0]]
+ context: {'tree_view_ref': "yuthon_hr_attendance.view_vacation_meal_line_tree",
+ 'group_by': ['employee_id'],
+ },
+ domain: [
+ '&',
+ ['start_date', '>=', lastMonthStart],
+ ['start_date', '<', lastMonthEnd]
+ ],
});
}
}
diff --git a/yuthon_hr_attendance/static/src/xml/button_vacation_confirm.xml b/yuthon_hr_attendance/static/src/xml/button_vacation_confirm.xml
new file mode 100644
index 000000000..dfe90d6ce
--- /dev/null
+++ b/yuthon_hr_attendance/static/src/xml/button_vacation_confirm.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/yuthon_hr_attendance/views/yuthon_holiday_statement.xml b/yuthon_hr_attendance/views/yuthon_holiday_statement.xml
index b70095a71..e6fab2b51 100644
--- a/yuthon_hr_attendance/views/yuthon_holiday_statement.xml
+++ b/yuthon_hr_attendance/views/yuthon_holiday_statement.xml
@@ -4,17 +4,18 @@
hr.leave.statement.tree
hr.leave
-
+
-
+
+
@@ -40,6 +41,16 @@
请假情况
hr.leave
tree,search
+ [('is_filing','=', False)]
+ {'search_default_now_month': 1, 'search_default_group_by_employee_id':1}
+
+
+
+
+ 请假归档
+ hr.leave
+ tree,search
+ [('is_filing','=', True)]
{'search_default_now_month': 1, 'search_default_group_by_employee_id':1}
diff --git a/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml b/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml
index 8f0308028..c962ec04c 100644
--- a/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml
+++ b/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml
@@ -1,15 +1,36 @@
-
- yuthon.vacation.reports.tree
- yuthon.vacation.reports
+
+ vacation.meal.line.tree
+ vacation.meal.line
-
+
-
+
+
+
+
+
+ vacation.meal.line.search
+ vacation.meal.line
+
+
+
+
+
+
+
+
+
+
yuthon.vacation.reports.tree
yuthon.vacation.reports
@@ -518,7 +539,7 @@
vacation.over.line.overtime.briefly.tree
vacation.over.line
-
+
@@ -526,6 +547,7 @@
+
@@ -540,7 +562,7 @@
-
+
@@ -588,7 +610,7 @@
加班情况
vacation.over.line
tree
- ['|', ('workflow_record_id','!=', False), ('is_initial_number', '=', True), ('update_type', '=', 'add')]
+ ['|', ('workflow_record_id','!=', False), ('is_initial_number', '=', True), ('update_type', '=', 'add'), ('is_filing','=', False)]
{'search_default_now_month': 1, 'search_default_group_by_employee_id':1}
@@ -597,7 +619,7 @@
加班情况(归档)
vacation.over.line
tree
- ['|', ('workflow_record_id','!=', False), ('is_initial_number', '=', True), ('update_type', '=', 'add'), ('is_active', '=', True)]
+ ['|', ('workflow_record_id','!=', False), ('is_initial_number', '=', True), ('update_type', '=', 'add'), ('is_filing','=', True)]
{'search_default_group_by_employee_id':1}
@@ -611,18 +633,46 @@
误餐情况
- yuthon.vacation.reports
+ vacation.meal.line
tree
- [('meal_fee', '!=', 0)]
+ [('is_filing','=', False)]
{'search_default_now_month': 1, 'search_default_group_by_employee_id':1}
-
+
+ (0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_hr_attendance.view_vacation_meal_line_tree')}),]"/>
+
+
+ 触发计算逻辑
+
+
+ code
+ action = model.update_line()
+
+
+ 误餐归档
+ vacation.meal.line
+ tree
+ [('is_filing','=', True)]
+ {'search_default_now_month': 1, 'search_default_group_by_employee_id':1}
+
+
考勤确认单台账
yuthon.vacation.reports
tree,form
+ [('is_filing','=', False)]
+ {'search_default_now_month': 1, 'search_default_group_by_employee_id':1}
+
+
+
+ 考勤确认单归档
+ yuthon.vacation.reports
+ tree,form
+ [('is_filing','=', True)]
{'search_default_now_month': 1, 'search_default_group_by_employee_id':1}
+
+
+
+
+
+