diff --git a/yuthon_property_approval/data/property_bills_payable_cron.xml b/yuthon_property_approval/data/property_bills_payable_cron.xml
new file mode 100644
index 000000000..fb72cc248
--- /dev/null
+++ b/yuthon_property_approval/data/property_bills_payable_cron.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+ 定时任务:每天10点应付账单待付提醒
+
+
+ code
+
+ model.cron_send_payable_reminder()
+
+
+
+
+ 1
+ days
+
+
+
+ -1
+
+
+
+
diff --git a/yuthon_property_approval/data/receivable_unpaid_cron.xml b/yuthon_property_approval/data/receivable_unpaid_cron.xml
new file mode 100644
index 000000000..601641c86
--- /dev/null
+++ b/yuthon_property_approval/data/receivable_unpaid_cron.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+ 定时任务:每月25号应收账单未收提醒
+
+
+ code
+
+ model.cron_send_unpaid_reminder()
+
+
+
+
+ 1
+ months
+
+
+
+ -1
+
+
+
+
diff --git a/yuthon_property_approval/models/property_accounts_receivable_summary.py b/yuthon_property_approval/models/property_accounts_receivable_summary.py
index 09c9094c8..8defda491 100644
--- a/yuthon_property_approval/models/property_accounts_receivable_summary.py
+++ b/yuthon_property_approval/models/property_accounts_receivable_summary.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from odoo import fields, models, tools
+from odoo import api, fields, models, tools
class PropertyAccountsReceivableSummary(models.Model):
@@ -85,3 +85,81 @@ class PropertyAccountsReceivableSummary(models.Model):
},
'target': 'current',
}
+
+ # ======================================================================
+ # 定时提醒:每月25号给「存在未收金额」的片区管理员发企业微信消息
+ # 对应定时任务:data/receivable_unpaid_cron.xml
+ # ======================================================================
+
+ @api.model
+ def _unpaid_reminder_content(self, admin):
+ """拼装单个片区管理员的未收提醒内容,返回 title / description / url 三件套
+
+ :param admin: 片区管理员(res.users)
+ """
+ # 提醒正文:只说该片区应收账单有未处理的合同、请及时处理,具体明细由管理员点「详细信息」进去看
+ description = '%s,您负责的片区应收账单有未处理的合同,请及时处理。' % (admin.name or '')
+ # 消息卡片跳转地址:走后台「应收账单 - 汇总」菜单的服务端动作,
+ # 管理员点开后 searchpanel 会自动定位到本人负责的片区
+ menu = self.sudo().env.ref('yuthon_work_brand.menu_receivable_summary')
+ url = 'https://oa.thtzjt.com/web#cids=2&action=%s&model=property.accounts.receivable.summary&view_type=list&menu_id=%s' % (
+ menu.action.id, menu.id)
+ return {
+ 'title': '应收账单未收提醒', # 消息卡片标题
+ 'description': description, # 消息卡片正文
+ 'url': url, # 点击「详细信息」跳转的地址
+ }
+
+ @api.model
+ def _log_unpaid_reminder(self, message):
+ """写一条运行日志到 ir.logging,便于在后台(设置-技术-日志)排查定时任务执行情况"""
+ self.env['ir.logging'].sudo().create({
+ 'name': '应收账单未收提醒', # 日志来源
+ 'type': 'server', # 服务端日志
+ 'message': message, # 日志正文
+ 'path': __name__, # 代码文件
+ 'func': 'cron_send_unpaid_reminder', # 所属方法
+ 'line': 0, # 行号占位
+ })
+
+ @api.model
+ def cron_send_unpaid_reminder(self):
+ """定时任务(每月25号):把存在未收金额的记录按片区管理员汇总,逐个发企业微信消息提醒"""
+ # 汇总视图本身只统计「未收/收部分 且 未对账」的应收账单,因此查出来的每一行都代表存在未收值
+ summaries = self.sudo().search([])
+ # 一单未收都没有时直接记日志返回,不发消息
+ if not summaries:
+ self._log_unpaid_reminder('当前没有任何应收账单存在未收金额,本次不发送提醒')
+ return True
+ # 本次成功发送的条数
+ sent_count = 0
+ # 按片区管理员分组:一位管理员收到一条提醒;admin_id 为空的记录没有可通知的人,自动排除
+ for admin in summaries.mapped('admin_id'):
+ # 该管理员名下存在未收金额的合同行
+ admin_summaries = summaries.filtered(lambda summary: summary.admin_id == admin)
+ # 没有绑定企业微信 UserID 就无法推送,跳过并记日志
+ if not admin.oauth_uid:
+ self._log_unpaid_reminder('%s 未绑定企业微信,跳过提醒(涉及 %s 份合同)' % (admin.name, len(admin_summaries)))
+ continue
+ # 拼装提醒内容(标题 / 正文 / 跳转地址)
+ content = self._unpaid_reminder_content(admin)
+ try:
+ # 调用企业微信接口推送文本卡片消息
+ self.sudo().env['wecom.apps'].sync_send_message_textcard(
+ category='notice', # 走「通知公告消息通知」应用
+ user_id=admin.oauth_uid, # 收件人:管理员的企微成员 UserID
+ title=content['title'], # 卡片标题
+ description=content['description'], # 卡片正文
+ url=content['url'], # 点击跳转地址
+ btntxt='详细信息', # 按钮文字
+ )
+ # 发送成功计数
+ sent_count += 1
+ # 记录成功日志
+ self._log_unpaid_reminder('已发送未收提醒给 %s(%s 份合同)' % (admin.name, len(admin_summaries)))
+ except Exception as error:
+ # 单个管理员发送失败不影响其他管理员,记日志后继续
+ self._log_unpaid_reminder('发送未收提醒给 %s 失败:%s' % (admin.name, error))
+ # 记录本次整体发送结果
+ self._log_unpaid_reminder('应收账单未收提醒执行完成,共发送 %s 条' % sent_count)
+ return True
diff --git a/yuthon_property_approval/models/property_bills_list.py b/yuthon_property_approval/models/property_bills_list.py
index 46cdefd7c..250c33216 100644
--- a/yuthon_property_approval/models/property_bills_list.py
+++ b/yuthon_property_approval/models/property_bills_list.py
@@ -1,5 +1,11 @@
from odoo import models, fields, api
+# 待付提醒收件人的判定条件:辅助角色「出纳 (财务部)」+ 该角色所属公司「天河智城」
+# (soong.user.role 的显示名 = 角色名称 + (所属部门),所以「出纳 (财务部)」= 角色名出纳 + 部门财务部)
+PAYABLE_REMINDER_COMPANY = '天河智城' # 角色所属公司
+PAYABLE_REMINDER_ROLE = '出纳' # 角色名称
+PAYABLE_REMINDER_DEPARTMENT = '财务部' # 角色所属部门
+
class PropertyBillsList(models.Model):
_name = 'property.bills.list'
@@ -65,6 +71,82 @@ class PropertyBillsList(models.Model):
}
}
+ # ======================================================================
+ # 定时提醒:每天早上10点,有待付账单就提醒「出纳(财务部)」及时处理
+ # 对应定时任务:data/property_bills_payable_cron.xml
+ # ======================================================================
+
+ @api.model
+ def _payable_reminder_employees(self):
+ """取出待付提醒的收件人:辅助角色为「出纳 (财务部)」、且该角色属于天河智城 的员工"""
+ # 先定位角色:公司=天河智城 + 角色名=出纳 + 所属部门=财务部(三者合起来正是显示名「出纳 (财务部)」)
+ roles = self.env['soong.user.role'].sudo().search([
+ ('company_id.name', '=', PAYABLE_REMINDER_COMPANY), # 角色所属公司
+ ('name', '=', PAYABLE_REMINDER_ROLE), # 角色名称
+ ('department_id.name', '=', PAYABLE_REMINDER_DEPARTMENT), # 角色所属部门
+ ])
+ # 辅助角色字段(hr.employee.user_role_ids)包含上述角色的员工
+ return self.env['hr.employee'].sudo().search([('user_role_ids', 'in', roles.ids)])
+
+ @api.model
+ def _log_payable_reminder(self, message):
+ """写一条运行日志到 ir.logging,便于在后台(设置-技术-日志)排查定时任务执行情况"""
+ self.env['ir.logging'].sudo().create({
+ 'name': '应付账单待付提醒', # 日志来源
+ 'type': 'server', # 服务端日志
+ 'message': message, # 日志正文
+ 'path': __name__, # 代码文件
+ 'func': 'cron_send_payable_reminder', # 所属方法
+ 'line': 0, # 行号占位
+ })
+
+ @api.model
+ def cron_send_payable_reminder(self):
+ """定时任务(每天早上10点):存在待付账单时,给「出纳(财务部)」发企业微信消息提醒"""
+ # 取所有待付(pay_state=unpaid)的应付账单
+ bills = self.env['property.bills.list'].sudo().search([('pay_state', '=', 'unpaid')])
+ # 没有待付账单就不发提醒,记日志返回
+ if not bills:
+ self._log_payable_reminder('当前没有待付的应付账单,本次不发送提醒')
+ return True
+ # 收件人:辅助角色=出纳(财务部) 且 角色所属公司=天河智城 的员工
+ employees = self._payable_reminder_employees()
+ # 找不到收件人时记日志返回
+ if not employees:
+ self._log_payable_reminder('未找到「辅助角色=出纳 (财务部)、公司=天河智城」的员工,本次不发送提醒')
+ return True
+ # 消息卡片跳转地址:后台「应付账单」菜单
+ menu = self.sudo().env.ref('yuthon_work_brand.property_bills_payable_menu')
+ url = 'https://oa.thtzjt.com/web#cids=2&action=%s&model=property.bills.list&view_type=list&menu_id=%s' % (
+ menu.action.id, menu.id)
+ # 本次成功发送的条数
+ sent_count = 0
+ for employee in employees:
+ # 没有绑定企业微信 UserID 就推送不了,跳过并记日志
+ if not employee.wecom_userid:
+ self._log_payable_reminder('%s 未绑定企业微信,跳过提醒' % employee.name)
+ continue
+ try:
+ # 调用企业微信接口推送文本卡片消息
+ self.sudo().env['wecom.apps'].sync_send_message_textcard(
+ category='notice', # 走「通知公告消息通知」应用
+ user_id=employee.wecom_userid, # 收件人:员工的企微成员 UserID
+ title='应付账单待付提醒', # 卡片标题
+ description='%s,您有未处理的待付,请及时处理。' % (employee.name or ''), # 卡片正文
+ url=url, # 点击跳转地址
+ btntxt='详细信息', # 按钮文字
+ )
+ # 发送成功计数
+ sent_count += 1
+ # 记录成功日志
+ self._log_payable_reminder('已发送待付提醒给 %s' % employee.name)
+ except Exception as error:
+ # 单个收件人发送失败不影响其他收件人,记日志后继续
+ self._log_payable_reminder('发送待付提醒给 %s 失败:%s' % (employee.name, error))
+ # 记录本次整体发送结果
+ self._log_payable_reminder('应付账单待付提醒执行完成,共发送 %s 条' % sent_count)
+ return True
+