Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)])
|
||||
|
||||
@@ -40,15 +40,6 @@
|
||||
<group>
|
||||
<field name="flow_start"/>
|
||||
<field name="flow_end" invisible="1"/>
|
||||
<field name="is_carbon_copy"/>
|
||||
<field name="user_role_ids" widget="many2many_tags" invisible="not is_carbon_copy"
|
||||
options="{'no_create': True}"/>
|
||||
<field name="users_role_group_ids" widget="many2many_tags" invisible="not is_carbon_copy"
|
||||
options="{'no_create': True}"/>
|
||||
<field name="department1_ids" widget="many2many_tags" invisible="not is_carbon_copy"
|
||||
options="{'no_create': True}"/>
|
||||
<field name="employee_ids" widget="many2many_tags" invisible="not is_carbon_copy"
|
||||
options="{'no_create': True}"/>
|
||||
<field name="is_apostille"/>
|
||||
<field name="is_end"/>
|
||||
<field name="is_confirm"/>
|
||||
@@ -212,6 +203,18 @@
|
||||
<field name="specify_field_id" domain="[('ttype', '=', 'many2one'), ('model', '=', invisible_expression_model)]"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="is_node_automatic"/>
|
||||
<field name="is_carbon_copy"/>
|
||||
<field name="user_role_ids" widget="many2many_tags" invisible="not is_carbon_copy"
|
||||
options="{'no_create': True}"/>
|
||||
<field name="users_role_group_ids" widget="many2many_tags" invisible="not is_carbon_copy"
|
||||
options="{'no_create': True}"/>
|
||||
<field name="department1_ids" widget="many2many_tags" invisible="not is_carbon_copy"
|
||||
options="{'no_create': True}"/>
|
||||
<field name="employee_ids" widget="many2many_tags" invisible="not is_carbon_copy"
|
||||
options="{'no_create': True}"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Condition">
|
||||
<group>
|
||||
|
||||
@@ -549,11 +549,11 @@
|
||||
<field name="state">code</field>
|
||||
<field name="code">action = model.append_date()</field>
|
||||
</record>
|
||||
<!-- <record id="all_holiday_update_self_users_ids" model="ir.actions.server">-->
|
||||
<!-- <field name="name">一次性</field>-->
|
||||
<!-- <field name="model_id" ref="soong_workflow.model_soong_workflow_record"/>-->
|
||||
<!-- <field name="binding_model_id" ref="soong_workflow.model_soong_workflow_record"/>-->
|
||||
<!-- <field name="state">code</field>-->
|
||||
<!-- <field name="code">action = model.update_self_users_ids()</field>-->
|
||||
<!-- </record>-->
|
||||
<record id="all_holiday_update_self_users_ids" model="ir.actions.server">
|
||||
<field name="name">一次性</field>
|
||||
<field name="model_id" ref="soong_workflow.model_soong_workflow_record"/>
|
||||
<field name="binding_model_id" ref="soong_workflow.model_soong_workflow_record"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">action = model.update_users_ids()</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
<group invisible="1">
|
||||
<field name="approval_concatenation" widget="radio"/>
|
||||
<field name="invalid_link_ids" widget="many2many_tags"/>
|
||||
<field name="filter_personnel_ids" widget="many2many_tags"/>
|
||||
<field name="user_role_ids"/>
|
||||
<field name="comment"/>
|
||||
</group>
|
||||
@@ -46,7 +45,7 @@
|
||||
<group>
|
||||
<field name="is_this_company"/>
|
||||
<field name="circulated_employee_ids" widget="many2many_tags" options="{'no_create': True}"
|
||||
domain="[('company_id', '=', company_id), ('id', 'not in' , filter_personnel_ids)] if is_this_company else [('id', 'not in' , filter_personnel_ids)]"/>
|
||||
domain="[('company_id', '=', company_id)] if is_this_company else []"/>
|
||||
</group>
|
||||
</group>
|
||||
<group invisible="node_btn_name != '结束'">
|
||||
@@ -63,7 +62,7 @@
|
||||
</div>
|
||||
<group>
|
||||
<field name="circulated_employee_ids" widget="many2many_tags" options="{'no_create': True}"
|
||||
domain="[('company_id', '=', company_id),('id', 'not in' , filter_personnel_ids)]"/>
|
||||
domain="[('company_id', '=', company_id)]"/>
|
||||
</group>
|
||||
</group>
|
||||
<group invisible="node_btn_name != '确认'">
|
||||
@@ -77,7 +76,7 @@
|
||||
</div>
|
||||
<group>
|
||||
<field name="circulated_employee_ids" widget="many2many_tags" options="{'no_create': True}"
|
||||
domain="[('company_id', '=', company_id), ('id', 'not in' , filter_personnel_ids)]"/>
|
||||
domain="[('company_id', '=', company_id)]"/>
|
||||
</group>
|
||||
</group>
|
||||
<group invisible="node_btn_name != '分发'">
|
||||
@@ -126,7 +125,7 @@
|
||||
context="{'tree_view_ref':'yuthon_hr_employee.view_employee_phone_tree'}"/>
|
||||
<field name="circulated_employee_ids" invisible="node_btn_name == '退回'"
|
||||
widget="many2many_tags" options="{'no_create': True}"
|
||||
domain="[('company_id', '=', company_id),('id', 'not in' , filter_personnel_ids)]"/>
|
||||
domain="[('company_id', '=', company_id)]"/>
|
||||
</group>
|
||||
</group>
|
||||
<div invisible="node_btn_name != '收回'">
|
||||
@@ -149,7 +148,7 @@
|
||||
</group>
|
||||
<group>
|
||||
<field name="circulated_employee_ids" widget="many2many_tags" options="{'no_create': True}"
|
||||
domain="[('company_id', '=', company_id), ('id', 'not in' , filter_personnel_ids)] if is_this_company else [('id', 'not in' , filter_personnel_ids)]"/>
|
||||
domain="[('company_id', '=', company_id)] if is_this_company else []"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="supervising_employee_ids" widget="many2many_tags" invisible="not is_supervising"
|
||||
|
||||
@@ -286,6 +286,31 @@ class YuthonCatering(models.Model):
|
||||
'lunch': True,
|
||||
})
|
||||
|
||||
def fill_march6_missing_catering(self):
|
||||
"""表3月6日缺失员工的餐饮收集任务数据"""
|
||||
from datetime import date as date_cls
|
||||
target_date = date_cls(2026, 3, 6)
|
||||
conf_records = self.env['yuthon.catering.conf'].search([])
|
||||
if not conf_records:
|
||||
return
|
||||
employee_ids = conf_records.mapped('employee_ids')
|
||||
if not employee_ids:
|
||||
return
|
||||
create_vals = []
|
||||
for employee in employee_ids:
|
||||
existing = self.search([
|
||||
('employee_id', '=', employee.id),
|
||||
('date', '=', target_date),
|
||||
], limit=1)
|
||||
if not existing:
|
||||
create_vals.append({
|
||||
'date': target_date,
|
||||
'employee_id': employee.id,
|
||||
'state': 'unconfirmed',
|
||||
})
|
||||
if create_vals:
|
||||
self.create(create_vals)
|
||||
|
||||
def batch_generate_catering(self):
|
||||
"""批量生成2025-12、2026-01、2026-02的餐饮收集任务数据,排除节假日"""
|
||||
from datetime import date as date_cls
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<button name="task_catering22" string="一次修复" type="object" class="btn btn-primary"/>
|
||||
<button name="batch_confirm_catering" string="批量确认" type="object" class="btn btn-primary"/>
|
||||
<button name="batch_generate_catering" string="批量生成12/1/2月餐饮数据" type="object" class="btn btn-primary"/>
|
||||
<button name="fill_march6_missing_catering" string="表3月6日缺失数据" type="object" class="btn btn-primary"/>
|
||||
</header>
|
||||
<field name="date"/>
|
||||
<field name="breakfast"/>
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
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
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<field name="name">vacation.meal.line.tree</field>
|
||||
<field name="model">vacation.meal.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="误餐情况" create="false" delete="false">
|
||||
<tree string="误餐情况" create="false">
|
||||
<header>
|
||||
<button name="confirm" type="object" string="归档" class="oe_highlight"/>
|
||||
</header>
|
||||
@@ -19,6 +19,7 @@
|
||||
<field name="reasons"/>
|
||||
<field name="meal"/>
|
||||
<field name="is_filing" column_invisible="1"/>
|
||||
<button name="open_record" string="详情" type="object" class="btn-primary"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -222,7 +223,7 @@
|
||||
<notebook>
|
||||
<page string="年假">
|
||||
<field name="leave_ids" context="{'default_employee_id': employee_id}">
|
||||
<tree editable="bottom" delete="false" decoration-muted="is_void">
|
||||
<tree editable="bottom" delete="false" decoration-muted="is_void" decoration-danger="update_type == 'reduce'">
|
||||
<button name="open_record" type="object" string="详情" class="btn-primary"
|
||||
invisible="not workflow_record_id"/>
|
||||
<field name="employee_id" column_invisible="1"/>
|
||||
@@ -249,8 +250,23 @@
|
||||
</page>
|
||||
<page string="加班">
|
||||
<button name="overtime_allocation" string="分配" type="object" class="btn btn-primary"/>
|
||||
<button name="action_overtime_change" string="变更" type="object" class="btn btn-secondary"/>
|
||||
<group col="4">
|
||||
<group>
|
||||
<field name="overtime_workday_total" readonly="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="overtime_legal_day_total" readonly="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="overtime_public_holidays_total" readonly="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="overtime_grand_total" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<field name="overtime_ids" context="{'default_employee_id': employee_id}">
|
||||
<tree editable="bottom" delete="false" decoration-muted="is_void">
|
||||
<tree editable="bottom" delete="false" decoration-muted="is_void" decoration-danger="update_type == 'reduce'">
|
||||
<button name="open_record" type="object" string="详情" class="btn-primary"
|
||||
invisible="not workflow_record_id"/>
|
||||
<field name="vacation_id" column_invisible="1"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- 加班变更向导 Form 视图 -->
|
||||
<record id="view_overtime_change_wizard_form" model="ir.ui.view">
|
||||
<field name="name">overtime.change.wizard.form</field>
|
||||
<field name="model">overtime.change.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="加班变更">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="employee_id" readonly="1"/>
|
||||
<field name="update_type"/>
|
||||
<field name="update_date"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="workday"/>
|
||||
<field name="legal_day"/>
|
||||
<field name="public_holidays"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group>
|
||||
<field name="valid_date"/>
|
||||
<field name="adjust_date"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="overtime_methods"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="text"/>
|
||||
</group>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button name="action_confirm" string="确认" type="object" class="btn-primary"/>
|
||||
<button string="取消" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- 加班变更向导 Action -->
|
||||
<record id="action_overtime_change_wizard" model="ir.actions.act_window">
|
||||
<field name="name">加班变更</field>
|
||||
<field name="res_model">overtime.change.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user