diff --git a/yuthon_hr_employee/__manifest__.py b/yuthon_hr_employee/__manifest__.py index fd3ff90fe..2536ebbb0 100644 --- a/yuthon_hr_employee/__manifest__.py +++ b/yuthon_hr_employee/__manifest__.py @@ -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': [ diff --git a/yuthon_hr_employee/data/abnormal_time_data.xml b/yuthon_hr_employee/data/abnormal_time_data.xml index 13a12a4ad..c6945016e 100644 --- a/yuthon_hr_employee/data/abnormal_time_data.xml +++ b/yuthon_hr_employee/data/abnormal_time_data.xml @@ -68,5 +68,31 @@ -1 + + 定时任务:退休提醒 + + code + model.task_retire_reminder() + + + 1 + days + + -1 + + + + 定时任务:离职提醒 + + code + model.task_separation_reminder() + + + 1 + days + + -1 + + diff --git a/yuthon_hr_employee/models/__init__.py b/yuthon_hr_employee/models/__init__.py index 7fb0850f7..400f869be 100644 --- a/yuthon_hr_employee/models/__init__.py +++ b/yuthon_hr_employee/models/__init__.py @@ -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 diff --git a/yuthon_hr_employee/models/yuthon_hr_employee.py b/yuthon_hr_employee/models/yuthon_hr_employee.py index de14e4843..39988e3ac 100644 --- a/yuthon_hr_employee/models/yuthon_hr_employee.py +++ b/yuthon_hr_employee/models/yuthon_hr_employee.py @@ -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}) diff --git a/yuthon_hr_employee/security/ir.model.access.csv b/yuthon_hr_employee/security/ir.model.access.csv index 11eb7fb05..57c27c481 100644 --- a/yuthon_hr_employee/security/ir.model.access.csv +++ b/yuthon_hr_employee/security/ir.model.access.csv @@ -24,4 +24,6 @@ access_child_care_line,child_care_line,model_child_care_line,base.group_user,1,1 access_child_education_line,child_education_line,model_child_education_line,base.group_user,1,1,1,1 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 \ No newline at end of file +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 \ No newline at end of file diff --git a/yuthon_property/models/property_bank_statements.py b/yuthon_property/models/property_bank_statements.py index c1238f551..489ca525b 100644 --- a/yuthon_property/models/property_bank_statements.py +++ b/yuthon_property/models/property_bank_statements.py @@ -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( diff --git a/yuthon_property_approval/__manifest__.py b/yuthon_property_approval/__manifest__.py index 4bea54a1c..d2e448b48 100644 --- a/yuthon_property_approval/__manifest__.py +++ b/yuthon_property_approval/__manifest__.py @@ -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', diff --git a/yuthon_property_approval/data/property_contract_time.xml b/yuthon_property_approval/data/property_contract_time.xml index 2901db9e3..6869c9a67 100644 --- a/yuthon_property_approval/data/property_contract_time.xml +++ b/yuthon_property_approval/data/property_contract_time.xml @@ -40,4 +40,30 @@ -1 + + 定时任务:物业空置提醒 + + code + model.task_property_vacant_reminder() + + + 1 + days + + -1 + + + + 定时任务:物业合同到期提醒 + + code + model.task_property_contract_reminder() + + + 1 + days + + -1 + + \ No newline at end of file diff --git a/yuthon_property_approval/models/__init__.py b/yuthon_property_approval/models/__init__.py index 1b98f6ae4..51944a381 100644 --- a/yuthon_property_approval/models/__init__.py +++ b/yuthon_property_approval/models/__init__.py @@ -63,4 +63,5 @@ from . import property_payment_notice from . import property_bills_list from . import property_fee_spending from . import property_fee_form -from . import receivable_modify_line \ No newline at end of file +from . import receivable_modify_line +from . import yuthon_property_task diff --git a/yuthon_property_approval/models/property_lease_contract.py b/yuthon_property_approval/models/property_lease_contract.py index e38895f93..983bb6000 100644 --- a/yuthon_property_approval/models/property_lease_contract.py +++ b/yuthon_property_approval/models/property_lease_contract.py @@ -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 = '物业列表明细行' diff --git a/yuthon_property_approval/security/ir.model.access.csv b/yuthon_property_approval/security/ir.model.access.csv index 44684b12c..2b242369f 100644 --- a/yuthon_property_approval/security/ir.model.access.csv +++ b/yuthon_property_approval/security/ir.model.access.csv @@ -143,4 +143,6 @@ access_refund_wizard,refund_wizard,model_refund_wizard,base.group_user,1,1,1,1 access_refund_wizard_line,refund_wizard_line,model_refund_wizard_line,base.group_user,1,1,1,1 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 \ No newline at end of file +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 \ No newline at end of file