Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
H
hh_ccs
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
贺阳
hh_ccs
Commits
9e6a63bc
提交
9e6a63bc
authored
11月 03, 2025
作者:
贺阳
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
提单有一个小包是清关开始状态,有同步日志,提单获取pod并推送,该小包产生了倒叙的数据
上级
d5ccac2a
显示空白字符变更
内嵌
并排
正在显示
2 个修改的文件
包含
121 行增加
和
28 行删除
+121
-28
batch_get_pod_info_wizard.py
ccs_base/wizard/batch_get_pod_info_wizard.py
+46
-3
batch_get_pod_info_wizard.py
ccs_connect_tiktok/wizard/batch_get_pod_info_wizard.py
+75
-25
没有找到文件。
ccs_base/wizard/batch_get_pod_info_wizard.py
浏览文件 @
9e6a63bc
...
@@ -1765,7 +1765,7 @@ class BatchGetPodInfoWizard(models.TransientModel):
...
@@ -1765,7 +1765,7 @@ class BatchGetPodInfoWizard(models.TransientModel):
def
get_detail_info
(
self
,
processed_files
):
def
get_detail_info
(
self
,
processed_files
):
"""
"""
获取提单对应的节点以及时间
获取提单对应的节点以及时间
:param processed_files: 处理后的文件数组
:param processed_files: 处理后的文件数组
(应该已经包含valid_packages字段,只包含满足条件的小包)
:return: 提单对应的节点以及节点操作时间
:return: 提单对应的节点以及节点操作时间
"""
"""
ship_packages
=
[]
ship_packages
=
[]
...
@@ -1782,6 +1782,30 @@ class BatchGetPodInfoWizard(models.TransientModel):
...
@@ -1782,6 +1782,30 @@ class BatchGetPodInfoWizard(models.TransientModel):
if
not
pod_node
:
if
not
pod_node
:
_logger
.
info
(
f
"未找到尾程POD节点匹配的节点,提单号: {bl.bl_no}"
)
_logger
.
info
(
f
"未找到尾程POD节点匹配的节点,提单号: {bl.bl_no}"
)
continue
continue
# 只使用满足条件的小包(经过验证的valid_packages)
valid_packages
=
file_info
.
get
(
'valid_packages'
,
[])
if
not
valid_packages
:
_logger
.
warning
(
f
"提单 {bl.bl_no} 没有满足条件的小包,跳过节点推送"
)
continue
# 从valid_packages中提取小包ID(记录集对象或列表)
if
hasattr
(
valid_packages
,
'ids'
):
# 如果是记录集对象,直接获取IDs
valid_package_ids
=
valid_packages
.
ids
elif
isinstance
(
valid_packages
,
list
):
# 如果是列表,提取每个对象的ID
valid_package_ids
=
[
p
.
id
for
p
in
valid_packages
if
hasattr
(
p
,
'id'
)]
else
:
_logger
.
warning
(
f
"提单 {bl.bl_no} valid_packages格式不正确: {type(valid_packages)}"
)
valid_package_ids
=
[]
_logger
.
info
(
f
"提单 {bl.bl_no} 满足条件的小包ID: {valid_package_ids} (共 {len(valid_package_ids)} 个)"
)
if
not
valid_package_ids
:
_logger
.
warning
(
f
"提单 {bl.bl_no} 满足条件的小包ID为空,跳过节点推送"
)
continue
# 从PDF文件提取红色框的时间
# 从PDF文件提取红色框的时间
file_data
=
file_info
.
get
(
'file_data'
)
file_data
=
file_info
.
get
(
'file_data'
)
if
not
file_data
:
if
not
file_data
:
...
@@ -1795,10 +1819,10 @@ class BatchGetPodInfoWizard(models.TransientModel):
...
@@ -1795,10 +1819,10 @@ class BatchGetPodInfoWizard(models.TransientModel):
if
extracted_times
:
if
extracted_times
:
# 取最早的时间作为节点操作时间
# 取最早的时间作为节点操作时间
earliest_time
=
min
(
extracted_times
)
earliest_time
=
min
(
extracted_times
)
_logger
.
info
(
f
"提取到最早时间: {earliest_time},将作为节点操作时间"
)
_logger
.
info
(
f
"提取到最早时间: {earliest_time},将作为节点操作时间
,满足条件的小包数量: {len(valid_package_ids)},小包ID: {valid_package_ids}
"
)
ship_packages
.
append
({
ship_packages
.
append
({
'bl_id'
:
bl
.
id
,
'bl_id'
:
bl
.
id
,
'id'
:
bl
.
ship_package_ids
.
ids
,
'id'
:
valid_package_ids
,
# 只包含满足条件的小包ID
'tally_time'
:
str
(
earliest_time
)
'tally_time'
:
str
(
earliest_time
)
})
})
else
:
else
:
...
@@ -2318,6 +2342,18 @@ class BatchGetPodInfoWizard(models.TransientModel):
...
@@ -2318,6 +2342,18 @@ class BatchGetPodInfoWizard(models.TransientModel):
# OCR文本数据量小,可以直接存储
# OCR文本数据量小,可以直接存储
if
'ocr_texts'
in
file_info
:
if
'ocr_texts'
in
file_info
:
data
[
'ocr_texts'
]
=
file_info
[
'ocr_texts'
]
data
[
'ocr_texts'
]
=
file_info
[
'ocr_texts'
]
# 保存valid_packages的ID列表(记录集对象无法直接序列化)
if
'valid_packages'
in
file_info
and
file_info
[
'valid_packages'
]:
valid_packages
=
file_info
[
'valid_packages'
]
# 如果是记录集对象,提取ID列表
if
hasattr
(
valid_packages
,
'ids'
):
data
[
'valid_package_ids'
]
=
valid_packages
.
ids
elif
isinstance
(
valid_packages
,
list
):
# 如果是列表,提取每个对象的ID
data
[
'valid_package_ids'
]
=
[
p
.
id
for
p
in
valid_packages
if
hasattr
(
p
,
'id'
)]
else
:
data
[
'valid_package_ids'
]
=
[]
_logger
.
info
(
f
"序列化时保存valid_packages: 提单 {bl.bl_no}, 满足条件的小包ID: {data['valid_package_ids']}"
)
serialized_data
.
append
(
data
)
serialized_data
.
append
(
data
)
return
json
.
dumps
(
serialized_data
,
ensure_ascii
=
False
)
return
json
.
dumps
(
serialized_data
,
ensure_ascii
=
False
)
...
@@ -2377,6 +2413,13 @@ class BatchGetPodInfoWizard(models.TransientModel):
...
@@ -2377,6 +2413,13 @@ class BatchGetPodInfoWizard(models.TransientModel):
# 如果有OCR文本,也恢复
# 如果有OCR文本,也恢复
if
'ocr_texts'
in
data
:
if
'ocr_texts'
in
data
:
file_info
[
'ocr_texts'
]
=
data
[
'ocr_texts'
]
file_info
[
'ocr_texts'
]
=
data
[
'ocr_texts'
]
# 恢复valid_packages(从ID列表重建记录集对象)
if
'valid_package_ids'
in
data
and
data
[
'valid_package_ids'
]:
valid_package_ids
=
data
[
'valid_package_ids'
]
# 重建记录集对象
valid_packages
=
self
.
env
[
'cc.ship.package'
]
.
browse
(
valid_package_ids
)
file_info
[
'valid_packages'
]
=
valid_packages
_logger
.
info
(
f
"反序列化时恢复valid_packages: 提单 {bl.bl_no}, 满足条件的小包ID: {valid_package_ids}, 数量: {len(valid_packages)}"
)
processed_files
.
append
(
file_info
)
processed_files
.
append
(
file_info
)
return
processed_files
return
processed_files
except
Exception
as
e
:
except
Exception
as
e
:
...
...
ccs_connect_tiktok/wizard/batch_get_pod_info_wizard.py
浏览文件 @
9e6a63bc
...
@@ -5,7 +5,7 @@ from odoo import models, fields, api, _
...
@@ -5,7 +5,7 @@ from odoo import models, fields, api, _
import
pytz
import
pytz
from
datetime
import
datetime
from
datetime
import
datetime
_logger
=
logging
.
getLogger
(
__name__
)
_logger
=
logging
.
getLogger
(
__name__
)
from
odoo.exceptions
import
ValidationError
class
BatchGetPodInfoWizard
(
models
.
TransientModel
):
class
BatchGetPodInfoWizard
(
models
.
TransientModel
):
_inherit
=
'batch.get.pod.info.wizard'
_inherit
=
'batch.get.pod.info.wizard'
...
@@ -93,7 +93,6 @@ class BatchGetPodInfoWizard(models.TransientModel):
...
@@ -93,7 +93,6 @@ class BatchGetPodInfoWizard(models.TransientModel):
except
Exception
as
e
:
except
Exception
as
e
:
_logger
.
error
(
f
"验证提单 {bl.bl_no} 节点推送条件失败: {str(e)}"
)
_logger
.
error
(
f
"验证提单 {bl.bl_no} 节点推送条件失败: {str(e)}"
)
continue
continue
return
valid_files
return
valid_files
def
_check_packages_same_status
(
self
,
ship_packages
):
def
_check_packages_same_status
(
self
,
ship_packages
):
...
@@ -106,24 +105,40 @@ class BatchGetPodInfoWizard(models.TransientModel):
...
@@ -106,24 +105,40 @@ class BatchGetPodInfoWizard(models.TransientModel):
if
not
ship_packages
or
len
(
ship_packages
)
<=
1
:
if
not
ship_packages
or
len
(
ship_packages
)
<=
1
:
return
True
return
True
# 使用SQL批量查询所有小包的
状态ID
# 使用SQL批量查询所有小包的
不同状态(包括NULL)
package_ids
=
ship_packages
.
ids
package_ids
=
ship_packages
.
ids
# 直接查询不同的状态(DISTINCT),更高效
sql_query
=
"""
sql_query
=
"""
SELECT DISTINCT state
SELECT DISTINCT state
FROM cc_ship_package
FROM cc_ship_package
WHERE id IN
%
s
AND state IS NOT NULL
WHERE id IN
%
s
"""
"""
self
.
env
.
cr
.
execute
(
sql_query
,
(
tuple
(
package_ids
),))
self
.
env
.
cr
.
execute
(
sql_query
,
(
tuple
(
package_ids
),))
results
=
self
.
env
.
cr
.
fetchall
()
# 直接解包元组中的state值
distinct_states
=
[
row
[
0
]
for
row
in
self
.
env
.
cr
.
fetchall
()]
unique_status_count
=
len
(
distinct_states
)
# 检查是否有NULL状态
has_null
=
None
in
distinct_states
# 如果只有一个不同的状态ID,说明所有小包都是同一状态
# 详细日志
unique_status_count
=
len
(
results
)
_logger
.
info
(
f
"提单小包状态检查: {len(package_ids)} 个小包,有 {unique_status_count} 种不同状态"
if
unique_status_count
>
1
else
f
"提单小包状态检查: {len(package_ids)} 个小包,所有小包都是同一状态"
)
_logger
.
info
(
f
"提单小包状态检查: {len(package_ids)} 个小包,{unique_status_count} 种不同状态"
)
return
unique_status_count
<=
1
# 判断是否同一状态
# 如果只有1种状态且不是NULL,说明所有小包都是同一状态
if
unique_status_count
==
1
and
not
has_null
:
return
True
# 如果有多于1种状态,或者有NULL状态,都认为状态不一致
else
:
return
False
except
Exception
as
e
:
except
Exception
as
e
:
_logger
.
error
(
f
"检查小包状态一致性失败: {str(e)}"
)
_logger
.
error
(
f
"检查小包状态一致性失败: {str(e)}"
)
import
traceback
_logger
.
error
(
traceback
.
format_exc
())
# 即使检查失败,也返回False(状态不一致),继续执行后续的逐个验证逻辑,而不是中断整个流程
_logger
.
warning
(
f
"状态检查异常,将逐个验证所有小包"
)
return
False
return
False
def
_get_package_operation_times
(
self
,
package_ids
):
def
_get_package_operation_times
(
self
,
package_ids
):
...
@@ -152,7 +167,7 @@ class BatchGetPodInfoWizard(models.TransientModel):
...
@@ -152,7 +167,7 @@ class BatchGetPodInfoWizard(models.TransientModel):
MAX(n.operate_time) as latest_operation_time
MAX(n.operate_time) as latest_operation_time
FROM cc_ship_package p
FROM cc_ship_package p
LEFT JOIN cc_ship_package_sync_log n ON p.id = n.package_id
LEFT JOIN cc_ship_package_sync_log n ON p.id = n.package_id
AND n.process_code =
p.tk_code
AND n.process_code =
(SELECT tk_code FROM cc_node WHERE id = p.state ORDER BY operate_time DESC LIMIT 1)
WHERE p.id IN
%
s
WHERE p.id IN
%
s
GROUP BY p.id
GROUP BY p.id
"""
"""
...
@@ -200,44 +215,64 @@ class BatchGetPodInfoWizard(models.TransientModel):
...
@@ -200,44 +215,64 @@ class BatchGetPodInfoWizard(models.TransientModel):
第一步:对比小包当前节点的操作时间是否小于提取时间(同时区对比)
第一步:对比小包当前节点的操作时间是否小于提取时间(同时区对比)
第二步:如果是需要补推的,提取时间 − 小包当前节点的操作时间 < 前序间隔时间的不能推送
第二步:如果是需要补推的,提取时间 − 小包当前节点的操作时间 < 前序间隔时间的不能推送
:param package: 小包对象
:param package: 小包对象
:param extract_time: 提取时间
:param extract_time: 提取时间
(UTC时间)
:param bl_no: 提单号
:param bl_no: 提单号
:return: 是否满足推送条件
:return: 是否满足推送条件
"""
"""
try
:
try
:
# 获取小包当前节点的操作时间
# 获取小包当前节点的操作时间
(数据库中的时间,应该是UTC时间)
current_node_operation_time
=
self
.
_get_package_operation_times
(
package
.
id
)
current_node_operation_time
=
self
.
_get_package_operation_times
(
package
.
id
)
#没有操作时间,代表没有推送过,可以推送
#没有操作时间,代表没有推送过,可以推送
if
not
current_node_operation_time
:
if
not
current_node_operation_time
:
_logger
.
info
(
f
"小包 {package.id} 没有操作时间,可以推送"
)
return
True
return
True
# 记录详细信息用于调试
_logger
.
info
(
f
"小包 {package.id} (状态: {package.state.name if package.state else 'None'}) 验证推送条件:"
)
_logger
.
info
(
f
" - 提取时间(UTC): {extract_time}"
)
_logger
.
info
(
f
" - 操作时间(UTC): {current_node_operation_time}"
)
# 第一步:对比小包当前节点的操作时间是否小于提取时间(同时区对比)
# 第一步:对比小包当前节点的操作时间是否小于提取时间(同时区对比)
if
current_node_operation_time
>=
extract_time
:
if
current_node_operation_time
>=
extract_time
:
_logger
.
warning
(
f
"小包 {package.id} 当前节点操作时间 {current_node_operation_time} >= 提取时间 {extract_time},不满足推送条件"
)
_logger
.
warning
(
f
"小包 {package.id} 当前节点操作时间 {current_node_operation_time} >= 提取时间 {extract_time},不满足推送条件
(倒挂)
"
)
return
False
return
False
# 计算时间差
time_diff
=
extract_time
-
current_node_operation_time
time_diff_minutes
=
time_diff
.
total_seconds
()
/
60
_logger
.
info
(
f
" - 时间差: {time_diff_minutes:.1f} 分钟"
)
# 第二步:如果是需要补推的,提取时间 − 小包当前节点的操作时间 < 前序间隔时间的不能推送
# 第二步:如果是需要补推的,提取时间 − 小包当前节点的操作时间 < 前序间隔时间的不能推送
flag
,
match_node
=
self
.
_need_supplement_push
(
package
)
flag
,
match_node
=
self
.
_need_supplement_push
(
package
)
_logger
.
info
(
f
" - 是否需要补推: {flag}, 匹配节点: {match_node.name if match_node else 'None'}"
)
# 如果需要补推,检查前序间隔时间
if
flag
:
if
flag
:
interval_time
=
self
.
_get_write_node_previous_interval_time
(
package
,
match_node
)
interval_time
=
self
.
_get_write_node_previous_interval_time
(
package
,
match_node
)
if
interval_time
:
if
interval_time
:
# 计算提取时间减去小包当前节点的操作时间
time_diff
=
extract_time
-
current_node_operation_time
# 将时间差转换为分钟数
time_diff_minutes
=
time_diff
.
total_seconds
()
/
60
# 将间隔时间转换为分钟数
# 将间隔时间转换为分钟数
interval_minutes
=
interval_time
.
total_seconds
()
/
60
interval_minutes
=
interval_time
.
total_seconds
()
/
60
_logger
.
info
(
f
" - 前序间隔时间: {interval_minutes:.1f} 分钟"
)
_logger
.
info
(
f
" - 时间差 {time_diff_minutes:.1f} 分钟 vs 前序间隔时间 {interval_minutes:.1f} 分钟"
)
# 检查:提取时间 − 小包当前节点的操作时间 < 前序间隔时间的不能推送
if
time_diff_minutes
<
interval_minutes
:
if
time_diff_minutes
<
interval_minutes
:
_logger
.
warning
(
f
"小包 {package.id} 时间差 {time_diff_minutes:.1f} 分钟 < 前序间隔时间 {interval_minutes:.1f} 分钟,不满足补推条件"
)
_logger
.
warning
(
f
"小包 {package.id} 时间差 {time_diff_minutes:.1f} 分钟 < 前序间隔时间 {interval_minutes:.1f} 分钟,不满足补推条件
(倒挂)
"
)
return
False
return
False
else
:
else
:
_logger
.
info
(
f
"小包 {package.id} 时间差 {time_diff_minutes:.1f} 分钟 >= 前序间隔时间 {interval_minutes:.1f} 分钟,满足补推条件"
)
_logger
.
info
(
f
"小包 {package.id} 时间差 {time_diff_minutes:.1f} 分钟 >= 前序间隔时间 {interval_minutes:.1f} 分钟,满足补推条件"
)
else
:
_logger
.
warning
(
f
"小包 {package.id} 需要补推但未获取到前序间隔时间,跳过补推检查"
)
else
:
_logger
.
info
(
f
"小包 {package.id} 不需要补推(当前状态: {package.state.name if package.state else 'None'}, 状态ID: {package.state.id if package.state else 'None'}),跳过前序间隔时间检查"
)
_logger
.
info
(
f
"小包 {package.id} 满足推送条件"
)
_logger
.
info
(
f
"小包 {package.id} 满足推送条件"
)
return
True
return
True
except
Exception
as
e
:
except
Exception
as
e
:
_logger
.
error
(
f
"验证小包 {package.id} 推送条件失败: {str(e)}"
)
_logger
.
error
(
f
"验证小包 {package.id} 推送条件失败: {str(e)}"
)
import
traceback
_logger
.
error
(
traceback
.
format_exc
())
return
False
return
False
...
@@ -245,22 +280,37 @@ class BatchGetPodInfoWizard(models.TransientModel):
...
@@ -245,22 +280,37 @@ class BatchGetPodInfoWizard(models.TransientModel):
"""
"""
判断是否需要补推节点
判断是否需要补推节点
:param package: 小包对象
:param package: 小包对象
:return:
是否需要补推
:return:
(是否需要补推, POD节点对象)
"""
"""
pod_node
=
False
pod_node
=
False
try
:
try
:
# 查找对应的清关节点(勾选了POD节点匹配的节点)
# 查找对应的清关节点(勾选了POD节点匹配的节点)
pod_node
=
self
.
env
[
'cc.node'
]
.
search
([
pod_node
=
self
.
env
[
'cc.node'
]
.
search
([
(
'is_pod_node'
,
'='
,
True
),
(
'is_pod_node'
,
'='
,
True
),
(
'node_type'
,
'='
,
'package'
)
(
'node_type'
,
'='
,
'package'
)
],
limit
=
1
)
],
limit
=
1
)
#小包节点在这个节点之前,则需要补推节点
if
pod_node
:
if
not
pod_node
:
return
package
.
state
.
id
<
pod_node
.
id
,
pod_node
_logger
.
info
(
f
"小包 {package.id} 未找到POD节点,不需要补推"
)
return
False
,
pod_node
return
False
,
pod_node
# 小包节点在这个节点之前,则需要补推节点
current_state_id
=
package
.
state
.
id
if
package
.
state
else
None
pod_node_id
=
pod_node
.
id
_logger
.
info
(
f
"小包 {package.id} 补推判断: 当前状态ID={current_state_id}, POD节点ID={pod_node_id}, 需要补推={current_state_id < pod_node_id if current_state_id else False}"
)
if
current_state_id
:
need_push
=
current_state_id
<
pod_node_id
return
need_push
,
pod_node
else
:
_logger
.
warning
(
f
"小包 {package.id} 没有状态,不需要补推"
)
return
False
,
pod_node
except
Exception
as
e
:
except
Exception
as
e
:
_logger
.
error
(
f
"判断小包 {package.id} 是否需要补推失败: {str(e)}"
)
_logger
.
error
(
f
"判断小包 {package.id} 是否需要补推失败: {str(e)}"
)
return
False
,
pod_node
import
traceback
_logger
.
error
(
traceback
.
format_exc
())
return
False
,
pod_node
def
_get_write_node_previous_interval_time
(
self
,
package
,
match_node
):
def
_get_write_node_previous_interval_time
(
self
,
package
,
match_node
):
"""
"""
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论