Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
627aefc46f | ||
|
|
34d99e7f1e | ||
|
|
c60767c8ce | ||
|
|
db2cff0678 | ||
|
|
4f008112e4 | ||
|
|
183c1f1cb9 | ||
|
|
f1725e021d | ||
|
|
f619877334 | ||
|
|
7c42d293c9 | ||
|
|
cd0419a242 | ||
|
|
32194b376f | ||
|
|
5ebfbfe9c9 | ||
|
|
2fe7d68122 | ||
|
|
248772f990 | ||
|
|
b1dcc2a78b | ||
|
|
44ffc024aa | ||
|
|
1b8e7a5988 | ||
|
|
3972c8c2b3 | ||
|
|
d16362330e | ||
|
|
c85b2e1635 | ||
|
|
90b180c5a4 | ||
|
|
e820d703c6 | ||
|
|
e20328afea | ||
|
|
3b4b61a56f | ||
|
|
d9afe0f7ef | ||
|
|
0b3d1dfa70 | ||
|
|
66f7c1d615 | ||
|
|
45d3c1cbd0 | ||
|
|
c8ebc93d6e |
@@ -33,9 +33,9 @@
|
||||
流水号:<span t-esc="doc.code or ''"/>
|
||||
</div>
|
||||
|
||||
<!-- 标题 -->
|
||||
<!-- 标题:取字段 name(会议标题)的值 -->
|
||||
<div class="title">
|
||||
党总支(扩大)会议议程安排表
|
||||
<span t-esc="doc.name or ''"/>
|
||||
</div>
|
||||
|
||||
<!-- 会议基本信息 -->
|
||||
@@ -46,6 +46,11 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 会议形式:位于会议时间之后 -->
|
||||
<div class="info-line">
|
||||
会议形式:<span t-esc="dict(doc._fields['meeting_type'].selection).get(doc.meeting_type or '') or ''"/>
|
||||
</div>
|
||||
|
||||
<div class="info-line">
|
||||
会议地点:<span t-esc="doc.room_id.name or ''"/>
|
||||
</div>
|
||||
@@ -55,7 +60,7 @@
|
||||
</div>
|
||||
|
||||
<div class="info-line">
|
||||
出席人员:
|
||||
参会人员:
|
||||
<t t-foreach="doc.room_users_ids" t-as="user">
|
||||
<span t-esc="user.name"/>
|
||||
<t t-if="not user_last">、</t>
|
||||
@@ -65,6 +70,15 @@
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- 其他人员:位于参会人员之后 -->
|
||||
<div class="info-line">
|
||||
其他人员:
|
||||
<t t-foreach="doc.other_company_user_ids" t-as="other_user">
|
||||
<span t-esc="other_user.name"/>
|
||||
<t t-if="not other_user_last">、</t>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<div class="info-line">
|
||||
记录员:
|
||||
<t t-foreach="doc.recorder_ids" t-as="recorder">
|
||||
@@ -100,9 +114,11 @@
|
||||
<span t-esc="line.employee_id.name or ''"/>
|
||||
</td>
|
||||
<td class="content-cell">
|
||||
<t t-foreach="line.attendees_ids" t-as="attendee">
|
||||
<span t-esc="attendee.name"/>
|
||||
<t t-if="not attendee_last">、</t>
|
||||
<!-- 列席人员 = 参会人员 + 其他人员 -->
|
||||
<t t-set="attendee_names" t-value="[u.name for u in doc.room_users_ids] + [o.name for o in doc.other_company_user_ids]"/>
|
||||
<t t-foreach="attendee_names" t-as="attendee_name">
|
||||
<span t-esc="attendee_name"/>
|
||||
<t t-if="not attendee_name_last">、</t>
|
||||
</t>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- 定时任务:每天早上10点,存在待付账单时给「出纳(财务部)」发企业微信消息提醒
|
||||
(发送逻辑见 models/property_bills_list.py: cron_send_payable_reminder) -->
|
||||
<record id="cron_property_bills_payable_reminder" model="ir.cron">
|
||||
<field name="name">定时任务:每天10点应付账单待付提醒</field>
|
||||
<!-- 挂到应付账单模型上 -->
|
||||
<field name="model_id" ref="model_property_bills_list"/>
|
||||
<field name="state">code</field>
|
||||
<!-- 执行入口:应付账单模型上的 cron_send_payable_reminder 方法 -->
|
||||
<field name="code">model.cron_send_payable_reminder()</field>
|
||||
<field eval="True" name="active"/>
|
||||
<!-- 以超级管理员身份执行,避免权限不足 -->
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<!-- 每 1 天执行一次 -->
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<!-- 下次执行时间:每天 10:00(北京时间)= 02:00 UTC;
|
||||
若当天已过10点则顺延到明天,避免刚装好就立刻触发一次 -->
|
||||
<field name="nextcall" eval="((DateTime.now() + timedelta(hours=8)) if (DateTime.now() + timedelta(hours=8)).hour < 10 else (DateTime.now() + timedelta(hours=8) + relativedelta(days=1))).strftime('%Y-%m-%d 02:00:00')"/>
|
||||
<!-- 不限执行次数,永久循环 -->
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- 定时任务:每月25号给「存在未收金额」的记录对应的片区管理员发企业微信消息提醒
|
||||
(发送逻辑见 models/property_accounts_receivable_summary.py: cron_send_unpaid_reminder) -->
|
||||
<record id="cron_receivable_unpaid_reminder" model="ir.cron">
|
||||
<field name="name">定时任务:每月25号应收账单未收提醒</field>
|
||||
<!-- 挂到应收未收汇总模型上 -->
|
||||
<field name="model_id" ref="model_property_accounts_receivable_summary"/>
|
||||
<field name="state">code</field>
|
||||
<!-- 执行入口:汇总模型上的 cron_send_unpaid_reminder 方法 -->
|
||||
<field name="code">model.cron_send_unpaid_reminder()</field>
|
||||
<field eval="True" name="active"/>
|
||||
<!-- 以超级管理员身份执行,避免权限不足 -->
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<!-- 每 1 个月执行一次 -->
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">months</field>
|
||||
<!-- 下次执行时间:每月25号 09:00(北京时间)= 01:00 UTC;
|
||||
若当天已过25号则顺延到下月25号,避免刚装好就立刻触发一次 -->
|
||||
<field name="nextcall" eval="((DateTime.now() + timedelta(hours=8)) if (DateTime.now() + timedelta(hours=8)).day < 25 else (DateTime.now() + timedelta(hours=8) + relativedelta(months=1))).strftime('%Y-%m-25 01:00:00')"/>
|
||||
<!-- 不限执行次数,永久循环 -->
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ class PropertyTenantCheckout(models.Model):
|
||||
contract_term_end = fields.Date(related='contract_id.term_end_time', string='合同结束', readonly=True) # 合同期限止
|
||||
deposit_amount = fields.Float(related='contract_id.deposit', string='合同保证金', readonly=True) # 合同保证金
|
||||
rent_address = fields.Char(string='租赁地址') # 租赁地址(关联合同后自动带出,可改)
|
||||
is_chapter = fields.Selection([('yes', '是'), ('no', '否')], string="是否用章")
|
||||
type_stamp_ids = fields.Many2many('seals.type.setting', string='用章类型',domain="['&', ('start_date', '<=', context_today()), '|', ('end_date', '=', False), ('end_date', '>=', context_today())]")
|
||||
|
||||
# ===== 节点一:发起申请 =====
|
||||
# 退租原因(同时兼任「合同办理方式」):驱动终止逻辑与表单显隐;early_quitting_date 为终止统一日期(退租截止日期与退租日期统一使用此字段)
|
||||
@@ -55,6 +57,7 @@ class PropertyTenantCheckout(models.Model):
|
||||
], string='退租原因', default='normal')
|
||||
early_quitting_date = fields.Date(string='退租日期') # 实际退租日期(终止逻辑统一使用此字段)
|
||||
early_explain = fields.Text(string='退租说明') # 退租说明(进入终止/押金处理逻辑)
|
||||
handling_opinions = fields.Text(string='经办意见') # 经办意见(申请人填写,沿用成交确认同名字段)
|
||||
other_note = fields.Text(string='其它(补充说明)') # 其它补充说明
|
||||
|
||||
# ===== 节点二:工程组检查与确认 =====
|
||||
|
||||
@@ -82,6 +82,10 @@
|
||||
<field name="early_quitting_date" required="1"/>
|
||||
<!-- 退租说明:进入终止/押金处理逻辑 -->
|
||||
<field name="early_explain" widget="text"/>
|
||||
<field name="is_chapter"/>
|
||||
<field name="type_stamp_ids" widget="many2many_tags" options="{'no_create': True, 'no_open': True}" context="{'search_default_group_by_scope': 1, 'search_default_filter_effective': 1}" invisible="is_chapter in [False, 'no']"/>
|
||||
<!-- 经办意见(多行文字,沿用成交确认同名字段) -->
|
||||
<field name="handling_opinions" widget="text"/>
|
||||
<!-- 其它(补充说明) -->
|
||||
<field name="other_note" widget="text"/>
|
||||
</group>
|
||||
|
||||
@@ -221,6 +221,13 @@ class ReceivableReport extends Component {
|
||||
orm: this.serviceOrm, // 传递 orm 用于调用模型方法
|
||||
},
|
||||
onFilterChanged: () => {
|
||||
// 仅当行数据带 row_type(应收流水那种 合同/收款 分层报表)才按过滤结果重排序号;
|
||||
// 其他复用本组件的报表行数据没有 row_type,直接跳过,保留后端返回的序号
|
||||
let hasRowType = false;
|
||||
this.grid.forEachNode((node) => {
|
||||
if (node.data && node.data.row_type) hasRowType = true;
|
||||
});
|
||||
if (!hasRowType) return;
|
||||
// 过滤后重新编号序号列(仅合同头行编号)
|
||||
let num = 1;
|
||||
this.grid.forEachNodeAfterFilter((node) => {
|
||||
|
||||
Reference in New Issue
Block a user