Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
H
hh_ccs
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
贺阳
hh_ccs
Commits
8f8ddcb9
提交
8f8ddcb9
authored
9月 18, 2025
作者:
贺阳
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
1、清关中可操作“完成”,点击“完成”进行提单巡查的逻辑,没有问题的,则可变成已完成。有问题的进行提示。权限归清关员/清关经理
2、已完成可点击“追回”,追回变成清关中 3、清关中到已完成支持批量操作 4、根据小包状态自动变成已完成时,也需检查要进行提单巡查的逻辑,有问题的发邮件
上级
d424a312
隐藏空白字符变更
内嵌
并排
正在显示
6 个修改的文件
包含
466 行增加
和
369 行删除
+466
-369
zh_CN.po
ccs_base/i18n/zh_CN.po
+33
-18
cc_bill_loading.py
ccs_base/models/cc_bill_loading.py
+15
-1
cc_bl_view.xml
ccs_base/views/cc_bl_view.xml
+28
-1
batch_input_ship_package_statu_wizard.py
ccs_base/wizard/batch_input_ship_package_statu_wizard.py
+9
-6
bl_patrol.py
ccs_connect_tiktok/models/bl_patrol.py
+2
-340
cc_bill_loading.py
ccs_connect_tiktok/models/cc_bill_loading.py
+379
-3
没有找到文件。
ccs_base/i18n/zh_CN.po
浏览文件 @
8f8ddcb9
...
...
@@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-0
7-15 02:3
9+0000\n"
"PO-Revision-Date: 2025-0
7-15 10:45
+0800\n"
"POT-Creation-Date: 2025-0
9-18 08:2
9+0000\n"
"PO-Revision-Date: 2025-0
9-18 16:30
+0800\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: zh_CN\n"
...
...
@@ -531,6 +531,11 @@ msgstr "批量"
msgid "Batch Add Package Exception Information"
msgstr "批量添加异常信息"
#. module: ccs_base
#: model:ir.actions.server,name:ccs_base.bl_complete_server_action
msgid "Batch Complete"
msgstr "批量完成"
#. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.view_batch_update_transfer_bl_no_wizard
msgid "Batch Link Transfer B/L No"
...
...
@@ -586,15 +591,6 @@ msgstr "大包"
msgid "Big Package No"
msgstr "大包号"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/associate_pallet_wizard.py:0
#, python-format
msgid ""
"Big Package No :%s ,The same bill of lading, same pallet number, and usage "
"date must be consistent!"
msgstr "大包号:%s ,同一提单、同一托盘号的使用日期必须一致!"
#. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__big_package_no
#: model:ir.model.fields,field_description:ccs_base.field_cc_history_big_package__big_package_no
...
...
@@ -959,6 +955,11 @@ msgstr "公司"
msgid "Company Code"
msgstr "公司编码"
#. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_bl_view
msgid "Complete"
msgstr "完成"
#. module: ccs_base
#: model:ir.model,name:ccs_base.model_res_config_settings
msgid "Config Settings"
...
...
@@ -2385,6 +2386,13 @@ msgstr ""
msgid "ONLINE SELLING PLACE"
msgstr "网上销售网站"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/models/cc_bill_loading.py:0
#, python-format
msgid "Only completed bills of loading can be recalled!"
msgstr "只有已完成状态的提单可以追回!"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
...
...
@@ -2392,6 +2400,13 @@ msgstr "网上销售网站"
msgid "Only excel files can be uploaded!"
msgstr "只能上传excel文件!"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/models/cc_bill_loading.py:0
#, python-format
msgid "Only the status of the bill of loading is ccing can be completed!"
msgstr "只有清关中状态的提单可以完成!"
#. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_history_package_sync_log__operate_remark
#: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_history_package_sync_log_view
...
...
@@ -2692,6 +2707,11 @@ msgstr "收件人邮政编码"
msgid "Real Weight"
msgstr "实际重量"
#. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_bl_view
msgid "Recall"
msgstr "追回"
#. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.tree_cc_bl_view
msgid "Receive Big Package"
...
...
@@ -3390,13 +3410,6 @@ msgstr "使用日期不能大于当前日期!"
msgid "This B/L No does not exist in the system"
msgstr "提单号在系统不存在"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#, python-format
msgid "This Transfer B/L No already exists"
msgstr "转单号已存在"
#. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.search_cc_big_package_view
#: model_terms:ir.ui.view,arch_db:ccs_base.search_cc_history_big_package_view
...
...
@@ -3518,6 +3531,7 @@ msgstr "转单号必填"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/models/cc_bill_loading.py:0
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#, python-format
msgid "Transfer B/L No. cannot be the same as B/L No."
msgstr "转单号不能与提单号相同。"
...
...
@@ -3525,6 +3539,7 @@ msgstr "转单号不能与提单号相同。"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/models/cc_bill_loading.py:0
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#, python-format
msgid "Transfer B/L No. cannot be the same as B/L No. or Transfer B/L No."
msgstr "转单号不能与提单号或转单号相同。"
...
...
ccs_base/models/cc_bill_loading.py
浏览文件 @
8f8ddcb9
...
...
@@ -810,7 +810,14 @@ class CcBL(models.Model):
if
item
.
state
==
'draft'
:
item
.
state
=
'ccing'
def
done_func
(
self
):
def
complete_func
(
self
):
"""点完成按钮,状态变为已完成"""
for
item
in
self
:
if
item
.
state
!=
'ccing'
:
raise
ValidationError
(
_
(
'Only the status of the bill of loading is ccing can be completed!'
))
#只有清关中状态的提单可以完成
item
.
done_func
()
def
done_func
(
self
,
is_email
=
False
):
"""
变为已完成
"""
...
...
@@ -818,6 +825,13 @@ class CcBL(models.Model):
if
item
.
state
==
'ccing'
:
item
.
state
=
'done'
def
action_recall
(
self
):
"""追回操作,将状态从已完成改为清关中"""
for
record
in
self
:
if
record
.
state
!=
'done'
:
raise
ValidationError
(
_
(
'Only completed bills of loading can be recalled!'
))
#只有已完成状态的提单可以追回
record
.
state
=
'ccing'
# 定义3个方法,分别创建显示该提单大包,包裹,商品的action
# 创建显示大包的action
def
action_show_big_package
(
self
):
...
...
ccs_base/views/cc_bl_view.xml
浏览文件 @
8f8ddcb9
...
...
@@ -59,7 +59,19 @@
string=
"Update Bill Of Loading Status"
context=
"{'active_id': id,'default_bl_id': active_id,
'default_last_process_time':process_time,'default_current_status':customs_clearance_status}"
/>
<field
name=
"state"
widget=
"statusbar"
options=
"{'clickable': '1'}"
/>
<!-- 完成按钮 - 仅在清关中状态显示,仅清关员和清关经理可操作 -->
<button
name=
"complete_func"
type=
"object"
class=
"oe_highlight"
string=
"Complete"
attrs=
"{'invisible': [('state', '!=', 'ccing')]}"
groups=
"ccs_base.group_clearance_of_customs_user,ccs_base.group_clearance_of_customs_manager"
/>
<!-- 追回按钮 - 仅在已完成状态显示,仅清关员和清关经理可操作 -->
<button
name=
"action_recall"
type=
"object"
string=
"Recall"
attrs=
"{'invisible': [('state', '!=', 'done')]}"
groups=
"ccs_base.group_clearance_of_customs_user,ccs_base.group_clearance_of_customs_manager"
/>
<field
name=
"state"
widget=
"statusbar"
/>
<!-- options="{'clickable': '1'}" -->
</header>
<header>
<field
name=
"customs_clearance_status"
widget=
"statusbar"
/>
...
...
@@ -432,4 +444,18 @@
</field>
</record>
<record
id=
"bl_complete_server_action"
model=
"ir.actions.server"
>
<field
name=
"name"
>
Batch Complete
</field>
<field
name=
"model_id"
ref=
"model_cc_bl"
/>
<field
name=
"binding_model_id"
ref=
"model_cc_bl"
/>
<field
name=
"state"
>
code
</field>
<field
name=
"binding_view_types"
>
list
</field>
<field
name=
"groups_id"
eval=
"[(4, ref('ccs_base.group_clearance_of_customs_user'))]"
/>
<field
name=
"code"
>
if records:
action = records.complete_func()
</field>
</record>
</odoo>
\ No newline at end of file
ccs_base/wizard/batch_input_ship_package_statu_wizard.py
浏览文件 @
8f8ddcb9
...
...
@@ -2,8 +2,9 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import
logging
from
odoo
import
models
,
api
,
fields
,
_
from
odoo.exceptions
import
Warning
,
ValidationError
from
odoo.exceptions
import
ValidationError
# 定义一个批量更新小包状态的向导, 用于批量更新小包状态
...
...
@@ -82,6 +83,7 @@ class BatchInputShipPackageStatusWizard(models.TransientModel):
state_explain
=
fields
.
Text
(
'State Explain'
,
help
=
'State Explain'
)
node_exception_reason_id
=
fields
.
Many2one
(
'cc.node.exception.reason'
,
'Exception Reason'
,
domain
=
"[('code_id', '=', update_status)]"
)
# 批量更新小包状态
def
submit
(
self
):
# 确认数据
...
...
@@ -89,7 +91,8 @@ class BatchInputShipPackageStatusWizard(models.TransientModel):
raise
ValidationError
(
'Please confirm that the above data is correct.'
)
# 请确认以上数据正确
parcels
=
self
.
get_process_package
()
logging
.
info
(
'更新小包状态的小包:
%
s,当前状态:
%
s,更新状态:
%
s'
%
(
parcels
,
self
.
current_status
,
self
.
update_status
))
if
not
parcels
:
raise
ValidationError
(
_
(
'No package to update found.'
))
# 没有找到要更新的小包
# 1.若选择的更新节点为是当前节点【清关节点设置,是当前节点字段名称改为初始节点】,当更新节点为初始节点时,无需填写操作时间;
...
...
@@ -106,7 +109,7 @@ class BatchInputShipPackageStatusWizard(models.TransientModel):
# 更新状态
parcels
.
write
(
{
'state'
:
self
.
update_status
.
id
,
'node_exception_reason_id'
:
self
.
node_exception_reason_id
.
id
,
'process_time'
:
self
.
process_time
,
'state_explain'
:
self
.
state_explain
,
'is_sync'
:
is_sync
})
'process_time'
:
self
.
process_time
,
'state_explain'
:
self
.
state_explain
,
'is_sync'
:
is_sync
})
# if parcels:
# where_sql = " where id={0}".format(parcels[0].id) if len(
# parcels) == 1 else " where id in {0}".format(tuple(parcels.ids))
...
...
@@ -116,9 +119,9 @@ class BatchInputShipPackageStatusWizard(models.TransientModel):
# where_sql)
# update_sql = update_sql.replace("'False'", "null").replace("False", "null")
# self._cr.execute(update_sql)
# parcels.write({'state': self.update_status.id})
# for parcel in parcels:
# parcel.message_post(body='%s改为%s' % (self.current_status.name, self.update_status.name))
# parcels.write({'state': self.update_status.id})
# for parcel in parcels:
# parcel.message_post(body='%s改为%s' % (self.current_status.name, self.update_status.name))
# 生成sns日志
# self.bl_id.message_post(body='%s更新为%s' % (self.current_status.name or '', self.update_status.name or ''))
# 跳转显示本次更新状态的小包 更新小包状态
...
...
ccs_connect_tiktok/models/bl_patrol.py
浏览文件 @
8f8ddcb9
# -*- coding: utf-8 -*-
import
logging
import
re
from
datetime
import
timedelta
from
odoo
import
models
,
fields
,
api
...
...
@@ -60,7 +59,7 @@ class BlPatrol(models.Model):
# 如果有问题,发送邮件
if
result
[
'issue_count'
]
>
0
:
patrol
.
_send_patrol_email
(
result
)
self
.
env
[
'cc.bl'
]
.
sudo
()
.
_send_patrol_email
(
result
,
patrol_obj
=
patrol
)
_logger
.
info
(
f
"提单巡查完成: {patrol.name}, 发现问题: {result['issue_count']}"
)
...
...
@@ -87,344 +86,7 @@ class BlPatrol(models.Model):
bls
=
self
.
env
[
'cc.bl'
]
.
sudo
()
.
search
(
[(
'bl_date'
,
'>='
,
start_date
),
(
'bl_date'
,
'<='
,
end_date
),
(
'state'
,
'in'
,
[
'ccing'
,
'done'
])])
_logger
.
info
(
f
"开始巡查提单,检查范围: {start_date} 到 {end_date}, 提单数量: {len(bls)}"
)
error_package_issues
=
[]
error_bl_issues
=
[]
package_issue_counter
=
1
# 小包问题独立编号
bl_issue_counter
=
1
# 提单问题独立编号
for
bl
in
bls
:
# 检查小包轨迹问题
bl_package_issues
=
self
.
_check_package_tracking_issues
(
bl
)
if
bl_package_issues
:
# 为小包问题添加独立编号
for
i
,
issue
in
enumerate
(
bl_package_issues
):
# 使用正则表达式匹配任何数字+顿号的格式
if
re
.
match
(
r'^\d+、'
,
issue
):
# 替换开头的编号
bl_package_issues
[
i
]
=
re
.
sub
(
r'^\d+、'
,
f
"{package_issue_counter}、"
,
issue
,
1
)
else
:
# 如果没有编号,添加编号
bl_package_issues
[
i
]
=
f
"{package_issue_counter}、{issue}"
package_issue_counter
+=
1
error_package_issues
.
extend
(
bl_package_issues
)
# 检查提单关务节点问题
bl_node_issues
=
self
.
_check_bl_node_issues
(
bl
)
if
bl_node_issues
:
# 为提单问题添加独立编号
for
i
,
blissue
in
enumerate
(
bl_node_issues
):
# 使用正则表达式匹配任何数字+顿号的格式
if
re
.
match
(
r'^\d+、'
,
blissue
):
# 替换开头的编号
bl_node_issues
[
i
]
=
re
.
sub
(
r'^\d+、'
,
f
"{bl_issue_counter}、"
,
blissue
,
1
)
else
:
# 如果没有编号,添加编号
bl_node_issues
[
i
]
=
f
"{bl_issue_counter}、{blissue}"
bl_issue_counter
+=
1
error_bl_issues
.
extend
(
bl_node_issues
)
return
{
'success'
:
True
,
'bl_count'
:
len
(
bls
),
'issue_count'
:
len
(
error_package_issues
)
+
len
(
error_bl_issues
),
'package_issues'
:
'
\n
'
.
join
(
error_package_issues
)
if
error_package_issues
else
''
,
'bl_issues'
:
'
\n
'
.
join
(
error_bl_issues
)
if
error_bl_issues
else
''
}
def
_check_package_tracking_issues
(
self
,
bl
):
"""
检查小包轨迹问题 - 只检查真正的小包节点,不检查关务节点
"""
issues
=
[]
# 按问题类型分组统计
reverse_issues
=
{}
# 倒挂问题
missing_issues
=
{}
# 漏推问题
# 获取该提单下所有小包的同步日志
ship_packages
=
bl
.
ship_package_ids
for
package
in
ship_packages
:
# 按节点编码分组,根据同步时间降序,得到对应的同步日志对象,然后再根据节点编码的顺序排序,得到同步日志对象数组
logs_by_process
=
{}
for
log
in
package
.
sync_log_ids
:
if
log
.
process_code
not
in
logs_by_process
:
logs_by_process
[
log
.
process_code
]
=
log
else
:
# 如果已有该节点的日志,比较sync_time,保留最新的
if
log
.
sync_time
and
log
.
sync_time
>
logs_by_process
[
log
.
process_code
]
.
sync_time
:
logs_by_process
[
log
.
process_code
]
=
log
# 方法1: 根据节点的seq进行排序(推荐)
sync_logs
=
[]
bl_nodes
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
([(
'node_type'
,
'='
,
'package'
),(
'is_must'
,
'='
,
True
)],
order
=
'seq'
)
if
bl_nodes
:
for
node
in
bl_nodes
:
if
node
.
is_default
:
continue
if
node
.
tk_code
in
logs_by_process
:
sync_logs
.
append
(
logs_by_process
[
node
.
tk_code
])
sync_logs
=
[
log
for
log
in
sync_logs
if
log
is
not
None
]
#根据key排序获取logs
if
len
(
sync_logs
)
>=
2
:
for
i
in
range
(
1
,
len
(
sync_logs
)):
current_log
=
sync_logs
[
i
]
# 当前日志
previous_log
=
sync_logs
[
i
-
1
]
# 前一个日志
current_time
=
current_log
.
operate_time
# 当前日志时间
previous_time
=
previous_log
.
operate_time
# 前一个日志时间
if
current_time
and
previous_time
and
current_time
<
previous_time
:
try
:
# 检查节点类型,只处理小包节点
node
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
([(
'node_type'
,
'='
,
'package'
),
(
'tk_code'
,
'='
,
current_log
.
process_code
)])
if
node
:
# 只处理小包节点
progress_name
=
node
.
name
or
"空"
process_code
=
node
.
tk_code
or
"空"
issue_key
=
f
"{progress_name}({process_code})倒挂"
if
issue_key
not
in
reverse_issues
:
reverse_issues
[
issue_key
]
=
[]
reverse_issues
[
issue_key
]
.
append
(
package
.
logistic_order_no
)
except
Exception
as
e
:
_logger
.
warning
(
f
"构建小包倒挂问题描述失败: {str(e)}"
)
# 如果无法确定节点类型,跳过
continue
# 检查漏推问题 - 只检查小包节点
package_issues
=
self
.
_check_package_missing_nodes
(
package
,
sync_logs
)
for
issue
in
package_issues
:
# 提取问题类型
if
"轨迹漏推"
in
issue
:
try
:
# 安全地提取问题类型,避免索引越界
if
",出现"
in
issue
and
",涉及"
in
issue
:
issue_key
=
issue
.
split
(
",出现"
)[
1
]
.
split
(
",涉及"
)[
0
]
else
:
# 如果格式不匹配,使用整个问题描述作为key
issue_key
=
issue
if
issue_key
not
in
missing_issues
:
missing_issues
[
issue_key
]
=
[]
missing_issues
[
issue_key
]
.
append
(
package
.
logistic_order_no
)
except
(
IndexError
,
AttributeError
)
as
e
:
_logger
.
warning
(
f
"解析小包问题失败: {issue}, 错误: {str(e)}"
)
# 使用整个问题描述作为key
issue_key
=
issue
if
issue_key
not
in
missing_issues
:
missing_issues
[
issue_key
]
=
[]
missing_issues
[
issue_key
]
.
append
(
package
.
logistic_order_no
)
# 格式化倒挂问题
issue_counter
=
1
for
issue_type
,
packages
in
reverse_issues
.
items
():
package_list
=
self
.
_format_package_list
(
packages
)
issues
.
append
(
f
"{issue_counter}、{bl.bl_no},出现{issue_type},涉及小包{len(packages)},小包追踪号包括{package_list}"
)
issue_counter
+=
1
# 格式化漏推问题
for
issue_type
,
packages
in
missing_issues
.
items
():
package_list
=
self
.
_format_package_list
(
packages
)
issues
.
append
(
f
"{issue_counter}、{bl.bl_no},出现{issue_type},涉及小包{len(packages)},小包追踪号包括{package_list}"
)
issue_counter
+=
1
return
issues
def
_format_package_list
(
self
,
packages
):
"""
格式化小包列表,最多显示10个
"""
try
:
# 过滤掉None和空字符串
valid_packages
=
[
pkg
for
pkg
in
packages
if
pkg
and
str
(
pkg
)
.
strip
()]
if
not
valid_packages
:
return
"无有效追踪号"
if
len
(
valid_packages
)
<=
10
:
return
"/"
.
join
(
valid_packages
)
else
:
return
"/"
.
join
(
valid_packages
[:
10
])
+
"等"
except
Exception
as
e
:
_logger
.
warning
(
f
"格式化小包列表失败: {str(e)}"
)
return
"格式化失败"
def
_check_package_missing_nodes
(
self
,
package
,
sync_logs
):
"""
检查小包漏推节点
"""
issues
=
[]
# 获取所有小包节点,按顺序排序
package_nodes
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
([(
'node_type'
,
'='
,
'package'
),(
'is_must'
,
'='
,
True
)],
order
=
'seq'
)
if
not
package_nodes
:
return
issues
# 检查每个节点是否有对应的同步日志
for
node
in
package_nodes
:
# 跳过当前节点(最后一个节点)
if
node
==
package_nodes
[
-
1
]:
continue
# 跳过初始节点(is_default=True)
if
getattr
(
node
,
'is_default'
,
False
):
continue
# 检查是否有该节点的同步日志
has_node_log
=
any
(
log
.
process_code
==
node
.
tk_code
for
log
in
sync_logs
)
# 检查后续节点是否有日志
next_nodes
=
package_nodes
.
filtered
(
lambda
n
:
n
.
seq
>
node
.
seq
)
for
next_node
in
next_nodes
:
has_next_log
=
any
(
log
.
process_code
==
next_node
.
tk_code
for
log
in
sync_logs
)
# 如果后续节点有日志但当前节点没有,说明漏推
if
has_next_log
and
not
has_node_log
:
try
:
node_name
=
node
.
name
or
"空"
tk_code
=
node
.
tk_code
or
"空"
issues
.
append
(
f
"{node_name}({tk_code})轨迹漏推"
)
except
Exception
as
e
:
_logger
.
warning
(
f
"构建小包漏推问题描述失败: {str(e)}"
)
issues
.
append
(
"节点轨迹漏推"
)
break
return
issues
def
_check_bl_node_issues
(
self
,
bl
):
"""
检查提单关务节点问题
"""
issues
=
[]
# 按节点编码分组,根据同步时间降序,得到对应的同步日志对象
logs_by_bl_process
=
{}
for
log
in
bl
.
bl_sync_log_ids
:
if
log
.
process_code
not
in
logs_by_bl_process
:
logs_by_bl_process
[
log
.
process_code
]
=
log
else
:
# 如果已有该节点的日志,比较sync_time,保留最新的
if
log
.
sync_time
and
log
.
sync_time
>
logs_by_bl_process
[
log
.
process_code
]
.
sync_time
:
logs_by_bl_process
[
log
.
process_code
]
=
log
# 根据节点的seq进行排序
sync_logs
=
[]
# 获取所有提单节点,按业务顺序排序
bl_nodes
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
([(
'node_type'
,
'='
,
'bl'
),(
'is_must'
,
'='
,
True
)],
order
=
'seq'
)
if
bl_nodes
:
for
node
in
bl_nodes
:
if
node
.
tk_code
in
logs_by_bl_process
.
keys
():
sync_logs
.
append
(
logs_by_bl_process
[
node
.
tk_code
])
# 检查倒挂问题
if
len
(
sync_logs
)
>=
2
:
# 收集所有有日志的节点及其时间
node_logs
=
{}
# {node_index: (node, log, time)}
for
i
,
node
in
enumerate
(
bl_nodes
):
for
log
in
sync_logs
:
if
log
.
process_code
==
node
.
tk_code
:
node_logs
[
i
]
=
(
node
,
log
,
log
.
operate_time
)
break
# 遍历每个有日志的节点,与前面所有有日志的节点比较
for
current_idx
in
sorted
(
node_logs
.
keys
()):
current_node
,
current_log
,
current_time
=
node_logs
[
current_idx
]
# 与前面所有有日志的节点比较
for
prev_idx
in
sorted
(
node_logs
.
keys
()):
if
prev_idx
>=
current_idx
:
# 跳过自己和自己后面的节点
continue
prev_node
,
prev_log
,
prev_time
=
node_logs
[
prev_idx
]
# 检查时间顺序
if
current_time
and
prev_time
:
if
current_time
<
prev_time
:
try
:
progress_name
=
current_log
.
progress_name
or
"空"
process_code
=
current_log
.
process_code
or
"空"
issues
.
append
(
f
"{bl.bl_no},出现{progress_name}({process_code})倒挂"
)
except
Exception
as
e
:
_logger
.
warning
(
f
"构建倒序问题描述失败: {str(e)}"
)
issues
.
append
(
f
"{bl.bl_no},出现节点倒挂"
)
# 检查漏推问题
missing_issues
=
self
.
_check_bl_missing_nodes
(
bl
,
sync_logs
)
if
missing_issues
:
issues
.
extend
(
missing_issues
)
return
issues
def
_check_bl_missing_nodes
(
self
,
bl
,
sync_logs
):
"""
检查提单漏推节点
"""
issues
=
[]
# 获取所有提单节点,按顺序排序
bl_nodes
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
([(
'node_type'
,
'='
,
'bl'
),(
'is_must'
,
'='
,
True
)],
order
=
'seq'
)
if
not
bl_nodes
:
return
issues
# 检查每个节点是否有对应的同步日志
for
node
in
bl_nodes
:
# 跳过当前节点(最后一个节点)
if
node
==
bl_nodes
[
-
1
]:
continue
# 跳过初始节点(is_default=True)
if
getattr
(
node
,
'is_default'
,
False
):
continue
# 检查是否有该节点的同步日志
has_node_log
=
any
(
log
.
process_code
==
node
.
tk_code
for
log
in
sync_logs
)
# 检查后续节点是否有日志
next_nodes
=
bl_nodes
.
filtered
(
lambda
n
:
n
.
seq
>
node
.
seq
)
for
next_node
in
next_nodes
:
has_next_log
=
any
(
log
.
process_code
==
next_node
.
tk_code
for
log
in
sync_logs
)
# 如果后续节点有日志但当前节点没有,说明漏推
if
has_next_log
and
not
has_node_log
:
try
:
node_name
=
node
.
name
or
"空"
tk_code
=
node
.
tk_code
or
"空"
issues
.
append
(
f
"{bl.bl_no},出现{node_name}({tk_code})轨迹漏推"
)
except
Exception
as
e
:
_logger
.
warning
(
f
"构建提单漏推问题描述失败: {str(e)}"
)
issues
.
append
(
f
"{bl.bl_no},出现节点轨迹漏推"
)
break
return
issues
def
_send_patrol_email
(
self
,
result
):
"""
发送巡查邮件
"""
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
:
_logger
.
warning
(
"邮件配置不完整,跳过邮件发送"
)
return
# 解析接收邮箱
receiver_list
=
[
email
.
strip
()
for
email
in
receiver_emails
.
split
(
';'
)
if
email
.
strip
()]
# 构建邮件内容
subject
=
f
"(重要)推送预警/{fields.Date.today()}系统巡查轨迹"
# 构建邮件内容
package_content
=
result
[
'package_issues'
]
if
result
[
'package_issues'
]
else
""
bl_content
=
result
[
'bl_issues'
]
if
result
[
'bl_issues'
]
else
""
content
=
f
"""您好,经系统巡查。
%
s
%
s
%
s
%
s
请立即处理!!!"""
%
(
"发现以下提单存在小包轨迹倒挂/轨迹漏推"
if
package_content
else
""
,
package_content
if
package_content
else
""
,
"发现以下提单存在提单关务节点轨迹倒挂/轨迹漏推"
if
bl_content
else
""
,
bl_content
if
bl_content
else
""
)
# 发送邮件
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
,
})
.
send
()
# 更新发送记录
self
.
write
({
'email_sent'
:
True
,
'email_sent_time'
:
fields
.
Datetime
.
now
()
})
_logger
.
info
(
f
"巡查邮件发送成功,接收人: {receiver_list}"
)
except
Exception
as
e
:
_logger
.
error
(
f
"发送巡查邮件失败: {str(e)}"
)
return
bls
.
check_bl_patrol
()
def
action_manual_patrol
(
self
):
"""
...
...
ccs_connect_tiktok/models/cc_bill_loading.py
浏览文件 @
8f8ddcb9
...
...
@@ -4,12 +4,12 @@ import json
import
logging
import
ssl
from
datetime
import
timedelta
,
datetime
import
re
import
aiohttp
import
certifi
import
pytz
from
odoo
import
models
,
fields
,
api
,
_
from
odoo.exceptions
import
ValidationError
def
get_rfc339_time
(
utc_time
=
None
):
if
not
utc_time
:
...
...
@@ -362,7 +362,7 @@ class CcBl(models.Model):
# 如果提单所有小包的清关节点变成"是完成节点",则该提单状态变成已完成
if
all
(
line
.
state
.
is_done
for
line
in
self
.
ship_package_ids
)
and
self
.
unsync_package_count
<=
0
and
self
.
customs_clearance_status
.
is_done
and
self
.
is_bl_sync
:
self
.
done_func
()
self
.
done_func
(
is_email
=
True
)
def
change_customs_state_by_ship_package
(
self
,
package_state_obj
,
user_obj
=
False
):
"""
...
...
@@ -444,6 +444,382 @@ class CcBl(models.Model):
result
=
self
.
env
.
cr
.
fetchone
()
return
result
and
result
[
0
]
or
False
def
done_func
(
self
,
is_email
=
False
):
"""
变为已完成.先进行提单巡查,再进行提单状态变更
"""
bls
=
self
.
filtered
(
lambda
x
:
x
.
state
==
'ccing'
)
# 提单巡查
result
=
bls
.
check_bl_patrol
()
logging
.
info
(
f
"提单完成时手动巡查完成, 发现问题: {result['issue_count']}"
)
if
is_email
:
# 如果有问题,发送邮件
if
result
[
'issue_count'
]
>
0
:
bls
.
_send_patrol_email
(
result
)
else
:
#把提示进行提示
content
=
self
.
get_patrol_email_content
(
result
)
raise
ValidationError
(
content
)
super
(
CcBl
,
self
)
.
done_func
(
is_email
=
is_email
)
def
check_bl_patrol
(
self
):
"""
执行巡查逻辑
检查对象:提单日期为近x天(默认5天),清关中和完成的提单。
检查内容:符合要求的提单,提单关务节点同步日志是否出现倒挂
(根据配置的清关节点排序,若后一个节点的操作时间,小于前序节点时间,则属于倒挂),是否有漏推(若节点已产生同步日期,但前序节点无同步日期,则属于漏推。是当前节点的节点无需纳入判断);
"""
error_package_issues
=
[]
error_bl_issues
=
[]
package_issue_counter
=
1
# 小包问题独立编号
bl_issue_counter
=
1
# 提单问题独立编号
for
bl
in
self
:
# 检查小包轨迹问题
bl_package_issues
=
self
.
_check_package_tracking_issues
(
bl
)
if
bl_package_issues
:
# 为小包问题添加独立编号
for
i
,
issue
in
enumerate
(
bl_package_issues
):
# 使用正则表达式匹配任何数字+顿号的格式
if
re
.
match
(
r'^\d+、'
,
issue
):
# 替换开头的编号
bl_package_issues
[
i
]
=
re
.
sub
(
r'^\d+、'
,
f
"{package_issue_counter}、"
,
issue
,
1
)
else
:
# 如果没有编号,添加编号
bl_package_issues
[
i
]
=
f
"{package_issue_counter}、{issue}"
package_issue_counter
+=
1
error_package_issues
.
extend
(
bl_package_issues
)
# 检查提单关务节点问题
bl_node_issues
=
self
.
_check_bl_node_issues
(
bl
)
if
bl_node_issues
:
# 为提单问题添加独立编号
for
i
,
blissue
in
enumerate
(
bl_node_issues
):
# 使用正则表达式匹配任何数字+顿号的格式
if
re
.
match
(
r'^\d+、'
,
blissue
):
# 替换开头的编号
bl_node_issues
[
i
]
=
re
.
sub
(
r'^\d+、'
,
f
"{bl_issue_counter}、"
,
blissue
,
1
)
else
:
# 如果没有编号,添加编号
bl_node_issues
[
i
]
=
f
"{bl_issue_counter}、{blissue}"
bl_issue_counter
+=
1
error_bl_issues
.
extend
(
bl_node_issues
)
return
{
'success'
:
True
,
'bl_count'
:
len
(
self
),
'issue_count'
:
len
(
error_package_issues
)
+
len
(
error_bl_issues
),
'package_issues'
:
'
\n
'
.
join
(
error_package_issues
)
if
error_package_issues
else
''
,
'bl_issues'
:
'
\n
'
.
join
(
error_bl_issues
)
if
error_bl_issues
else
''
}
def
_check_package_tracking_issues
(
self
,
bl
):
"""
检查小包轨迹问题 - 只检查真正的小包节点,不检查关务节点
"""
issues
=
[]
# 按问题类型分组统计
reverse_issues
=
{}
# 倒挂问题
missing_issues
=
{}
# 漏推问题
# 获取该提单下所有小包的同步日志
ship_packages
=
bl
.
ship_package_ids
for
package
in
ship_packages
:
# 按节点编码分组,根据同步时间降序,得到对应的同步日志对象,然后再根据节点编码的顺序排序,得到同步日志对象数组
logs_by_process
=
{}
for
log
in
package
.
sync_log_ids
:
if
log
.
process_code
not
in
logs_by_process
:
logs_by_process
[
log
.
process_code
]
=
log
else
:
# 如果已有该节点的日志,比较sync_time,保留最新的
if
log
.
sync_time
and
log
.
sync_time
>
logs_by_process
[
log
.
process_code
]
.
sync_time
:
logs_by_process
[
log
.
process_code
]
=
log
# 方法1: 根据节点的seq进行排序(推荐)
sync_logs
=
[]
bl_nodes
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
([(
'node_type'
,
'='
,
'package'
),
(
'is_must'
,
'='
,
True
)],
order
=
'seq'
)
if
bl_nodes
:
for
node
in
bl_nodes
:
if
node
.
is_default
:
continue
if
node
.
tk_code
in
logs_by_process
:
sync_logs
.
append
(
logs_by_process
[
node
.
tk_code
])
sync_logs
=
[
log
for
log
in
sync_logs
if
log
is
not
None
]
# 根据key排序获取logs
if
len
(
sync_logs
)
>=
2
:
for
i
in
range
(
1
,
len
(
sync_logs
)):
current_log
=
sync_logs
[
i
]
# 当前日志
previous_log
=
sync_logs
[
i
-
1
]
# 前一个日志
current_time
=
current_log
.
operate_time
# 当前日志时间
previous_time
=
previous_log
.
operate_time
# 前一个日志时间
if
current_time
and
previous_time
and
current_time
<
previous_time
:
try
:
# 检查节点类型,只处理小包节点
node
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
(
[(
'node_type'
,
'='
,
'package'
),
(
'tk_code'
,
'='
,
current_log
.
process_code
)])
if
node
:
# 只处理小包节点
progress_name
=
node
.
name
or
"空"
process_code
=
node
.
tk_code
or
"空"
issue_key
=
f
"{progress_name}({process_code})倒挂"
if
issue_key
not
in
reverse_issues
:
reverse_issues
[
issue_key
]
=
[]
reverse_issues
[
issue_key
]
.
append
(
package
.
logistic_order_no
)
except
Exception
as
e
:
logging
.
warning
(
f
"构建小包倒挂问题描述失败: {str(e)}"
)
# 如果无法确定节点类型,跳过
continue
# 检查漏推问题 - 只检查小包节点
package_issues
=
self
.
_check_package_missing_nodes
(
package
,
sync_logs
)
for
issue
in
package_issues
:
# 提取问题类型
if
"轨迹漏推"
in
issue
:
try
:
# 安全地提取问题类型,避免索引越界
if
",出现"
in
issue
and
",涉及"
in
issue
:
issue_key
=
issue
.
split
(
",出现"
)[
1
]
.
split
(
",涉及"
)[
0
]
else
:
# 如果格式不匹配,使用整个问题描述作为key
issue_key
=
issue
if
issue_key
not
in
missing_issues
:
missing_issues
[
issue_key
]
=
[]
missing_issues
[
issue_key
]
.
append
(
package
.
logistic_order_no
)
except
(
IndexError
,
AttributeError
)
as
e
:
logging
.
warning
(
f
"解析小包问题失败: {issue}, 错误: {str(e)}"
)
# 使用整个问题描述作为key
issue_key
=
issue
if
issue_key
not
in
missing_issues
:
missing_issues
[
issue_key
]
=
[]
missing_issues
[
issue_key
]
.
append
(
package
.
logistic_order_no
)
# 格式化倒挂问题
issue_counter
=
1
for
issue_type
,
packages
in
reverse_issues
.
items
():
package_list
=
self
.
_format_package_list
(
packages
)
issues
.
append
(
f
"{issue_counter}、{bl.bl_no},出现{issue_type},涉及小包{len(packages)},小包追踪号包括{package_list}"
)
issue_counter
+=
1
# 格式化漏推问题
for
issue_type
,
packages
in
missing_issues
.
items
():
package_list
=
self
.
_format_package_list
(
packages
)
issues
.
append
(
f
"{issue_counter}、{bl.bl_no},出现{issue_type},涉及小包{len(packages)},小包追踪号包括{package_list}"
)
issue_counter
+=
1
return
issues
def
_format_package_list
(
self
,
packages
):
"""
格式化小包列表,最多显示10个
"""
try
:
# 过滤掉None和空字符串
valid_packages
=
[
pkg
for
pkg
in
packages
if
pkg
and
str
(
pkg
)
.
strip
()]
if
not
valid_packages
:
return
"无有效追踪号"
if
len
(
valid_packages
)
<=
10
:
return
"/"
.
join
(
valid_packages
)
else
:
return
"/"
.
join
(
valid_packages
[:
10
])
+
"等"
except
Exception
as
e
:
logging
.
warning
(
f
"格式化小包列表失败: {str(e)}"
)
return
"格式化失败"
def
_check_package_missing_nodes
(
self
,
package
,
sync_logs
):
"""
检查小包漏推节点
"""
issues
=
[]
# 获取所有小包节点,按顺序排序
package_nodes
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
([(
'node_type'
,
'='
,
'package'
),
(
'is_must'
,
'='
,
True
)],
order
=
'seq'
)
if
not
package_nodes
:
return
issues
# 检查每个节点是否有对应的同步日志
for
node
in
package_nodes
:
# 跳过当前节点(最后一个节点)
if
node
==
package_nodes
[
-
1
]:
continue
# 跳过初始节点(is_default=True)
if
getattr
(
node
,
'is_default'
,
False
):
continue
# 检查是否有该节点的同步日志
has_node_log
=
any
(
log
.
process_code
==
node
.
tk_code
for
log
in
sync_logs
)
# 检查后续节点是否有日志
next_nodes
=
package_nodes
.
filtered
(
lambda
n
:
n
.
seq
>
node
.
seq
)
for
next_node
in
next_nodes
:
has_next_log
=
any
(
log
.
process_code
==
next_node
.
tk_code
for
log
in
sync_logs
)
# 如果后续节点有日志但当前节点没有,说明漏推
if
has_next_log
and
not
has_node_log
:
try
:
node_name
=
node
.
name
or
"空"
tk_code
=
node
.
tk_code
or
"空"
issues
.
append
(
f
"{node_name}({tk_code})轨迹漏推"
)
except
Exception
as
e
:
logging
.
warning
(
f
"构建小包漏推问题描述失败: {str(e)}"
)
issues
.
append
(
"节点轨迹漏推"
)
break
return
issues
def
_check_bl_node_issues
(
self
,
bl
):
"""
检查提单关务节点问题
"""
issues
=
[]
# 按节点编码分组,根据同步时间降序,得到对应的同步日志对象
logs_by_bl_process
=
{}
for
log
in
bl
.
bl_sync_log_ids
:
if
log
.
process_code
not
in
logs_by_bl_process
:
logs_by_bl_process
[
log
.
process_code
]
=
log
else
:
# 如果已有该节点的日志,比较sync_time,保留最新的
if
log
.
sync_time
and
log
.
sync_time
>
logs_by_bl_process
[
log
.
process_code
]
.
sync_time
:
logs_by_bl_process
[
log
.
process_code
]
=
log
# 根据节点的seq进行排序
sync_logs
=
[]
# 获取所有提单节点,按业务顺序排序
bl_nodes
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
([(
'node_type'
,
'='
,
'bl'
),
(
'is_must'
,
'='
,
True
)],
order
=
'seq'
)
if
bl_nodes
:
for
node
in
bl_nodes
:
if
node
.
tk_code
in
logs_by_bl_process
.
keys
():
sync_logs
.
append
(
logs_by_bl_process
[
node
.
tk_code
])
# 检查倒挂问题
if
len
(
sync_logs
)
>=
2
:
# 收集所有有日志的节点及其时间
node_logs
=
{}
# {node_index: (node, log, time)}
for
i
,
node
in
enumerate
(
bl_nodes
):
for
log
in
sync_logs
:
if
log
.
process_code
==
node
.
tk_code
:
node_logs
[
i
]
=
(
node
,
log
,
log
.
operate_time
)
break
# 遍历每个有日志的节点,与前面所有有日志的节点比较
for
current_idx
in
sorted
(
node_logs
.
keys
()):
current_node
,
current_log
,
current_time
=
node_logs
[
current_idx
]
# 与前面所有有日志的节点比较
for
prev_idx
in
sorted
(
node_logs
.
keys
()):
if
prev_idx
>=
current_idx
:
# 跳过自己和自己后面的节点
continue
prev_node
,
prev_log
,
prev_time
=
node_logs
[
prev_idx
]
# 检查时间顺序
if
current_time
and
prev_time
:
if
current_time
<
prev_time
:
try
:
progress_name
=
current_log
.
progress_name
or
"空"
process_code
=
current_log
.
process_code
or
"空"
issues
.
append
(
f
"{bl.bl_no},出现{progress_name}({process_code})倒挂"
)
except
Exception
as
e
:
logging
.
warning
(
f
"构建倒序问题描述失败: {str(e)}"
)
issues
.
append
(
f
"{bl.bl_no},出现节点倒挂"
)
# 检查漏推问题
missing_issues
=
self
.
_check_bl_missing_nodes
(
bl
,
sync_logs
)
if
missing_issues
:
issues
.
extend
(
missing_issues
)
return
issues
def
_check_bl_missing_nodes
(
self
,
bl
,
sync_logs
):
"""
检查提单漏推节点
"""
issues
=
[]
# 获取所有提单节点,按顺序排序
bl_nodes
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
([(
'node_type'
,
'='
,
'bl'
),
(
'is_must'
,
'='
,
True
)],
order
=
'seq'
)
if
not
bl_nodes
:
return
issues
# 检查每个节点是否有对应的同步日志
for
node
in
bl_nodes
:
# 跳过当前节点(最后一个节点)
if
node
==
bl_nodes
[
-
1
]:
continue
# 跳过初始节点(is_default=True)
if
getattr
(
node
,
'is_default'
,
False
):
continue
# 检查是否有该节点的同步日志
has_node_log
=
any
(
log
.
process_code
==
node
.
tk_code
for
log
in
sync_logs
)
# 检查后续节点是否有日志
next_nodes
=
bl_nodes
.
filtered
(
lambda
n
:
n
.
seq
>
node
.
seq
)
for
next_node
in
next_nodes
:
has_next_log
=
any
(
log
.
process_code
==
next_node
.
tk_code
for
log
in
sync_logs
)
# 如果后续节点有日志但当前节点没有,说明漏推
if
has_next_log
and
not
has_node_log
:
try
:
node_name
=
node
.
name
or
"空"
tk_code
=
node
.
tk_code
or
"空"
issues
.
append
(
f
"{bl.bl_no},出现{node_name}({tk_code})轨迹漏推"
)
except
Exception
as
e
:
logging
.
warning
(
f
"构建提单漏推问题描述失败: {str(e)}"
)
issues
.
append
(
f
"{bl.bl_no},出现节点轨迹漏推"
)
break
return
issues
def
_send_patrol_email
(
self
,
result
,
patrol_obj
=
False
):
"""
发送巡查邮件
"""
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
"(重要)推送预警/{fields.Date.today()}系统巡查轨迹"
content
=
self
.
get_patrol_email_content
(
result
)
# 发送邮件
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
,
})
.
send
()
# 更新发送记录
if
patrol_obj
:
patrol_obj
.
write
({
'email_sent'
:
True
,
'email_sent_time'
:
fields
.
Datetime
.
now
()
})
logging
.
info
(
f
"巡查邮件发送成功,接收人: {receiver_list}"
)
except
Exception
as
e
:
logging
.
error
(
f
"发送巡查邮件失败: {str(e)}"
)
def
get_patrol_email_content
(
self
,
result
,
title
=
'您好,经系统巡查。'
):
"""
获取巡查邮件内容
"""
# 构建邮件内容
package_content
=
result
[
'package_issues'
]
if
result
[
'package_issues'
]
else
""
bl_content
=
result
[
'bl_issues'
]
if
result
[
'bl_issues'
]
else
""
content
=
f
"""
%
s
%
s
%
s
%
s
%
s
请立即处理!!!"""
%
(
title
,
"发现以下提单存在小包轨迹倒挂/轨迹漏推"
if
package_content
else
""
,
package_content
if
package_content
else
""
,
"发现以下提单存在提单关务节点轨迹倒挂/轨迹漏推"
if
bl_content
else
""
,
bl_content
if
bl_content
else
""
)
return
content
# =============同步提单状态==================================
# 定义一个方法, 获取提单,并回传提单状态
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论