Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
H
hh_ccs
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
贺阳
hh_ccs
Commits
b49fb174
提交
b49fb174
authored
6月 15, 2026
作者:
刘擎阳
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
1.优化bug
上级
8279ed7a
隐藏空白字符变更
内嵌
并排
正在显示
3 个修改的文件
包含
107 行增加
和
44 行删除
+107
-44
template.xlsx
ccs_connect_tiktok/static/xlsx/template.xlsx
+0
-0
excel_wizard.py
ccs_connect_tiktok/wizard/excel_wizard.py
+104
-42
excel_wizard.xml
ccs_connect_tiktok/wizard/excel_wizard.xml
+3
-2
没有找到文件。
ccs_connect_tiktok/static/xlsx/template.xlsx
浏览文件 @
b49fb174
No preview for this file type
ccs_connect_tiktok/wizard/excel_wizard.py
浏览文件 @
b49fb174
...
@@ -6,6 +6,7 @@ import pytz
...
@@ -6,6 +6,7 @@ import pytz
from
datetime
import
datetime
,
date
from
datetime
import
datetime
,
date
from
odoo
import
models
,
fields
,
api
,
_
from
odoo
import
models
,
fields
,
api
,
_
from
odoo.exceptions
import
UserError
from
odoo.exceptions
import
UserError
from
dateutil
import
parser
import
openpyxl
import
openpyxl
_logger
=
logging
.
getLogger
(
__name__
)
_logger
=
logging
.
getLogger
(
__name__
)
...
@@ -55,9 +56,14 @@ class PackageDataWizard(models.TransientModel):
...
@@ -55,9 +56,14 @@ class PackageDataWizard(models.TransientModel):
return
False
return
False
elif
isinstance
(
val
,
str
):
elif
isinstance
(
val
,
str
):
try
:
try
:
# 优先尝试框架原生的标准解析,效率高
dt
=
fields
.
Datetime
.
from_string
(
val
)
dt
=
fields
.
Datetime
.
from_string
(
val
)
except
ValueError
:
except
(
ValueError
,
AttributeError
):
return
False
try
:
# 原生解析失败时,使用 dateutil 兜底解析 "2026/6/25" 等非标格式
dt
=
parser
.
parse
(
val
)
except
Exception
:
return
False
elif
isinstance
(
val
,
datetime
):
elif
isinstance
(
val
,
datetime
):
dt
=
val
dt
=
val
elif
isinstance
(
val
,
date
):
elif
isinstance
(
val
,
date
):
...
@@ -79,8 +85,8 @@ class PackageDataWizard(models.TransientModel):
...
@@ -79,8 +85,8 @@ class PackageDataWizard(models.TransientModel):
def
action_import_data_with_validation
(
self
):
def
action_import_data_with_validation
(
self
):
"""
"""
表格数据校验与导入核心逻辑:
表格数据校验与导入核心逻辑:
1. 校验未来时
1. 校验未来时
(含托盘使用日期)
2. 根据当前 node_id 校验与系统存量数据的时序
2. 根据当前 node_id 校验与系统存量数据的时序
及必填项
3. 错误回写表格,保存到当前向导的 report_file 字段
3. 错误回写表格,保存到当前向导的 report_file 字段
4. 成功返回 JSON 格式数据
4. 成功返回 JSON 格式数据
"""
"""
...
@@ -103,7 +109,7 @@ class PackageDataWizard(models.TransientModel):
...
@@ -103,7 +109,7 @@ class PackageDataWizard(models.TransientModel):
header_map
=
{
col
:
idx
for
idx
,
col
in
enumerate
(
headers
)}
header_map
=
{
col
:
idx
for
idx
,
col
in
enumerate
(
headers
)}
# 预设错误回写列
(如果已经有错误原因列则覆盖,否则新增一列)
# 预设错误回写列
error_col_idx
=
header_map
.
get
(
'错误原因'
,
sheet
.
max_column
)
+
1
error_col_idx
=
header_map
.
get
(
'错误原因'
,
sheet
.
max_column
)
+
1
if
'错误原因'
not
in
headers
:
if
'错误原因'
not
in
headers
:
sheet
.
cell
(
row
=
1
,
column
=
error_col_idx
,
value
=
"错误原因"
)
sheet
.
cell
(
row
=
1
,
column
=
error_col_idx
,
value
=
"错误原因"
)
...
@@ -120,11 +126,28 @@ class PackageDataWizard(models.TransientModel):
...
@@ -120,11 +126,28 @@ class PackageDataWizard(models.TransientModel):
all_pkg_nums
.
append
(
str
(
pkg_num
)
.
strip
())
all_pkg_nums
.
append
(
str
(
pkg_num
)
.
strip
())
system_packages
=
self
.
env
[
'cc.big.package'
]
.
search
([(
'big_package_no'
,
'in'
,
all_pkg_nums
)])
system_packages
=
self
.
env
[
'cc.big.package'
]
.
search
([(
'big_package_no'
,
'in'
,
all_pkg_nums
)])
# 【优化点】:因为上面搜索的是 big_package_no,字典映射改用 big_package_no 更安全
sys_pkg_map
=
{
pkg
.
big_package_no
:
pkg
for
pkg
in
system_packages
if
pkg
.
big_package_no
}
sys_pkg_map
=
{
pkg
.
big_package_no
:
pkg
for
pkg
in
system_packages
if
pkg
.
big_package_no
}
current_node_name
=
self
.
node_id
.
name
if
self
.
node_id
else
''
current_node_name
=
self
.
node_id
.
name
if
self
.
node_id
else
''
# 【新增优化 4】:预先批量查询“已提货”节点ID及所有相关小包数据
picked_up_node_id
=
False
sp_by_bp_map
=
{}
if
current_node_name
==
'待尾程提货'
:
picked_up_node
=
self
.
env
[
'cc.node'
]
.
search
([(
'node_type'
,
'='
,
'package'
),
(
'name'
,
'='
,
'已提货'
)],
limit
=
1
)
if
picked_up_node
:
picked_up_node_id
=
picked_up_node
.
id
# 一次性查出表格里所有大包关联的小包
small_packages
=
self
.
env
[
'cc.ship.package'
]
.
search
(
[(
'big_package_id.big_package_no'
,
'in'
,
all_pkg_nums
)])
# 按大包号对小包进行分组字典映射
for
sp
in
small_packages
:
bp_no
=
sp
.
big_package_id
.
big_package_no
if
bp_no
:
if
bp_no
not
in
sp_by_bp_map
:
sp_by_bp_map
[
bp_no
]
=
[]
sp_by_bp_map
[
bp_no
]
.
append
(
sp
)
# --- 3. 逐行遍历校验 ---
# --- 3. 逐行遍历校验 ---
for
row_idx
,
row
in
enumerate
(
sheet
.
iter_rows
(
min_row
=
2
,
max_col
=
error_col_idx
),
start
=
2
):
for
row_idx
,
row
in
enumerate
(
sheet
.
iter_rows
(
min_row
=
2
,
max_col
=
error_col_idx
),
start
=
2
):
pkg_num_cell
=
row
[
header_map
.
get
(
'大包号'
)]
if
header_map
.
get
(
'大包号'
)
is
not
None
else
None
pkg_num_cell
=
row
[
header_map
.
get
(
'大包号'
)]
if
header_map
.
get
(
'大包号'
)
is
not
None
else
None
...
@@ -142,20 +165,82 @@ class PackageDataWizard(models.TransientModel):
...
@@ -142,20 +165,82 @@ class PackageDataWizard(models.TransientModel):
continue
continue
row_data_dict
[
col_name
]
=
row
[
col_idx
]
.
value
row_data_dict
[
col_name
]
=
row
[
col_idx
]
.
value
# 获取并转换需要校验的
两个时间
# 获取并转换需要校验的
时间字段
raw_tally
=
row_data_dict
.
get
(
'理货时间'
)
raw_tally
=
row_data_dict
.
get
(
'理货时间'
)
raw_delivery
=
row_data_dict
.
get
(
'尾程交货时间'
)
raw_delivery
=
row_data_dict
.
get
(
'尾程交货时间'
)
raw_pallet_date
=
row_data_dict
.
get
(
'托盘使用日期'
)
tally_utc
=
self
.
_parse_and_convert_tz
(
raw_tally
)
tally_utc
=
self
.
_parse_and_convert_tz
(
raw_tally
)
delivery_utc
=
self
.
_parse_and_convert_tz
(
raw_delivery
)
delivery_utc
=
self
.
_parse_and_convert_tz
(
raw_delivery
)
pallet_date_utc
=
self
.
_parse_and_convert_tz
(
raw_pallet_date
)
if
tally_utc
:
if
tally_utc
:
row_data_dict
[
'理货时间'
]
=
tally_utc
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
row_data_dict
[
'理货时间'
]
=
tally_utc
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
delivery_utc
:
if
delivery_utc
:
row_data_dict
[
'尾程交货时间'
]
=
delivery_utc
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
row_data_dict
[
'尾程交货时间'
]
=
delivery_utc
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
pallet_date_utc
:
row_data_dict
[
'托盘使用日期'
]
=
pallet_date_utc
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
# 提取人员与托盘号文本(防呆:去空格判断)
tally_person
=
row_data_dict
.
get
(
'理货人'
)
delivery_person
=
row_data_dict
.
get
(
'尾程交货人'
)
pallet_no
=
row_data_dict
.
get
(
'托盘号'
)
tally_person_str
=
str
(
tally_person
)
.
strip
()
if
tally_person
else
''
delivery_person_str
=
str
(
delivery_person
)
.
strip
()
if
delivery_person
else
''
pallet_no_str
=
str
(
pallet_no
)
.
strip
()
if
pallet_no
else
''
# -----------------------------------------------------
# -----------------------------------------------------
# 【需求 1】:理货时间、尾程交货时间不能是未来时间
# 【优化 1, 2, 4】:根据变更状态校验时序、必填项及小包状态
# -----------------------------------------------------
if
current_node_name
==
'待尾程提货'
:
if
not
raw_tally
:
row_errors
.
append
(
"变更状态为待尾程提货时,理货时间不能为空"
)
if
not
tally_person_str
:
row_errors
.
append
(
"变更状态为待尾程提货时,理货人不能为空"
)
if
tally_person_str
:
user
=
self
.
env
[
'res.users'
]
.
sudo
()
.
search
([(
'login'
,
'='
,
tally_person_str
)],
limit
=
1
)
if
not
user
:
row_errors
.
append
(
"系统不存在该理货人"
)
# 【新增优化 4】:小包“已提货”状态交叉校验
if
picked_up_node_id
:
sps
=
sp_by_bp_map
.
get
(
pkg_num
,
[])
if
not
sps
:
row_errors
.
append
(
"该大包下未找到任何系统小包记录"
)
else
:
# 找出所有状态不是“已提货”的小包 (注: 这里假定小包模型代表单号的字段为 name)
not_ready_sps
=
[
sp
.
logistic_order_no
for
sp
in
sps
if
sp
.
state
.
id
!=
picked_up_node_id
]
if
len
(
not_ready_sps
)
==
len
(
sps
):
row_errors
.
append
(
"该大包下的小包全部不是已提货状态"
)
elif
len
(
not_ready_sps
)
>
0
:
# 部分未提货,截取前10个单号展示
show_sps
=
not_ready_sps
[:
10
]
row_errors
.
append
(
f
"部分小包不是已提货状态: {', '.join(show_sps)}"
)
elif
current_node_name
==
'尾程交接'
:
if
not
raw_delivery
:
row_errors
.
append
(
"变更状态为尾程交接时,尾程交货时间不能为空"
)
if
not
delivery_person_str
:
row_errors
.
append
(
"变更状态为尾程交接时,尾程交货人不能为空"
)
if
delivery_person_str
:
user
=
self
.
env
[
'res.users'
]
.
sudo
()
.
search
([(
'login'
,
'='
,
delivery_person_str
)],
limit
=
1
)
if
not
user
:
row_errors
.
append
(
"系统不存在该尾程交货人"
)
# -----------------------------------------------------
# 【优化 3】:托盘号、托盘使用日期必填及未来日期校验
# -----------------------------------------------------
if
not
pallet_no_str
:
row_errors
.
append
(
"托盘号不能为空"
)
# print(raw_pallet_date, pallet_date_utc, now_utc)
if
not
raw_pallet_date
:
row_errors
.
append
(
"托盘使用日期不能为空"
)
elif
pallet_date_utc
and
pallet_date_utc
>
now_utc
:
row_errors
.
append
(
"托盘使用日期不能是未来日期"
)
# -----------------------------------------------------
# 原逻辑:理货时间、尾程交货时间不能是未来时间
# -----------------------------------------------------
# -----------------------------------------------------
if
tally_utc
and
tally_utc
>
now_utc
:
if
tally_utc
and
tally_utc
>
now_utc
:
row_errors
.
append
(
"理货时间不能是未来时间"
)
row_errors
.
append
(
"理货时间不能是未来时间"
)
...
@@ -163,46 +248,40 @@ class PackageDataWizard(models.TransientModel):
...
@@ -163,46 +248,40 @@ class PackageDataWizard(models.TransientModel):
row_errors
.
append
(
"尾程交货时间不能是未来时间"
)
row_errors
.
append
(
"尾程交货时间不能是未来时间"
)
# -----------------------------------------------------
# -----------------------------------------------------
# 结合系统数据的状态时序校验
(需求 2, 3)
# 结合系统数据的状态时序校验
# -----------------------------------------------------
# -----------------------------------------------------
sys_pkg
=
sys_pkg_map
.
get
(
pkg_num
)
sys_pkg
=
sys_pkg_map
.
get
(
pkg_num
)
# 【需求 2】:系统选择“待尾程提货”,理货时间不能小于系统的提货时间
if
current_node_name
==
'待尾程提货'
and
tally_utc
and
sys_pkg
and
sys_pkg
.
pickup_time
:
if
current_node_name
==
'待尾程提货'
and
tally_utc
and
sys_pkg
and
sys_pkg
.
pickup_time
:
if
tally_utc
<
sys_pkg
.
pickup_time
:
if
tally_utc
<
=
sys_pkg
.
pickup_time
:
row_errors
.
append
(
"理货时间
不能小
于系统内该大包的提货时间"
)
row_errors
.
append
(
"理货时间
必须大
于系统内该大包的提货时间"
)
# 【需求 3】:系统选择“尾程交接”,尾程交货时间不能小于系统的理货时间
if
current_node_name
==
'尾程交接'
and
delivery_utc
and
sys_pkg
and
sys_pkg
.
tally_time
:
if
current_node_name
==
'尾程交接'
and
delivery_utc
and
sys_pkg
and
sys_pkg
.
tally_time
:
if
delivery_utc
<
sys_pkg
.
tally_time
:
if
delivery_utc
<
=
sys_pkg
.
tally_time
:
row_errors
.
append
(
"尾程交货时间
不能小
于系统内该大包的理货时间"
)
row_errors
.
append
(
"尾程交货时间
必须大
于系统内该大包的理货时间"
)
# --- 4. 组装结果与回写 ---
# --- 4. 组装结果与回写 ---
if
row_errors
:
if
row_errors
:
has_error
=
True
has_error
=
True
error_msg
=
" | "
.
join
(
row_errors
)
error_msg
=
" | "
.
join
(
row_errors
)
# 将错误写在这一行的最后一列
row
[
error_col_idx
-
1
]
.
value
=
error_msg
row
[
error_col_idx
-
1
]
.
value
=
error_msg
else
:
else
:
final_data_to_insert
.
append
(
row_data_dict
)
final_data_to_insert
.
append
(
row_data_dict
)
# --- 5.
【修改部分】:
异常数据保存到模型字段,刷新页面 ---
# --- 5. 异常数据保存到模型字段,刷新页面 ---
if
has_error
:
if
has_error
:
out_stream
=
io
.
BytesIO
()
out_stream
=
io
.
BytesIO
()
wb
.
save
(
out_stream
)
wb
.
save
(
out_stream
)
out_stream
.
seek
(
0
)
out_stream
.
seek
(
0
)
# 生成文件名和 Base64 数据
error_file_name
=
f
'异常数据.xlsx'
error_file_name
=
f
'异常数据.xlsx'
encoded_file
=
base64
.
b64encode
(
out_stream
.
read
())
encoded_file
=
base64
.
b64encode
(
out_stream
.
read
())
# 写入当前向导记录
self
.
write
({
self
.
write
({
'report_file'
:
encoded_file
,
'report_file'
:
encoded_file
,
'file_name'
:
error_file_name
'file_name'
:
error_file_name
})
})
# 返回前端动作:刷新当前向导页面 (用户将能看到并点击下载你的 report_file)
return
{
return
{
'type'
:
'ir.actions.act_window'
,
'type'
:
'ir.actions.act_window'
,
'res_model'
:
'package.data.wizard'
,
'res_model'
:
'package.data.wizard'
,
...
@@ -214,28 +293,8 @@ class PackageDataWizard(models.TransientModel):
...
@@ -214,28 +293,8 @@ class PackageDataWizard(models.TransientModel):
# --- 6. 校验全部通过,返回 JSON 格式数据 ---
# --- 6. 校验全部通过,返回 JSON 格式数据 ---
json_result
=
json
.
dumps
(
final_data_to_insert
,
ensure_ascii
=
False
,
indent
=
4
)
json_result
=
json
.
dumps
(
final_data_to_insert
,
ensure_ascii
=
False
,
indent
=
4
)
print
(
json_result
)
return
json_result
return
json_result
# _logger.info("\n" + "=" * 50)
# _logger.info(f"所有数据校验完美通过!共计 {len(final_data_to_insert)} 行。")
# _logger.info("返回的 JSON 数据如下:\n%s", json_result)
# _logger.info("=" * 50)
#
# # 如果需要彻底清空错误文件字段,避免下次正常上传时依然显示,可以在这里加上清理代码
# self.write({
# 'report_file': False,
# 'file_name': False
# })
#
# return {
# 'type': 'ir.actions.client',
# 'tag': 'display_notification',
# 'params': {
# 'title': '校验与转换成功',
# 'message': f'全部 {len(final_data_to_insert)} 条数据校验通过!已转换为 JSON 数据格式。',
# 'type': 'success',
# 'sticky': False,
# }
# }
# def submit(self):
# def submit(self):
# for attachment_obj in self.attachment_ids:
# for attachment_obj in self.attachment_ids:
...
@@ -415,7 +474,10 @@ class PackageDataWizard(models.TransientModel):
...
@@ -415,7 +474,10 @@ class PackageDataWizard(models.TransientModel):
wj_time
,
wj_time
,
big_package_obj
.
id
big_package_obj
.
id
))
))
if
self
.
node_id
.
name
==
'待尾程提货'
:
big_package_obj
.
tally_state
=
'checked_goods'
else
:
big_package_obj
.
tally_state
=
'handover_completed'
# 7. 更新托盘状态
# 7. 更新托盘状态
if
pallet_no
:
if
pallet_no
:
pallet_obj
=
self
.
env
[
'cc.pallet'
]
.
sudo
()
.
search
([(
'name'
,
'='
,
pallet_no
)],
limit
=
1
)
pallet_obj
=
self
.
env
[
'cc.pallet'
]
.
sudo
()
.
search
([(
'name'
,
'='
,
pallet_no
)],
limit
=
1
)
...
...
ccs_connect_tiktok/wizard/excel_wizard.xml
浏览文件 @
b49fb174
...
@@ -54,10 +54,10 @@
...
@@ -54,10 +54,10 @@
<field
name=
"target"
>
new
</field>
<field
name=
"target"
>
new
</field>
</record>
</record>
<menuitem
id=
"menu_package_data_wizard"
<menuitem
id=
"menu_
import_
package_data_wizard"
name=
"导入包裹数据"
name=
"导入包裹数据"
action=
"action_package_data_wizard"
action=
"action_package_data_wizard"
groups=
"
base.group_system
"
groups=
"
ccs_base.group_import_package
"
sequence=
"50"
/>
sequence=
"50"
/>
</data>
</data>
</odoo>
</odoo>
\ No newline at end of file
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论