添加功能
This commit is contained in:
@@ -40,6 +40,7 @@
|
||||
'wizard/hr_job_add_wizard_views.xml',
|
||||
'wizard/medical_manage_wizard_viwes.xml',
|
||||
'wizard/reset_password_wizard_viwes.xml',
|
||||
'views/yuthon_hr_task_views.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
|
||||
@@ -68,5 +68,31 @@
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
</record>
|
||||
<record forcecreate="True" id="cron_retire_reminder" model="ir.cron">
|
||||
<field name="name">定时任务:退休提醒</field>
|
||||
<field name="model_id" ref="model_hr_employee"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.task_retire_reminder()</field>
|
||||
<field eval="True" name="active"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="nextcall" eval="(DateTime.now() + relativedelta(hour=9, minute=0, second=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
</record>
|
||||
<record forcecreate="True" id="cron_separation_reminder" model="ir.cron">
|
||||
<field name="name">定时任务:离职提醒</field>
|
||||
<field name="model_id" ref="model_hr_employee"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.task_separation_reminder()</field>
|
||||
<field eval="True" name="active"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="nextcall" eval="(DateTime.now() + relativedelta(hour=9, minute=0, second=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -14,4 +14,5 @@ from . import soong_user_role
|
||||
from . import yuthon_medical_manage
|
||||
from . import yuthon_medical_rule
|
||||
from . import res_users
|
||||
from . import yuthon_hr_task
|
||||
|
||||
|
||||
@@ -1230,3 +1230,107 @@ class YuthonHrEmployee(models.Model):
|
||||
url=url, btntxt="详细信息")
|
||||
|
||||
employee.write({'is_create': True})
|
||||
|
||||
def _get_notify_users(self, task_config):
|
||||
"""从 task_config 获取需要通知的用户列表.
|
||||
"""
|
||||
user_ids = set(task_config.user_ids.ids)
|
||||
role_ids = set(task_config.users_role_group_ids.mapped('user_role_ids').ids)
|
||||
if task_config.users_role_id:
|
||||
role_ids.add(task_config.users_role_id.id)
|
||||
if role_ids:
|
||||
all_target_roles = self.env['soong.user.role'].browse(list(role_ids))
|
||||
role_employees = self.env['hr.employee'].search([
|
||||
'|',
|
||||
('user_role_id', 'in', all_target_roles.ids),
|
||||
('user_role_ids', 'in', all_target_roles.ids),
|
||||
('user_id', '!=', False),
|
||||
])
|
||||
for emp in role_employees:
|
||||
user_ids.add(emp.user_id.id)
|
||||
return self.env['res.users'].browse(list(user_ids))
|
||||
|
||||
def task_retire_reminder(self):
|
||||
today = fields.Date.today()
|
||||
target_date = today + timedelta(days=30)
|
||||
employees = self.search([
|
||||
('retire_date', '=', target_date),
|
||||
('user_state', 'in', ['in', 'borrow', 'expatriate', 'adjunct']),
|
||||
])
|
||||
if not employees:
|
||||
return
|
||||
task_config = self.env['yuthon.hr.task'].search([('cron_id.name', '=', '定时任务:退休提醒')], limit=1)
|
||||
if not task_config:
|
||||
return
|
||||
notify_users = self._get_notify_users(task_config)
|
||||
if not notify_users:
|
||||
return
|
||||
count = len(employees)
|
||||
for user in notify_users:
|
||||
hr_employee = user.employee_ids.filtered(lambda e: e.wecom_userid)[:1]
|
||||
if not hr_employee:
|
||||
continue
|
||||
result_data = self.sudo().env['wecom.apps'].sync_send_message_textcard(
|
||||
category='oa',
|
||||
user_id=hr_employee.wecom_userid,
|
||||
title='退休提醒',
|
||||
description=f"提醒:有{count}个人即将退休,请移至工作台查看。",
|
||||
url='https://oa.thtzjt.com/web?debug=1#action=3584&cids=1&menu_id=9668',
|
||||
btntxt='详细信息',
|
||||
)
|
||||
confirm = self.env['yuthon.task.confirm'].create({
|
||||
'task_id': task_config.id,
|
||||
'user_id': user.id,
|
||||
'state': 'read',
|
||||
})
|
||||
if result_data and len(result_data) > 1:
|
||||
msg_id = result_data[1].get('msgid')
|
||||
if msg_id:
|
||||
confirm.sudo().write({'msg_id': msg_id})
|
||||
|
||||
def task_separation_reminder(self):
|
||||
today = fields.Date.today()
|
||||
target_date = today + timedelta(days=30)
|
||||
workflow_records = self.env['soong.workflow.record'].search([
|
||||
('workflow_id.name', '=', '离职申请'),
|
||||
('approve_state', '=', 'end'),
|
||||
('reclaim_state', '=', False),
|
||||
])
|
||||
leaving_ids = []
|
||||
for record in workflow_records:
|
||||
try:
|
||||
leaving = self.env[record.res_model].browse(record.res_id)
|
||||
if leaving and leaving.separation_date == target_date and not leaving.is_execution:
|
||||
leaving_ids.append(leaving)
|
||||
except Exception:
|
||||
continue
|
||||
if not leaving_ids:
|
||||
return
|
||||
task_config = self.env['yuthon.hr.task'].search([('cron_id.name', '=', '定时任务:离职提醒')], limit=1)
|
||||
if not task_config:
|
||||
return
|
||||
notify_users = self._get_notify_users(task_config)
|
||||
if not notify_users:
|
||||
return
|
||||
count = len(leaving_ids)
|
||||
for user in notify_users:
|
||||
hr_employee = user.employee_ids.filtered(lambda e: e.wecom_userid)[:1]
|
||||
if not hr_employee:
|
||||
continue
|
||||
result_data = self.sudo().env['wecom.apps'].sync_send_message_textcard(
|
||||
category='oa',
|
||||
user_id=hr_employee.wecom_userid,
|
||||
title='离职提醒',
|
||||
description=f"提醒:有{count}个人即将离职,请移至工作台查看。",
|
||||
url='https://oa.thtzjt.com/web?debug=1#action=3584&cids=1&menu_id=9668',
|
||||
btntxt='详细信息',
|
||||
)
|
||||
confirm = self.env['yuthon.task.confirm'].create({
|
||||
'task_id': task_config.id,
|
||||
'user_id': user.id,
|
||||
'state': 'read',
|
||||
})
|
||||
if result_data and len(result_data) > 1:
|
||||
msg_id = result_data[1].get('msgid')
|
||||
if msg_id:
|
||||
confirm.sudo().write({'msg_id': msg_id})
|
||||
|
||||
@@ -25,3 +25,5 @@ access_child_education_line,child_education_line,model_child_education_line,base
|
||||
access_elderly_support_line,elderly_support_line,model_elderly_support_line,base.group_user,1,1,1,1
|
||||
access_reset_password_wizard, reset_password_wizard,model_reset_password_wizard,base.group_user,1,1,1,1
|
||||
access_medical_manage_wizard, medical_manage_wizard,model_medical_manage_wizard,base.group_user,1,1,1,1
|
||||
access_yuthon_hr_task,yuthon_hr_task,model_yuthon_hr_task,base.group_user,1,1,1,1
|
||||
access_yuthon_task_confirm,yuthon_task_confirm,model_yuthon_task_confirm,base.group_user,1,1,1,1
|
||||
|
@@ -255,6 +255,7 @@ class ReceivableModifyLine(models.Model):
|
||||
continue
|
||||
|
||||
all_lines = self.search([('receivable_id', '=', rec.receivable_id.id)])
|
||||
all_lines |= rec # 确保当前记录包含在内(处理新建未保存的记录)
|
||||
sorted_lines = all_lines.sorted(
|
||||
key=lambda l: (
|
||||
0 if l.reconciliation_date else 1,
|
||||
@@ -270,6 +271,9 @@ class ReceivableModifyLine(models.Model):
|
||||
rec.delivered1 = rec.receivable_id.original_amount if remaining <= 0 else remaining
|
||||
break
|
||||
cumsum += line.amount
|
||||
else:
|
||||
# 兜底:确保 delivered1 总是被赋值
|
||||
rec.delivered1 = 0
|
||||
|
||||
remake = fields.Text(related='receivable_id.remark', string='备注', store=False)
|
||||
# lessee_id = fields.Many2one(
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
'views/property_delegated_agent_views.xml',
|
||||
'views/property_transaction_confirmed.xml',
|
||||
'views/property_meeting_approval_views.xml',
|
||||
'views/yuthon_property_task_views.xml',
|
||||
'views/receivable_modify_line_views.xml',
|
||||
'wizard/property_add_wizard_views.xml',
|
||||
'wizard/property_contract_wizard.xml',
|
||||
|
||||
@@ -40,4 +40,30 @@
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
</record>
|
||||
<record forcecreate="True" id="cron_property_vacant_reminder" model="ir.cron">
|
||||
<field name="name">定时任务:物业空置提醒</field>
|
||||
<field name="model_id" ref="model_property_lease_contract"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.task_property_vacant_reminder()</field>
|
||||
<field eval="True" name="active"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="nextcall" eval="(datetime.now() + relativedelta(hour=9, minute=0, second=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
</record>
|
||||
<record forcecreate="True" id="cron_property_contract_reminder" model="ir.cron">
|
||||
<field name="name">定时任务:物业合同到期提醒</field>
|
||||
<field name="model_id" ref="model_property_lease_contract"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.task_property_contract_reminder()</field>
|
||||
<field eval="True" name="active"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="nextcall" eval="(datetime.now() + relativedelta(hour=9, minute=0, second=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -64,3 +64,4 @@ from . import property_bills_list
|
||||
from . import property_fee_spending
|
||||
from . import property_fee_form
|
||||
from . import receivable_modify_line
|
||||
from . import yuthon_property_task
|
||||
|
||||
@@ -2547,6 +2547,124 @@ class PropertyLeaseContract(models.Model):
|
||||
report_ref = self.env.ref('yuthon_property_approval.action_property_lease_contract_print_report')
|
||||
return report_ref.report_action(self)
|
||||
|
||||
def _get_property_notify_users(self, task_config):
|
||||
"""从 task_config 获取需要通知的用户列表.
|
||||
- user_ids: 直接指定的人
|
||||
- users_role_id + users_role_group_ids: 角色组 -> 角色 -> 花名册中 user_role_id(主角色)或 user_role_ids(辅助角色)匹配的员工
|
||||
"""
|
||||
user_ids = set(task_config.user_ids.ids)
|
||||
role_ids = set(task_config.users_role_group_ids.mapped('user_role_ids').ids)
|
||||
if task_config.users_role_id:
|
||||
role_ids.add(task_config.users_role_id.id)
|
||||
if role_ids:
|
||||
all_target_roles = self.env['soong.user.role'].browse(list(role_ids))
|
||||
role_employees = self.env['hr.employee'].search([
|
||||
'|',
|
||||
('user_role_id', 'in', all_target_roles.ids),
|
||||
('user_role_ids', 'in', all_target_roles.ids),
|
||||
('user_id', '!=', False),
|
||||
])
|
||||
for emp in role_employees:
|
||||
user_ids.add(emp.user_id.id)
|
||||
return self.env['res.users'].browse(list(user_ids))
|
||||
|
||||
def task_property_vacant_reminder(self):
|
||||
"""物业空置提醒:以物业为单位,合同实际结束日期距今已3个月或以上,或没有合同."""
|
||||
today = fields.Date.today()
|
||||
three_months_ago = today - timedelta(days=90)
|
||||
properties = self.env['yuthon.property'].search([])
|
||||
vacant_properties = self.env['yuthon.property']
|
||||
for prop in properties:
|
||||
contract_lines = self.env['property.lease.contract.line'].search([
|
||||
('address_id', '=', prop.id),
|
||||
])
|
||||
if not contract_lines:
|
||||
vacant_properties |= prop
|
||||
continue
|
||||
contracts = contract_lines.mapped('lease_id')
|
||||
active_contracts = contracts.filtered(
|
||||
lambda c: c.contact_state not in ['complete', 'terminate', 'early_termination', 'settle']
|
||||
)
|
||||
if active_contracts:
|
||||
continue
|
||||
ended_contracts = contracts.filtered(
|
||||
lambda c: c.contact_state in ['complete', 'terminate', 'early_termination', 'settle'] and c.actual_end_time
|
||||
)
|
||||
if not ended_contracts:
|
||||
continue
|
||||
last_end_time = max(ended_contracts.mapped('actual_end_time'))
|
||||
if last_end_time <= three_months_ago:
|
||||
vacant_properties |= prop
|
||||
if not vacant_properties:
|
||||
return
|
||||
task_config = self.env['yuthon.property.task'].search([('cron_id.name', '=', '定时任务:物业空置提醒')], limit=1)
|
||||
if not task_config:
|
||||
return
|
||||
notify_users = self._get_property_notify_users(task_config)
|
||||
if not notify_users:
|
||||
return
|
||||
count = len(vacant_properties)
|
||||
for user in notify_users:
|
||||
hr_employee = user.employee_ids.filtered(lambda e: e.wecom_userid)[:1]
|
||||
if not hr_employee:
|
||||
continue
|
||||
result_data = self.sudo().env['wecom.apps'].sync_send_message_textcard(
|
||||
category='oa',
|
||||
user_id=hr_employee.wecom_userid,
|
||||
title='物业空置提醒',
|
||||
description=f"提醒:有{count}个物业已空置,请移至工作台查看。",
|
||||
url='https://oa.thtzjt.com/web?debug=1#action=3584&cids=1&menu_id=9668',
|
||||
btntxt='详细信息',
|
||||
)
|
||||
confirm = self.env['yuthon.property.task.confirm'].create({
|
||||
'task_id': task_config.id,
|
||||
'user_id': user.id,
|
||||
'state': 'read',
|
||||
})
|
||||
if result_data and len(result_data) > 1:
|
||||
msg_id = result_data[1].get('msgid')
|
||||
if msg_id:
|
||||
confirm.sudo().write({'msg_id': msg_id})
|
||||
|
||||
def task_property_contract_reminder(self):
|
||||
"""物业合同到期提醒:距实际结束日期还有3个月."""
|
||||
today = fields.Date.today()
|
||||
target_date = today + timedelta(days=90)
|
||||
contracts = self.search([
|
||||
('actual_end_time', '=', target_date),
|
||||
('contact_state', 'in', ['to', 'ing']),
|
||||
])
|
||||
if not contracts:
|
||||
return
|
||||
task_config = self.env['yuthon.property.task'].search([('cron_id.name', '=', '定时任务:物业合同到期提醒')], limit=1)
|
||||
if not task_config:
|
||||
return
|
||||
notify_users = self._get_property_notify_users(task_config)
|
||||
if not notify_users:
|
||||
return
|
||||
count = len(contracts)
|
||||
for user in notify_users:
|
||||
hr_employee = user.employee_ids.filtered(lambda e: e.wecom_userid)[:1]
|
||||
if not hr_employee:
|
||||
continue
|
||||
result_data = self.sudo().env['wecom.apps'].sync_send_message_textcard(
|
||||
category='oa',
|
||||
user_id=hr_employee.wecom_userid,
|
||||
title='物业合同到期提醒',
|
||||
description=f"提醒:有{count}个物业即将到期,请移至工作台查看。",
|
||||
url='https://oa.thtzjt.com/web?debug=1#action=3584&cids=1&menu_id=9668',
|
||||
btntxt='详细信息',
|
||||
)
|
||||
confirm = self.env['yuthon.property.task.confirm'].create({
|
||||
'task_id': task_config.id,
|
||||
'user_id': user.id,
|
||||
'state': 'read',
|
||||
})
|
||||
if result_data and len(result_data) > 1:
|
||||
msg_id = result_data[1].get('msgid')
|
||||
if msg_id:
|
||||
confirm.sudo().write({'msg_id': msg_id})
|
||||
|
||||
class PropertyLeaseContractLine(models.Model):
|
||||
_name = 'property.lease.contract.line'
|
||||
_description = '物业列表明细行'
|
||||
|
||||
@@ -144,3 +144,5 @@ access_refund_wizard_line,refund_wizard_line,model_refund_wizard_line,base.group
|
||||
access_property_business_type,property_business_type,model_property_business_type,base.group_user,1,1,1,1
|
||||
access_property_payment_type,property_payment_type,model_property_payment_type,base.group_user,1,1,1,1
|
||||
access_property_contract_rental_wizard,property_contract_rental_wizard,model_property_contract_rental_wizard,base.group_user,1,1,1,1
|
||||
access_yuthon_property_task,yuthon_property_task,model_yuthon_property_task,base.group_user,1,1,1,1
|
||||
access_yuthon_property_task_confirm,yuthon_property_task_confirm,model_yuthon_property_task_confirm,base.group_user,1,1,1,1
|
||||
|
Reference in New Issue
Block a user