Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
H
hh_ccs
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
贺阳
hh_ccs
Commits
8279ed7a
提交
8279ed7a
authored
6月 12, 2026
作者:
刘擎阳
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
1.优化
上级
53af0a0d
隐藏空白字符变更
内嵌
并排
正在显示
11 个修改的文件
包含
650 行增加
和
89 行删除
+650
-89
zh_CN.po
ccs_base/i18n/zh_CN.po
+18
-2
cc_bill_loading.py
ccs_base/models/cc_bill_loading.py
+1
-1
account_security.xml
ccs_base/security/account_security.xml
+5
-0
cc_bl_view.xml
ccs_base/views/cc_bl_view.xml
+4
-0
zh_CN.po
ccs_connect_tiktok/i18n/zh_CN.po
+11
-0
template.xlsx
ccs_connect_tiktok/static/xlsx/template.xlsx
+0
-0
cc_ship_package_view.xml
ccs_connect_tiktok/views/cc_ship_package_view.xml
+6
-0
11.py
ccs_connect_tiktok/wizard/11.py
+104
-0
excel_wizard.py
ccs_connect_tiktok/wizard/excel_wizard.py
+448
-77
excel_wizard.xml
ccs_connect_tiktok/wizard/excel_wizard.xml
+32
-9
multi_search.py
multi_record_search/models/multi_search.py
+21
-0
没有找到文件。
ccs_base/i18n/zh_CN.po
浏览文件 @
8279ed7a
...
...
@@ -2710,15 +2710,20 @@ msgid "Package Goods"
msgstr "小包商品"
#. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__goods_qty
#: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__ship_package_qty
#: model:ir.model.fields,field_description:ccs_base.field_cc_history_big_package__goods_qty
#: model:ir.model.fields,field_description:ccs_base.field_cc_history_big_package__ship_package_qty
#: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_big_package_view
#: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_history_big_package_view
msgid "Package Qty"
msgstr "小包数"
#. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__goods_qty
#: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_big_package_view
msgid "Goods Qty"
msgstr "商品数"
#. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_history_ship_package__buyer_region
#: model:ir.model.fields,field_description:ccs_base.field_cc_ship_package__buyer_region
...
...
@@ -4463,3 +4468,13 @@ msgstr ""
#, python-format
msgid "预览操作失败: %s"
msgstr ""
#. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.search_cc_bl_view
msgid "Customs Clearance Status"
msgstr "关务提单状态"
#. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.search_cc_bl_view
msgid "Last Mile Provider"
msgstr "尾程服务商"
\ No newline at end of file
ccs_base/models/cc_bill_loading.py
浏览文件 @
8279ed7a
...
...
@@ -40,7 +40,7 @@ class CcBigPackage(models.Model):
# 增加包裹数量字段, 用于显示包裹数量, 根据big_package_line_ids计算
ship_package_qty
=
fields
.
Integer
(
string
=
'Package Qty'
,
compute
=
'_compute_big_package_qty'
,
store
=
True
)
# 商品纪录数
goods_qty
=
fields
.
Integer
(
string
=
'
Package
Qty'
,
compute
=
'_compute_big_package_qty'
,
store
=
True
)
goods_qty
=
fields
.
Integer
(
string
=
'
Goods
Qty'
,
compute
=
'_compute_big_package_qty'
,
store
=
True
)
# New fields: pallet_number (char type), pallet_usage_date
# 托盘号(char型),托盘使用日期
...
...
ccs_base/security/account_security.xml
浏览文件 @
8279ed7a
...
...
@@ -20,6 +20,11 @@
<field
name=
"implied_ids"
eval=
"[(4, ref('group_clearance_of_customs_user'))]"
/>
</record>
<record
id=
"group_import_package"
model=
"res.groups"
>
<field
name=
"name"
>
包裹数据导入
</field>
<field
name=
"category_id"
ref=
"ccs_base.module_category_clearance_of_customs"
/>
<field
name=
"implied_ids"
eval=
"[(4, ref('base.group_user'))]"
/>
</record>
</data>
...
...
ccs_base/views/cc_bl_view.xml
浏览文件 @
8279ed7a
...
...
@@ -313,6 +313,10 @@
context=
"{'group_by': 'cc_country_id'}"
/>
<filter
domain=
"[]"
name=
"groupby_state"
string=
"Status"
context=
"{'group_by': 'state'}"
/>
<filter
domain=
"[]"
name=
"groupby_customs_clearance_status"
string=
"Customs Clearance Status"
context=
"{'group_by': 'customs_clearance_status'}"
/>
<filter
domain=
"[]"
name=
"groupby_last_mile_provider_ids"
string=
"Last Mile Provider"
context=
"{'group_by': 'last_mile_provider_ids'}"
/>
</group>
<searchpanel>
<field
icon=
"fa-users"
select=
"multi"
name=
"cc_company_id"
/>
...
...
ccs_connect_tiktok/i18n/zh_CN.po
浏览文件 @
8279ed7a
...
...
@@ -992,3 +992,14 @@ msgstr ""
#: model:ir.model.fields,field_description:ccs_connect_tiktok.field_bl_patrol__email_sent
msgid "邮件已发送"
msgstr ""
#. module: ccs_connect_tiktok
#: model_terms:ir.ui.view,arch_db:ccs_connect_tiktok.search_cc_ship_package_view_inherit
msgid "Is Sync"
msgstr "是否同步"
#. module: ccs_connect_tiktok
#: model_terms:ir.ui.view,arch_db:ccs_connect_tiktok.search_cc_ship_package_view_inherit
msgid "Next Provider"
msgstr "下阶段服务商"
ccs_connect_tiktok/static/xlsx/template.xlsx
0 → 100644
浏览文件 @
8279ed7a
File added
ccs_connect_tiktok/views/cc_ship_package_view.xml
浏览文件 @
8279ed7a
...
...
@@ -34,6 +34,12 @@
<search
position=
"inside"
>
<filter
string=
"Not Sync"
name=
"filter_is_sync"
domain=
"[('is_sync','=',False)]"
/>
</search>
<xpath
expr=
"//search//group"
position=
"inside"
>
<filter
domain=
"[]"
name=
"groupby_is_sync"
string=
"Is Sync"
context=
"{'group_by': 'is_sync'}"
/>
<filter
domain=
"[]"
name=
"groupby_next_provider_name"
string=
"Next Provider"
context=
"{'group_by': 'next_provider_name'}"
/>
</xpath>
</field>
</record>
...
...
ccs_connect_tiktok/wizard/11.py
0 → 100644
浏览文件 @
8279ed7a
# 文件:wizards/collection_receive_wizard.py
import
xlrd
from
odoo
import
models
,
fields
,
api
,
_
from
odoo.exceptions
import
ValidationError
from
datetime
import
date
,
datetime
import
logging
import
pytz
_logger
=
logging
.
getLogger
(
__name__
)
class
PackageDataWizard
(
models
.
TransientModel
):
_name
=
'package.data.wizard'
_description
=
'包裹数据处理'
attachment_ids
=
fields
.
Many2many
(
'ir.attachment'
,
'ref_package_data_attachment'
,
string
=
'附件'
)
node_id
=
fields
.
Many2one
(
'cc.node'
,
string
=
'变更状态'
)
# 新增时区字段
timezone
=
fields
.
Selection
(
selection
=
lambda
self
:
[(
tz
,
tz
)
for
tz
in
pytz
.
all_timezones
],
string
=
'时区'
,
default
=
lambda
self
:
self
.
env
.
user
.
tz
or
self
.
env
.
context
.
get
(
'tz'
)
or
'UTC'
)
def
get_file_path
(
self
,
report_file_ids
):
"""
得到excel表格的内容
:return:
"""
report_path
=
report_file_ids
.
_full_path
(
report_file_ids
.
store_fname
)
if
report_file_ids
.
name
[
-
3
:]
==
'xls'
:
data
=
xlrd
.
open_workbook
(
report_path
,
formatting_info
=
True
)
else
:
data
=
xlrd
.
open_workbook
(
report_path
)
return
data
def
submit
(
self
):
for
attachment_obj
in
self
.
attachment_ids
:
data
=
self
.
get_file_path
(
attachment_obj
)
first_table
=
data
.
sheets
()[
0
]
try
:
if
self
.
node_id
.
name
==
'待尾程提货'
:
select_node_obj
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
([(
'node_type'
,
'='
,
'package'
),
(
'name'
,
'='
,
'已提货'
)],
limit
=
1
)
else
:
select_node_obj
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
([(
'node_type'
,
'='
,
'package'
),
(
'name'
,
'='
,
'待尾程提货'
)],
limit
=
1
)
# 新增:定义一个累加器,记录整个 Excel 总共更新了多少个小包
total_updated_count
=
0
for
i
in
range
(
1
,
first_table
.
nrows
):
line
=
first_table
.
row_values
(
i
)
big_no
=
str
(
line
[
1
])
.
strip
()
if
not
big_no
:
continue
lh_time
=
line
[
10
]
wj_time
=
line
[
5
]
# 尾程交货时间
# 大包数据
wj_user_name
=
line
[
4
]
# 尾程交货人
lh_user_name
=
line
[
8
]
# 理货人
pallet_date
=
line
[
6
]
# 托盘日期
pallet_no
=
str
(
line
[
7
])
.
replace
(
'.0'
,
''
)
.
replace
(
' '
,
''
)
# 托盘号
wj_user_obj
=
self
.
env
[
'res.users'
]
.
sudo
()
.
search
([(
'login'
,
'='
,
wj_user_name
)],
limit
=
1
)
lh_user_obj
=
self
.
env
[
'res.users'
]
.
sudo
()
.
search
([(
'login'
,
'='
,
lh_user_name
)],
limit
=
1
)
update_time
=
lh_time
if
self
.
node_id
.
name
==
'待尾程提货'
else
wj_time
ship_package_objs
=
self
.
env
[
'cc.ship.package'
]
.
sudo
()
.
search
([
(
'big_package_id.big_package_no'
,
'='
,
big_no
),
(
'state'
,
'='
,
select_node_obj
.
id
)
])
# 已提货
if
ship_package_objs
:
# 核心修改:计算当前大包查到的小包数量
current_count
=
len
(
ship_package_objs
)
total_updated_count
+=
current_count
# 累加到总数
# 日志记录:每个大包具体查到了多少个小包
_logger
.
info
(
f
"处理第 {i} 行: 大包号 [{big_no}] 查到 {current_count} 个小包准备更新。"
)
sql
=
"""
UPDATE cc_ship_package
SET state =
%
s, process_time =
%
s , is_sync=False
WHERE id IN
%
s
"""
self
.
env
.
cr
.
execute
(
sql
,
(
self
.
node_id
.
id
,
update_time
,
tuple
(
ship_package_objs
.
ids
)))
# 更新大包
big_package_obj
=
self
.
env
[
'cc.big.package'
]
.
sudo
()
.
search
([(
'big_package_no'
,
'='
,
big_no
)],
limit
=
1
)
sql
=
"""
UPDATE cc_big_package
SET pallet_number =
%
s, pallet_usage_date =
%
s , tally_user_id=
%
s,
tally_time =
%
s, delivery_user_id =
%
s , delivery_time=
%
s
WHERE id =
%
s
"""
self
.
env
.
cr
.
execute
(
sql
,
(
pallet_no
,
pallet_date
,
lh_user_obj
.
id
if
lh_user_obj
else
None
,
lh_time
,
wj_user_obj
.
id
if
wj_user_obj
else
None
,
wj_time
,
big_package_obj
.
id
))
pallet_obj
=
self
.
env
[
'cc.pallet'
]
.
sudo
()
.
search
([(
'name'
,
'='
,
pallet_no
)],
limit
=
1
)
pallet_obj
.
usage_state
=
'used'
else
:
# 选做:把没查到数据的大包也用 warning 级别记录下来,方便后期排查漏刷的数据
_logger
.
warning
(
f
"处理第 {i} 行: 大包号 [{big_no}] 未查到状态为'已提货'的小包,跳过更新。"
)
# 循环结束后,记录该文件的最终战果
_logger
.
info
(
f
"==== 附件处理完毕 ==== 整个表格共查到并更新了 {total_updated_count} 个小包!"
)
except
Exception
as
err
:
_logger
.
error
(
f
'package_data_xls error : {str(err)}'
)
ccs_connect_tiktok/wizard/excel_wizard.py
浏览文件 @
8279ed7a
# 文件:wizards/collection_receive_wizard.py
import
xlrd
from
odoo
import
models
,
fields
,
api
,
_
from
odoo.exceptions
import
ValidationError
from
datetime
import
date
,
datetime
import
base64
import
io
import
json
import
logging
import
pytz
from
datetime
import
datetime
,
date
from
odoo
import
models
,
fields
,
api
,
_
from
odoo.exceptions
import
UserError
import
openpyxl
_logger
=
logging
.
getLogger
(
__name__
)
...
...
@@ -13,84 +17,451 @@ class PackageDataWizard(models.TransientModel):
attachment_ids
=
fields
.
Many2many
(
'ir.attachment'
,
'ref_package_data_attachment'
,
string
=
'附件'
)
node_id
=
fields
.
Many2one
(
'cc.node'
,
string
=
'变更状态'
)
report_file
=
fields
.
Binary
(
string
=
u'异常数据'
,
readonly
=
True
)
file_name
=
fields
.
Char
(
u'名称'
)
def
get_file_path
(
self
,
report_file_ids
):
timezone
=
fields
.
Selection
(
selection
=
lambda
self
:
[(
tz
,
tz
)
for
tz
in
pytz
.
all_timezones
],
string
=
'时区'
,
default
=
lambda
self
:
self
.
env
.
user
.
tz
or
self
.
env
.
context
.
get
(
'tz'
)
or
'UTC'
,
required
=
True
)
def
_parse_and_convert_tz
(
self
,
val
):
"""
得到excel表格的内容
:return:
将任意时间格式(字符串/日期/时间/Excel数字/时间戳)转为 0时区 (UTC) 时间
"""
report_path
=
report_file_ids
.
_full_path
(
report_file_ids
.
store_fname
)
if
report_file_ids
.
name
[
-
3
:]
==
'xls'
:
data
=
xlrd
.
open_workbook
(
report_path
,
formatting_info
=
True
)
else
:
data
=
xlrd
.
open_workbook
(
report_path
)
return
data
if
val
is
None
or
val
==
''
:
return
False
def
submit
(
self
):
for
attachment_obj
in
self
.
attachment_ids
:
data
=
self
.
get_file_path
(
attachment_obj
)
first_table
=
data
.
sheets
()[
0
]
dt
=
False
if
isinstance
(
val
,
(
int
,
float
)):
if
val
>
1000000
:
if
val
>
100000000000
:
val
=
val
/
1000.0
try
:
dt
=
datetime
.
fromtimestamp
(
val
)
except
Exception
:
return
False
else
:
try
:
dt
=
datetime
(
1899
,
12
,
30
)
+
fields
.
Datetime
.
context_timestamp
(
self
,
datetime
.
now
())
.
tzinfo
.
utcoffset
(
None
)
*
0
from
datetime
import
timedelta
dt
=
datetime
(
1899
,
12
,
30
)
+
timedelta
(
days
=
val
)
except
Exception
:
return
False
elif
isinstance
(
val
,
str
):
try
:
if
self
.
node_id
.
name
==
'待尾程提货'
:
select_node_obj
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
([(
'node_type'
,
'='
,
'package'
),
(
'name'
,
'='
,
'已提货'
)],
limit
=
1
)
else
:
select_node_obj
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
([(
'node_type'
,
'='
,
'package'
),
(
'name'
,
'='
,
'待尾程提货'
)],
limit
=
1
)
# 新增:定义一个累加器,记录整个 Excel 总共更新了多少个小包
total_updated_count
=
0
for
i
in
range
(
1
,
first_table
.
nrows
):
line
=
first_table
.
row_values
(
i
)
big_no
=
str
(
line
[
0
])
.
strip
()
if
not
big_no
:
continue
lh_time
=
line
[
9
]
wj_time
=
line
[
4
]
# 尾程交货时间
# 大包数据
wj_user_name
=
line
[
3
]
# 尾程交货人
lh_user_name
=
line
[
8
]
# 理货人
pallet_date
=
line
[
5
]
# 托盘日期
pallet_no
=
str
(
line
[
6
])
.
replace
(
'.0'
,
''
)
.
replace
(
' '
,
''
)
# 托盘号
wj_user_obj
=
self
.
env
[
'res.users'
]
.
sudo
()
.
search
([(
'login'
,
'='
,
wj_user_name
)],
limit
=
1
)
lh_user_obj
=
self
.
env
[
'res.users'
]
.
sudo
()
.
search
([(
'login'
,
'='
,
lh_user_name
)],
limit
=
1
)
update_time
=
lh_time
if
self
.
node_id
.
name
==
'待尾程提货'
else
wj_time
ship_package_objs
=
self
.
env
[
'cc.ship.package'
]
.
sudo
()
.
search
([
(
'big_package_id.big_package_no'
,
'='
,
big_no
),
(
'state'
,
'='
,
select_node_obj
.
id
)
])
# 已提货
if
ship_package_objs
:
# 核心修改:计算当前大包查到的小包数量
current_count
=
len
(
ship_package_objs
)
total_updated_count
+=
current_count
# 累加到总数
# 日志记录:每个大包具体查到了多少个小包
_logger
.
info
(
f
"处理第 {i} 行: 大包号 [{big_no}] 查到 {current_count} 个小包准备更新。"
)
sql
=
"""
UPDATE cc_ship_package
SET state =
%
s, process_time =
%
s , is_sync=False
WHERE id IN
%
s
dt
=
fields
.
Datetime
.
from_string
(
val
)
except
ValueError
:
return
False
elif
isinstance
(
val
,
datetime
):
dt
=
val
elif
isinstance
(
val
,
date
):
dt
=
datetime
.
combine
(
val
,
datetime
.
min
.
time
())
if
not
dt
:
return
False
local_tz
=
pytz
.
timezone
(
self
.
timezone
)
if
dt
.
tzinfo
is
None
:
local_dt
=
local_tz
.
localize
(
dt
)
else
:
local_dt
=
dt
.
astimezone
(
local_tz
)
utc_dt
=
local_dt
.
astimezone
(
pytz
.
UTC
)
.
replace
(
tzinfo
=
None
)
return
utc_dt
def
action_import_data_with_validation
(
self
):
"""
表格数据校验与导入核心逻辑:
1. 校验未来时
2. 根据当前 node_id 校验与系统存量数据的时序
3. 错误回写表格,保存到当前向导的 report_file 字段
4. 成功返回 JSON 格式数据
"""
if
not
self
.
attachment_ids
:
raise
UserError
(
"请先上传 Excel 附件!"
)
attachment
=
self
.
attachment_ids
[
0
]
file_data
=
base64
.
b64decode
(
attachment
.
datas
)
try
:
wb
=
openpyxl
.
load_workbook
(
io
.
BytesIO
(
file_data
))
sheet
=
wb
.
active
except
Exception
as
e
:
raise
UserError
(
f
"读取 Excel 文件失败,请检查格式: {e}"
)
# 1. 动态映射表头
headers
=
[
str
(
cell
.
value
)
.
strip
()
if
cell
.
value
else
''
for
cell
in
sheet
[
1
]]
if
'大包号'
not
in
headers
:
raise
UserError
(
"缺少必要的表头: 【大包号】,请检查模板!"
)
header_map
=
{
col
:
idx
for
idx
,
col
in
enumerate
(
headers
)}
# 预设错误回写列 (如果已经有错误原因列则覆盖,否则新增一列)
error_col_idx
=
header_map
.
get
(
'错误原因'
,
sheet
.
max_column
)
+
1
if
'错误原因'
not
in
headers
:
sheet
.
cell
(
row
=
1
,
column
=
error_col_idx
,
value
=
"错误原因"
)
now_utc
=
datetime
.
utcnow
()
has_error
=
False
final_data_to_insert
=
[]
# --- 2. 批量查库,优化性能 ---
all_pkg_nums
=
[]
for
row
in
sheet
.
iter_rows
(
min_row
=
2
,
values_only
=
True
):
pkg_num
=
row
[
header_map
[
'大包号'
]]
if
header_map
.
get
(
'大包号'
)
is
not
None
else
False
if
pkg_num
:
all_pkg_nums
.
append
(
str
(
pkg_num
)
.
strip
())
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
}
current_node_name
=
self
.
node_id
.
name
if
self
.
node_id
else
''
# --- 3. 逐行遍历校验 ---
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
=
str
(
pkg_num_cell
.
value
)
.
strip
()
if
pkg_num_cell
and
pkg_num_cell
.
value
else
False
if
not
pkg_num
:
continue
row_errors
=
[]
row_data_dict
=
{}
# 读取所有列数据存入字典
for
col_name
,
col_idx
in
header_map
.
items
():
if
col_name
==
'错误原因'
:
continue
row_data_dict
[
col_name
]
=
row
[
col_idx
]
.
value
# 获取并转换需要校验的两个时间
raw_tally
=
row_data_dict
.
get
(
'理货时间'
)
raw_delivery
=
row_data_dict
.
get
(
'尾程交货时间'
)
tally_utc
=
self
.
_parse_and_convert_tz
(
raw_tally
)
delivery_utc
=
self
.
_parse_and_convert_tz
(
raw_delivery
)
if
tally_utc
:
row_data_dict
[
'理货时间'
]
=
tally_utc
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
delivery_utc
:
row_data_dict
[
'尾程交货时间'
]
=
delivery_utc
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
# -----------------------------------------------------
# 【需求 1】:理货时间、尾程交货时间不能是未来时间
# -----------------------------------------------------
if
tally_utc
and
tally_utc
>
now_utc
:
row_errors
.
append
(
"理货时间不能是未来时间"
)
if
delivery_utc
and
delivery_utc
>
now_utc
:
row_errors
.
append
(
"尾程交货时间不能是未来时间"
)
# -----------------------------------------------------
# 结合系统数据的状态时序校验 (需求 2, 3)
# -----------------------------------------------------
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
tally_utc
<
sys_pkg
.
pickup_time
:
row_errors
.
append
(
"理货时间不能小于系统内该大包的提货时间"
)
# 【需求 3】:系统选择“尾程交接”,尾程交货时间不能小于系统的理货时间
if
current_node_name
==
'尾程交接'
and
delivery_utc
and
sys_pkg
and
sys_pkg
.
tally_time
:
if
delivery_utc
<
sys_pkg
.
tally_time
:
row_errors
.
append
(
"尾程交货时间不能小于系统内该大包的理货时间"
)
# --- 4. 组装结果与回写 ---
if
row_errors
:
has_error
=
True
error_msg
=
" | "
.
join
(
row_errors
)
# 将错误写在这一行的最后一列
row
[
error_col_idx
-
1
]
.
value
=
error_msg
else
:
final_data_to_insert
.
append
(
row_data_dict
)
# --- 5. 【修改部分】:异常数据保存到模型字段,刷新页面 ---
if
has_error
:
out_stream
=
io
.
BytesIO
()
wb
.
save
(
out_stream
)
out_stream
.
seek
(
0
)
# 生成文件名和 Base64 数据
error_file_name
=
f
'异常数据.xlsx'
encoded_file
=
base64
.
b64encode
(
out_stream
.
read
())
# 写入当前向导记录
self
.
write
({
'report_file'
:
encoded_file
,
'file_name'
:
error_file_name
})
# 返回前端动作:刷新当前向导页面 (用户将能看到并点击下载你的 report_file)
return
{
'type'
:
'ir.actions.act_window'
,
'res_model'
:
'package.data.wizard'
,
'res_id'
:
self
.
id
,
'view_mode'
:
'form'
,
'target'
:
'new'
,
'name'
:
'导入包裹数据'
}
# --- 6. 校验全部通过,返回 JSON 格式数据 ---
json_result
=
json
.
dumps
(
final_data_to_insert
,
ensure_ascii
=
False
,
indent
=
4
)
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):
# for attachment_obj in self.attachment_ids:
# data = self.get_file_path(attachment_obj)
# first_table = data.sheets()[0]
# try:
# if self.node_id.name == '待尾程提货':
# select_node_obj = self.env['cc.node'].sudo().search([('node_type', '=', 'package'),
# ('name', '=', '已提货')],
# limit=1)
# else:
# select_node_obj = self.env['cc.node'].sudo().search([('node_type', '=', 'package'),
# ('name', '=', '待尾程提货')],
# limit=1)
# # 新增:定义一个累加器,记录整个 Excel 总共更新了多少个小包
# total_updated_count = 0
# for i in range(1, first_table.nrows):
# line = first_table.row_values(i)
# big_no = str(line[1]).strip()
# if not big_no:
# continue
# lh_time = line[10] # 理货时间
# wj_time = line[5] # 尾程交货时间
# # 大包数据
# wj_user_name = line[4] # 尾程交货人
# lh_user_name = line[8] # 理货人
# pallet_date = line[6] # 托盘日期
# pallet_no = str(line[7]).replace('.0', '').replace(' ', '') # 托盘号
# wj_user_obj = self.env['res.users'].sudo().search([('login', '=', wj_user_name)], limit=1)
# lh_user_obj = self.env['res.users'].sudo().search([('login', '=', lh_user_name)], limit=1)
#
# update_time = lh_time if self.node_id.name == '待尾程提货' else wj_time
# ship_package_objs = self.env['cc.ship.package'].sudo().search([
# ('big_package_id.big_package_no', '=', big_no),
# ('state', '=', select_node_obj.id)
# ]) # 已提货
# if ship_package_objs:
# # 核心修改:计算当前大包查到的小包数量
# current_count = len(ship_package_objs)
# total_updated_count += current_count # 累加到总数
# # 日志记录:每个大包具体查到了多少个小包
# _logger.info(f"处理第 {i} 行: 大包号 [{big_no}] 查到 {current_count} 个小包准备更新。")
# sql = """
# UPDATE cc_ship_package
# SET state = %s, process_time = %s , is_sync=False
# WHERE id IN %s
# """
# self.env.cr.execute(sql, (self.node_id.id, update_time, tuple(ship_package_objs.ids)))
# # 更新大包
# big_package_obj = self.env['cc.big.package'].sudo().search([('big_package_no', '=', big_no)], limit=1)
# sql = """
# UPDATE cc_big_package
# SET pallet_number = %s, pallet_usage_date = %s , tally_user_id= %s,
# tally_time = %s, delivery_user_id = %s , delivery_time= %s
# WHERE id = %s
# """
# self.env.cr.execute(sql, (pallet_no, pallet_date, lh_user_obj.id if lh_user_obj else None,
# lh_time, wj_user_obj.id if wj_user_obj else None, wj_time, big_package_obj.id))
# pallet_obj = self.env['cc.pallet'].sudo().search([('name', '=', pallet_no)], limit=1)
# pallet_obj.usage_state = 'used'
# else:
# # 选做:把没查到数据的大包也用 warning 级别记录下来,方便后期排查漏刷的数据
# _logger.warning(f"处理第 {i} 行: 大包号 [{big_no}] 未查到状态为'已提货'的小包,跳过更新。")
# # 循环结束后,记录该文件的最终战果
# _logger.info(f"==== 附件处理完毕 ==== 整个表格共查到并更新了 {total_updated_count} 个小包!")
# except Exception as err:
# _logger.error(f'package_data_xls error : {str(err)}')
def
process_validated_json_data
(
self
,
json_data
):
"""
接收 action_import_data_with_validation 返回的 JSON 数据并执行数据库更新
:param json_data: 可以是 JSON 字符串,也可以是解析后的 Python 字典列表 (List[Dict])
"""
try
:
# 1. 确保数据是可迭代的列表对象
if
isinstance
(
json_data
,
str
):
data_list
=
json
.
loads
(
json_data
)
else
:
data_list
=
json_data
if
not
data_list
:
_logger
.
warning
(
"未接收到有效的 JSON 数据进行更新。"
)
return
# 2. 确定需要匹配的“上一节点”状态
if
self
.
node_id
.
name
==
'待尾程提货'
:
select_node_obj
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
([
(
'node_type'
,
'='
,
'package'
),
(
'name'
,
'='
,
'已提货'
)
],
limit
=
1
)
else
:
select_node_obj
=
self
.
env
[
'cc.node'
]
.
sudo
()
.
search
([
(
'node_type'
,
'='
,
'package'
),
(
'name'
,
'='
,
'待尾程提货'
)
],
limit
=
1
)
if
not
select_node_obj
:
_logger
.
error
(
"在系统中未找到匹配的 cc.node 状态字典数据!"
)
return
# 3. 初始化累加器与用户查询缓存(避免重复查库拖慢速度)
total_updated_count
=
0
user_cache
=
{}
def
get_user_id
(
login_name
):
"""本地缓存获取用户 ID"""
if
not
login_name
:
return
None
if
login_name
not
in
user_cache
:
user
=
self
.
env
[
'res.users'
]
.
sudo
()
.
search
([(
'login'
,
'='
,
login_name
)],
limit
=
1
)
user_cache
[
login_name
]
=
user
.
id
if
user
else
None
return
user_cache
[
login_name
]
# 4. 遍历处理 JSON 数据
for
index
,
row
in
enumerate
(
data_list
,
start
=
1
):
big_no
=
str
(
row
.
get
(
'大包号'
,
''
))
.
strip
()
if
not
big_no
:
continue
# 提取数据(如果为空,转换为 None,防止 PostgreSQL 写入日期/时间字段时报错)
lh_time
=
row
.
get
(
'理货时间'
)
or
None
wj_time
=
row
.
get
(
'尾程交货时间'
)
or
None
wj_user_name
=
str
(
row
.
get
(
'尾程交货人'
,
''
))
.
strip
()
lh_user_name
=
str
(
row
.
get
(
'理货人'
,
''
))
.
strip
()
pallet_date
=
row
.
get
(
'托盘使用日期'
)
or
None
# 处理托盘号(防呆:去除小数和空格)
raw_pallet_no
=
str
(
row
.
get
(
'托盘号'
,
''
))
pallet_no
=
raw_pallet_no
.
replace
(
'.0'
,
''
)
.
replace
(
' '
,
''
)
if
raw_pallet_no
else
None
# 获取用户 ID
wj_user_id
=
get_user_id
(
wj_user_name
)
lh_user_id
=
get_user_id
(
lh_user_name
)
# 确定最终更新到小包上的时间
update_time
=
lh_time
if
self
.
node_id
.
name
==
'待尾程提货'
else
wj_time
# 5. 查找符合条件的小包并执行 SQL 更新
ship_package_objs
=
self
.
env
[
'cc.ship.package'
]
.
sudo
()
.
search
([
(
'big_package_id.big_package_no'
,
'='
,
big_no
),
(
'state'
,
'='
,
select_node_obj
.
id
)
])
if
ship_package_objs
:
current_count
=
len
(
ship_package_objs
)
total_updated_count
+=
current_count
_logger
.
info
(
f
"处理第 {index} 行: 大包号 [{big_no}] 查到 {current_count} 个小包准备更新。"
)
# 原生 SQL 批量更新小包
sql_small
=
"""
UPDATE cc_ship_package
SET state =
%
s, process_time =
%
s, is_sync = False
WHERE id IN
%
s
"""
self
.
env
.
cr
.
execute
(
sql_small
,
(
self
.
node_id
.
id
,
update_time
,
tuple
(
ship_package_objs
.
ids
)))
# 6. 查找大包并更新
big_package_obj
=
self
.
env
[
'cc.big.package'
]
.
sudo
()
.
search
([(
'big_package_no'
,
'='
,
big_no
)],
limit
=
1
)
if
big_package_obj
:
sql_big
=
"""
UPDATE cc_big_package
SET pallet_number =
%
s,
pallet_usage_date =
%
s,
tally_user_id =
%
s,
tally_time =
%
s,
delivery_user_id =
%
s,
delivery_time =
%
s
WHERE id =
%
s
"""
self
.
env
.
cr
.
execute
(
sql
,
(
self
.
node_id
.
id
,
update_time
,
tuple
(
ship_package_objs
.
ids
)))
# 更新大包
big_package_obj
=
self
.
env
[
'cc.big.package'
]
.
sudo
()
.
search
([(
'big_package_no'
,
'='
,
big_no
)],
limit
=
1
)
sql
=
"""
UPDATE cc_big_package
SET pallet_number =
%
s, pallet_usage_date =
%
s , tally_user_id=
%
s,
tally_time =
%
s, delivery_user_id =
%
s , delivery_time=
%
s
WHERE id =
%
s
"""
self
.
env
.
cr
.
execute
(
sql
,
(
pallet_no
,
pallet_date
,
lh_user_obj
.
id
if
lh_user_obj
else
None
,
lh_time
,
wj_user_obj
.
id
if
wj_user_obj
else
None
,
wj_time
,
big_package_obj
.
id
))
self
.
env
.
cr
.
execute
(
sql_big
,
(
pallet_no
,
pallet_date
,
lh_user_id
,
lh_time
,
wj_user_id
,
wj_time
,
big_package_obj
.
id
))
# 7. 更新托盘状态
if
pallet_no
:
pallet_obj
=
self
.
env
[
'cc.pallet'
]
.
sudo
()
.
search
([(
'name'
,
'='
,
pallet_no
)],
limit
=
1
)
pallet_obj
.
usage_state
=
'used'
else
:
# 选做:把没查到数据的大包也用 warning 级别记录下来,方便后期排查漏刷的数据
_logger
.
warning
(
f
"处理第 {i} 行: 大包号 [{big_no}] 未查到状态为'已提货'的小包,跳过更新。"
)
# 循环结束后,记录该文件的最终战果
_logger
.
info
(
f
"==== 附件处理完毕 ==== 整个表格共查到并更新了 {total_updated_count} 个小包!"
)
except
Exception
as
err
:
_logger
.
error
(
f
'package_data_xls error : {str(err)}'
)
if
pallet_obj
:
pallet_obj
.
usage_state
=
'used'
else
:
_logger
.
warning
(
f
"处理第 {index} 行: 大包号 [{big_no}] 未查到状态为 '{select_node_obj.name}' 的小包,跳过更新。"
)
_logger
.
info
(
f
"==== JSON 数据处理完毕 ==== 整个队列共查到并更新了 {total_updated_count} 个小包!"
)
return
True
except
Exception
as
err
:
self
.
env
.
cr
.
rollback
()
# 发生异常时回滚数据库操作
_logger
.
error
(
f
'process_validated_json_data error: {str(err)}'
)
raise
def
submit
(
self
):
# 1. 获取校验结果
# 可能的返回值有两种:
# - 校验失败:返回一个 Dict (ir.actions.act_window 动作)
# - 校验成功:返回一个 JSON 字符串
result
=
self
.
action_import_data_with_validation
()
# 2. 拦截错误动作:如果返回的是字典且包含前端动作,说明校验报错了
if
isinstance
(
result
,
dict
)
and
result
.
get
(
'type'
)
==
'ir.actions.act_window'
:
# 直接将动作抛给前端,刷新弹窗显示异常文件,终止后续入库逻辑
return
result
# 3. 校验完美通过,执行入库更新
self
.
process_validated_json_data
(
result
)
# 4. 清理残留的错误文件(防呆设计)
self
.
write
({
'report_file'
:
False
,
'file_name'
:
False
})
# 5. 返回成功的前端提示,给用户明确的反馈
return
{
'type'
:
'ir.actions.client'
,
'tag'
:
'display_notification'
,
'params'
:
{
'title'
:
'导入与更新成功'
,
'message'
:
'包裹数据校验通过,并已成功写入系统!'
,
'type'
:
'success'
,
'sticky'
:
False
,
}
}
ccs_connect_tiktok/wizard/excel_wizard.xml
浏览文件 @
8279ed7a
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record
id=
"view_package_data_wizard_form"
model=
"ir.ui.view"
>
<field
name=
"name"
>
package.data.wizard.form
</field>
<field
name=
"model"
>
package.data.wizard
</field>
<field
name=
"arch"
type=
"xml"
>
<form
string=
"包裹数据处理向导"
>
<div
class=
"alert alert-info"
role=
"alert"
style=
"margin-bottom: 16px;"
>
<strong><i
class=
"fa fa-info-circle"
/>
操作指引:
</strong>
<ul
class=
"mb-0 mt-1"
>
<li>
请先
<a
href=
"/ccs_connect_tiktok/static/xlsx/template.xlsx?v=20250715001"
class=
"alert-link fw-bold"
target=
"_blank"
><i
class=
"fa fa-download"
/>
下载数据模板
</a>
。
</li>
<li
class=
"text-danger fw-bold"
>
请务必严格按照模板填写信息,切勿修改表头,否则系统无法识别!
</li>
</ul>
</div>
<group>
<group>
<field
name=
"attachment_ids"
widget=
"many2many_binary"
string=
"上传Excel附件"
required=
"1"
/>
<field
name=
"node_id"
required=
"1"
domain=
"[('node_type', '=', 'package'), ('name', 'in', ['待尾程提货', '尾程交接'])]"
options=
"{'no_edit': True, 'no_create': True}"
/>
<field
name=
"attachment_ids"
widget=
"many2many_binary"
string=
"上传Excel附件"
required=
"True"
/>
<field
name=
"node_id"
required=
"True"
domain=
"[('node_type', '=', 'package'), ('name', 'in', ['待尾程提货', '尾程交接'])]"
options=
"{'no_edit': True, 'no_create': True}"
/>
<field
name=
"timezone"
/>
</group>
<group>
<field
name=
"file_name"
invisible=
"1"
/>
<field
name=
"report_file"
filename=
"file_name"
widget=
"binary"
attrs=
"{'invisible': [('report_file', '=', False)]}"
/>
</group>
</group>
<div
attrs=
"{'invisible': [('report_file', '=', False)]}"
class=
"alert alert-warning mt-2"
role=
"alert"
>
<strong><i
class=
"fa fa-exclamation-triangle"
/>
检测到异常数据!
</strong>
<br/>
请点击上方下载
<b>
异常数据文件
</b>
,根据文件最后一列的错误提示修改原表,然后再重新上传导入。
</div>
<footer>
<button
name=
"submit"
string=
"确认处理"
type=
"object"
class=
"btn-primary"
/>
<button
string=
"取消"
class=
"btn-secondary"
special=
"cancel"
/>
<button
name=
"submit"
string=
"确认处理"
type=
"object"
class=
"btn-primary"
data-hotkey=
"q"
/>
<button
string=
"取消"
class=
"btn-secondary"
special=
"cancel"
data-hotkey=
"z"
/>
</footer>
</form>
</field>
</record>
<record
id=
"action_package_data_wizard"
model=
"ir.actions.act_window"
>
<field
name=
"name"
>
包裹数据导入
处理
</field>
<field
name=
"name"
>
包裹数据导入
</field>
<field
name=
"type"
>
ir.actions.act_window
</field>
<field
name=
"res_model"
>
package.data.wizard
</field>
<field
name=
"view_mode"
>
form
</field>
...
...
@@ -32,8 +56,8 @@
<menuitem
id=
"menu_package_data_wizard"
name=
"导入包裹数据"
action=
"action_package_data_wizard"
groups=
"base.group_system"
action=
"action_package_data_wizard"
groups=
"base.group_system"
sequence=
"50"
/>
</data>
</odoo>
\ No newline at end of file
multi_record_search/models/multi_search.py
浏览文件 @
8279ed7a
...
...
@@ -157,3 +157,23 @@ class BaseModel(models.AbstractModel):
return
super
(
BaseModel
,
self
)
.
name_search
(
name
=
name
,
args
=
args
,
operator
=
operator
,
limit
=
limit
)
@api.model
def
read_group
(
self
,
domain
,
fields
,
groupby
,
offset
=
0
,
limit
=
None
,
orderby
=
False
,
lazy
=
True
):
"""Override read_group for all models to support multi-search domains"""
try
:
# 1. 拦截前端传来的 domain,使用你已经写好的转换器进行解析
new_domain
=
self
.
_process_multi_search_args
(
domain
)
# 2. 将解析后正确的 domain 传给原生的 read_group 去做聚合查询
return
super
(
BaseModel
,
self
)
.
read_group
(
new_domain
,
fields
,
groupby
,
offset
=
offset
,
limit
=
limit
,
orderby
=
orderby
,
lazy
=
lazy
)
except
Exception
as
e
:
_logger
.
warning
(
"Multi-search processing failed in read_group, falling back to normal:
%
s"
,
str
(
e
))
# 降级处理:如果转换出错,用原始 domain 继续执行,防止系统崩溃
return
super
(
BaseModel
,
self
)
.
read_group
(
domain
,
fields
,
groupby
,
offset
=
offset
,
limit
=
limit
,
orderby
=
orderby
,
lazy
=
lazy
)
\ No newline at end of file
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论