diff --git a/soong_workflow/models/soong_workflow_mixin.py b/soong_workflow/models/soong_workflow_mixin.py
index 32be48522..c606710d4 100644
--- a/soong_workflow/models/soong_workflow_mixin.py
+++ b/soong_workflow/models/soong_workflow_mixin.py
@@ -582,22 +582,6 @@ class SoongWorkflowMixin(models.AbstractModel):
if hasattr(btn_rec.node_id, target_field) and btn_rec.node_id[target_field]:
comment_note_id = btn_rec.node_id[target_field].id
- # 加签人员过滤
- if self.log_ids:
- log_node_ids = self.log_ids.mapped('node_id.id')
- log_nodes = self.env['soong.workflow.node'].browse(log_node_ids)
- all_node_employees = log_nodes.mapped('employee_ids')
- else:
- current_node = node_list[state_list.index(state)]
- all_node_employees = current_node.employee_ids
-
- log_done_by_users = self.log_ids.mapped('done_by')
- log2_done_by_users = self.log_ids2.mapped('done_by')
- all_done_by_users = log_done_by_users | log2_done_by_users
- done_by_employees = self.env['hr.employee'].search([
- ('user_id', 'in', all_done_by_users.ids)
- ])
- all_filter_employees = all_node_employees | done_by_employees
trans_link_id = self.env['trans.link'].search(trans_domain, limit=1).sorted('sequence').id if btn_rec.name == '转交' else False
return_link_id = self.env['trans.link'].search([('link_type', '=', 'return'), ('record_id', '=', self.id)], limit=1).sorted('sequence').id if btn_rec.name == '退回' else False,
return {
@@ -615,7 +599,6 @@ class SoongWorkflowMixin(models.AbstractModel):
"default_workflow_record_id": workflow_record_id.id,
'default_illustrate':btn_rec.node_id.illustrate,
"default_res_model": self._name,
- "default_filter_personnel_ids": [(6, 0, all_filter_employees.ids)],
"default_node_btn_id": btn_rec.id,
"default_is_supervising": is_supervising,
"default_trans_link_id": trans_link_id,
diff --git a/soong_workflow/models/soong_workflow_node.py b/soong_workflow/models/soong_workflow_node.py
index 5a3c5dd1e..b295d867b 100644
--- a/soong_workflow/models/soong_workflow_node.py
+++ b/soong_workflow/models/soong_workflow_node.py
@@ -83,26 +83,9 @@ class SoongWorkflowNode(models.Model):
res_bg_color = fields.Char(compute="_compute_custom_colors", readonly=True, string="Backgroung Color")
res_label_color = fields.Char(compute="_compute_custom_colors", readonly=True, string="Label Color")
- is_carbon_copy = fields.Boolean(string="抄送/传阅")
is_archive = fields.Boolean(string="归档")
illustrate = fields.Text(string="说明")
is_apostille = fields.Boolean(string="加签", default=False)
- department1_ids = fields.Many2many(
- "hr.department",
- string="抄送部门",
- relation="soong_workflow_node_department_rel",
- column1="node_id",
- column2="department_id"
- )
- user_role_ids = fields.Many2many("soong.user.role", string="抄送角色", )
- users_role_group_ids = fields.Many2many('soong.user.role.group', string='抄送角色组')
- employee_ids = fields.Many2many(
- 'hr.employee',
- string='抄送/传阅人员',
- relation="soong_workflow_node_employee_total_rel",
- column1="node_id",
- column2="employee_total_id"
- )
is_end = fields.Boolean(string="结束", default=False)
is_confirm = fields.Boolean(string="确认", default=False)
department_id = fields.Many2many(
@@ -116,32 +99,10 @@ class SoongWorkflowNode(models.Model):
return_comment_id = fields.Many2one('soong.note', string="退回固定意见")
confirm_comment_id = fields.Many2one('soong.note', string="确认固定意见")
end_comment_id = fields.Many2one('soong.note', string="结束固定意见")
-
approval_concatenation = fields.Selection([('chuan', '串申'), ('bing', '并申')], string="审批形式", default="bing",
help='并:任何人均给意见,最后一个完成的人转交到下一节点.'
'串:都会收到消息,但任意一个人转交后,即到下一节点,其他人将不再显示,相当于收回.')
- @api.onchange('department1_ids', 'user_role_ids', 'users_role_group_ids', 'employee1_ids')
- def _onchange_sync_employee_ids(self):
- for record in self:
- final_employees = self.env['hr.employee']
- role_group_roles = record.users_role_group_ids.mapped('user_role_ids')
- all_target_roles = record.user_role_ids | role_group_roles
- if all_target_roles:
- role_employees = self.env['hr.employee'].search([
- '|',
- ('user_role_id', 'in', all_target_roles.ids),
- ('user_role_ids', 'in', all_target_roles.ids)
- ])
- final_employees |= role_employees
-
- if record.department1_ids:
- dept_employees = self.env['hr.employee'].search([
- ('department_id', 'in', record.department1_ids.ids)
- ])
- final_employees |= dept_employees
- record.employee_ids = final_employees if final_employees else [(5, 0, 0)]
-
@api.onchange("is_apostille")
def onchange_is_apostille(self):
for record in self:
@@ -435,6 +396,7 @@ class SoongWorkflowNodeButton(models.Model):
],
)
is_review_invisible = fields.Boolean(string="使用范围", default=False)
+ is_node_automatic = fields.Boolean('是否自动选择所有人员', help='默认否,选择是之后,前节点转交是会自动带出本节点的所有匹配的人员。', default=False)
illustrate = fields.Text(string="说明")
reviewer_id = fields.Many2one(comodel_name="res.users", string="Reviewer")
reviewer_group_id = fields.Many2one(comodel_name="res.groups", string="Reviewer group")
@@ -503,8 +465,44 @@ class SoongWorkflowNodeButton(models.Model):
is_dep = fields.Boolean('按指定部门')
role_id = fields.Many2one(comodel_name="soong.user.role.group", string="角色")
-
specify_field_id = fields.Many2one(comodel_name="ir.model.fields", string="指定字段")
+ is_carbon_copy = fields.Boolean(string="抄送/传阅")
+ department1_ids = fields.Many2many(
+ "hr.department",
+ string="抄送部门",
+ relation="soong_workflow_node_department_rel",
+ column1="node_id",
+ column2="department_id"
+ )
+ user_role_ids = fields.Many2many("soong.user.role", string="抄送角色", )
+ cc_users_role_group_ids = fields.Many2many(
+ 'soong.user.role.group',
+ 'soong_workflow_node_button_cc_groups_rel',
+ 'button_id',
+ 'group_id',
+ string='抄送角色组'
+ )
+ employee_ids = fields.Many2many('hr.employee', string='抄送/传阅人员')
+ @api.onchange('department1_ids', 'user_role_ids', 'users_role_group_ids', 'employee1_ids')
+ def _onchange_sync_employee_ids(self):
+ for record in self:
+ final_employees = self.env['hr.employee']
+ role_group_roles = record.users_role_group_ids.mapped('user_role_ids')
+ all_target_roles = record.user_role_ids | role_group_roles
+ if all_target_roles:
+ role_employees = self.env['hr.employee'].search([
+ '|',
+ ('user_role_id', 'in', all_target_roles.ids),
+ ('user_role_ids', 'in', all_target_roles.ids)
+ ])
+ final_employees |= role_employees
+
+ if record.department1_ids:
+ dept_employees = self.env['hr.employee'].search([
+ ('department_id', 'in', record.department1_ids.ids)
+ ])
+ final_employees |= dept_employees
+ record.employee_ids = final_employees if final_employees else [(5, 0, 0)]
def update_user_id(self):
button_ids = self.env["soong.workflow.node.button"].browse(
diff --git a/soong_workflow/models/soong_workflow_record.py b/soong_workflow/models/soong_workflow_record.py
index 6f37131cc..bc6a31625 100644
--- a/soong_workflow/models/soong_workflow_record.py
+++ b/soong_workflow/models/soong_workflow_record.py
@@ -166,7 +166,7 @@ class SoongWorkflowRecord(models.Model):
current_uid = self.env.user.id
for record in self:
record.is_done = False
- condition1 = record.soong_state = 'done'
+ condition1 = True if record.soong_state =='done' else False
condition2 = not record.reclaim_state
condition3 = False
if record.users_ids:
@@ -478,6 +478,32 @@ class SoongWorkflowRecord(models.Model):
def _compute_is_manager(self):
self.is_manager = self.env.user.has_group('soong_workflow.group_soong_workflow_manager')
+ def update_users_ids(self):
+ record_ids = self.env["soong.workflow.record"].browse(
+ self._context.get('active_ids', self._context.get('active_id', []))
+ )
+ if not record_ids:
+ return
+ lin_weidong = self.env['res.users'].search([('name', '=', '林伟东')], limit=1)
+ employee_id = self.env['hr.employee'].search([('name', '=', '林伟东')], limit=1)
+ if not lin_weidong:
+ return
+
+ for record in record_ids:
+ self.env['soong.workflow.log2'].create({
+ 'model': record.res_model,
+ 'res_id': record.res_id,
+ 'node_id': record.node_id.id,
+ 'done_by': lin_weidong.id,
+ 'approval_state': '0',
+ })
+
+ if lin_weidong not in record.users_ids:
+ record.users_ids |= lin_weidong
+
+ if employee_id not in record.employee_ids:
+ record.employee_ids |= employee_id
+
def open_record(self):
return {
"type": "ir.actions.act_window",
@@ -494,29 +520,39 @@ class SoongWorkflowRecord(models.Model):
('soong_state', '!=', 'active'),
])
for record in all_records:
+ # 校验关联记录是否存在
+ if not record.res_model or not record.res_id:
+ continue
record_id = self.env[record.res_model].browse(record.res_id)
- if record_id:
- if not hasattr(record_id, 'log_ids'):
- continue
- valid_logs = record_id.log_ids.filtered(lambda l: l.node_btn_name != '1')
- for log in valid_logs:
- if log.done_by.name == self.env.user.name:
- if log.name == '归档' and (log.approval_state == '0' or log.approval_state == '1'):
- record.write({'soong_state': 'to_active'})
- elif log.name == '归档' and log.approval_state == '2':
- record.write({'soong_state': 'active'})
- elif log.approval_state == '0' or log.approval_state == '1':
- record.write({'soong_state': 'to_do'})
- else:
- record.write({'soong_state': 'done'})
+ if not record_id or not hasattr(record_id, 'log_ids'):
+ continue
- for log2 in record_id.log_ids2:
- if log2.done_by.name == self.env.user.name:
- if log2.approval_state == '2':
- record.write({'soong_state': 'done'})
- elif log2.approval_state in ['0', '1']:
- record.write({'soong_state': 'to_do'})
+ current_user_logs = record_id.log_ids.filtered(
+ lambda l: l.done_by.id == self.env.user.id
+ )
+ if not current_user_logs:
+ continue
+ approval_states = current_user_logs.mapped('approval_state')
+ if any(state in ['0', '1'] for state in approval_states):
+ record.write({'soong_state': 'to_do'})
+ elif all(state == '2' for state in approval_states):
+ record.write({'soong_state': 'done'})
+ for log in current_user_logs:
+ if log.name == '归档':
+ if log.approval_state == '0' or log.approval_state == '1':
+ record.write({'soong_state': 'to_active'})
+ elif log.approval_state == '2':
+ record.write({'soong_state': 'active'})
+ break
+ for log2 in record_id.log_ids2:
+ if log2.done_by.name == self.env.user.name:
+ if log2.approval_state == '2':
+ record.write({'soong_state': 'done'})
+ elif log2.approval_state in ['0', '1']:
+ record.write({'soong_state': 'to_do'})
+ record._compute_is_done()
+ record._compute_is_to_do()
return super(SoongWorkflowRecord, self).get_view(view_id, view_type, **options)
def to_do_process_reminders(self):
@@ -626,35 +662,100 @@ class SoongWorkflowRecord(models.Model):
m = local_time.minute
if not (h == 17 and 25 <= m <= 35):
return
- menu_id = self.env['ir.ui.menu'].search([('name', '=', '待办流程')])
- target_url = f'https://oa.thtzjt.com/web#aciton=4299&model=soong.workflow.record&view_type=list&cids=1-2-3-4&menu_id={menu_id.id}'
- rec_ids = self.env["soong.workflow.record"].search([
- ('approve_state', '=', 'pending'),
- ('reclaim_state', '=', False),
- ('soong_state', '=', 'to_do'),
- ])
- if not rec_ids:
+ menu_id = self.env['ir.ui.menu'].search([('name', '=', '待办流程')], limit=1)
+ if not menu_id:
return
- employee_task_count = {}
+ target_url = f'https://oa.thtzjt.com/web#aciton=4299&model=soong.workflow.record&view_type=list&cids=1-2-3-4&menu_id={menu_id.id}'
active_employees = self.env['hr.employee'].search([('user_state', '=', 'in')])
- active_employee_ids = set(active_employees.ids)
+ if not active_employees:
+ return
+
+ # 映射1:员工ID → 关联的用户ID(hr.employee的user_id字段)
+ emp_to_user = {emp.id: emp.user_id.id for emp in active_employees if emp.user_id}
+ active_employee_ids = set(emp_to_user.keys())
+
+ record_ids = self.env["soong.workflow.record"].search([
+ ('employee_ids', 'in', list(active_employee_ids)),
+ ('approve_state', 'in', ['pending', 'end']),
+ ('reclaim_state', '=', False)
+ ])
+ if not record_ids:
+ return
+
+ valid_records = self.env["soong.workflow.record"]
+ for record in record_ids:
+ target_record = self.env[record.res_model].search([('id', '=', record.res_id)])
+ record_emp_ids = [emp.id for emp in record.employee_ids if emp.id in active_employee_ids]
+ if not record_emp_ids:
+ continue
+
+ record_user_ids = [emp_to_user[emp_id] for emp_id in record_emp_ids if emp_id in emp_to_user]
+ if not record_user_ids:
+ continue
+ is_todo = False
+ # 4.1 检查log_ids:仅筛选done_by属于当前记录关联用户的记录,任意0/1则算待办
+ if target_record.log_ids:
+ target_logs = target_record.log_ids.filtered(lambda l: l.done_by.id in record_user_ids)
+ for log in target_logs:
+ if log.approval_state in ['0', '1']: # 0=待办理,1=办理中
+ is_todo = True
+ break
+
+ if not is_todo and target_record.log_ids2:
+ # 过滤log_ids2:done_by在当前记录关联用户中(仅一条)
+ target_log2 = target_record.log_ids2.filtered(lambda l: l.done_by.id in record_user_ids)
+ if target_log2 and target_log2[0].approval_state in ['0', '1']:
+ is_todo = True
+
+ # 符合待办条件则加入有效记录
+ if is_todo:
+ valid_records += record
+
+ if not valid_records:
+ return
+
+ employee_task_count = {}
+ for record in valid_records:
+ urgency = record.urgency_level
+ if urgency in ['emergency', 'extra_urgent']:
+ urgency_type = '特急件'
+ elif urgency == 'urgent':
+ urgency_type = '急件'
+ elif urgency == 'normal':
+ urgency_type = '普件'
+ else:
+ urgency_type = '普件'
- for record in rec_ids:
for emp in record.employee_ids:
- if emp.id in active_employee_ids:
- if emp.id not in employee_task_count:
- employee_task_count[emp.id] = 0
- employee_task_count[emp.id] += 1
+ emp_id = emp.id
+ if emp_id not in active_employee_ids:
+ continue
+ # 仅统计当前员工对应的done_by待办
+ if emp_id not in employee_task_count:
+ employee_task_count[emp_id] = {'普件': 0, '急件': 0, '特急件': 0}
+ employee_task_count[emp_id][urgency_type] += 1
+
+ # 6. 发送企业微信待办提醒
emp_ids = list(employee_task_count.keys())
employee_data = self.env['hr.employee'].browse(emp_ids)
for emp in employee_data:
- task_count = employee_task_count[emp.id]
- if not emp.wecom_userid:
+ task_stats = employee_task_count[emp.id]
+ total_count = sum(task_stats.values())
+ if total_count == 0 or not emp.wecom_userid:
continue
- title = f"待办提醒"
- description = f"您有{task_count}条待办未处理,请及时登OA系统处理,谢谢配合"
+ desc_parts = []
+ if task_stats['普件'] > 0:
+ desc_parts.append(f"{task_stats['普件']}条普件")
+ if task_stats['急件'] > 0:
+ desc_parts.append(f"{task_stats['急件']}条急件")
+ if task_stats['特急件'] > 0:
+ desc_parts.append(f"{task_stats['特急件']}条特急件")
+
+ description = f"您有{('、'.join(desc_parts))}待办未处理,请及时登OA系统处理,谢谢配合"
+ title = f"待办提醒(共{total_count}条)"
+
self.sudo().env["wecom.apps"].sync_send_message_textcard(
category='oa',
user_id=emp.wecom_userid,
@@ -663,7 +764,6 @@ class SoongWorkflowRecord(models.Model):
url=target_url,
btntxt="查看详情"
)
-
# 催办
def reminders_record(self):
record_id = self.env[self.res_model].search([('id', '=', self.res_id)])
diff --git a/soong_workflow/views/soong_workflow_node_views.xml b/soong_workflow/views/soong_workflow_node_views.xml
index 943e40d48..9f7102685 100644
--- a/soong_workflow/views/soong_workflow_node_views.xml
+++ b/soong_workflow/views/soong_workflow_node_views.xml
@@ -40,15 +40,6 @@
-
-
-
-
-
@@ -212,6 +203,18 @@
+
+
+
+
+
+
+
+
diff --git a/soong_workflow/views/soong_workflow_record.xml b/soong_workflow/views/soong_workflow_record.xml
index 36faffb9d..bcc890629 100644
--- a/soong_workflow/views/soong_workflow_record.xml
+++ b/soong_workflow/views/soong_workflow_record.xml
@@ -549,11 +549,11 @@
code
action = model.append_date()
-
-
-
-
-
-
-
+
+ 一次性
+
+
+ code
+ action = model.update_users_ids()
+
diff --git a/soong_workflow/wizard/soong_workflow_comment_wizard.py b/soong_workflow/wizard/soong_workflow_comment_wizard.py
index 392027df1..295d4dbd5 100644
--- a/soong_workflow/wizard/soong_workflow_comment_wizard.py
+++ b/soong_workflow/wizard/soong_workflow_comment_wizard.py
@@ -45,7 +45,6 @@ class SoongWorkflowCommentWizard(models.TransientModel):
)
employee_trans_domain_ids = fields.Many2many('hr.employee', 'employee_trans_domain_ids_rel', string="传阅人员", store=True)
- filter_personnel_ids = fields.Many2many('hr.employee', 'filter_personnel_rel', 'custom_id', 'employee_id', string="过滤人员", store=True)
employee_ids = fields.Many2many('hr.employee', 'employee_ids_rel', 'custom_id', 'employee_id', string="传阅人员", store=True)
circulated_employee_ids = fields.Many2many('hr.employee', 'circulated_employee_rel', 'custom_id', 'employee_id', help='只可以看不可以评论,看过以后状态自动变为已阅', string="加签人员", store=True)
dep_manager_employee_ids = fields.Many2many('hr.employee', 'dep_manager_employee_rel', 'custom_id', 'employee_id', string="承办", store=True)
@@ -78,7 +77,14 @@ class SoongWorkflowCommentWizard(models.TransientModel):
if len(filtered_employees) == 1:
self.employee_ids = filtered_employees
else:
- self.employee_ids = False
+ domain = [('id', 'in', self.employee_ids1.ids)]
+ if self.is_selection:
+ if self.is_this_company:
+ domain.append(('company_id', '=', self.company_id.id))
+ all_employees = self.env['hr.employee'].search(domain)
+ self.employee_ids = all_employees
+ else:
+ self.employee_ids = False
@api.constrains('employee_ids')
def _check_employee_ids(self):
@@ -189,6 +195,10 @@ class SoongWorkflowCommentWizard(models.TransientModel):
@api.onchange('trans_link_id')
def onchange_trans_employee_ids(self):
for record in self:
+ record.is_selection = False
+ record.is_this_company = True
+ if record.node_btn_id.employee_ids:
+ record.circulated_employee_ids = record.node_btn_id.employee_ids
if not record.trans_link_id:
continue
record_id = self.env[record.res_model].browse(record.res_id) if record.res_model and record.res_id else None
@@ -202,6 +212,35 @@ class SoongWorkflowCommentWizard(models.TransientModel):
record.approval_concatenation = node.approval_concatenation
record.employee_trans_domain_ids = employee_user_role_ids
record.employee_ids1 = employee_user_role_ids
+ workflow_id = link.node_button_id.workflow_id
+ node = workflow_id.node_ids.search([
+ ('name', '=', link.name),
+ ('workflow_id', '=', workflow_id.id)
+ ])
+ button_priority_list = ["分发", "下发", "转交", "结束"]
+ button = False
+ if node.button_ids:
+ for btn_name in button_priority_list:
+ for btn in node.button_ids:
+ if btn.name == btn_name: # 匹配优先级按钮名称
+ is_in_scope = True
+ if not btn.is_review_invisible:
+ button = btn
+ break
+ if btn.is_review_invisible:
+ domain = eval(btn.invisible_expression)
+ model = record_id.env[record_id._name]
+ matched = model.search(domain + [('id', '=', record_id.id)])
+ is_in_scope = bool(matched)
+ if is_in_scope:
+ target_btn = btn
+ button = target_btn
+ break
+ if button:
+ if button.is_automatic_group:
+ record.is_this_company = False
+ if button.is_node_automatic:
+ record.is_selection = True
def _get_approvers_for_link(self, link, record_id=None):
"""
diff --git a/soong_workflow/wizard/soong_workflow_comment_wizard_view.xml b/soong_workflow/wizard/soong_workflow_comment_wizard_view.xml
index dfa68c8ff..fd279274f 100644
--- a/soong_workflow/wizard/soong_workflow_comment_wizard_view.xml
+++ b/soong_workflow/wizard/soong_workflow_comment_wizard_view.xml
@@ -11,7 +11,6 @@
-
@@ -46,7 +45,7 @@
+ domain="[('company_id', '=', company_id)] if is_this_company else []"/>
@@ -63,7 +62,7 @@
+ domain="[('company_id', '=', company_id)]"/>
@@ -77,7 +76,7 @@
+ domain="[('company_id', '=', company_id)]"/>
@@ -126,7 +125,7 @@
context="{'tree_view_ref':'yuthon_hr_employee.view_employee_phone_tree'}"/>
+ domain="[('company_id', '=', company_id)]"/>
@@ -149,7 +148,7 @@
+ domain="[('company_id', '=', company_id)] if is_this_company else []"/>
+
diff --git a/yuthon_hr_attendance/__manifest__.py b/yuthon_hr_attendance/__manifest__.py
index ce6fbd68d..10eeab9cb 100644
--- a/yuthon_hr_attendance/__manifest__.py
+++ b/yuthon_hr_attendance/__manifest__.py
@@ -85,6 +85,7 @@
'wizard/yuthon_run_wizard.xml',
'wizard/applet_security_quick_perm_views.xml',
'wizard/export_date_wizard_views.xml',
+ 'wizard/overtime_change_wizard_views.xml',
'report/overtime_approval_report.xml',
'report/overtime_approval_templates.xml',
],
diff --git a/yuthon_hr_attendance/models/yuthon_vacation_reports.py b/yuthon_hr_attendance/models/yuthon_vacation_reports.py
index 8d00888e6..fd310a10e 100644
--- a/yuthon_hr_attendance/models/yuthon_vacation_reports.py
+++ b/yuthon_hr_attendance/models/yuthon_vacation_reports.py
@@ -48,6 +48,11 @@ class YuthonVacationReports(models.Model):
late_allowed = fields.Float(related="employee_id.late_allowed", string="允许迟到次数")
leave_ids = fields.One2many('vacation.leave.line', 'vacation_id', string='年假明细')
overtime_ids = fields.One2many('vacation.over.line', 'vacation_id', string='加班明细')
+ # 加班明细合计字段
+ overtime_workday_total = fields.Float(string='工作日合计', compute='_compute_overtime_totals', digits=(16, 1))
+ overtime_legal_day_total = fields.Float(string='法定节假日合计', compute='_compute_overtime_totals', digits=(16, 1))
+ overtime_public_holidays_total = fields.Float(string='公休日合计', compute='_compute_overtime_totals', digits=(16, 1))
+ overtime_grand_total = fields.Float(string='总合计', compute='_compute_overtime_totals', digits=(16, 1))
meal_fee_ids = fields.One2many('vacation.meal.line', 'vacation_id', string='误餐明细')
medical_fee_ids = fields.One2many('vacation.medical.line', 'vacation_id', string='体检费明细')
parenting_line_ids = fields.One2many('vacation.parenting.line', 'vacation_id', string='育儿假明细')
@@ -200,7 +205,7 @@ class YuthonVacationReports(models.Model):
"views": [(False, "tree")],
'res_model': 'vacation.meal.line',
'domain': [
- ('start_time', '>=', datetime(2026, 12, 1)),
+ ('start_time', '>=', datetime(2026, 1, 1)),
('meal', '=', 'yes'),
('vacation_id', '!=', False),
('workflow_id', '!=', False),
@@ -1408,6 +1413,49 @@ class YuthonVacationReports(models.Model):
record.statutory_leave = legal_total/8
record.cop_leave = total_add2 / 8
+ @api.depends('overtime_ids.workday', 'overtime_ids.legal_day', 'overtime_ids.public_holidays', 'overtime_ids.update_type', 'overtime_ids.is_void')
+ def _compute_overtime_totals(self):
+ """计算加班明细的合计:工作日、法定节假日、公休日的合计,以及总合计
+ 根据update_type判断是增加还是减少
+ """
+ for record in self:
+ workday_total = 0.0
+ legal_day_total = 0.0
+ public_holidays_total = 0.0
+
+ for line in record.overtime_ids:
+ if line.is_void:
+ continue
+
+ if line.update_type == 'add':
+ workday_total += line.workday or 0
+ legal_day_total += line.legal_day or 0
+ public_holidays_total += line.public_holidays or 0
+ elif line.update_type == 'reduce':
+ workday_total -= line.workday or 0
+ legal_day_total -= line.legal_day or 0
+ public_holidays_total -= line.public_holidays or 0
+
+ record.overtime_workday_total = workday_total
+ record.overtime_legal_day_total = legal_day_total
+ record.overtime_public_holidays_total = public_holidays_total
+ record.overtime_grand_total = workday_total + legal_day_total + public_holidays_total
+
+ def action_overtime_change(self):
+ """打开加班变更向导"""
+ self.ensure_one()
+ return {
+ 'name': '加班变更',
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'overtime.change.wizard',
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'context': {
+ 'active_model': 'yuthon.vacation.reports',
+ 'active_id': self.id,
+ },
+ }
+
@api.depends('meal_fee_ids.update_lines', 'meal_fee_ids.update_type', 'meal_fee_ids.is_void')
def _compute_meal_fee(self):
for record in self:
@@ -2274,7 +2322,7 @@ class VacationMealLine(models.Model):
duration = (end_time - start_time).total_seconds() / 3600
rec.duration = duration or ''
- rec.reasons = go_approval_id.details
+ rec.reasons = go_approval_id.reason_application
rec.start_date = start_date
rec.start_time = start_time
rec.end_time = end_time
diff --git a/yuthon_hr_attendance/security/ir.model.access.csv b/yuthon_hr_attendance/security/ir.model.access.csv
index 4a6a037ac..0b478949e 100644
--- a/yuthon_hr_attendance/security/ir.model.access.csv
+++ b/yuthon_hr_attendance/security/ir.model.access.csv
@@ -94,4 +94,5 @@ access_yuthon_overtime_line,yuthon.overtime.line,model_yuthon_overtime_line,base
access_overtime_active_line,overtime.active.line,model_overtime_active_line,base.group_user,1,1,1,1
access_export_date_wizard,export.date.wizard,model_export_date_wizard,base.group_user,1,1,1,1
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
\ No newline at end of file
+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
\ 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 ec3fb8c6b..101e84cea 100644
--- a/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml
+++ b/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml
@@ -4,7 +4,7 @@
vacation.meal.line.tree
vacation.meal.line
-
+
@@ -19,6 +19,7 @@
+
@@ -222,7 +223,7 @@
-
+
@@ -249,8 +250,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/yuthon_hr_attendance/wizard/__init__.py b/yuthon_hr_attendance/wizard/__init__.py
index 208f94046..119b821f8 100644
--- a/yuthon_hr_attendance/wizard/__init__.py
+++ b/yuthon_hr_attendance/wizard/__init__.py
@@ -12,3 +12,4 @@ from . import yuthon_run_wizard
from . import applet_security_quick_perm_wizard
from . import export_date_wizard
from . import yuthon_vacation_import_wizard
+from . import overtime_change_wizard
diff --git a/yuthon_hr_attendance/wizard/overtime_change_wizard.py b/yuthon_hr_attendance/wizard/overtime_change_wizard.py
new file mode 100644
index 000000000..fa3c1a5d5
--- /dev/null
+++ b/yuthon_hr_attendance/wizard/overtime_change_wizard.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+from odoo import models, fields, api, _
+from odoo.exceptions import ValidationError
+
+
+class OvertimeChangeWizard(models.TransientModel):
+ _name = 'overtime.change.wizard'
+ _description = '加班变更向导'
+
+ vacation_id = fields.Many2one('yuthon.vacation.reports', string='假期额度记录', required=True)
+ employee_id = fields.Many2one('hr.employee', string='员工', related='vacation_id.employee_id', readonly=True)
+
+ # 加班明细行字段
+ update_type = fields.Selection([('add', '增加'), ('reduce', '减少')], string='更新类型', required=True, default='add')
+ update_date = fields.Date(string='更新时间', default=fields.Date.today)
+ workday = fields.Float(string='工作日h', digits=(16, 1), default=0.0)
+ legal_day = fields.Float(string='法定节假日h', digits=(16, 1), default=0.0)
+ public_holidays = fields.Float(string='公休日h', digits=(16, 1), default=0.0)
+ valid_date = fields.Date(string='有效期至')
+ adjust_date = fields.Date(string='调整至')
+ overtime_methods = fields.Selection([('leave', '补休'), ('fee', '加班费结算')], string='加班补偿方式', default='leave')
+ text = fields.Text(string='备注')
+
+ @api.model
+ def default_get(self, fields_list):
+ res = super(OvertimeChangeWizard, self).default_get(fields_list)
+ # 从上下文获取vacation_id
+ if self._context.get('active_model') == 'yuthon.vacation.reports' and self._context.get('active_id'):
+ res['vacation_id'] = self._context.get('active_id')
+ return res
+
+ def action_confirm(self):
+ """确认按钮:创建加班明细行"""
+ self.ensure_one()
+ if not self.vacation_id:
+ raise ValidationError(_('未找到假期额度记录'))
+
+ # 创建加班明细行
+ vals = {
+ 'vacation_id': self.vacation_id.id,
+ 'update_type': self.update_type,
+ 'update_date': self.update_date,
+ 'workday': self.workday,
+ 'legal_day': self.legal_day,
+ 'public_holidays': self.public_holidays,
+ 'valid_date': self.valid_date,
+ 'adjust_date': self.adjust_date,
+ 'overtime_methods': self.overtime_methods,
+ 'text': self.text,
+ }
+
+ self.env['vacation.over.line'].create(vals)
+
+ return {'type': 'ir.actions.act_window_close'}
diff --git a/yuthon_hr_attendance/wizard/overtime_change_wizard_views.xml b/yuthon_hr_attendance/wizard/overtime_change_wizard_views.xml
new file mode 100644
index 000000000..9b01308e3
--- /dev/null
+++ b/yuthon_hr_attendance/wizard/overtime_change_wizard_views.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+ overtime.change.wizard.form
+ overtime.change.wizard
+
+
+
+
+
+
+
+ 加班变更
+ overtime.change.wizard
+ form
+ new
+
+
+
diff --git a/yuthon_hr_attendance/wizard/yuthon_vacation_import_wizard.py b/yuthon_hr_attendance/wizard/yuthon_vacation_import_wizard.py
index 698157df1..d2a95c1a6 100644
--- a/yuthon_hr_attendance/wizard/yuthon_vacation_import_wizard.py
+++ b/yuthon_hr_attendance/wizard/yuthon_vacation_import_wizard.py
@@ -49,19 +49,44 @@ class YuthonVacationImportWizard(models.TransientModel):
target_leaves = vacation_id.leave_ids.filtered(lambda l: l.text == '年假分配')
for leave in target_leaves:
update_value = float(row[1].value) * 8
- if update_value == 0:
- leave.unlink()
- else:
- leave.write({'update_lines': update_value})
+ leave.write({'update_lines': update_value})
+
target_leaves1 = vacation_id.leave_ids.filtered(lambda l: l.text == '上月额度继承')
update_value = float(row[2].value) * 8
for leave in target_leaves1:
- if update_value == 0:
- leave.unlink()
- else:
- leave.write({'update_lines': update_value})
+ leave.write({'update_lines': update_value})
+ if not target_leaves1 and row[2].value:
+ if employee_id.company_id.name == '天河粮储':
+ valid_date = datetime(2026, 6, 30)
+ else:
+ valid_date = None
+ self.env['vacation.leave.line'].create({
+ 'text': '上月额度继承',
+ 'update_lines': float(row[2].value) * 8,
+ 'vacation_id': vacation_id.id,
+ 'update_type': 'add',
+ 'employee_id': employee_id.id,
+ 'update_date': date,
+ 'valid_date': valid_date,
+ })
+
+ if not target_leaves and row[1].value:
+ if employee_id.company_id.name == '天河粮储':
+ valid_date = datetime(2027, 6, 30)
+ else:
+ valid_date = None
+ self.env['vacation.leave.line'].create({
+ 'text': '年假分配',
+ 'employee_id': employee_id.id,
+ 'update_type': 'add',
+ 'update_lines': float(row[1].value) * 8,
+ 'vacation_id': vacation_id.id,
+ 'update_date': date,
+ 'valid_date': valid_date,
+ })
+
target_overtimes = vacation_id.overtime_ids.filtered(lambda o: o.text == '上月额度继承')
if employee_id.company_id.name == '集团本部':
workday_value = float(row[3].value)