Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
H
hh_ccs
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
贺阳
hh_ccs
Commits
799749cb
提交
799749cb
authored
3月 24, 2026
作者:
刘擎阳
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
1.优化
上级
ee0abc37
显示空白字符变更
内嵌
并排
正在显示
6 个修改的文件
包含
629 行增加
和
1 行删除
+629
-1
__manifest__.py
ccs_connect_tiktok/__manifest__.py
+1
-0
timer.xml
ccs_connect_tiktok/data/timer.xml
+14
-0
__init__.py
ccs_connect_tiktok/models/__init__.py
+1
-1
warn_config.py
ccs_connect_tiktok/models/warn_config.py
+520
-0
ir.model.access.csv
ccs_connect_tiktok/security/ir.model.access.csv
+3
-0
warn_config_views.xml
ccs_connect_tiktok/views/warn_config_views.xml
+90
-0
没有找到文件。
ccs_connect_tiktok/__manifest__.py
浏览文件 @
799749cb
...
@@ -21,6 +21,7 @@
...
@@ -21,6 +21,7 @@
'wizard/batch_input_ship_package_statu_wizard.xml'
,
'wizard/batch_input_ship_package_statu_wizard.xml'
,
'wizard/update_bl_status_wizard.xml'
,
'wizard/update_bl_status_wizard.xml'
,
'wizard/excel_wizard.xml'
,
'wizard/excel_wizard.xml'
,
'views/warn_config_views.xml'
,
# 'wizard/again_push_wizard.xml',
# 'wizard/again_push_wizard.xml',
# 'wizard/batch_push_tiktok.xml',
# 'wizard/batch_push_tiktok.xml',
# view
# view
...
...
ccs_connect_tiktok/data/timer.xml
浏览文件 @
799749cb
...
@@ -25,5 +25,18 @@
...
@@ -25,5 +25,18 @@
<field
name=
"doall"
eval=
"False"
/>
<field
name=
"doall"
eval=
"False"
/>
</record>
</record>
<record
id=
"cron_cron_warn_cc_order"
model=
"ir.cron"
>
<field
name=
"name"
>
节点进度预警
</field>
<field
name=
"model_id"
ref=
"ccs_connect_tiktok.model_warning_config"
/>
<field
name=
"state"
>
code
</field>
<field
name=
"code"
>
model.cron_warn_cc_order()
</field>
<field
name=
'interval_number'
>
1
</field>
<field
name=
'interval_type'
>
days
</field>
<field
name=
"numbercall"
>
-1
</field>
<field
name=
"active"
eval=
"True"
/>
<field
name=
"nextcall"
eval=
"(DateTime.now().replace(hour=0, minute=0)).strftime('%Y-%m-%d %H:%M:%S')"
/>
<field
name=
"doall"
eval=
"False"
/>
</record>
</data>
</data>
</odoo>
</odoo>
\ No newline at end of file
ccs_connect_tiktok/models/__init__.py
浏览文件 @
799749cb
...
@@ -12,5 +12,5 @@ from . import pda_scan_record
...
@@ -12,5 +12,5 @@ from . import pda_scan_record
from
.
import
bl_patrol
from
.
import
bl_patrol
from
.
import
cc_pallet
from
.
import
cc_pallet
from
.
import
warn_config
ccs_connect_tiktok/models/warn_config.py
0 → 100644
浏览文件 @
799749cb
from
odoo
import
models
,
fields
,
api
from
odoo.exceptions
import
ValidationError
from
datetime
import
datetime
,
timedelta
,
timezone
from
collections
import
defaultdict
import
logging
_logger
=
logging
.
getLogger
(
__name__
)
class
WarningConfig
(
models
.
Model
):
_name
=
'warning.config'
_description
=
'预警配置'
name
=
fields
.
Char
(
string
=
'预警名称'
,
required
=
True
,
copy
=
False
)
time_type
=
fields
.
Selection
([
(
'clearance_node'
,
'清关进度节点'
),
(
'flight_landing'
,
'航班落地'
)
],
string
=
'预警时间类型'
,
required
=
True
,
default
=
'clearance_node'
)
# 当类型为"清关进度节点"时使用的字段
time_point_id
=
fields
.
Many2one
(
'cc.node'
,
string
=
'预警时间点'
)
# 当类型为"航班落地"时使用的占位展示字段
flight_landing_time
=
fields
.
Char
(
string
=
'预警时间点'
,
default
=
'预计到达时间'
,
readonly
=
True
)
remaining_time
=
fields
.
Integer
(
string
=
'剩余时间'
,
help
=
'填正整数或负整数。整数代表距离该时间还剩多长时间,负数代表超过时间多长时间。'
)
unsynced_node_id
=
fields
.
Many2one
(
'cc.node'
,
string
=
'未同步节点状态'
)
warning_reason
=
fields
.
Text
(
string
=
'预警原因'
)
active
=
fields
.
Boolean
(
string
=
'有效'
,
default
=
True
)
# 数据库层面的唯一性约束
_sql_constraints
=
[
(
'name_unique'
,
'UNIQUE(name)'
,
'预警名称必须唯一,不能重复!'
)
]
# Python 层面的逻辑校验
@api.constrains
(
'time_point_id'
,
'unsynced_node_id'
,
'time_type'
)
def
_check_node_difference
(
self
):
for
record
in
self
:
# 只有在时间类型为清关节点时,才去对比两个节点是否相同
if
record
.
time_type
==
'clearance_node'
:
if
record
.
time_point_id
and
record
.
unsynced_node_id
and
record
.
time_point_id
==
record
.
unsynced_node_id
:
raise
ValidationError
(
'未同步节点状态 不能跟 预警时间点 一致!'
)
# 当切换时间类型时,清空不相关的数据
@api.onchange
(
'time_type'
)
def
_onchange_time_type
(
self
):
if
self
.
time_type
==
'flight_landing'
:
self
.
time_point_id
=
False
def
convert_to_utc
(
self
,
time_str
):
"""
将带时区的时间字符串转换为 0 时区 (UTC) 的常规格式
"""
# 1. 解析时间字符串
dt
=
datetime
.
fromisoformat
(
time_str
)
# 2. 转换到 0 时区
utc_dt
=
dt
.
astimezone
(
timezone
.
utc
)
# 3. 返回格式化后的字符串
return
datetime
.
strptime
(
utc_dt
.
strftime
(
"
%
Y-
%
m-
%
d
%
H:
%
M:
%
S"
),
'
%
d/
%
m/
%
Y
%
H:
%
M:
%
S'
)
# def cron_warn_cc_order(self):
# # 获取最近几天的提单 未完成的
# # 根据配置条件去检查
# config_objs = self.env['warning.config'].sudo().search([])
# warn_order_days = self.env['ir.config_parameter'].sudo().get_param('warn_order_days') or 10
# warn_order_days = int(warn_order_days)
# utc_time = datetime.utcnow()
# c_time = utc_time - timedelta(days=warn_order_days)
# domain = [('create_date', '>=', c_time.strftime('%Y-%m-%d %H:%M:%S')), ('state', '!=', 'done')]
# bl_objs = self.env['cc.bl'].sudo().search(domain)
# ship_package_arr = defaultdict(list)
# for config_obj in config_objs:
# # 需要一个时间然后检查同步日志里面有没有这个节点
# if config_obj.time_type == 'clearance_node':
# if config_obj.time_point_id.node_type == 'bl':
# # 找这个提单的节点的这个时间 去查有没有上传
# for bl_obj in bl_objs:
# log_obj = self.env['cc.bl.sync.log'].sudo().search([('bl_id', '=', bl_obj.id),
# ('process_code', '=', config_obj.time_point_id.tk_code)], order='operate_time desc', limit=1)
# not_log_obj = self.env['cc.bl.sync.log'].sudo().search([('bl_id', '=', bl_obj.id),
# ('process_code', '=',
# config_obj.unsynced_node_id.tk_code)],
# order='operate_time desc', limit=1)
# if utc_time > (log_obj.operate_time + timedelta(hours=-int(config_obj.remaining_time))) and not not_log_obj:
# # 发送消息
# content = f"""
# 提单号{bl_obj.bl_no}
# 关务节点{config_obj.unsynced_node_id.name})未同步,{config_obj.warning_reason or ''}
# 请及时操作!
# """
# self.send_email(content)
# pass
# elif config_obj.time_point_id.node_type == 'package':
# for bl_obj in bl_objs:
# package_objs = self.env['cc.ship.package'].sudo().search([('bl_id', '=', bl_obj.id)])
# package_arr = []
# for package_obj in package_objs:
# log_obj = self.env['cc.ship.package.sync.log'].sudo().search([('package_id', '=', package_obj.id),
# ('process_code', '=', config_obj.time_point_id.tk_code)], order='operate_time desc', limit=1)
# not_log_obj = self.env['cc.ship.package.sync.log'].sudo().search([('package_id', '=', package_obj.id),
# ('process_code', '=',
# config_obj.unsynced_node_id.tk_code)],
# order='operate_time desc', limit=1)
# if utc_time > (log_obj.operate_time + timedelta(hours=-int(config_obj.remaining_time))) and not not_log_obj:
# # 发送消息
# package_arr.append(package_obj)
# pass
# if bl_obj.bl_no in ship_package_arr:
# ship_package_arr.append({
# 'name': config_obj.unsynced_node_id.name or '',
# 'arr': package_arr
# })
# else:
# ship_package_arr[bl_obj.bl_no] = [{
# 'name': config_obj.unsynced_node_id.name or '',
# 'arr': package_arr
# }]
# self.send_ship_email(ship_package_arr)
# else:
# if config_obj.unsynced_node_id.node_type == 'bl':
# # 找这个提单的节点的这个时间 去查有没有上传
# for bl_obj in bl_objs:
# not_log_obj = self.env['cc.bl.sync.log'].sudo().search([('bl_id', '=', bl_obj.id),
# ('process_code', '=',
# config_obj.unsynced_node_id.tk_code)],
# order='operate_time desc', limit=1)
# eta = self.convert_to_utc(bl_obj.eta)
# # 输出: 2025-09-30 13:35:00
# if utc_time > (eta + timedelta(hours=-int(config_obj.remaining_time))) and not not_log_obj:
# # 发送消息
# content = f"""
# 提单号{bl_obj.bl_no}
# 关务节点{config_obj.unsynced_node_id.name})未同步,{config_obj.warning_reason or ''}
# 请及时操作!
# """
# self.send_email(content)
# pass
# elif config_obj.unsynced_node_id.node_type == 'package':
# for bl_obj in bl_objs:
# package_objs = self.env['cc.ship.package'].sudo().search([('bl_id', '=', bl_obj.id)])
# package_arr = []
# for package_obj in package_objs:
# not_log_obj = self.env['cc.ship.package.sync.log'].sudo().search([('package_id', '=', package_obj.id),
# ('process_code', '=',
# config_obj.unsynced_node_id.tk_code)],
# order='operate_time desc', limit=1)
# eta = self.convert_to_utc(bl_obj.eta)
# if utc_time > (eta + timedelta(hours=-int(config_obj.remaining_time))) and not not_log_obj:
# # 发送消息
# package_arr.append(package_obj)
# pass
# if bl_obj.bl_no in ship_package_arr:
# ship_package_arr.append({
# 'name': config_obj.unsynced_node_id.name or '',
# 'arr': package_arr
# })
# else:
# ship_package_arr[bl_obj.bl_no] = [{
# 'name': config_obj.unsynced_node_id.name or '',
# 'arr': package_arr
# }]
# self.send_ship_email(ship_package_arr)
# def cron_warn_cc_order(self):
# # 1. 初始化基础参数
# utc_time = datetime.utcnow()
# warn_days = int(self.env['ir.config_parameter'].sudo().get_param('warn_order_days', 10))
# c_time = utc_time - timedelta(days=warn_days)
# config_objs = self.env['warning.config'].sudo().search([])
# bl_objs = self.env['cc.bl'].sudo().search([
# ('create_date', '>=', c_time.strftime('%Y-%m-%d %H:%M:%S')),
# ('state', '!=', 'done')
# ])
# # bl_objs = self.env['cc.bl'].sudo().search([('id', '=', 71)]) # 本地测试提单
# if not config_objs or not bl_objs:
# return
# # 2. 一次性获取所有相关的包裹,并按 bl_id 分组映射到内存中
# all_packages = self.env['cc.ship.package'].sudo().search([('bl_id', 'in', bl_objs.ids)])
# packages_by_bl = defaultdict(list)
# for pkg in all_packages:
# packages_by_bl[pkg.bl_id.id].append(pkg)
# # 3. 核心工具函数:批量获取最新日志字典 {record_id: {process_code: operate_time}}
#
# def get_latest_logs_time(model_name, rel_field, record_ids, process_codes):
# if not record_ids or not process_codes:
# return {}
# logs = self.env[model_name].sudo().search([
# (rel_field, 'in', tuple(record_ids)),
# ('process_code', 'in', tuple(process_codes))
# ], order='operate_time desc')
# log_dict = defaultdict(dict)
# for log in logs:
# rec_id = getattr(log, rel_field).id
# code = log.process_code
# if code not in log_dict[rec_id]: # 只记录最新的那条时间
# log_dict[rec_id][code] = log.operate_time
# return log_dict
#
# ship_package_arr = defaultdict(list)
# bl_arr = defaultdict(list)
# # 4. 遍历配置规则进行校验
# for config in config_objs:
# is_clearance = (config.time_type == 'clearance_node')
# node_type = config.time_point_id.node_type if is_clearance else config.unsynced_node_id.node_type
#
# sync_code = config.time_point_id.tk_code if is_clearance else None
# unsync_code = config.unsynced_node_id.tk_code
# codes_to_fetch = {c for c in [sync_code, unsync_code] if c}
#
# time_offset = timedelta(hours=-int(config.remaining_time or 0))
# node_name = config.unsynced_node_id.name or ''
# warning_reason = config.warning_reason or ''
#
# # 处理提单级别 (BL)
# if node_type == 'bl':
# bl_logs = get_latest_logs_time('cc.bl.sync.log', 'bl_id', bl_objs.ids, codes_to_fetch)
# for bl in bl_objs:
# logs = bl_logs.get(bl.id, {})
# if unsync_code in logs: # 已同步,跳过
# continue
#
# # 确定计算基准时间
# base_time = logs.get(sync_code) if is_clearance else (
# self.convert_to_utc(bl.eta) if bl.eta else None)
#
# if base_time and utc_time > (base_time + time_offset):
# content = f"提单号{bl.bl_no}\n关务节点({node_name})未同步,{warning_reason}\n请及时操作!"
# bl_arr[bl.bl_no].append({
# 'name': node_name,
# 'warning_reason': warning_reason
# })
# # self.send_bl_email(config.name, content)
# # 处理包裹级别 (Package)
# elif node_type == 'package':
# pkg_logs = get_latest_logs_time('cc.ship.package.sync.log', 'package_id', all_packages.ids,
# codes_to_fetch)
# for bl in bl_objs:
# alert_pkgs = []
# # 获取该提单下的所有包裹
# for pkg in packages_by_bl.get(bl.id, []):
# logs = pkg_logs.get(pkg.id, {})
# if unsync_code in logs: # 已同步,跳过
# continue
# # 确定计算基准时间
# base_time = logs.get(sync_code) if is_clearance else (
# self.convert_to_utc(bl.eta) if bl.eta else None)
# if base_time and utc_time > (base_time + time_offset):
# alert_pkgs.append(pkg)
# # 修复 Bug: 正确地向 defaultdict(list) 的子列表中追加数据
# if alert_pkgs:
# ship_package_arr[bl.bl_no].append({
# 'name': node_name,
# 'arr': alert_pkgs,
# 'warning_reason': warning_reason
# })
# # 5. 循环外部:一次性发送包裹邮件汇总!
# if ship_package_arr:
# # print(ship_package_arr)
# self.send_warn_email(self.format_package_warning_email(ship_package_arr))
# if bl_arr:
# # print(bl_arr)
# self.send_warn_email(self.format_email_content_grouped(bl_arr))
def
cron_warn_cc_order
(
self
):
# 1. 初始化基础参数
utc_time
=
datetime
.
utcnow
()
warn_days
=
int
(
self
.
env
[
'ir.config_parameter'
]
.
sudo
()
.
get_param
(
'warn_order_days'
,
7
))
# c_time = utc_time - timedelta(days=warn_days)
end_date
=
fields
.
Date
.
today
()
start_date
=
end_date
-
timedelta
(
days
=
warn_days
)
print
(
start_date
)
config_objs
=
self
.
env
[
'warning.config'
]
.
sudo
()
.
search
([])
bl_objs
=
self
.
env
[
'cc.bl'
]
.
sudo
()
.
search
([
(
'bl_date'
,
'>='
,
start_date
),
(
'state'
,
'!='
,
'done'
)
])
if
not
config_objs
or
not
bl_objs
:
return
# 2. 获取包裹及预计算提单的 ETA (避免重复转换时间)
all_packages
=
self
.
env
[
'cc.ship.package'
]
.
sudo
()
.
search
([(
'bl_id'
,
'in'
,
bl_objs
.
ids
)])
packages_by_bl
=
defaultdict
(
list
)
for
pkg
in
all_packages
:
packages_by_bl
[
pkg
.
bl_id
.
id
]
.
append
(
pkg
)
bl_eta_utc_dict
=
{
bl
.
id
:
self
.
convert_to_utc
(
bl
.
eta
)
if
bl
.
eta
else
None
for
bl
in
bl_objs
}
# 3. 核心优化:提前收集所有配置中用到的 process_code,一次性查询!
bl_codes_needed
=
set
()
pkg_codes_needed
=
set
()
for
config
in
config_objs
:
is_clearance
=
(
config
.
time_type
==
'clearance_node'
)
node_type
=
config
.
time_point_id
.
node_type
if
is_clearance
else
config
.
unsynced_node_id
.
node_type
if
node_type
==
'bl'
:
if
is_clearance
:
bl_codes_needed
.
add
(
config
.
time_point_id
.
tk_code
)
bl_codes_needed
.
add
(
config
.
unsynced_node_id
.
tk_code
)
elif
node_type
==
'package'
:
if
is_clearance
:
pkg_codes_needed
.
add
(
config
.
time_point_id
.
tk_code
)
pkg_codes_needed
.
add
(
config
.
unsynced_node_id
.
tk_code
)
# 4. 超级工具函数:使用 search_read 避开 ORM 实例化开销
def
get_all_logs_dict
(
model_name
,
rel_field
,
record_ids
,
process_codes
):
if
not
record_ids
or
not
process_codes
:
return
{}
# search_read 直接返回字典列表,比 search 返回对象快 10 倍以上
logs_data
=
self
.
env
[
model_name
]
.
sudo
()
.
search_read
(
[(
rel_field
,
'in'
,
tuple
(
record_ids
)),
(
'process_code'
,
'in'
,
tuple
(
process_codes
))],
[
rel_field
,
'process_code'
,
'operate_time'
],
order
=
'operate_time desc'
)
log_dict
=
defaultdict
(
dict
)
for
data
in
logs_data
:
# search_read 中的 Many2one 字段会返回 (id, name) 元组
rec_id
=
data
[
rel_field
][
0
]
if
isinstance
(
data
[
rel_field
],
tuple
)
else
data
[
rel_field
]
code
=
data
[
'process_code'
]
if
code
not
in
log_dict
[
rec_id
]:
# 依靠 order='desc',最先遍历到的一定是最新的
log_dict
[
rec_id
][
code
]
=
data
[
'operate_time'
]
return
log_dict
# 无论有多少条配置规则,查提单日志和包裹日志永远只各查 1 次数据库!
bl_logs_dict
=
get_all_logs_dict
(
'cc.bl.sync.log'
,
'bl_id'
,
bl_objs
.
ids
,
bl_codes_needed
)
pkg_logs_dict
=
get_all_logs_dict
(
'cc.ship.package.sync.log'
,
'package_id'
,
all_packages
.
ids
,
pkg_codes_needed
)
ship_package_arr
=
defaultdict
(
list
)
bl_arr
=
defaultdict
(
list
)
# 5. 遍历配置规则进行校验 (此时全是纯内存/字典操作,耗时接近 0)
for
config
in
config_objs
:
is_clearance
=
(
config
.
time_type
==
'clearance_node'
)
node_type
=
config
.
time_point_id
.
node_type
if
is_clearance
else
config
.
unsynced_node_id
.
node_type
sync_code
=
config
.
time_point_id
.
tk_code
if
is_clearance
else
None
unsync_code
=
config
.
unsynced_node_id
.
tk_code
time_offset
=
timedelta
(
hours
=-
int
(
config
.
remaining_time
or
0
))
node_name
=
config
.
unsynced_node_id
.
name
or
''
warning_reason
=
config
.
warning_reason
or
''
if
node_type
==
'bl'
:
for
bl
in
bl_objs
:
logs
=
bl_logs_dict
.
get
(
bl
.
id
,
{})
if
unsync_code
in
logs
:
# 已同步,跳过
continue
base_time
=
logs
.
get
(
sync_code
)
if
is_clearance
else
bl_eta_utc_dict
.
get
(
bl
.
id
)
if
base_time
and
utc_time
>
(
base_time
+
time_offset
):
bl_arr
[
bl
.
bl_no
]
.
append
({
'name'
:
node_name
,
'warning_reason'
:
warning_reason
})
elif
node_type
==
'package'
:
for
bl
in
bl_objs
:
alert_pkgs
=
[]
# 获取该提单预计算的基准时间 (非clearance模式下)
bl_eta
=
bl_eta_utc_dict
.
get
(
bl
.
id
)
if
not
is_clearance
else
None
for
pkg
in
packages_by_bl
.
get
(
bl
.
id
,
[]):
logs
=
pkg_logs_dict
.
get
(
pkg
.
id
,
{})
if
unsync_code
in
logs
:
continue
base_time
=
logs
.
get
(
sync_code
)
if
is_clearance
else
bl_eta
if
base_time
and
utc_time
>
(
base_time
+
time_offset
):
alert_pkgs
.
append
(
pkg
)
if
alert_pkgs
:
ship_package_arr
[
bl
.
bl_no
]
.
append
({
'name'
:
node_name
,
'arr'
:
alert_pkgs
,
'warning_reason'
:
warning_reason
})
# 6. 发送邮件汇总
if
ship_package_arr
:
self
.
send_warn_email
(
self
.
format_package_warning_email
(
ship_package_arr
))
if
bl_arr
:
self
.
send_warn_email
(
self
.
format_email_content_grouped
(
bl_arr
))
def
format_package_warning_email
(
self
,
data
):
"""
将小包预警数据格式化为指定的邮件内容(已移除开头标题)
"""
email_lines
=
[]
for
bl_no
,
node_list
in
data
.
items
():
email_lines
.
append
(
f
"提单号{bl_no}"
)
for
node
in
node_list
:
packages
=
node
.
get
(
'arr'
,
[])
total_count
=
len
(
packages
)
if
total_count
==
0
:
continue
# 提取小包号(最多10个)
# 注意:这里的 pkg.name 假设你的小包号字段叫 name,请根据实际情况替换
pkg_names
=
[
pkg
.
logistic_order_no
for
pkg
in
packages
[:
10
]]
pkg_str
=
"/"
.
join
(
pkg_names
)
node_name
=
node
.
get
(
'name'
,
'未知节点'
)
reason
=
node
.
get
(
'warning_reason'
,
'无预警原因'
)
# 按照要求的格式拼接
email_lines
.
append
(
f
"小包{pkg_str}等{total_count}个小包,节点{node_name}未同步,{reason}"
)
email_lines
.
append
(
"请及时操作!"
)
# 用换行符将所有行连接起来
return
"
\n
"
.
join
(
email_lines
)
def
format_email_content_grouped
(
self
,
data
):
"""
将预警数据格式化为邮件文本(按提单号聚合节点)
"""
email_lines
=
[]
for
bl_no
,
nodes
in
data
.
items
():
email_lines
.
append
(
f
"提单号{bl_no}"
)
# 将该提单下的所有异常节点列出来
for
node
in
nodes
:
node_name
=
node
.
get
(
'name'
,
'未知节点'
)
reason
=
node
.
get
(
'warning_reason'
,
'无'
)
email_lines
.
append
(
f
"关务节点{node_name}未同步,{reason}"
)
email_lines
.
append
(
"请及时操作!
\n
"
)
# \n 用于和下一个提单隔开
return
"
\n
"
.
join
(
email_lines
)
.
strip
()
def
send_warn_email
(
self
,
content
):
"""
发送邮件
"""
try
:
# 获取邮件配置
config
=
self
.
env
[
'ir.config_parameter'
]
.
sudo
()
receiver_emails
=
config
.
get_param
(
'patrol_receiver_emails'
,
default
=
''
)
sender_email
=
config
.
get_param
(
'patrol_sender_email'
,
default
=
''
)
if
not
receiver_emails
or
not
sender_email
:
logging
.
warning
(
"邮件配置不完整,跳过邮件发送"
)
return
# 解析接收邮箱
receiver_list
=
[
email
.
strip
()
for
email
in
receiver_emails
.
split
(
';'
)
if
email
.
strip
()]
# 构建邮件内容
subject
=
f
"节点未及时推送预警通知"
# 标题
# 发送邮件
self
.
env
[
'mail.mail'
]
.
sudo
()
.
create
({
'subject'
:
subject
,
'body_html'
:
content
.
replace
(
'
\n
'
,
'<br/>'
),
'email_from'
:
sender_email
,
'email_to'
:
','
.
join
(
receiver_list
),
'auto_delete'
:
True
,
'date'
:
datetime
.
utcnow
()
})
.
send
()
# 更新发送记录
# if patrol_obj:
# patrol_obj.write({
# 'email_sent': True,
# 'email_sent_time': fields.Datetime.now()
# })
_logger
.
info
(
f
"预警邮件发送成功,接收人: {receiver_list}"
)
except
Exception
as
e
:
_logger
.
error
(
f
"发送预警邮件失败: {str(e)}"
)
# def send_ship_email(self, content):
# """
# 发送邮件
# """
# try:
# # 获取邮件配置
# config = self.env['ir.config_parameter'].sudo()
# receiver_emails = config.get_param('patrol_receiver_emails', default='')
# sender_email = config.get_param('patrol_sender_email', default='')
# if not receiver_emails or not sender_email:
# logging.warning("邮件配置不完整,跳过邮件发送")
# return
# # 解析接收邮箱
# receiver_list = [email.strip() for email in receiver_emails.split(';') if email.strip()]
# # 构建邮件内容
# subject = f"节点未及时推送预警通知" # 标题
# # 发送邮件
# self.env['mail.mail'].sudo().create({
# 'subject': subject,
# 'body_html': content.replace('\n', '<br/>'),
# 'email_from': sender_email,
# 'email_to': ','.join(receiver_list),
# 'auto_delete': True,
# 'date': datetime.utcnow()
# }).send()
#
# # 更新发送记录
# # if patrol_obj:
# # patrol_obj.write({
# # 'email_sent': True,
# # 'email_sent_time': fields.Datetime.now()
# # })
#
# _logger.info(f"预警邮件发送成功,接收人: {receiver_list}")
#
# except Exception as e:
# _logger.error(f"发送预警邮件失败: {str(e)}")
ccs_connect_tiktok/security/ir.model.access.csv
浏览文件 @
799749cb
...
@@ -23,3 +23,6 @@ access_bl_patrol_user,bl.patrol.user,model_bl_patrol,base.group_user,1,0,0,0
...
@@ -23,3 +23,6 @@ access_bl_patrol_user,bl.patrol.user,model_bl_patrol,base.group_user,1,0,0,0
access_bl_patrol_manager,bl.patrol.manager,model_bl_patrol,base.group_system,1,1,1,1
access_bl_patrol_manager,bl.patrol.manager,model_bl_patrol,base.group_system,1,1,1,1
access_package_data_wizard_base.group_user,package_data_wizard base.group_user,ccs_connect_tiktok.model_package_data_wizard,base.group_user,1,1,1,1
access_package_data_wizard_base.group_user,package_data_wizard base.group_user,ccs_connect_tiktok.model_package_data_wizard,base.group_user,1,1,1,1
access_warning_config_user,warning.config.user,model_warning_config,base.group_user,1,1,1,1
ccs_connect_tiktok/views/warn_config_views.xml
0 → 100644
浏览文件 @
799749cb
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record
id=
"view_warning_config_form"
model=
"ir.ui.view"
>
<field
name=
"name"
>
warning.config.form
</field>
<field
name=
"model"
>
warning.config
</field>
<field
name=
"arch"
type=
"xml"
>
<form
string=
"预警配置"
>
<sheet>
<widget
name=
"web_ribbon"
title=
"归档"
bg_color=
"bg-danger"
attrs=
"{'invisible': [('active', '=', True)]}"
/>
<div
class=
"oe_title"
>
<h1>
<field
name=
"name"
placeholder=
"请输入预警名称..."
/>
</h1>
</div>
<group>
<group>
<field
name=
"time_type"
widget=
"radio"
/>
<field
name=
"time_point_id"
attrs=
"{
'invisible': [('time_type', '=', 'flight_landing')],
'required': [('time_type', '=', 'clearance_node')]
}"
/>
<field
name=
"flight_landing_time"
attrs=
"{'invisible': [('time_type', '=', 'clearance_node')]}"
/>
<field
name=
"remaining_time"
/>
</group>
<group>
<field
name=
"unsynced_node_id"
/>
<field
name=
"active"
invisible=
"1"
/>
</group>
</group>
<notebook>
<page
string=
"预警原因"
name=
"reason"
>
<field
name=
"warning_reason"
placeholder=
"请详细描述预警原因..."
/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record
id=
"view_warning_config_tree"
model=
"ir.ui.view"
>
<field
name=
"name"
>
warning.config.tree
</field>
<field
name=
"model"
>
warning.config
</field>
<field
name=
"arch"
type=
"xml"
>
<tree
string=
"预警配置"
>
<field
name=
"name"
/>
<field
name=
"time_type"
/>
<field
name=
"time_point_id"
attrs=
"{'invisible': [('time_type', '=', 'flight_landing')]}"
/>
<field
name=
"flight_landing_time"
attrs=
"{'invisible': [('time_type', '=', 'clearance_node')]}"
/>
<field
name=
"remaining_time"
/>
<field
name=
"unsynced_node_id"
/>
<field
name=
"active"
widget=
"boolean_toggle"
/>
</tree>
</field>
</record>
<record
id=
"view_warning_config_search"
model=
"ir.ui.view"
>
<field
name=
"name"
>
warning.config.search
</field>
<field
name=
"model"
>
warning.config
</field>
<field
name=
"arch"
type=
"xml"
>
<search
string=
"搜索预警配置"
>
<field
name=
"name"
/>
<field
name=
"time_point_id"
/>
<filter
string=
"航班落地"
name=
"type_flight"
domain=
"[('time_type', '=', 'flight_landing')]"
/>
<filter
string=
"清关进度节点"
name=
"type_clearance"
domain=
"[('time_type', '=', 'clearance_node')]"
/>
<separator/>
<filter
string=
"归档"
name=
"inactive"
domain=
"[('active', '=', False)]"
/>
</search>
</field>
</record>
<record
id=
"action_warning_config"
model=
"ir.actions.act_window"
>
<field
name=
"name"
>
预警配置
</field>
<field
name=
"res_model"
>
warning.config
</field>
<field
name=
"view_mode"
>
tree,form
</field>
<field
name=
"help"
type=
"html"
>
<p
class=
"o_view_nocontent_smiling_face"
>
创建第一条预警配置
</p>
</field>
</record>
<menuitem
id=
"menu_warning_config"
name=
"预警配置"
action=
"action_warning_config"
sequence=
"10"
/>
</odoo>
\ No newline at end of file
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论