Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
H
hh_ccs
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
贺阳
hh_ccs
Commits
0c22b124
提交
0c22b124
authored
1月 15, 2026
作者:
贺阳
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
批量获取尾程pod和获取货站pod的优化
上级
8f37205a
显示空白字符变更
内嵌
并排
正在显示
5 个修改的文件
包含
499 行增加
和
938 行删除
+499
-938
timer.xml
ccs_base/data/timer.xml
+1
-1
common_common.py
ccs_base/models/common_common.py
+445
-2
batch_get_lastmile_pod_info_wizard.py
ccs_base/wizard/batch_get_lastmile_pod_info_wizard.py
+23
-458
batch_get_pod_info_wizard.py
ccs_base/wizard/batch_get_pod_info_wizard.py
+29
-476
mail_push.py
consumers/mail_push.py
+1
-1
没有找到文件。
ccs_base/data/timer.xml
浏览文件 @
0c22b124
...
@@ -28,7 +28,7 @@
...
@@ -28,7 +28,7 @@
<!-- 清理向导生成的临时附件-->
<!-- 清理向导生成的临时附件-->
<record
id=
"cron_cleanup_temp_attachments"
model=
"ir.cron"
>
<record
id=
"cron_cleanup_temp_attachments"
model=
"ir.cron"
>
<field
name=
"name"
>
清理向导临时附件
</field>
<field
name=
"name"
>
清理向导临时附件
</field>
<field
name=
"model_id"
ref=
"model_
batch_get_pod_info_wizard
"
/>
<field
name=
"model_id"
ref=
"model_
common_common
"
/>
<field
name=
"state"
>
code
</field>
<field
name=
"state"
>
code
</field>
<field
name=
"code"
>
model.cron_cleanup_temp_attachments()
</field>
<field
name=
"code"
>
model.cron_cleanup_temp_attachments()
</field>
<field
name=
'interval_number'
>
1
</field>
<field
name=
'interval_number'
>
1
</field>
...
...
ccs_base/models/common_common.py
浏览文件 @
0c22b124
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
import
base64
import
datetime
import
datetime
import
gc
import
json
import
logging
import
logging
import
os
import
tempfile
import
time
import
pytz
import
pytz
from
odoo
import
models
import
requests
from
odoo
import
models
,
_
from
odoo.exceptions
import
ValidationError
from
.redis_connection
import
redis_connection
from
.redis_connection
import
redis_connection
...
@@ -16,7 +24,6 @@ class CommonCommon(models.Model):
...
@@ -16,7 +24,6 @@ class CommonCommon(models.Model):
_name
=
'common.common'
_name
=
'common.common'
_description
=
u'公用基础类'
_description
=
u'公用基础类'
def
process_num
(
self
,
input_str
):
def
process_num
(
self
,
input_str
):
"""
"""
处理导入
处理导入
...
@@ -168,6 +175,442 @@ class CommonCommon(models.Model):
...
@@ -168,6 +175,442 @@ class CommonCommon(models.Model):
"""
"""
return
r
return
r
def
get_pod_pdf_files
(
self
,
bill_numbers
,
api_sub_path
):
"""
获取POD PDF文件
:param bill_numbers: 订单号列表
:param api_sub_path: API子路径
:return: PDF文件列表
"""
api_url
=
self
.
env
[
'ir.config_parameter'
]
.
sudo
()
.
get_param
(
'last_mile_pod_api_url'
,
'http://172.104.52.150:7002'
)
if
not
api_url
:
raise
ValidationError
(
_
(
'API URL not configured'
))
request_data
=
{
"bill_numbers"
:
bill_numbers
}
try
:
response
=
requests
.
post
(
f
"{api_url}{api_sub_path}"
,
headers
=
{
'Content-Type'
:
'application/json'
,
'Accept'
:
'application/json'
},
json
=
request_data
)
if
response
.
status_code
==
200
:
result
=
response
.
json
()
if
not
result
:
raise
ValidationError
(
_
(
'API returned empty response'
))
if
not
result
.
get
(
'success'
):
error_msg
=
result
.
get
(
'message'
,
'Unknown error'
)
raise
ValidationError
(
_
(
'API returned error:
%
s'
)
%
error_msg
)
results
=
result
.
get
(
'results'
,
[])
if
not
results
:
raise
ValidationError
(
_
(
'No PDF files found in API response'
))
pdf_file_arr
=
[]
for
result_item
in
results
:
if
result_item
.
get
(
'success'
):
bill_number
=
result_item
.
get
(
'bill_number'
)
filename
=
result_item
.
get
(
'filename'
)
base64_data
=
result_item
.
get
(
'base64'
)
pdf_file_arr
.
append
({
'bl_no'
:
bill_number
,
'file_name'
:
filename
,
'file_data'
:
base64_data
})
return
pdf_file_arr
else
:
raise
ValidationError
(
_
(
'Failed to get PDF file from API:
%
s'
)
%
response
.
text
)
except
requests
.
exceptions
.
RequestException
as
e
:
raise
ValidationError
(
_
(
'API request failed:
%
s'
)
%
str
(
e
))
def
push_sync_pod_task
(
self
,
processed_files
,
file_type
,
pod_desc
,
filter_temu
=
False
):
"""
推送POD同步任务到redis
:param processed_files: 处理后的文件数组
:param file_type: 清关文件类型名称
:param pod_desc: 用于提示的信息描述,例如“尾程POD”或“货站提货POD”
:param filter_temu: 是否过滤掉bl_type为temu的提单
"""
redis_conn
=
self
.
get_redis
()
if
not
redis_conn
or
redis_conn
==
'no'
:
raise
ValidationError
(
'未连接redis,无法同步
%
s,请联系管理员'
%
pod_desc
)
bl_ids
=
[]
for
file_info
in
processed_files
:
bl
=
file_info
.
get
(
'bl'
)
if
not
bl
:
continue
if
filter_temu
and
getattr
(
bl
,
'bl_type'
,
False
)
==
'temu'
:
continue
clearance_file
=
file_info
.
get
(
'clearance_file'
)
if
not
clearance_file
:
continue
bl_ids
.
append
(
bl
.
id
)
if
not
bl_ids
:
return
payload
=
{
'ids'
:
bl_ids
,
'action_type'
:
'sync_last_mile_pod'
,
'user_login'
:
self
.
env
.
user
.
login
,
'file_type'
:
file_type
}
try
:
redis_conn
.
lpush
(
'mail_push_package_list'
,
json
.
dumps
(
payload
))
except
Exception
as
e
:
logging
.
error
(
'sync_last_mile_pod redis error:
%
s'
%
e
)
raise
ValidationError
(
'推送
%
s同步任务到redis失败,请重试或联系管理员'
%
pod_desc
)
def
cleanup_temp_attachments
(
self
,
bl_objs
):
"""
清理与当前向导相关的临时附件,包括服务器和本地开发环境的物理文件
"""
try
:
if
not
bl_objs
:
return
attachments
=
self
.
env
[
'ir.attachment'
]
.
search
([
(
'res_model'
,
'='
,
bl_objs
.
_name
),
(
'res_id'
,
'in'
,
bl_objs
.
ids
),
(
'name'
,
'like'
,
'temp_pod_
%
'
)
])
if
attachments
:
attachments
.
unlink
()
except
Exception
as
e
:
_logger
.
error
(
'清理临时附件失败:
%
s'
%
str
(
e
))
def
cron_cleanup_temp_attachments
(
self
):
"""
定时清理向导生成的临时附件
每天早上8点执行,删除1天之前创建的temp_pod_开头的附件
"""
try
:
today
=
datetime
.
datetime
.
now
()
.
replace
(
hour
=
0
,
minute
=
0
,
second
=
0
,
microsecond
=
0
)
cutoff
=
today
-
datetime
.
timedelta
(
days
=
1
)
_logger
.
info
(
'开始执行定时清理临时附件任务,清理时间点:
%
s'
%
cutoff
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
))
sql_query
=
"""
SELECT id, name, res_model, res_id, create_date, store_fname
FROM ir_attachment
WHERE name LIKE 'temp_pod_
%%
'
AND create_date < '
%
s'
ORDER BY create_date DESC
"""
%
(
cutoff
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
))
self
.
env
.
cr
.
execute
(
sql_query
)
sql_results
=
self
.
env
.
cr
.
fetchall
()
if
sql_results
:
attachment_ids
=
[
result
[
0
]
for
result
in
sql_results
]
temp_attachments
=
self
.
env
[
'ir.attachment'
]
.
sudo
()
.
browse
(
attachment_ids
)
attachment_count
=
len
(
temp_attachments
)
_logger
.
info
(
'找到
%
s 个
%
s之前创建的临时附件,开始清理'
%
(
attachment_count
,
cutoff
.
strftime
(
'
%
Y-
%
m-
%
d'
)))
import
os
from
odoo.tools
import
config
data_dir
=
config
.
filestore
(
self
.
env
.
cr
.
dbname
)
for
attachment
in
temp_attachments
:
try
:
if
hasattr
(
attachment
,
'store_fname'
)
and
attachment
.
store_fname
:
file_path
=
attachment
.
store_fname
elif
hasattr
(
attachment
,
'datas_fname'
)
and
attachment
.
datas_fname
:
file_path
=
attachment
.
datas_fname
else
:
file_path
=
attachment
.
name
if
data_dir
and
file_path
:
full_path
=
os
.
path
.
join
(
data_dir
,
file_path
)
if
os
.
path
.
exists
(
full_path
):
os
.
remove
(
full_path
)
except
Exception
as
file_e
:
_logger
.
warning
(
'删除物理文件失败
%
s:
%
s'
%
(
attachment
.
name
,
str
(
file_e
)))
temp_attachments
.
unlink
()
except
Exception
as
e
:
_logger
.
error
(
'定时清理临时附件失败:
%
s'
%
str
(
e
))
def
merge_pod_pdfs
(
self
,
processed_files
):
"""
合并处理后的POD PDF文件
:param processed_files: 处理后的文件列表,每个元素为字典包含bl_no, file_data, bl
:return: 合并后的PDF文件数据和文件名
"""
import
fitz
temp_file_path
=
None
try
:
valid_files
=
[]
for
file_info
in
processed_files
:
if
file_info
.
get
(
'bl_no'
)
and
file_info
.
get
(
'file_data'
)
and
file_info
.
get
(
'bl'
):
valid_files
.
append
(
file_info
)
if
not
valid_files
:
_logger
.
warning
(
"没有有效的PDF文件可以合并"
)
return
None
,
None
if
len
(
valid_files
)
==
1
:
file_info
=
valid_files
[
0
]
bl
=
file_info
[
'bl'
]
bl_no
=
bl
.
bl_no
file_data
=
file_info
[
'file_data'
]
timestamp
=
datetime
.
datetime
.
now
()
.
strftime
(
'
%
Y
%
m
%
d_
%
H
%
M
%
S'
)
pdf_filename
=
f
"POD文件_{bl_no}_{timestamp}.pdf"
_logger
.
info
(
"单个PDF文件直接使用:
%
s"
,
pdf_filename
)
return
file_data
,
pdf_filename
_logger
.
info
(
"开始合并
%
s 个PDF文件"
,
len
(
valid_files
))
temp_file_path
=
tempfile
.
mktemp
(
suffix
=
'.pdf'
)
merged_pdf
=
fitz
.
open
()
bl_numbers
=
[]
for
file_info
in
valid_files
:
bl
=
file_info
[
'bl'
]
bl_no
=
bl
.
bl_no
file_data
=
file_info
[
'file_data'
]
bl_numbers
.
append
(
bl_no
)
source_pdf
=
None
try
:
pdf_binary
=
base64
.
b64decode
(
file_data
)
source_pdf
=
fitz
.
open
(
stream
=
pdf_binary
,
filetype
=
"pdf"
)
merged_pdf
.
insert_pdf
(
source_pdf
)
_logger
.
info
(
"已添加提单
%
s 的PDF到合并文档(
%
s 页)"
,
bl_no
,
len
(
source_pdf
))
except
Exception
as
e
:
_logger
.
error
(
"合并提单
%
s 的PDF失败:
%
s"
,
bl_no
,
str
(
e
))
continue
finally
:
if
source_pdf
:
source_pdf
.
close
()
gc
.
collect
()
if
len
(
merged_pdf
)
>
0
:
merged_pdf
.
save
(
temp_file_path
,
garbage
=
4
,
deflate
=
True
,
clean
=
True
)
merged_pdf
.
close
()
with
open
(
temp_file_path
,
'rb'
)
as
f
:
pdf_data
=
f
.
read
()
merged_pdf_base64
=
base64
.
b64encode
(
pdf_data
)
.
decode
(
'utf-8'
)
del
pdf_data
gc
.
collect
()
bl_numbers_str
=
'_'
.
join
(
bl_numbers
[:
5
])
if
len
(
bl_numbers
)
>
5
:
bl_numbers_str
+=
f
'_等{len(bl_numbers)}个'
timestamp
=
datetime
.
datetime
.
now
()
.
strftime
(
'
%
Y
%
m
%
d_
%
H
%
M
%
S'
)
pdf_filename
=
f
"合并POD文件_{bl_numbers_str}_{timestamp}.pdf"
_logger
.
info
(
"成功合并
%
s 个PDF文件,文件名:
%
s"
,
len
(
bl_numbers
),
pdf_filename
)
return
merged_pdf_base64
,
pdf_filename
_logger
.
warning
(
"没有有效的PDF文件可以合并"
)
return
None
,
None
except
Exception
as
e
:
_logger
.
error
(
"合并PDF文件失败:
%
s"
,
str
(
e
))
return
None
,
None
finally
:
if
temp_file_path
and
os
.
path
.
exists
(
temp_file_path
):
try
:
os
.
remove
(
temp_file_path
)
_logger
.
info
(
"已删除临时文件:
%
s"
,
temp_file_path
)
except
Exception
as
e
:
_logger
.
warning
(
"删除临时文件失败:
%
s"
,
str
(
e
))
def
match_pod_files
(
self
,
pdf_file_arr
,
bl_obj
,
include_processing_failed
=
False
):
"""
匹配POD文件与提单
:param pdf_file_arr: PDF文件数组,每个元素为字典包含file_name, file_data, bl_no
:param bl_obj: 提单记录集
:param include_processing_failed: 是否包含处理失败的文件
:return: 匹配后的文件数组,每个元素为字典包含bl, file_name, file_data, bl_no
"""
processed_files
=
[]
for
bl
in
bl_obj
:
select_bl_no
=
self
.
process_match_str
(
bl
.
bl_no
)
if
not
select_bl_no
:
continue
for
pdf_file
in
pdf_file_arr
:
file_name
=
pdf_file
.
get
(
'file_name'
)
file_data
=
pdf_file
.
get
(
'file_data'
)
raw_bl_no
=
pdf_file
.
get
(
'bl_no'
)
bl_no
=
self
.
process_match_str
(
raw_bl_no
)
if
bl_no
and
select_bl_no
==
bl_no
:
file_info
=
{
'bl'
:
bl
,
'file_name'
:
file_name
,
'file_data'
:
file_data
,
'bl_no'
:
raw_bl_no
or
bl
.
bl_no
,
}
if
include_processing_failed
:
file_info
[
'processing_failed'
]
=
False
processed_files
.
append
(
file_info
)
break
return
processed_files
def
write_pod_pdf_files
(
self
,
processed_files
,
fix_name
):
"""
Write PDF file to clearance files # 回写PDF文件到清关文件
:param processed_files: 处理后的文件数组
:param fix_name:
"""
clearance_model
=
self
.
env
[
'cc.clearance.file'
]
valid_entries
=
[]
bl_ids
=
set
()
for
file_info
in
processed_files
:
bl
=
file_info
.
get
(
'bl'
)
if
not
bl
:
_logger
.
warning
(
"跳过没有提单信息的文件"
)
continue
file_name
=
file_info
.
get
(
'file_name'
,
''
)
file_data
=
file_info
.
get
(
'file_data'
,
''
)
if
not
file_data
:
continue
valid_entries
.
append
((
file_info
,
bl
,
file_name
,
file_data
))
bl_ids
.
add
(
bl
.
id
)
if
not
valid_entries
:
return
existing_clearance
=
clearance_model
.
search
(
[(
'bl_id'
,
'in'
,
list
(
bl_ids
)),
(
'file_name'
,
'='
,
fix_name
),
(
'file'
,
'='
,
False
)]
)
existing_by_bl
=
{
rec
.
bl_id
.
id
:
rec
for
rec
in
existing_clearance
}
create_vals_list
=
[]
create_infos
=
[]
for
file_info
,
bl
,
file_name
,
file_data
in
valid_entries
:
clearance_file
=
existing_by_bl
.
get
(
bl
.
id
)
if
clearance_file
:
clearance_file
.
write
({
'attachment_name'
:
file_name
,
'file'
:
file_data
})
file_info
[
'clearance_file'
]
=
clearance_file
else
:
create_vals_list
.
append
({
'bl_id'
:
bl
.
id
,
'file_name'
:
fix_name
,
'attachment_name'
:
file_name
,
'file'
:
file_data
})
create_infos
.
append
(
file_info
)
if
create_vals_list
:
new_records
=
clearance_model
.
create
(
create_vals_list
)
for
clearance_file
,
file_info
in
zip
(
new_records
,
create_infos
):
bl
=
file_info
[
'bl'
]
file_info
[
'clearance_file'
]
=
clearance_file
def
serialize_pod_processed_files
(
self
,
processed_files
):
"""
将processed_files序列化为JSON字符串,文件数据存储到临时附件中
:param processed_files: 处理后的文件数组
:return: JSON字符串(只包含引用信息,不包含文件数据)
注意:不在这里清理临时附件,因为预览时需要保留附件数据,
只有在确认操作完成后才清理临时附件
"""
serialized_data
=
[]
for
file_info
in
processed_files
:
if
not
file_info
.
get
(
'bl'
):
continue
bl
=
file_info
[
'bl'
]
file_data
=
file_info
.
get
(
'file_data'
,
''
)
file_name
=
file_info
.
get
(
'file_name'
,
f
"{bl.bl_no}.pdf"
)
attachment_id
=
None
if
file_data
:
try
:
attachment
=
self
.
env
[
'ir.attachment'
]
.
create
({
'name'
:
f
"temp_pod_{bl.bl_no}_{int(time.time())}.pdf"
,
'datas'
:
file_data
,
'type'
:
'binary'
,
'res_model'
:
bl
.
_name
,
'res_id'
:
bl
.
id
,
})
attachment_id
=
attachment
.
id
_logger
.
info
(
"已创建临时附件存储文件:
%
s, ID:
%
s"
,
attachment
.
name
,
attachment_id
)
except
Exception
as
e
:
_logger
.
error
(
"创建临时附件失败:
%
s"
,
str
(
e
))
else
:
_logger
.
warning
(
"提单
%
s 的文件数据为空,无法创建附件"
,
bl
.
bl_no
)
data
=
{
'bl_id'
:
bl
.
id
,
'bl_no'
:
bl
.
bl_no
,
'file_name'
:
file_name
,
'attachment_id'
:
attachment_id
,
}
if
'ocr_texts'
in
file_info
:
data
[
'ocr_texts'
]
=
file_info
[
'ocr_texts'
]
if
'valid_packages'
in
file_info
and
file_info
[
'valid_packages'
]:
valid_packages
=
file_info
[
'valid_packages'
]
if
hasattr
(
valid_packages
,
'ids'
):
data
[
'valid_package_ids'
]
=
valid_packages
.
ids
elif
isinstance
(
valid_packages
,
list
):
data
[
'valid_package_ids'
]
=
[
p
.
id
for
p
in
valid_packages
if
hasattr
(
p
,
'id'
)]
else
:
data
[
'valid_package_ids'
]
=
[]
_logger
.
info
(
"序列化时保存valid_packages: 提单
%
s, 满足条件的小包ID:
%
s"
,
bl
.
bl_no
,
data
[
'valid_package_ids'
]
)
serialized_data
.
append
(
data
)
return
json
.
dumps
(
serialized_data
,
ensure_ascii
=
False
)
def
deserialize_pod_processed_files
(
self
,
json_data
):
"""
将JSON字符串反序列化为processed_files(从附件中读取文件数据)
:param json_data: JSON字符串
:return: 处理后的文件数组
"""
if
not
json_data
:
return
[]
try
:
serialized_data
=
json
.
loads
(
json_data
)
processed_files
=
[]
for
data
in
serialized_data
:
bl_id
=
data
.
get
(
'bl_id'
)
attachment_id
=
data
.
get
(
'attachment_id'
)
if
not
bl_id
:
continue
bl
=
self
.
env
[
'cc.bl'
]
.
browse
(
bl_id
)
if
not
bl
.
exists
():
continue
file_data
=
''
if
attachment_id
:
try
:
attachment
=
self
.
env
[
'ir.attachment'
]
.
browse
(
attachment_id
)
if
attachment
.
exists
():
file_data
=
attachment
.
datas
_logger
.
info
(
"从附件读取文件:
%
s, ID:
%
s, 数据长度:
%
s"
,
attachment
.
name
,
attachment_id
,
len
(
file_data
)
if
file_data
else
0
)
else
:
_logger
.
warning
(
"附件不存在:
%
s"
,
attachment_id
)
except
Exception
as
e
:
_logger
.
error
(
"读取附件失败:
%
s"
,
str
(
e
))
else
:
_logger
.
warning
(
"提单
%
s 没有附件ID,无法读取文件数据"
,
bl
.
bl_no
)
file_info
=
{
'bl'
:
bl
,
'bl_no'
:
data
.
get
(
'bl_no'
,
''
),
'file_name'
:
data
.
get
(
'file_name'
,
''
),
'file_data'
:
file_data
,
}
if
'ocr_texts'
in
data
:
file_info
[
'ocr_texts'
]
=
data
[
'ocr_texts'
]
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
(
"反序列化时恢复valid_packages: 提单
%
s, 满足条件的小包ID:
%
s, 数量:
%
s"
,
bl
.
bl_no
,
valid_package_ids
,
len
(
valid_packages
)
)
processed_files
.
append
(
file_info
)
return
processed_files
except
Exception
as
e
:
_logger
.
error
(
"反序列化processed_files失败:
%
s"
,
str
(
e
))
return
[]
def
init_timezone_data
(
self
,
name
):
def
init_timezone_data
(
self
,
name
):
timezone_data
=
{}
timezone_data
=
{}
timezone_data
[
'Africa/Abidjan'
]
=
0
timezone_data
[
'Africa/Abidjan'
]
=
0
...
...
ccs_base/wizard/batch_get_lastmile_pod_info_wizard.py
浏览文件 @
0c22b124
...
@@ -198,59 +198,8 @@ class BatchGetLastMilePodInfoWizard(models.TransientModel):
...
@@ -198,59 +198,8 @@ class BatchGetLastMilePodInfoWizard(models.TransientModel):
从API获取PDF文件
从API获取PDF文件
"""
"""
bill_numbers
=
[
self
.
env
[
'common.common'
]
.
sudo
()
.
process_match_str
(
bl
.
bl_no
)
for
bl
in
bl_objs
]
bill_numbers
=
[
self
.
env
[
'common.common'
]
.
sudo
()
.
process_match_str
(
bl
.
bl_no
)
for
bl
in
bl_objs
]
# 调用API获取PDF文件
common
=
self
.
env
[
'common.common'
]
.
sudo
()
api_url
=
self
.
env
[
'ir.config_parameter'
]
.
sudo
()
.
get_param
(
'last_mile_pod_api_url'
,
return
common
.
get_pod_pdf_files
(
bill_numbers
,
'/api/pod/pdfs'
)
'http://172.104.52.150:7002'
)
if
not
api_url
:
raise
ValidationError
(
_
(
'API URL not configured'
))
# 构建请求数据
request_data
=
{
"bill_numbers"
:
bill_numbers
}
try
:
response
=
requests
.
post
(
f
"{api_url}/api/pod/pdfs"
,
headers
=
{
'Content-Type'
:
'application/json'
,
'Accept'
:
'application/json'
},
json
=
request_data
,
timeout
=
30
)
logging
.
info
(
'response:
%
s'
%
response
)
if
response
.
status_code
==
200
:
result
=
response
.
json
()
logging
.
info
(
'result:
%
s'
%
result
)
# 检查API响应结构
if
not
result
:
raise
ValidationError
(
_
(
'API returned empty response'
))
if
not
result
.
get
(
'success'
):
error_msg
=
result
.
get
(
'message'
,
'Unknown error'
)
raise
ValidationError
(
_
(
'API returned error:
%
s'
)
%
error_msg
)
# 处理结果数据
results
=
result
.
get
(
'results'
,
[])
if
not
results
:
raise
ValidationError
(
_
(
'No PDF files found in API response'
))
# 构建PDF文件数组
pdf_file_arr
=
[]
for
result_item
in
results
:
if
result_item
.
get
(
'success'
):
# 验证必要字段
bill_number
=
result_item
.
get
(
'bill_number'
)
filename
=
result_item
.
get
(
'filename'
)
base64_data
=
result_item
.
get
(
'base64'
)
pdf_file_arr
.
append
({
'bl_no'
:
bill_number
,
'file_name'
:
filename
,
'file_data'
:
base64_data
})
return
pdf_file_arr
else
:
raise
ValidationError
(
_
(
'Failed to get PDF file from API:
%
s'
)
%
response
.
text
)
except
requests
.
exceptions
.
RequestException
as
e
:
raise
ValidationError
(
_
(
'API request failed:
%
s'
)
%
str
(
e
))
def
_write_pdf_file
(
self
,
processed_files
,
fix_name
=
'尾程交接POD(待大包数量和箱号)'
):
def
_write_pdf_file
(
self
,
processed_files
,
fix_name
=
'尾程交接POD(待大包数量和箱号)'
):
"""
"""
...
@@ -258,435 +207,51 @@ class BatchGetLastMilePodInfoWizard(models.TransientModel):
...
@@ -258,435 +207,51 @@ class BatchGetLastMilePodInfoWizard(models.TransientModel):
:param processed_files: 处理后的文件数组
:param processed_files: 处理后的文件数组
:param fix_name:
:param fix_name:
"""
"""
clearance_model
=
self
.
env
[
'cc.clearance.file'
]
common
=
self
.
env
[
'common.common'
]
.
sudo
()
valid_entries
=
[]
common
.
write_pod_pdf_files
(
processed_files
,
fix_name
)
bl_ids
=
set
()
for
file_info
in
processed_files
:
bl
=
file_info
.
get
(
'bl'
)
if
not
bl
:
_logger
.
warning
(
"跳过没有提单信息的文件"
)
continue
file_name
=
file_info
.
get
(
'file_name'
,
''
)
file_data
=
file_info
.
get
(
'file_data'
,
''
)
if
not
file_data
:
continue
valid_entries
.
append
((
file_info
,
bl
,
file_name
,
file_data
))
bl_ids
.
add
(
bl
.
id
)
if
not
valid_entries
:
return
existing_clearance
=
clearance_model
.
search
(
[(
'bl_id'
,
'in'
,
list
(
bl_ids
)),
(
'file_name'
,
'='
,
fix_name
),
(
'file'
,
'='
,
False
)]
)
existing_by_bl
=
{
rec
.
bl_id
.
id
:
rec
for
rec
in
existing_clearance
}
create_vals_list
=
[]
create_infos
=
[]
for
file_info
,
bl
,
file_name
,
file_data
in
valid_entries
:
clearance_file
=
existing_by_bl
.
get
(
bl
.
id
)
if
clearance_file
:
clearance_file
.
write
({
'attachment_name'
:
file_name
,
'file'
:
file_data
})
_logger
.
info
(
f
"更新清关文件记录: 提单 {bl.bl_no}"
)
file_info
[
'clearance_file'
]
=
clearance_file
else
:
create_vals_list
.
append
({
'bl_id'
:
bl
.
id
,
'file_name'
:
fix_name
,
'attachment_name'
:
file_name
,
'file'
:
file_data
})
create_infos
.
append
(
file_info
)
if
create_vals_list
:
new_records
=
clearance_model
.
create
(
create_vals_list
)
for
clearance_file
,
file_info
in
zip
(
new_records
,
create_infos
):
bl
=
file_info
[
'bl'
]
_logger
.
info
(
f
"创建新的清关文件记录: 提单 {bl.bl_no}"
)
file_info
[
'clearance_file'
]
=
clearance_file
def
_merge_pdf_files
(
self
,
processed_files
):
def
_merge_pdf_files
(
self
,
processed_files
):
"""
common
=
self
.
env
[
'common.common'
]
.
sudo
()
合并所有涂抹后的PDF文件为一个PDF并保存到pdf_file字段
pdf_data
,
pdf_filename
=
common
.
merge_pod_pdfs
(
processed_files
)
使用临时文件方式减少内存占用
if
pdf_data
and
pdf_filename
:
:param processed_files: 处理后的文件数组
"""
import
fitz
# PyMuPDF
from
datetime
import
datetime
import
tempfile
import
os
import
gc
temp_file_path
=
None
try
:
# 过滤有效的PDF文件
valid_files
=
[]
for
file_info
in
processed_files
:
if
file_info
.
get
(
'bl_no'
)
and
file_info
.
get
(
'file_data'
):
valid_files
.
append
(
file_info
)
if
not
valid_files
:
_logger
.
warning
(
"没有有效的PDF文件可以合并"
)
return
# 如果只有一个PDF文件,直接使用,不需要合并
if
len
(
valid_files
)
==
1
:
file_info
=
valid_files
[
0
]
bl
=
file_info
[
'bl'
]
bl_no
=
bl
.
bl_no
file_data
=
file_info
[
'file_data'
]
file_name
=
file_info
.
get
(
'file_name'
,
f
"{bl_no}.pdf"
)
# 生成文件名(包含提单号和日期)
timestamp
=
datetime
.
now
()
.
strftime
(
'
%
Y
%
m
%
d_
%
H
%
M
%
S'
)
pdf_filename
=
f
"POD文件_{bl_no}_{timestamp}.pdf"
# 直接保存到字段
self
.
write
({
'pdf_file'
:
file_data
,
'pdf_filename'
:
pdf_filename
})
_logger
.
info
(
f
"单个PDF文件直接保存: {pdf_filename}"
)
return
_logger
.
info
(
f
"开始合并 {len(valid_files)} 个PDF文件"
)
temp_file_path
=
tempfile
.
mktemp
(
suffix
=
'.pdf'
)
merged_pdf
=
fitz
.
open
()
bl_numbers
=
[]
for
file_info
in
valid_files
:
bl
=
file_info
[
'bl'
]
bl_no
=
bl
.
bl_no
file_data
=
file_info
[
'file_data'
]
bl_numbers
.
append
(
bl_no
)
source_pdf
=
None
try
:
pdf_binary
=
base64
.
b64decode
(
file_data
)
source_pdf
=
fitz
.
open
(
stream
=
pdf_binary
,
filetype
=
"pdf"
)
merged_pdf
.
insert_pdf
(
source_pdf
)
_logger
.
info
(
f
"已添加提单 {bl_no} 的PDF到合并文档({len(source_pdf)} 页)"
)
except
Exception
as
e
:
_logger
.
error
(
f
"合并提单 {bl_no} 的PDF失败: {str(e)}"
)
continue
finally
:
if
source_pdf
:
source_pdf
.
close
()
gc
.
collect
()
if
len
(
merged_pdf
)
>
0
:
merged_pdf
.
save
(
temp_file_path
,
garbage
=
4
,
deflate
=
True
,
clean
=
True
)
merged_pdf
.
close
()
# 从临时文件读取并转换为base64
with
open
(
temp_file_path
,
'rb'
)
as
f
:
pdf_data
=
f
.
read
()
# 转换为base64
merged_pdf_base64
=
base64
.
b64encode
(
pdf_data
)
.
decode
(
'utf-8'
)
# 清理临时数据
del
pdf_data
gc
.
collect
()
# 生成文件名(包含提单号和日期)
bl_numbers_str
=
'_'
.
join
(
bl_numbers
[:
5
])
# 最多显示5个提单号
if
len
(
bl_numbers
)
>
5
:
bl_numbers_str
+=
f
'_等{len(bl_numbers)}个'
timestamp
=
datetime
.
now
()
.
strftime
(
'
%
Y
%
m
%
d_
%
H
%
M
%
S'
)
pdf_filename
=
f
"合并POD文件_{bl_numbers_str}_{timestamp}.pdf"
# 保存到字段
self
.
write
({
self
.
write
({
'pdf_file'
:
merged_pdf_base64
,
'pdf_file'
:
pdf_data
,
'pdf_filename'
:
pdf_filename
'pdf_filename'
:
pdf_filename
})
})
# 清理base64数据
del
merged_pdf_base64
gc
.
collect
()
_logger
.
info
(
f
"成功合并 {len(bl_numbers)} 个PDF文件,文件名: {pdf_filename}"
)
else
:
_logger
.
warning
(
"没有有效的PDF文件可以合并"
)
except
Exception
as
e
:
_logger
.
error
(
f
"合并PDF文件失败: {str(e)}"
)
finally
:
# 清理临时文件
if
temp_file_path
and
os
.
path
.
exists
(
temp_file_path
):
try
:
os
.
remove
(
temp_file_path
)
_logger
.
info
(
f
"已删除临时文件: {temp_file_path}"
)
except
Exception
as
e
:
_logger
.
warning
(
f
"删除临时文件失败: {str(e)}"
)
def
_match_bl_by_file_name
(
self
,
pdf_file_arr
,
bl_obj
):
def
_match_bl_by_file_name
(
self
,
pdf_file_arr
,
bl_obj
):
"""
"""
Match BL by file name and return processed array # 根据文件名匹配提单并返回处理后的数组
Match BL by file name and return processed array # 根据文件名匹配提单并返回处理后的数组
:param pdf_file_arr: PDF文件数组 [{'bill_number':'', 'filename':'', 'file_data':''}]
:param pdf_file_arr: PDF文件数组 [{'bill_number':'', 'filename':'', 'file_data':''}]
:return: 处理后的数组 [{'bl': bl_obj, 'file_name': 'xxx.pdf', 'file_data': 'xxx', 'matched': True/False}]
:return: 处理后的数组 [{'bl': bl_obj, 'file_name': 'xxx.pdf', 'file_data': 'xxx', 'matched': True/False}]
"""
"""
processed_files
=
[]
common
=
self
.
env
[
'common.common'
]
.
sudo
()
for
bl
in
bl_obj
:
return
common
.
match_pod_files
(
pdf_file_arr
,
bl_obj
,
include_processing_failed
=
True
)
select_bl_no
=
self
.
env
[
'common.common'
]
.
sudo
()
.
process_match_str
(
bl
.
bl_no
)
for
pdf_file
in
pdf_file_arr
:
file_name
=
pdf_file
.
get
(
'file_name'
)
# 获取文件名
file_data
=
pdf_file
.
get
(
'file_data'
)
# 获取文件数据
bl_no
=
self
.
env
[
'common.common'
]
.
sudo
()
.
process_match_str
(
pdf_file
.
get
(
'bl_no'
))
# 获取提单号
if
bl_no
and
select_bl_no
==
bl_no
:
# 构建处理后的文件信息
processed_file
=
{
'bl'
:
bl
,
'file_name'
:
file_name
,
'file_data'
:
file_data
,
'bl_no'
:
bl
.
bl_no
,
'processing_failed'
:
False
,
}
processed_files
.
append
(
processed_file
)
break
return
processed_files
def
_sync_last_mile_pod
(
self
,
processed_files
):
def
_sync_last_mile_pod
(
self
,
processed_files
):
"""
"""
Sync last mile POD information
Sync last mile POD information
:param processed_files: 处理后的文件数组
:param processed_files: 处理后的文件数组
"""
"""
redis_conn
=
self
.
env
[
'common.common'
]
.
sudo
()
.
get_redis
()
common
=
self
.
env
[
'common.common'
]
.
sudo
()
if
not
redis_conn
or
redis_conn
==
'no'
:
common
.
push_sync_pod_task
(
raise
ValidationError
(
'未连接redis,无法同步尾程POD,请联系管理员'
)
processed_files
=
processed_files
,
file_type
=
'尾程交接POD(待大包数量和箱号)'
,
bl_ids
=
[]
pod_desc
=
'尾程POD'
,
for
file_info
in
processed_files
:
filter_temu
=
True
bl
=
file_info
.
get
(
'bl'
)
)
if
not
bl
:
continue
if
bl
.
bl_type
==
'temu'
:
continue
clearance_file
=
file_info
.
get
(
'clearance_file'
)
if
not
clearance_file
:
continue
bl_ids
.
append
(
bl
.
id
)
if
not
bl_ids
:
return
payload
=
{
'ids'
:
bl_ids
,
'action_type'
:
'sync_last_mile_pod'
,
'user_login'
:
self
.
env
.
user
.
login
,
'file_type'
:
'尾程交接POD(待大包数量和箱号)'
}
try
:
redis_conn
.
lpush
(
'mail_push_package_list'
,
json
.
dumps
(
payload
))
except
Exception
as
e
:
logging
.
error
(
'sync_last_mile_pod redis error:
%
s'
%
e
)
raise
ValidationError
(
'推送尾程POD同步任务到redis失败,请重试或联系管理员'
)
def
_cleanup_temp_attachments
(
self
,
bl_objs
=
None
):
def
_cleanup_temp_attachments
(
self
,
bl_objs
=
None
):
"""
common
=
self
.
env
[
'common.common'
]
.
sudo
()
清理与当前向导相关的临时附件,包括服务器和本地开发环境的物理文件
common
.
cleanup_temp_attachments
(
bl_objs
)
"""
try
:
attachments
=
self
.
env
[
'ir.attachment'
]
.
search
([
(
'res_model'
,
'='
,
bl_objs
[
0
]
.
_name
),
(
'res_id'
,
'in'
,
bl_objs
.
ids
),
(
'name'
,
'like'
,
'temp_pod_
%
'
)
])
if
attachments
:
# 删除数据库记录
attachments
.
unlink
()
except
Exception
as
e
:
_logger
.
error
(
f
"清理临时附件失败: {str(e)}"
)
def
_serialize_processed_files
(
self
,
processed_files
):
def
_serialize_processed_files
(
self
,
processed_files
):
"""
common
=
self
.
env
[
'common.common'
]
.
sudo
()
将processed_files序列化为JSON字符串,文件数据存储到临时附件中
return
common
.
serialize_pod_processed_files
(
processed_files
)
:param processed_files: 处理后的文件数组
:return: JSON字符串(只包含引用信息,不包含文件数据)
"""
# 注意:不在这里清理临时附件,因为预览时需要保留附件数据
# 只有在确认操作完成后才清理临时附件
serialized_data
=
[]
for
file_info
in
processed_files
:
if
not
file_info
.
get
(
'bl'
):
continue
bl
=
file_info
[
'bl'
]
file_data
=
file_info
.
get
(
'file_data'
,
''
)
file_name
=
file_info
.
get
(
'file_name'
,
f
"{bl.bl_no}.pdf"
)
# 将文件数据存储到临时附件中
attachment_id
=
None
if
file_data
:
try
:
attachment
=
self
.
env
[
'ir.attachment'
]
.
create
({
'name'
:
f
"temp_pod_{bl.bl_no}_{int(time.time())}.pdf"
,
'datas'
:
file_data
,
'type'
:
'binary'
,
'res_model'
:
bl
.
_name
,
'res_id'
:
bl
.
id
,
})
attachment_id
=
attachment
.
id
_logger
.
info
(
f
"已创建临时附件存储文件: {attachment.name}, ID: {attachment_id}"
)
except
Exception
as
e
:
_logger
.
error
(
f
"创建临时附件失败: {str(e)}"
)
else
:
_logger
.
warning
(
f
"提单 {bl.bl_no} 的文件数据为空,无法创建附件"
)
data
=
{
'bl_id'
:
bl
.
id
,
'bl_no'
:
bl
.
bl_no
,
'file_name'
:
file_name
,
'attachment_id'
:
attachment_id
,
# 存储附件ID而不是文件数据
}
# OCR文本数据量小,可以直接存储
if
'ocr_texts'
in
file_info
:
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
)
return
json
.
dumps
(
serialized_data
,
ensure_ascii
=
False
)
def
_deserialize_processed_files
(
self
,
json_data
):
def
_deserialize_processed_files
(
self
,
json_data
):
"""
common
=
self
.
env
[
'common.common'
]
.
sudo
()
将JSON字符串反序列化为processed_files(从附件中读取文件数据)
return
common
.
deserialize_pod_processed_files
(
json_data
)
:param json_data: JSON字符串
:return: 处理后的文件数组
"""
if
not
json_data
:
return
[]
try
:
serialized_data
=
json
.
loads
(
json_data
)
processed_files
=
[]
for
data
in
serialized_data
:
bl_id
=
data
.
get
(
'bl_id'
)
attachment_id
=
data
.
get
(
'attachment_id'
)
if
bl_id
:
bl
=
self
.
env
[
'cc.bl'
]
.
browse
(
bl_id
)
if
bl
.
exists
():
# 从附件中读取文件数据
file_data
=
''
if
attachment_id
:
try
:
attachment
=
self
.
env
[
'ir.attachment'
]
.
browse
(
attachment_id
)
if
attachment
.
exists
():
# attachment.datas 已经是 base64 编码的字符串
file_data
=
attachment
.
datas
_logger
.
info
(
f
"从附件读取文件: {attachment.name}, ID: {attachment_id}, 数据长度: {len(file_data) if file_data else 0}"
)
else
:
_logger
.
warning
(
f
"附件不存在: {attachment_id}"
)
except
Exception
as
e
:
_logger
.
error
(
f
"读取附件失败: {str(e)}"
)
else
:
_logger
.
warning
(
f
"提单 {bl.bl_no} 没有附件ID,无法读取文件数据"
)
file_info
=
{
'bl'
:
bl
,
'bl_no'
:
data
.
get
(
'bl_no'
,
''
),
'file_name'
:
data
.
get
(
'file_name'
,
''
),
'file_data'
:
file_data
,
}
# 如果有OCR文本,也恢复
if
'ocr_texts'
in
data
:
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
)
return
processed_files
except
Exception
as
e
:
_logger
.
error
(
f
"反序列化processed_files失败: {str(e)}"
)
return
[]
@api.model
def
cron_cleanup_temp_attachments
(
self
):
"""
定时清理向导生成的临时附件
每天早上8点执行,删除1天之前创建的temp_pod_开头的附件
"""
try
:
# 计算1天前的时间(前一天23:59:59)
today
=
datetime
.
now
()
.
replace
(
hour
=
0
,
minute
=
0
,
second
=
0
,
microsecond
=
0
)
one_day_ago
=
today
+
timedelta
(
days
=
2
)
-
timedelta
(
seconds
=
1
)
# 前一天23:59:59
_logger
.
info
(
f
"开始执行定时清理临时附件任务,清理时间点: {one_day_ago.strftime('
%
Y-
%
m-
%
d
%
H:
%
M:
%
S')}"
)
# 构建SQL查询
sql_query
=
"""
SELECT id, name, res_model, res_id, create_date, store_fname
FROM ir_attachment
WHERE res_model = 'batch.get.pod.info.wizard'
AND create_date < '
%
s'
ORDER BY create_date DESC
"""
%
(
one_day_ago
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
))
# 执行SQL查询
self
.
env
.
cr
.
execute
(
sql_query
)
sql_results
=
self
.
env
.
cr
.
fetchall
()
# 将SQL结果转换为Odoo记录集
if
sql_results
:
attachment_ids
=
[
result
[
0
]
for
result
in
sql_results
]
temp_attachments
=
self
.
env
[
'ir.attachment'
]
.
sudo
()
.
browse
(
attachment_ids
)
attachment_count
=
len
(
temp_attachments
)
_logger
.
info
(
f
"找到 {attachment_count} 个{one_day_ago.strftime('
%
Y-
%
m-
%
d')}之前创建的临时附件,开始清理"
)
# 删除物理文件
for
attachment
in
temp_attachments
:
try
:
# 获取附件的物理文件路径
if
hasattr
(
attachment
,
'store_fname'
)
and
attachment
.
store_fname
:
# Odoo 12+ 使用 store_fname
file_path
=
attachment
.
store_fname
elif
hasattr
(
attachment
,
'datas_fname'
)
and
attachment
.
datas_fname
:
# 旧版本使用 datas_fname
file_path
=
attachment
.
datas_fname
else
:
# 尝试从 name 字段构建路径
file_path
=
attachment
.
name
# 构建完整的文件路径
import
os
from
odoo.tools
import
config
# 获取 Odoo 数据目录
data_dir
=
config
.
filestore
(
self
.
env
.
cr
.
dbname
)
if
data_dir
and
file_path
:
full_path
=
os
.
path
.
join
(
data_dir
,
file_path
)
if
os
.
path
.
exists
(
full_path
):
os
.
remove
(
full_path
)
except
Exception
as
file_e
:
_logger
.
warning
(
f
"删除物理文件失败 {attachment.name}: {str(file_e)}"
)
# 删除数据库记录
temp_attachments
.
unlink
()
except
Exception
as
e
:
_logger
.
error
(
f
"定时清理临时附件失败: {str(e)}"
)
@api.depends
()
@api.depends
()
def
_compute_show_sync_last_mile_pod
(
self
):
def
_compute_show_sync_last_mile_pod
(
self
):
...
...
ccs_base/wizard/batch_get_pod_info_wizard.py
浏览文件 @
0c22b124
...
@@ -6,9 +6,6 @@ import io
...
@@ -6,9 +6,6 @@ import io
import
json
import
json
import
logging
import
logging
import
time
import
time
from
datetime
import
datetime
,
timedelta
import
requests
from
odoo
import
models
,
fields
,
api
,
_
from
odoo
import
models
,
fields
,
api
,
_
from
odoo.exceptions
import
ValidationError
from
odoo.exceptions
import
ValidationError
...
@@ -103,6 +100,11 @@ class BatchGetPodInfoWizard(models.TransientModel):
...
@@ -103,6 +100,11 @@ class BatchGetPodInfoWizard(models.TransientModel):
processed_files_data
=
fields
.
Text
(
string
=
'已处理的文件数据'
,
help
=
'存储已处理的文件信息(JSON格式)'
)
processed_files_data
=
fields
.
Text
(
string
=
'已处理的文件数据'
,
help
=
'存储已处理的文件信息(JSON格式)'
)
def
_get_bill_numbers
(
self
,
bl_objs
):
def
_get_bill_numbers
(
self
,
bl_objs
):
"""
获取提单号
:param bl_objs: 提单记录集
:return: 提单号列表
"""
_logger
.
info
(
f
"开始预览操作,提单数量: {len(bl_objs)}"
)
_logger
.
info
(
f
"开始预览操作,提单数量: {len(bl_objs)}"
)
# 调用接口获取提单pdf文件
# 调用接口获取提单pdf文件
pdf_file_arr
=
self
.
_get_pdf_file_arr
(
bl_objs
)
pdf_file_arr
=
self
.
_get_pdf_file_arr
(
bl_objs
)
...
@@ -444,72 +446,8 @@ class BatchGetPodInfoWizard(models.TransientModel):
...
@@ -444,72 +446,8 @@ class BatchGetPodInfoWizard(models.TransientModel):
从API获取PDF文件
从API获取PDF文件
"""
"""
bill_numbers
=
[
self
.
env
[
'common.common'
]
.
sudo
()
.
process_match_str
(
bl
.
bl_no
)
for
bl
in
bl_objs
]
bill_numbers
=
[
self
.
env
[
'common.common'
]
.
sudo
()
.
process_match_str
(
bl
.
bl_no
)
for
bl
in
bl_objs
]
# 调用API获取PDF文件
common
=
self
.
env
[
'common.common'
]
.
sudo
()
api_url
=
self
.
env
[
'ir.config_parameter'
]
.
sudo
()
.
get_param
(
'last_mile_pod_api_url'
,
return
common
.
get_pod_pdf_files
(
bill_numbers
,
'/api/release-notes/pdfs'
)
'http://172.104.52.150:7002'
)
if
not
api_url
:
raise
ValidationError
(
_
(
'API URL not configured'
))
# 构建请求数据
request_data
=
{
"bill_numbers"
:
bill_numbers
}
try
:
response
=
requests
.
post
(
f
"{api_url}/api/release-notes/pdfs"
,
headers
=
{
'Content-Type'
:
'application/json'
},
json
=
request_data
)
if
response
.
status_code
==
200
:
result
=
response
.
json
()
# 检查API响应结构
if
not
result
:
raise
ValidationError
(
_
(
'API returned empty response'
))
if
not
result
.
get
(
'success'
):
error_msg
=
result
.
get
(
'message'
,
'Unknown error'
)
raise
ValidationError
(
_
(
'API returned error:
%
s'
)
%
error_msg
)
# 处理结果数据
results
=
result
.
get
(
'results'
,
[])
if
not
results
:
raise
ValidationError
(
_
(
'No PDF files found in API response'
))
# 提示:API调用成功,但没有PDF文件
# 构建PDF文件数组
pdf_file_arr
=
[]
for
result_item
in
results
:
if
result_item
.
get
(
'success'
):
# 验证必要字段
bill_number
=
result_item
.
get
(
'bill_number'
)
filename
=
result_item
.
get
(
'filename'
)
base64_data
=
result_item
.
get
(
'base64'
)
if
not
all
([
bill_number
,
filename
,
base64_data
]):
_logger
.
warning
(
f
"跳过无效的PDF文件项: {result_item}"
)
continue
# 验证PDF文件
try
:
pdf_binary
=
base64
.
b64decode
(
base64_data
)
# 验证PDF文件头
if
not
pdf_binary
.
startswith
(
b
'
%
PDF-'
):
_logger
.
warning
(
f
"API返回的文件不是有效的PDF格式,提单号: {bill_number}"
)
continue
pdf_file_arr
.
append
({
'bl_no'
:
bill_number
,
'file_name'
:
filename
,
'file_data'
:
base64_data
})
except
Exception
as
e
:
_logger
.
warning
(
f
"API PDF文件验证失败,提单号: {bill_number}, 错误: {str(e)}"
)
continue
return
pdf_file_arr
else
:
raise
ValidationError
(
_
(
'Failed to get PDF file from API:
%
s'
)
%
response
.
text
)
except
requests
.
exceptions
.
RequestException
as
e
:
raise
ValidationError
(
_
(
'API request failed:
%
s'
)
%
str
(
e
))
def
_write_pdf_file
(
self
,
processed_files
,
fix_name
=
'货站提货POD'
):
def
_write_pdf_file
(
self
,
processed_files
,
fix_name
=
'货站提货POD'
):
"""
"""
...
@@ -517,242 +455,43 @@ class BatchGetPodInfoWizard(models.TransientModel):
...
@@ -517,242 +455,43 @@ class BatchGetPodInfoWizard(models.TransientModel):
:param processed_files: 处理后的文件数组
:param processed_files: 处理后的文件数组
:param fix_name:
:param fix_name:
"""
"""
for
file_info
in
processed_files
:
common
=
self
.
env
[
'common.common'
]
.
sudo
()
if
not
file_info
.
get
(
'bl'
):
common
.
write_pod_pdf_files
(
processed_files
,
fix_name
)
_logger
.
warning
(
"跳过没有提单信息的文件"
)
continue
bl
=
file_info
[
'bl'
]
file_name
=
file_info
.
get
(
'file_name'
,
''
)
file_data
=
file_info
.
get
(
'file_data'
,
''
)
if
not
file_data
:
continue
# 如果有文件为空的就回写,否则就创建新的清关文件记录
clearance_file
=
self
.
env
[
'cc.clearance.file'
]
.
search
(
[(
'bl_id'
,
'='
,
bl
.
id
),
(
'file_name'
,
'='
,
fix_name
),
(
'file'
,
'='
,
False
)],
limit
=
1
)
if
clearance_file
:
clearance_file
.
write
({
'attachment_name'
:
file_name
,
'file'
:
file_data
})
_logger
.
info
(
f
"更新清关文件记录: 提单 {bl.bl_no}"
)
else
:
# 创建新的清关文件记录
clearance_file
=
self
.
env
[
'cc.clearance.file'
]
.
create
({
'bl_id'
:
bl
.
id
,
'file_name'
:
fix_name
,
'attachment_name'
:
file_name
,
'file'
:
file_data
})
_logger
.
info
(
f
"创建新的清关文件记录: 提单 {bl.bl_no}"
)
file_info
[
'clearance_file'
]
=
clearance_file
def
_merge_pdf_files
(
self
,
processed_files
):
def
_merge_pdf_files
(
self
,
processed_files
):
"""
"""
合并所有涂抹后的PDF文件为一个PDF并保存到pdf_file字段
合并处理后的POD PDF文件
使用临时文件方式减少内存占用
:param processed_files: 处理后的文件数组
:param processed_files: 处理后的文件数组
"""
"""
import
fitz
# PyMuPDF
common
=
self
.
env
[
'common.common'
]
.
sudo
()
from
datetime
import
datetime
pdf_data
,
pdf_filename
=
common
.
merge_pod_pdfs
(
processed_files
)
import
tempfile
if
pdf_data
and
pdf_filename
:
import
os
import
gc
temp_file_path
=
None
try
:
# 过滤有效的PDF文件
valid_files
=
[]
for
file_info
in
processed_files
:
if
file_info
.
get
(
'bl_no'
)
and
file_info
.
get
(
'file_data'
):
valid_files
.
append
(
file_info
)
if
not
valid_files
:
_logger
.
warning
(
"没有有效的PDF文件可以合并"
)
return
# 如果只有一个PDF文件,直接使用,不需要合并
if
len
(
valid_files
)
==
1
:
file_info
=
valid_files
[
0
]
bl
=
file_info
[
'bl'
]
bl_no
=
bl
.
bl_no
file_data
=
file_info
[
'file_data'
]
file_name
=
file_info
.
get
(
'file_name'
,
f
"{bl_no}.pdf"
)
# 生成文件名(包含提单号和日期)
timestamp
=
datetime
.
now
()
.
strftime
(
'
%
Y
%
m
%
d_
%
H
%
M
%
S'
)
pdf_filename
=
f
"POD文件_{bl_no}_{timestamp}.pdf"
# 直接保存到字段
self
.
write
({
'pdf_file'
:
file_data
,
'pdf_filename'
:
pdf_filename
})
_logger
.
info
(
f
"单个PDF文件直接保存: {pdf_filename}"
)
return
# 多个PDF文件需要合并
_logger
.
info
(
f
"开始合并 {len(valid_files)} 个PDF文件"
)
# 使用临时文件方式合并,避免内存占用过大
temp_file_path
=
tempfile
.
mktemp
(
suffix
=
'.pdf'
)
merged_pdf
=
fitz
.
open
()
bl_numbers
=
[]
# 遍历所有处理后的PDF文件,分批处理以减少内存占用
batch_size
=
5
# 每批处理5个PDF
for
batch_start
in
range
(
0
,
len
(
valid_files
),
batch_size
):
batch_files
=
valid_files
[
batch_start
:
batch_start
+
batch_size
]
_logger
.
info
(
f
"处理第 {batch_start // batch_size + 1} 批,共 {len(batch_files)} 个PDF"
)
for
file_info
in
batch_files
:
bl
=
file_info
[
'bl'
]
bl_no
=
bl
.
bl_no
file_data
=
file_info
[
'file_data'
]
bl_numbers
.
append
(
bl_no
)
source_pdf
=
None
try
:
# 将base64数据转换为二进制
pdf_binary
=
base64
.
b64decode
(
file_data
)
# 打开PDF文档
source_pdf
=
fitz
.
open
(
stream
=
pdf_binary
,
filetype
=
"pdf"
)
# 将源PDF的所有页面插入到合并的PDF中
merged_pdf
.
insert_pdf
(
source_pdf
)
_logger
.
info
(
f
"已添加提单 {bl_no} 的PDF到合并文档({len(source_pdf)} 页)"
)
except
Exception
as
e
:
_logger
.
error
(
f
"合并提单 {bl_no} 的PDF失败: {str(e)}"
)
continue
finally
:
# 立即释放资源
if
source_pdf
:
source_pdf
.
close
()
gc
.
collect
()
# 强制垃圾回收
# 每批处理完后,保存到临时文件并释放内存
if
batch_start
+
batch_size
<
len
(
valid_files
):
# 保存当前合并结果到临时文件
merged_pdf
.
save
(
temp_file_path
,
garbage
=
4
,
deflate
=
True
,
clean
=
True
)
merged_pdf
.
close
()
# 重新打开临时文件继续合并
merged_pdf
=
fitz
.
open
(
temp_file_path
)
gc
.
collect
()
# 如果有页面,保存合并后的PDF
if
len
(
merged_pdf
)
>
0
:
# 使用临时文件保存,减少内存占用
if
not
temp_file_path
:
temp_file_path
=
tempfile
.
mktemp
(
suffix
=
'.pdf'
)
merged_pdf
.
save
(
temp_file_path
,
garbage
=
4
,
deflate
=
True
,
clean
=
True
)
merged_pdf
.
close
()
# 从临时文件读取并转换为base64
with
open
(
temp_file_path
,
'rb'
)
as
f
:
pdf_data
=
f
.
read
()
# 转换为base64
merged_pdf_base64
=
base64
.
b64encode
(
pdf_data
)
.
decode
(
'utf-8'
)
# 清理临时数据
del
pdf_data
gc
.
collect
()
# 生成文件名(包含提单号和日期)
bl_numbers_str
=
'_'
.
join
(
bl_numbers
[:
5
])
# 最多显示5个提单号
if
len
(
bl_numbers
)
>
5
:
bl_numbers_str
+=
f
'_等{len(bl_numbers)}个'
timestamp
=
datetime
.
now
()
.
strftime
(
'
%
Y
%
m
%
d_
%
H
%
M
%
S'
)
pdf_filename
=
f
"合并POD文件_{bl_numbers_str}_{timestamp}.pdf"
# 保存到字段
self
.
write
({
self
.
write
({
'pdf_file'
:
merged_pdf_base64
,
'pdf_file'
:
pdf_data
,
'pdf_filename'
:
pdf_filename
'pdf_filename'
:
pdf_filename
})
})
# 清理base64数据
del
merged_pdf_base64
gc
.
collect
()
_logger
.
info
(
f
"成功合并 {len(bl_numbers)} 个PDF文件,文件名: {pdf_filename}"
)
else
:
_logger
.
warning
(
"没有有效的PDF文件可以合并"
)
except
Exception
as
e
:
_logger
.
error
(
f
"合并PDF文件失败: {str(e)}"
)
finally
:
# 清理临时文件
if
temp_file_path
and
os
.
path
.
exists
(
temp_file_path
):
try
:
os
.
remove
(
temp_file_path
)
_logger
.
info
(
f
"已删除临时文件: {temp_file_path}"
)
except
Exception
as
e
:
_logger
.
warning
(
f
"删除临时文件失败: {str(e)}"
)
def
_match_bl_by_file_name
(
self
,
pdf_file_arr
,
bl_obj
):
def
_match_bl_by_file_name
(
self
,
pdf_file_arr
,
bl_obj
):
"""
"""
Match BL by file name and return processed array # 根据文件名匹配提单并返回处理后的数组
Match BL by file name and return processed array # 根据文件名匹配提单并返回处理后的数组
:param pdf_file_arr: PDF文件数组 [{'bill_number':'', 'filename':'', 'file_data':''}]
:param pdf_file_arr: PDF文件数组 [{'bill_number':'', 'filename':'', 'file_data':''}]
:return: 处理后的数组 [{'bl': bl_obj, 'file_name': 'xxx.pdf', 'file_data': 'xxx', 'matched': True/False}]
:return: 处理后的数组 [{'bl': bl_obj, 'file_name': 'xxx.pdf', 'file_data': 'xxx', 'matched': True/False}]
"""
"""
processed_files
=
[]
common
=
self
.
env
[
'common.common'
]
.
sudo
()
for
bl
in
bl_obj
:
return
common
.
match_pod_files
(
pdf_file_arr
,
bl_obj
,
include_processing_failed
=
False
)
select_bl_no
=
self
.
env
[
'common.common'
]
.
sudo
()
.
process_match_str
(
bl
.
bl_no
)
for
pdf_file
in
pdf_file_arr
:
# 尝试不同的字段名(API可能使用不同的字段名)
file_name
=
pdf_file
.
get
(
'file_name'
)
# 获取文件名
file_data
=
pdf_file
.
get
(
'file_data'
)
# 获取文件数据
bl_no
=
pdf_file
.
get
(
'bl_no'
)
# 获取提单号
if
bl_no
and
select_bl_no
==
bl_no
:
# 构建处理后的文件信息
processed_file
=
{
'bl'
:
bl
,
'file_name'
:
file_name
,
'file_data'
:
file_data
,
'bl_no'
:
bl_no
,
}
processed_files
.
append
(
processed_file
)
break
return
processed_files
def
_sync_last_mile_pod
(
self
,
processed_files
):
def
_sync_last_mile_pod
(
self
,
processed_files
):
"""
"""
Sync pickup POD information # 同步货站提货POD信息
Sync pickup POD information # 同步货站提货POD信息
:param processed_files: 处理后的文件数组
:param processed_files: 处理后的文件数组
"""
"""
redis_conn
=
self
.
env
[
'common.common'
]
.
sudo
()
.
get_redis
()
common
=
self
.
env
[
'common.common'
]
.
sudo
()
if
not
redis_conn
or
redis_conn
==
'no'
:
common
.
push_sync_pod_task
(
raise
ValidationError
(
'未连接redis,无法同步货站提货POD,请联系管理员'
)
processed_files
=
processed_files
,
file_type
=
'货站提货POD'
,
bl_ids
=
[]
pod_desc
=
'货站提货POD'
,
for
file_info
in
processed_files
:
filter_temu
=
False
bl
=
file_info
.
get
(
'bl'
)
)
if
not
bl
:
continue
clearance_file
=
file_info
.
get
(
'clearance_file'
)
if
not
clearance_file
:
continue
bl_ids
.
append
(
bl
.
id
)
if
not
bl_ids
:
return
payload
=
{
'ids'
:
bl_ids
,
'action_type'
:
'sync_last_mile_pod'
,
'user_login'
:
self
.
env
.
user
.
login
,
'file_type'
:
'货站提货POD'
}
try
:
redis_conn
.
lpush
(
'mail_push_package_list'
,
json
.
dumps
(
payload
))
except
Exception
as
e
:
logging
.
info
(
'sync_last_mile_pod redis error:
%
s'
%
e
)
raise
ValidationError
(
'推送货站提货POD同步任务到redis失败,请重试或联系管理员'
)
def
_check_target_texts_exist
(
self
,
pdf_binary
,
bl_no
):
def
_check_target_texts_exist
(
self
,
pdf_binary
,
bl_no
):
"""
"""
...
@@ -2370,200 +2109,14 @@ class BatchGetPodInfoWizard(models.TransientModel):
...
@@ -2370,200 +2109,14 @@ class BatchGetPodInfoWizard(models.TransientModel):
return
False
,
[]
return
False
,
[]
def
_cleanup_temp_attachments
(
self
,
bl_objs
=
None
):
def
_cleanup_temp_attachments
(
self
,
bl_objs
=
None
):
"""
common
=
self
.
env
[
'common.common'
]
.
sudo
()
清理与当前向导相关的临时附件,包括服务器和本地开发环境的物理文件
common
.
cleanup_temp_attachments
(
bl_objs
)
"""
try
:
attachments
=
self
.
env
[
'ir.attachment'
]
.
search
([
(
'res_model'
,
'='
,
bl_objs
[
0
]
.
_name
),
(
'res_id'
,
'in'
,
bl_objs
.
ids
),
(
'name'
,
'like'
,
'temp_pod_
%
'
)
])
if
attachments
:
# 删除数据库记录
attachments
.
unlink
()
except
Exception
as
e
:
_logger
.
error
(
f
"清理临时附件失败: {str(e)}"
)
def
_serialize_processed_files
(
self
,
processed_files
):
def
_serialize_processed_files
(
self
,
processed_files
):
"""
common
=
self
.
env
[
'common.common'
]
.
sudo
()
将processed_files序列化为JSON字符串,文件数据存储到临时附件中
return
common
.
serialize_pod_processed_files
(
processed_files
)
:param processed_files: 处理后的文件数组
:return: JSON字符串(只包含引用信息,不包含文件数据)
"""
# 注意:不在这里清理临时附件,因为预览时需要保留附件数据
# 只有在确认操作完成后才清理临时附件
serialized_data
=
[]
for
file_info
in
processed_files
:
if
not
file_info
.
get
(
'bl'
):
continue
bl
=
file_info
[
'bl'
]
file_data
=
file_info
.
get
(
'file_data'
,
''
)
file_name
=
file_info
.
get
(
'file_name'
,
f
"{bl.bl_no}.pdf"
)
# 将文件数据存储到临时附件中
attachment_id
=
None
if
file_data
:
try
:
attachment
=
self
.
env
[
'ir.attachment'
]
.
create
({
'name'
:
f
"temp_pod_{bl.bl_no}_{int(time.time())}.pdf"
,
'datas'
:
file_data
,
'type'
:
'binary'
,
'res_model'
:
bl
.
_name
,
'res_id'
:
bl
.
id
,
})
attachment_id
=
attachment
.
id
_logger
.
info
(
f
"已创建临时附件存储文件: {attachment.name}, ID: {attachment_id}"
)
except
Exception
as
e
:
_logger
.
error
(
f
"创建临时附件失败: {str(e)}"
)
else
:
_logger
.
warning
(
f
"提单 {bl.bl_no} 的文件数据为空,无法创建附件"
)
data
=
{
'bl_id'
:
bl
.
id
,
'bl_no'
:
bl
.
bl_no
,
'file_name'
:
file_name
,
'attachment_id'
:
attachment_id
,
# 存储附件ID而不是文件数据
}
# OCR文本数据量小,可以直接存储
if
'ocr_texts'
in
file_info
:
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
)
return
json
.
dumps
(
serialized_data
,
ensure_ascii
=
False
)
def
_deserialize_processed_files
(
self
,
json_data
):
def
_deserialize_processed_files
(
self
,
json_data
):
"""
common
=
self
.
env
[
'common.common'
]
.
sudo
()
将JSON字符串反序列化为processed_files(从附件中读取文件数据)
return
common
.
deserialize_pod_processed_files
(
json_data
)
:param json_data: JSON字符串
:return: 处理后的文件数组
"""
if
not
json_data
:
return
[]
try
:
serialized_data
=
json
.
loads
(
json_data
)
processed_files
=
[]
for
data
in
serialized_data
:
bl_id
=
data
.
get
(
'bl_id'
)
attachment_id
=
data
.
get
(
'attachment_id'
)
if
bl_id
:
bl
=
self
.
env
[
'cc.bl'
]
.
browse
(
bl_id
)
if
bl
.
exists
():
# 从附件中读取文件数据
file_data
=
''
if
attachment_id
:
try
:
attachment
=
self
.
env
[
'ir.attachment'
]
.
browse
(
attachment_id
)
if
attachment
.
exists
():
# attachment.datas 已经是 base64 编码的字符串
file_data
=
attachment
.
datas
_logger
.
info
(
f
"从附件读取文件: {attachment.name}, ID: {attachment_id}, 数据长度: {len(file_data) if file_data else 0}"
)
else
:
_logger
.
warning
(
f
"附件不存在: {attachment_id}"
)
except
Exception
as
e
:
_logger
.
error
(
f
"读取附件失败: {str(e)}"
)
else
:
_logger
.
warning
(
f
"提单 {bl.bl_no} 没有附件ID,无法读取文件数据"
)
file_info
=
{
'bl'
:
bl
,
'bl_no'
:
data
.
get
(
'bl_no'
,
''
),
'file_name'
:
data
.
get
(
'file_name'
,
''
),
'file_data'
:
file_data
,
}
# 如果有OCR文本,也恢复
if
'ocr_texts'
in
data
:
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
)
return
processed_files
except
Exception
as
e
:
_logger
.
error
(
f
"反序列化processed_files失败: {str(e)}"
)
return
[]
@api.model
def
cron_cleanup_temp_attachments
(
self
):
"""
定时清理向导生成的临时附件
每天早上8点执行,删除1天之前创建的temp_pod_开头的附件
"""
try
:
# 计算1天前的时间(前一天23:59:59)
today
=
datetime
.
now
()
.
replace
(
hour
=
0
,
minute
=
0
,
second
=
0
,
microsecond
=
0
)
one_day_ago
=
today
+
timedelta
(
days
=
2
)
-
timedelta
(
seconds
=
1
)
# 前一天23:59:59
_logger
.
info
(
f
"开始执行定时清理临时附件任务,清理时间点: {one_day_ago.strftime('
%
Y-
%
m-
%
d
%
H:
%
M:
%
S')}"
)
# 构建SQL查询
sql_query
=
"""
SELECT id, name, res_model, res_id, create_date, store_fname
FROM ir_attachment
WHERE res_model = 'batch.get.pod.info.wizard'
AND create_date < '
%
s'
ORDER BY create_date DESC
"""
%
(
one_day_ago
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
))
# 执行SQL查询
self
.
env
.
cr
.
execute
(
sql_query
)
sql_results
=
self
.
env
.
cr
.
fetchall
()
# 将SQL结果转换为Odoo记录集
if
sql_results
:
attachment_ids
=
[
result
[
0
]
for
result
in
sql_results
]
temp_attachments
=
self
.
env
[
'ir.attachment'
]
.
sudo
()
.
browse
(
attachment_ids
)
attachment_count
=
len
(
temp_attachments
)
_logger
.
info
(
f
"找到 {attachment_count} 个{one_day_ago.strftime('
%
Y-
%
m-
%
d')}之前创建的临时附件,开始清理"
)
# 删除物理文件
for
attachment
in
temp_attachments
:
try
:
# 获取附件的物理文件路径
if
hasattr
(
attachment
,
'store_fname'
)
and
attachment
.
store_fname
:
# Odoo 12+ 使用 store_fname
file_path
=
attachment
.
store_fname
elif
hasattr
(
attachment
,
'datas_fname'
)
and
attachment
.
datas_fname
:
# 旧版本使用 datas_fname
file_path
=
attachment
.
datas_fname
else
:
# 尝试从 name 字段构建路径
file_path
=
attachment
.
name
# 构建完整的文件路径
import
os
from
odoo.tools
import
config
# 获取 Odoo 数据目录
data_dir
=
config
.
filestore
(
self
.
env
.
cr
.
dbname
)
if
data_dir
and
file_path
:
full_path
=
os
.
path
.
join
(
data_dir
,
file_path
)
if
os
.
path
.
exists
(
full_path
):
os
.
remove
(
full_path
)
except
Exception
as
file_e
:
_logger
.
warning
(
f
"删除物理文件失败 {attachment.name}: {str(file_e)}"
)
# 删除数据库记录
temp_attachments
.
unlink
()
except
Exception
as
e
:
_logger
.
error
(
f
"定时清理临时附件失败: {str(e)}"
)
consumers/mail_push.py
浏览文件 @
0c22b124
...
@@ -67,7 +67,7 @@ class Order_dispose(object):
...
@@ -67,7 +67,7 @@ class Order_dispose(object):
if
not
non_temu_ids
:
if
not
non_temu_ids
:
return
return
clearance_ids
=
clearance_model
.
search
([
clearance_ids
=
clearance_model
.
search
([
(
'bl_id'
,
'in'
,
non_temu_ids
),
(
'bl_id'
,
'in'
,
non_temu_ids
),
(
'is_upload'
,
'='
,
False
),
(
'file_name'
,
'='
,
data
.
get
(
'file_type'
)),
(
'file_name'
,
'='
,
data
.
get
(
'file_type'
)),
])
])
if
not
clearance_ids
:
if
not
clearance_ids
:
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论