提交 b85c538a authored 作者: 伍姿英's avatar 伍姿英

Merge branch 'release/2.8.0'

...@@ -6,14 +6,16 @@ msgid "" ...@@ -6,14 +6,16 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Odoo Server 16.0\n" "Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-03 02:14+0000\n" "POT-Creation-Date: 2025-06-09 07:48+0000\n"
"PO-Revision-Date: 2025-04-03 02:14+0000\n" "PO-Revision-Date: 2025-06-09 15:53+0800\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: \n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 3.5\n"
#. module: ccs_base #. module: ccs_base
#. odoo-python #. odoo-python
...@@ -62,26 +64,32 @@ msgid "" ...@@ -62,26 +64,32 @@ msgid ""
"<div style=\"margin: 0px; padding: 0px;\">\n" "<div style=\"margin: 0px; padding: 0px;\">\n"
" <p style=\"margin: 0px; padding: 0px;\">\n" " <p style=\"margin: 0px; padding: 0px;\">\n"
" Dear all\n" " Dear all\n"
" <br/>\n" " <br>\n"
" <!-- 检查 big_package_ids 是否有值 -->\n" " <!-- 检查 big_package_ids 是否有值 -->\n"
" <t t-if=\"object.big_package_ids\">\n" " <t t-if=\"object.big_package_ids\">\n"
" <t t-foreach=\"object.big_package_ids\" t-as=\"big_package_id\">\n" " <t t-foreach=\"object.big_package_ids\" t-"
"as=\"big_package_id\">\n"
" <div>Package:\n" " <div>Package:\n"
" <t t-esc=\"big_package_id.big_package_no or ''\"/>\n" " <t t-esc=\"big_package_id.big_package_no or "
"''\"></t>\n"
" </div>\n" " </div>\n"
" </t>\n" " </t>\n"
" </t>\n" " </t>\n"
" <!-- 检查 ship_package_ids 是否有值 -->\n" " <!-- 检查 ship_package_ids 是否有值 -->\n"
" <t t-if=\"object.ship_package_ids\">\n" " <t t-if=\"object.ship_package_ids\">\n"
" <t t-foreach=\"object.ship_package_ids\" t-as=\"ship_package_id\">\n" " <t t-foreach=\"object.ship_package_ids\" t-"
"as=\"ship_package_id\">\n"
" <div>Package:\n" " <div>Package:\n"
" <t t-esc=\"ship_package_id.logistic_order_no or ''\"/>\n" " <t t-esc=\"ship_package_id."
"logistic_order_no or ''\"></t>\n"
" </div>\n" " </div>\n"
" </t>\n" " </t>\n"
" </t>\n" " </t>\n"
" <div>Please know that there is an exception and the cause of the exception is\n" " <div>Please know that there is an exception and the "
"cause of the exception is\n"
" <t t-if=\"object.exception_ids\">\n" " <t t-if=\"object.exception_ids\">\n"
" <t t-esc=\"'/'.join([ex.reason for ex in object.exception_ids])\"/>\n" " <t t-esc=\"'/'.join([ex.reason for ex in object."
"exception_ids])\"></t>\n"
" </t>\n" " </t>\n"
" <t t-if=\"not object.exception_ids\">\n" " <t t-if=\"not object.exception_ids\">\n"
" No Exception\n" " No Exception\n"
...@@ -99,26 +107,31 @@ msgid "" ...@@ -99,26 +107,31 @@ msgid ""
"<div style=\"margin: 0px; padding: 0px;\">\n" "<div style=\"margin: 0px; padding: 0px;\">\n"
" <p style=\"margin: 0px; padding: 0px;\">\n" " <p style=\"margin: 0px; padding: 0px;\">\n"
" 您好\n" " 您好\n"
" <br/>\n" " <br>\n"
" <!-- 检查 big_package_ids 是否有值 -->\n" " <!-- 检查 big_package_ids 是否有值 -->\n"
" <t t-if=\"object.big_package_ids\">\n" " <t t-if=\"object.big_package_ids\">\n"
" <t t-foreach=\"object.big_package_ids\" t-as=\"big_package_id\">\n" " <t t-foreach=\"object.big_package_ids\" t-"
"as=\"big_package_id\">\n"
" <div>包裹:\n" " <div>包裹:\n"
" <t t-esc=\"big_package_id.big_package_no or ''\"/>\n" " <t t-esc=\"big_package_id.big_package_no or "
"''\"></t>\n"
" </div>\n" " </div>\n"
" </t>\n" " </t>\n"
" </t>\n" " </t>\n"
" <!-- 检查 ship_package_ids 是否有值 -->\n" " <!-- 检查 ship_package_ids 是否有值 -->\n"
" <t t-if=\"object.ship_package_ids\">\n" " <t t-if=\"object.ship_package_ids\">\n"
" <t t-foreach=\"object.ship_package_ids\" t-as=\"ship_package_id\">\n" " <t t-foreach=\"object.ship_package_ids\" t-"
"as=\"ship_package_id\">\n"
" <div>包裹:\n" " <div>包裹:\n"
" <t t-esc=\"ship_package_id.logistic_order_no or ''\"/>\n" " <t t-esc=\"ship_package_id."
"logistic_order_no or ''\"></t>\n"
" </div>\n" " </div>\n"
" </t>\n" " </t>\n"
" </t>\n" " </t>\n"
" <div>出现异常,异常原因:\n" " <div>出现异常,异常原因:\n"
" <t t-if=\"object.exception_ids\">\n" " <t t-if=\"object.exception_ids\">\n"
" <t t-esc=\"'/'.join([ex.reason for ex in object.exception_ids])\"/>,请知晓。\n" " <t t-esc=\"'/'.join([ex.reason for ex in object."
"exception_ids])\"></t>,请知晓。\n"
" </t>\n" " </t>\n"
" <t t-if=\"not object.exception_ids\">\n" " <t t-if=\"not object.exception_ids\">\n"
" 无异常,请知晓。\n" " 无异常,请知晓。\n"
...@@ -380,7 +393,6 @@ msgstr "添加异常信息" ...@@ -380,7 +393,6 @@ msgstr "添加异常信息"
#. module: ccs_base #. module: ccs_base
#. odoo-python #. odoo-python
#: code:addons/ccs_base/models/cc_bill_loading.py:0 #: code:addons/ccs_base/models/cc_bill_loading.py:0
#: code:addons/ccs_base/models/cc_bill_loading.py:0
#: 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_big_package_view
#: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_ship_package_view #: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_ship_package_view
#, python-format #, python-format
...@@ -836,7 +848,6 @@ msgstr "已理货" ...@@ -836,7 +848,6 @@ msgstr "已理货"
#. module: ccs_base #. module: ccs_base
#. odoo-python #. odoo-python
#: code:addons/ccs_base/wizard/add_exception_info_wizard.py:0 #: code:addons/ccs_base/wizard/add_exception_info_wizard.py:0
#: code:addons/ccs_base/wizard/add_exception_info_wizard.py:0
#: model:ir.model.fields.selection,name:ccs_base.selection__add_exception_info_wizard__email_language__zh_cn #: model:ir.model.fields.selection,name:ccs_base.selection__add_exception_info_wizard__email_language__zh_cn
#, python-format #, python-format
msgid "Chinese" msgid "Chinese"
...@@ -929,6 +940,11 @@ msgstr "对应大包状态" ...@@ -929,6 +940,11 @@ msgstr "对应大包状态"
msgid "Corresponding to the status of the package" msgid "Corresponding to the status of the package"
msgstr "对应小包状态" msgstr "对应小包状态"
#. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_last_mile_provider__logo
msgid "Courier Logo"
msgstr "快递logo"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_last_mile_provider__name #: model:ir.model.fields,field_description:ccs_base.field_cc_last_mile_provider__name
msgid "Courier Name" msgid "Courier Name"
...@@ -1244,7 +1260,6 @@ msgstr "目的地港口代码" ...@@ -1244,7 +1260,6 @@ msgstr "目的地港口代码"
#. module: ccs_base #. module: ccs_base
#. odoo-python #. odoo-python
#: code:addons/ccs_base/wizard/add_exception_info_wizard.py:0 #: code:addons/ccs_base/wizard/add_exception_info_wizard.py:0
#: code:addons/ccs_base/wizard/add_exception_info_wizard.py:0
#: model:ir.model.fields.selection,name:ccs_base.selection__add_exception_info_wizard__email_language__en_us #: model:ir.model.fields.selection,name:ccs_base.selection__add_exception_info_wizard__email_language__en_us
#, python-format #, python-format
msgid "English" msgid "English"
...@@ -1426,8 +1441,6 @@ msgstr "商品税号" ...@@ -1426,8 +1441,6 @@ msgstr "商品税号"
#. module: ccs_base #. module: ccs_base
#. odoo-python #. odoo-python
#: code:addons/ccs_base/models/cc_bill_loading.py:0 #: code:addons/ccs_base/models/cc_bill_loading.py:0
#: code:addons/ccs_base/models/cc_bill_loading.py:0
#: code:addons/ccs_base/models/cc_bill_loading.py:0
#: code:addons/ccs_base/models/cc_history_big_package.py:0 #: code:addons/ccs_base/models/cc_history_big_package.py:0
#: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__goods_ids #: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__goods_ids
#: model:ir.model.fields,field_description:ccs_base.field_cc_bl__good_ids #: model:ir.model.fields,field_description:ccs_base.field_cc_bl__good_ids
...@@ -1950,8 +1963,8 @@ msgid "Link" ...@@ -1950,8 +1963,8 @@ msgid "Link"
msgstr "链接" msgstr "链接"
#. module: ccs_base #. module: ccs_base
#. odoo-python
#. odoo-javascript #. odoo-javascript
#. odoo-python
#: code:addons/ccs_base/models/cc_bill_loading.py:0 #: code:addons/ccs_base/models/cc_bill_loading.py:0
#: code:addons/ccs_base/static/src/views/list.xml:0 #: code:addons/ccs_base/static/src/views/list.xml:0
#, python-format #, python-format
...@@ -2233,7 +2246,7 @@ msgstr "" ...@@ -2233,7 +2246,7 @@ msgstr ""
#: model:ir.model.fields,help:ccs_base.field_cc_history_ship_package__message_needaction_counter #: model:ir.model.fields,help:ccs_base.field_cc_history_ship_package__message_needaction_counter
#: model:ir.model.fields,help:ccs_base.field_cc_ship_package__message_needaction_counter #: model:ir.model.fields,help:ccs_base.field_cc_ship_package__message_needaction_counter
#: model:ir.model.fields,help:ccs_base.field_order_state_change_rule__message_needaction_counter #: model:ir.model.fields,help:ccs_base.field_order_state_change_rule__message_needaction_counter
msgid "Number of messages which requires an action" msgid "Number of messages requiring action"
msgstr "" msgstr ""
#. module: ccs_base #. module: ccs_base
...@@ -2389,13 +2402,26 @@ msgstr "托盘号" ...@@ -2389,13 +2402,26 @@ msgstr "托盘号"
msgid "Pallet Usage Date" msgid "Pallet Usage Date"
msgstr "托盘使用日期" msgstr "托盘使用日期"
#. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_last_mile_provider__placement_area
msgid "Placement Area"
msgstr "摆放区域"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/models/cc_last_mile_provider.py:0
#, python-format
msgid ""
"Placement Area must be unique !"
msgstr "摆放区域必须唯一!"
#. module: ccs_base #. module: ccs_base
#. odoo-python #. odoo-python
#: code:addons/ccs_base/models/cc_bill_loading.py:0 #: code:addons/ccs_base/models/cc_bill_loading.py:0
#, python-format #, python-format
msgid "" msgid ""
"Please configure the default customs clearance status of the bill of loading" "Please configure the default customs clearance status of the bill of "
" node type first." "loading node type first."
msgstr "请先配置默认的提单节点类型的清关节点" msgstr "请先配置默认的提单节点类型的清关节点"
#. module: ccs_base #. module: ccs_base
...@@ -2910,7 +2936,6 @@ msgstr "节点序号" ...@@ -2910,7 +2936,6 @@ msgstr "节点序号"
#. module: ccs_base #. module: ccs_base
#. odoo-python #. odoo-python
#: code:addons/ccs_base/models/cc_bill_loading.py:0 #: code:addons/ccs_base/models/cc_bill_loading.py:0
#: code:addons/ccs_base/models/cc_bill_loading.py:0
#: code:addons/ccs_base/models/cc_history_big_package.py:0 #: code:addons/ccs_base/models/cc_history_big_package.py:0
#: model:ir.actions.act_window,name:ccs_base.action_cc_ship_package #: model:ir.actions.act_window,name:ccs_base.action_cc_ship_package
#: model:ir.model,name:ccs_base.model_cc_history_ship_package #: model:ir.model,name:ccs_base.model_cc_history_ship_package
...@@ -3556,8 +3581,8 @@ msgstr "【清关公司】还没!点击左上角的创建按钮,沙发就是 ...@@ -3556,8 +3581,8 @@ msgstr "【清关公司】还没!点击左上角的创建按钮,沙发就是
#. module: ccs_base #. module: ccs_base
#: model_terms:ir.actions.act_window,help:ccs_base.action_cc_clearance_file #: model_terms:ir.actions.act_window,help:ccs_base.action_cc_clearance_file
msgid "" msgid ""
"[Clearance File] Not yet! Click the Create button in the top left corner and" "[Clearance File] Not yet! Click the Create button in the top left corner "
" the sofa is yours!" "and the sofa is yours!"
msgstr "【清关文件】 还没有!点击左上角的“创建”按钮,沙发就是你的了!" msgstr "【清关文件】 还没有!点击左上角的“创建”按钮,沙发就是你的了!"
#. module: ccs_base #. module: ccs_base
...@@ -3608,6 +3633,11 @@ msgstr "【小包】 还没有!点击左上角的“创建”按钮,沙发 ...@@ -3608,6 +3633,11 @@ msgstr "【小包】 还没有!点击左上角的“创建”按钮,沙发
msgid "base_info" msgid "base_info"
msgstr "基本信息" msgstr "基本信息"
#. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.res_config_settings_view_form_auto_push
msgid "pda扫码配置"
msgstr ""
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,help:ccs_base.field_add_exception_info_wizard__action_type #: model:ir.model.fields,help:ccs_base.field_add_exception_info_wizard__action_type
msgid "ship package/big package" msgid "ship package/big package"
...@@ -3627,7 +3657,18 @@ msgstr "" ...@@ -3627,7 +3657,18 @@ msgstr ""
#. module: ccs_base #. module: ccs_base
#: model:mail.template,subject:ccs_base.email_template_exception_notification #: model:mail.template,subject:ccs_base.email_template_exception_notification
msgid "{{ ('大包异常' if object.action_type == 'big package' else '小包异常') }}" msgid ""
"{{ ('大包异常' if object.action_type == 'big package' else '小包异常') }}"
msgstr ""
#. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_res_config_settings__package_scan_min
msgid "一键全扫完成时间(min)"
msgstr ""
#. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_res_config_settings__is_package_scan
msgid "一键全扫开关"
msgstr "" msgstr ""
#. module: ccs_base #. module: ccs_base
...@@ -3781,6 +3822,11 @@ msgstr "" ...@@ -3781,6 +3822,11 @@ msgstr ""
msgid "请求id" msgid "请求id"
msgstr "" msgstr ""
#. module: ccs_base
#: model:ir.model.fields,help:ccs_base.field_res_config_settings__package_scan_min
msgid "输入示范:10,即表示在10分钟内大包时间随机分配,并不能重复"
msgstr ""
#. module: ccs_base #. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.search_history_tt_api_log_view #: model_terms:ir.ui.view,arch_db:ccs_base.search_history_tt_api_log_view
msgid "近30日日志" msgid "近30日日志"
......
import random
from odoo import models, fields, api from odoo import models, fields, api
...@@ -21,7 +20,7 @@ class CCExceptionInfo(models.Model): ...@@ -21,7 +20,7 @@ class CCExceptionInfo(models.Model):
# 异常编码增加唯一约束 # 异常编码增加唯一约束
_sql_constraints = [('exception_code_uniq', 'unique(exception_code)', 'The Exception Code must be unique.')] _sql_constraints = [('exception_code_uniq', 'unique(exception_code)', 'The Exception Code must be unique.')]
def search_exception_info(self, pda_lang=False): def search_exception_info(self):
""" """
查询包裹异常原因 查询包裹异常原因
:return: :return:
......
...@@ -10,23 +10,70 @@ class CCLastMileProvider(models.Model): ...@@ -10,23 +10,70 @@ class CCLastMileProvider(models.Model):
def _check_matching_value(self): def _check_matching_value(self):
for record in self: for record in self:
if record.matching_value: if record.matching_value:
values = record.matching_value.split('\n') # 将当前记录的值转换为小写
existing_values = '\n'.join(self.search([('id', '!=', record.id)]).mapped('matching_value')).split('\n') values = [value.lower() for value in record.matching_value.split('\n')]
# 获取其他记录的所有匹配值并转换为小写
existing_values = [value.lower() for value in '\n'.join(self.search(
[('id', '!=', record.id)]).mapped('matching_value')).split('\n')]
# 检查是否有重复值(不区分大小写)
if len(values) != len(set(values)) or any(value in existing_values for value in values): if len(values) != len(set(values)) or any(value in existing_values for value in values):
raise ValidationError(_("Matching values must be unique!")) raise ValidationError(_("Matching values must be unique!"))
name = fields.Char(string='Courier Name', required=True) # 快递名称 @api.constrains('placement_area')
def _check_placement_area_unique(self):
for record in self:
if record.placement_area:
placement_area = self.env['common.common'].process_match_str(
record.placement_area)
# 检查所有记录的处理后值是否有重复
pro_map = self.get_all_placement_area(
[('id', '!=', record.id), ('placement_area', '!=', False)])
pro_id = pro_map.get(placement_area)
if pro_id:
raise ValidationError(
_("Placement Area must be unique !"))
logo = fields.Binary('Courier Logo') # 快递logo,英文
name = fields.Char(string='Courier Name', required=True, translate=True) # 快递名称
abbreviation = fields.Char(string='Abbreviation', required=True) # 简称 abbreviation = fields.Char(string='Abbreviation', required=True) # 简称
tape_color_value = fields.Char(string='Tape Color Value') # 胶带色值 tape_color_value = fields.Char(string='Tape Color Value') # 胶带色值
active = fields.Boolean('Active', default=True) # 有效☑️ active = fields.Boolean('Active', default=True) # 有效☑️
matching_value = fields.Text(string='Matching Value') # 尾程服务商匹配值 matching_value = fields.Text(string='Matching Value') # 尾程服务商匹配值
placement_area = fields.Char('Placement Area') # 摆放区域,英文
def match_provider(self, provider_name): def match_provider(self, provider_name):
"""Check if the provider name exists in matching values and return the record.""" """Check if the provider name exists in matching values and return the record."""
# 将输入的 provider_name 转换为小写
provider_name_lower = provider_name.lower()
# 查询所有匹配的记录 # 查询所有匹配的记录
matching_records = self.search([]) matching_records = self.sudo().search([])
# 检查是否有记录的 matching_value 包含 provider_name # 检查是否有记录的 matching_value 包含 provider_name(不区分大小写)
for record in matching_records: for record in matching_records:
if provider_name in record.matching_value.split('\n'): if record.matching_value:
return record # 返回找到的记录 # 将匹配值转换为小写进行比较
matching_values = [value.lower() for value in record.matching_value.split('\n')]
if provider_name_lower in matching_values:
return record # 返回找到的记录
return False # 如果没有找到,返回 None return False # 如果没有找到,返回 None
def get_all_placement_area(self, domain=[]):
"""
获取所有摆放区域
"""
all_providers = self.sudo().search(domain or [])
provider_map = {
self.env['common.common'].process_match_str(provider.placement_area): provider.id
for provider in all_providers
}
return provider_map
def search_pro_info(self):
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
return {
'provider_name': self.name, # 尾程快递名称
"abbreviation": self.abbreviation or '', # 简称
'placement_area': self.placement_area or '', # 摆放区域
'tape_color_value': self.tape_color_value or '', # 胶带色值
'matching_value': self.matching_value or '', # 匹配值
'logo': "%s/web/image/%s/%s/logo" % (base_url, self._name, self.id) if self.logo else '', # 快递logo
}
...@@ -16,6 +16,16 @@ class CommonCommon(models.Model): ...@@ -16,6 +16,16 @@ class CommonCommon(models.Model):
_name = 'common.common' _name = 'common.common'
_description = u'公用基础类' _description = u'公用基础类'
# 去杠去空格转大写
def process_match_str(self, input_str):
"""
处理匹配的字符串,去除杠和空格,并转换为大写
"""
if input_str:
return input_str.replace('-', '').replace('/', '').replace(' ', '').upper()
return input_str
def get_local_time(self, local_time=None, user_obj=False): def get_local_time(self, local_time=None, user_obj=False):
"""获取Odoo时区的时间 """获取Odoo时区的时间
Args: Args:
...@@ -30,8 +40,10 @@ class CommonCommon(models.Model): ...@@ -30,8 +40,10 @@ class CommonCommon(models.Model):
if not user_obj: if not user_obj:
user_obj = self.env.user user_obj = self.env.user
user_tz = user_obj.tz or 'UTC' user_tz = user_obj.tz or 'UTC'
timezone_offset = self.env['common.common'].sudo().get_time_zone(user_tz) timezone_offset = self.env['common.common'].sudo(
local_time = local_time + datetime.timedelta(hours=int(timezone_offset)) ).get_time_zone(user_tz)
local_time = local_time + \
datetime.timedelta(hours=int(timezone_offset))
return local_time.strftime('%Y-%m-%d %H:%M:%S'), timezone_offset return local_time.strftime('%Y-%m-%d %H:%M:%S'), timezone_offset
except Exception as e: except Exception as e:
# 如果出现任何错误,返回UTC时间 # 如果出现任何错误,返回UTC时间
...@@ -51,8 +63,10 @@ class CommonCommon(models.Model): ...@@ -51,8 +63,10 @@ class CommonCommon(models.Model):
if not user_obj: if not user_obj:
user_obj = self.env.user user_obj = self.env.user
user_tz = user_obj.tz or 'UTC' user_tz = user_obj.tz or 'UTC'
timezone_offset = self.env['common.common'].sudo().get_time_zone(user_tz) timezone_offset = self.env['common.common'].sudo(
local_time = local_time + datetime.timedelta(hours=int(timezone_offset)) ).get_time_zone(user_tz)
local_time = local_time + \
datetime.timedelta(hours=int(timezone_offset))
local_tz = pytz.timezone(user_tz) local_tz = pytz.timezone(user_tz)
# 确保时间是本地时区 # 确保时间是本地时区
if local_time.tzinfo is None: if local_time.tzinfo is None:
......
...@@ -12,7 +12,10 @@ class ResConfigSettings(models.TransientModel): ...@@ -12,7 +12,10 @@ class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings' _inherit = 'res.config.settings'
before_min = fields.Integer('清关时间取值(早于清关结束)') before_min = fields.Integer('清关时间取值(早于清关结束)')
package_scan_min = fields.Integer('一键全扫完成时间(min)', help='输入示范:10,即表示在10分钟内大包时间随机分配,并不能重复') package_scan_min = fields.Integer(
'一键全扫完成时间(min)', help='输入示范:10,即表示在10分钟内大包时间随机分配,并不能重复')
is_package_scan = fields.Boolean(
'一键全扫开关', default=False, config_parameter='is_package_scan')
@api.model @api.model
def get_values(self): def get_values(self):
...@@ -24,10 +27,9 @@ class ResConfigSettings(models.TransientModel): ...@@ -24,10 +27,9 @@ class ResConfigSettings(models.TransientModel):
config = self.env['ir.config_parameter'].sudo() config = self.env['ir.config_parameter'].sudo()
before_min = config.get_param('before_min', default=10) before_min = config.get_param('before_min', default=10)
package_scan_min = config.get_param('package_scan_min', default=10) package_scan_min = config.get_param('package_scan_min', default=10)
values.update( values.update(
before_min=before_min, before_min=before_min,
package_scan_min=package_scan_min, package_scan_min=package_scan_min
) )
return values return values
......
...@@ -7,11 +7,13 @@ ...@@ -7,11 +7,13 @@
<sheet> <sheet>
<group> <group>
<group> <group>
<field name="logo" widget="image" class="oe_avatar"/>
<field name="name"/> <!-- 快递名称 --> <field name="name"/> <!-- 快递名称 -->
<field name="abbreviation"/> <!-- 简称 --> <field name="abbreviation"/> <!-- 简称 -->
<field name="tape_color_value" widget="color" required="1"/> <!-- 胶带色值 --> <field name="tape_color_value" widget="color" required="1"/> <!-- 胶带色值 -->
<field name="matching_value" <field name="matching_value"
placeholder="Multiple entries can be made, one matching value per line"/> <!-- 尾程服务商匹配值 --> placeholder="Multiple entries can be made, one matching value per line"/> <!-- 尾程服务商匹配值 -->
<field name="placement_area"/>
</group> </group>
<group> <group>
<field name="active"/> <!-- 有效☑️ --> <field name="active"/> <!-- 有效☑️ -->
...@@ -31,6 +33,7 @@ ...@@ -31,6 +33,7 @@
<field name="abbreviation"/> <!-- 简称 --> <field name="abbreviation"/> <!-- 简称 -->
<field name="tape_color_value"/> <!-- 胶带色值 --> <field name="tape_color_value"/> <!-- 胶带色值 -->
<field name="matching_value"/> <!-- 尾程服务商匹配值 --> <field name="matching_value"/> <!-- 尾程服务商匹配值 -->
<field name="placement_area"/> <!-- 摆放区域 -->
<field name="active"/> <!-- 有效☑️ --> <field name="active"/> <!-- 有效☑️ -->
</tree> </tree>
</field> </field>
...@@ -43,6 +46,7 @@ ...@@ -43,6 +46,7 @@
<search string="Last Mile Provider"> <search string="Last Mile Provider">
<field name="name"/> <field name="name"/>
<field name="abbreviation"/> <field name="abbreviation"/>
<field name="placement_area"/>
</search> </search>
</field> </field>
</record> </record>
......
...@@ -27,6 +27,11 @@ ...@@ -27,6 +27,11 @@
<div class="col-12 col-lg-6 o_setting_box"> <div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"/> <div class="o_setting_left_pane"/>
<div class="o_setting_right_pane"> <div class="o_setting_right_pane">
<div class="text-muted">
<label for="is_package_scan"/>
<field name="is_package_scan"/>
</div>
<div class="text-muted"> <div class="text-muted">
<label for="package_scan_min"/> <label for="package_scan_min"/>
<field name="package_scan_min"/> <field name="package_scan_min"/>
......
from . import tt_controllers from . import tt_controllers
from . import order_controller from . import order_controller
from . import binary
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo.addons.web.controllers.binary import Binary
try:
from werkzeug.utils import send_file
except ImportError:
from odoo.tools._vendor.send_file import send_file
from odoo import http
from odoo.exceptions import UserError
from odoo.http import request
from odoo.tools import replace_exceptions, str2bool
from odoo.tools.image import image_guess_size_from_field_name
_logger = logging.getLogger(__name__)
BAD_X_SENDFILE_ERROR = """\
Odoo is running with --x-sendfile but is receiving /web/filestore requests.
With --x-sendfile enabled, NGINX should be serving the
/web/filestore route, however Odoo is receiving the
request.
This usually indicates that NGINX is badly configured,
please make sure the /web/filestore location block exists
in your configuration file and that it is similar to:
location /web/filestore {{
internal;
alias {data_dir}/filestore;
}}
"""
def clean(name):
return name.replace('\x3c', '')
class AttachmentBinary(Binary):
@http.route(['/web/image',
'/web/image/<string:xmlid>',
'/web/image/<string:xmlid>/<string:filename>',
'/web/image/<string:xmlid>/<int:width>x<int:height>',
'/web/image/<string:xmlid>/<int:width>x<int:height>/<string:filename>',
'/web/image/<string:model>/<int:id>/<string:field>',
'/web/image/<string:model>/<int:id>/<string:field>/<string:filename>',
'/web/image/<string:model>/<int:id>/<string:field>/<int:width>x<int:height>',
'/web/image/<string:model>/<int:id>/<string:field>/<int:width>x<int:height>/<string:filename>',
'/web/image/<int:id>',
'/web/image/<int:id>/<string:filename>',
'/web/image/<int:id>/<int:width>x<int:height>',
'/web/image/<int:id>/<int:width>x<int:height>/<string:filename>',
'/web/image/<int:id>-<string:unique>',
'/web/image/<int:id>-<string:unique>/<string:filename>',
'/web/image/<int:id>-<string:unique>/<int:width>x<int:height>',
'/web/image/<int:id>-<string:unique>/<int:width>x<int:height>/<string:filename>'], type='http',
auth="public", cors="*")
# pylint: disable=redefined-builtin,invalid-name
def content_image(self, xmlid=None, model='ir.attachment', id=None, field='raw',
filename_field='name', filename=None, mimetype=None, unique=False,
download=False, width=0, height=0, crop=False, access_token=None,
nocache=False):
try:
record = request.env['ir.binary'].sudo()._find_record(xmlid, model, id and int(id), access_token)
stream = request.env['ir.binary'].sudo()._get_image_stream_from(
record, field, filename=filename, filename_field=filename_field,
mimetype=mimetype, width=int(width), height=int(height), crop=crop,
)
except UserError as exc:
if download:
raise request.not_found() from exc
# Use the ratio of the requested field_name instead of "raw"
if (int(width), int(height)) == (0, 0):
width, height = image_guess_size_from_field_name(field)
record = request.env.ref('web.image_placeholder').sudo()
stream = request.env['ir.binary']._get_image_stream_from(
record, 'raw', width=int(width), height=int(height), crop=crop,
)
send_file_kwargs = {'as_attachment': download}
if unique:
send_file_kwargs['immutable'] = True
send_file_kwargs['max_age'] = http.STATIC_CACHE_LONG
if nocache:
send_file_kwargs['max_age'] = None
return stream.get_response(**send_file_kwargs)
@http.route(['/web/content',
'/web/content/<string:xmlid>',
'/web/content/<string:xmlid>/<string:filename>',
'/web/content/<int:id>',
'/web/content/<int:id>/<string:filename>',
'/web/content/<string:model>/<int:id>/<string:field>',
'/web/content/<string:model>/<int:id>/<string:field>/<string:filename>'], type='http', auth="public",
cors="*")
# pylint: disable=redefined-builtin,invalid-name
def content_common(self, xmlid=None, model='ir.attachment', id=None, field='raw',
filename=None, filename_field='name', mimetype=None, unique=False,
download=False, access_token=None, nocache=False):
with replace_exceptions(UserError, by=request.not_found()):
record = request.env['ir.binary'].sudo()._find_record(xmlid, model, id and int(id), access_token)
stream = request.env['ir.binary'].sudo()._get_stream_from(record, field, filename, filename_field, mimetype)
if request.httprequest.args.get('access_token'):
stream.public = True
send_file_kwargs = {'as_attachment': str2bool(download)}
if unique:
send_file_kwargs['immutable'] = True
send_file_kwargs['max_age'] = http.STATIC_CACHE_LONG
if nocache:
send_file_kwargs['max_age'] = None
return stream.get_response(**send_file_kwargs)
...@@ -49,7 +49,8 @@ class OrderController(http.Controller): ...@@ -49,7 +49,8 @@ class OrderController(http.Controller):
# system_user = system_user.with_context(lang=lang) # system_user = system_user.with_context(lang=lang)
try: try:
if kwargs.get('login') and kwargs.get('password'): if kwargs.get('login') and kwargs.get('password'):
uid = request.session.authenticate(request.session.db, kwargs['login'], kwargs['password']) uid = request.session.authenticate(
request.session.db, kwargs['login'], kwargs['password'])
user = request.env['res.users'].sudo().browse(uid) user = request.env['res.users'].sudo().browse(uid)
res['user_info'] = { res['user_info'] = {
'id': uid, # 用户id 'id': uid, # 用户id
...@@ -58,21 +59,37 @@ class OrderController(http.Controller): ...@@ -58,21 +59,37 @@ class OrderController(http.Controller):
res['state'] = 200 res['state'] = 200
else: else:
res['state'] = 202 res['state'] = 202
res['message'] = error_msg_dic[pda_lang] # _('Login name and password cannot be empty') # 登录名、密码不能为空 # _('Login name and password cannot be empty') # 登录名、密码不能为空
res['message'] = error_msg_dic[pda_lang]
except exceptions.AccessDenied as e: except exceptions.AccessDenied as e:
if e.args == exceptions.AccessDenied().args: if e.args == exceptions.AccessDenied().args:
res['message'] = exceptions_msg_dic[pda_lang] # _("Wrong login/password") # 错误的登录名或密码 # _("Wrong login/password") # 错误的登录名或密码
res['message'] = exceptions_msg_dic[pda_lang]
else: else:
res['message'] = e.args[0] res['message'] = e.args[0]
logging.info('api_cc_login error:%s' % res) logging.info('api_cc_login error:%s' % res)
return res return res
@http.route('/api/one_key_full_scan/info', type='json', auth='public', csrf=False)
def one_key_full_scan_info(self):
"""
查询一键全扫信息接口,返回是否一键全扫和完成时间
入参:bl_no
"""
# kwargs = json.loads(request.httprequest.data)
res = {'state': 201, 'message': ''}
res['package_scan_min'] = int(
request.env['res.config.settings'].sudo().get_values().get('package_scan_min'))
res['is_package_scan'] = bool(request.env['ir.config_parameter'].sudo(
).get_param('is_package_scan'))
res['state'] = 200
return res
@http.route('/api/bl/info', type='json', auth='public', methods=['GET', 'POST'], csrf=False) @http.route('/api/bl/info', type='json', auth='public', methods=['GET', 'POST'], csrf=False)
def bl_info(self): def bl_info(self):
""" """
查看提单以及大包,小包和托盘信息 查看提单以及大包,小包和托盘信息
:param kwargs:
:return: :return:
""" """
kwargs = json.loads(request.httprequest.data) kwargs = json.loads(request.httprequest.data)
...@@ -85,17 +102,22 @@ class OrderController(http.Controller): ...@@ -85,17 +102,22 @@ class OrderController(http.Controller):
bl_no = kwargs['bl_no'] bl_no = kwargs['bl_no']
# bl_obj = request.env['cc.bl'].sudo().search([('bl_no', '=', bl_no)]) # 提单 # bl_obj = request.env['cc.bl'].sudo().search([('bl_no', '=', bl_no)]) # 提单
state_arr = ['draft', 'ccing'] state_arr = ['draft', 'ccing']
bl_obj = request.env['cc.bl'].sudo().deal_bl_no(bl_no) # 提单号去掉杠和空格,并转换为小写 bl_obj = request.env['cc.bl'].sudo().deal_bl_no(
bl_no) # 提单号去掉杠和空格,并转换为小写
if bl_obj: if bl_obj:
if bl_obj.state in state_arr: if bl_obj.state in state_arr:
res['bl_info'] = bl_obj.search_bl_info(pda_lang=pda_lang, type=action_type) res['bl_info'] = bl_obj.search_bl_info(
res['package_scan_min'] = int(request.env['res.config.settings'].sudo().get_values().get('package_scan_min')) pda_lang=pda_lang, type=action_type)
res['package_scan_min'] = int(
request.env['res.config.settings'].sudo().get_values().get('package_scan_min'))
res['state'] = 200 res['state'] = 200
else: else:
res['message'] = bill_state_msg_dic[pda_lang] # 没有在系统中找到未完成清关的该提单信息 # 没有在系统中找到未完成清关的该提单信息
res['message'] = bill_state_msg_dic[pda_lang]
else: else:
res['state'] = 404 res['state'] = 404
res['message'] = bill_noexist_msg_dic[pda_lang] # _('Bill of lading does not exist') # 提单不存在 # _('Bill of lading does not exist') # 提单不存在
res['message'] = bill_noexist_msg_dic[pda_lang]
else: else:
res['state'] = 202 res['state'] = 202
res['message'] = bill_null_msg_dic[ res['message'] = bill_null_msg_dic[
...@@ -115,7 +137,6 @@ class OrderController(http.Controller): ...@@ -115,7 +137,6 @@ class OrderController(http.Controller):
def update_big_package_tally_detail(self): def update_big_package_tally_detail(self):
""" """
修改理货信息 修改理货信息
:param kwargs:
:return: :return:
""" """
kwargs = json.loads(request.httprequest.data) kwargs = json.loads(request.httprequest.data)
...@@ -128,7 +149,8 @@ class OrderController(http.Controller): ...@@ -128,7 +149,8 @@ class OrderController(http.Controller):
kwargs.get('big_package_arr') or kwargs.get('ship_package_arr') or kwargs.get('pallet_arr')): kwargs.get('big_package_arr') or kwargs.get('ship_package_arr') or kwargs.get('pallet_arr')):
bl_no = kwargs['bl_no'] bl_no = kwargs['bl_no']
state_arr = ['draft', 'ccing'] state_arr = ['draft', 'ccing']
bl_obj = request.env['cc.bl'].sudo().deal_bl_no(bl_no) # 提单号去掉杠和空格,并转换为小写 bl_obj = request.env['cc.bl'].sudo().deal_bl_no(
bl_no) # 提单号去掉杠和空格,并转换为小写
if bl_obj and bl_obj.state in state_arr: if bl_obj and bl_obj.state in state_arr:
ship_packages = [] ship_packages = []
big_package_exception_arr = {} big_package_exception_arr = {}
...@@ -141,7 +163,8 @@ class OrderController(http.Controller): ...@@ -141,7 +163,8 @@ class OrderController(http.Controller):
file_str = 'big_package_no' if package_type == 'big' else ( file_str = 'big_package_no' if package_type == 'big' else (
'logistic_order_no' if package_type == 'ship' else 'pallet_number') # 大包号/物流订单号 'logistic_order_no' if package_type == 'ship' else 'pallet_number') # 大包号/物流订单号
package_no = package_item.get(file_str) # 包裹号 package_no = package_item.get(file_str) # 包裹号
exception_cause_ids = package_item.get('exception_cause_ids') # 异常原因id数组 exception_cause_ids = package_item.get(
'exception_cause_ids') # 异常原因id数组
if package_type == 'pallet': if package_type == 'pallet':
package_obj = request.env['cc.big.package'].sudo().search( package_obj = request.env['cc.big.package'].sudo().search(
[('pallet_number', '=', package_no), ('bl_id', '=', bl_obj.id)]) # 多个 [('pallet_number', '=', package_no), ('bl_id', '=', bl_obj.id)]) # 多个
...@@ -153,13 +176,16 @@ class OrderController(http.Controller): ...@@ -153,13 +176,16 @@ class OrderController(http.Controller):
for excep_item in exception_cause_ids: for excep_item in exception_cause_ids:
if package_type == 'ship': if package_type == 'ship':
if excep_item not in ship_package_exception_arr: if excep_item not in ship_package_exception_arr:
ship_package_exception_arr[excep_item] = [] ship_package_exception_arr[excep_item] = [
]
ship_package_exception_arr[excep_item] += package_obj.ids ship_package_exception_arr[excep_item] += package_obj.ids
else: else:
if excep_item not in big_package_exception_arr: if excep_item not in big_package_exception_arr:
big_package_exception_arr[excep_item] = [] big_package_exception_arr[excep_item] = [
]
big_package_exception_arr[excep_item] += package_obj.ids big_package_exception_arr[excep_item] += package_obj.ids
package_obj.update_exception_info(exception_cause_ids) # 修改异常信息 package_obj.update_exception_info(
exception_cause_ids) # 修改异常信息
tally_time = package_item.get('tally_time') tally_time = package_item.get('tally_time')
if (action_type == 'tally' and package_item.get('tally_state') == 'checked_goods') or ( if (action_type == 'tally' and package_item.get('tally_state') == 'checked_goods') or (
action_type == 'handover' and package_item.get( action_type == 'handover' and package_item.get(
...@@ -179,7 +205,8 @@ class OrderController(http.Controller): ...@@ -179,7 +205,8 @@ class OrderController(http.Controller):
'tally_time': tally_time 'tally_time': tally_time
}) # 小包 }) # 小包
package_obj.update_big_package_info(action_type=action_type, package_obj.update_big_package_info(action_type=action_type,
tally_state=package_item.get('tally_state'), tally_state=package_item.get(
'tally_state'),
tally_user_id=package_item.get( tally_user_id=package_item.get(
'tally_user_id'), 'tally_user_id'),
tally_time=tally_time) # 修改理货信息 tally_time=tally_time) # 修改理货信息
...@@ -189,7 +216,8 @@ class OrderController(http.Controller): ...@@ -189,7 +216,8 @@ class OrderController(http.Controller):
# 处理小包、大包和托盘 # 处理小包、大包和托盘
if kwargs.get('ship_package_arr'): if kwargs.get('ship_package_arr'):
error_no_arr = process_packages(kwargs['ship_package_arr'], 'ship', ship_packages) error_no_arr = process_packages(
kwargs['ship_package_arr'], 'ship', ship_packages)
if error_no_arr: if error_no_arr:
res['message'] = { res['message'] = {
'en': 'Ship package number [%s] does not exist' % ','.join(error_no_arr), 'en': 'Ship package number [%s] does not exist' % ','.join(error_no_arr),
...@@ -198,7 +226,8 @@ class OrderController(http.Controller): ...@@ -198,7 +226,8 @@ class OrderController(http.Controller):
return res return res
if kwargs.get('big_package_arr'): if kwargs.get('big_package_arr'):
error_no_arr = process_packages(kwargs['big_package_arr'], 'big', ship_packages) error_no_arr = process_packages(
kwargs['big_package_arr'], 'big', ship_packages)
if error_no_arr: if error_no_arr:
res['message'] = { res['message'] = {
'en': 'Big package number [%s] does not exist' % ','.join(error_no_arr), 'en': 'Big package number [%s] does not exist' % ','.join(error_no_arr),
...@@ -207,7 +236,8 @@ class OrderController(http.Controller): ...@@ -207,7 +236,8 @@ class OrderController(http.Controller):
return res return res
if kwargs.get('pallet_arr'): if kwargs.get('pallet_arr'):
error_no_arr = process_packages(kwargs['pallet_arr'], 'pallet', ship_packages) error_no_arr = process_packages(
kwargs['pallet_arr'], 'pallet', ship_packages)
if error_no_arr: if error_no_arr:
res['message'] = { res['message'] = {
'en': 'Tray number [%s] does not have a corresponding big package' % ','.join( 'en': 'Tray number [%s] does not have a corresponding big package' % ','.join(
...@@ -238,10 +268,12 @@ class OrderController(http.Controller): ...@@ -238,10 +268,12 @@ class OrderController(http.Controller):
email_language=lang) email_language=lang)
ship_wizard_obj.confirm() # 发送邮件 ship_wizard_obj.confirm() # 发送邮件
res['state'] = 200 res['state'] = 200
logging.info('update_big_package_tally_detail ship_packages:%s' % len(ship_packages)) logging.info(
'update_big_package_tally_detail ship_packages:%s' % len(ship_packages))
# 有小包 就更新小包状态和同步 # 有小包 就更新小包状态和同步
if ship_packages: if ship_packages:
redis_conn = request.env['common.common'].sudo().get_redis() redis_conn = request.env['common.common'].sudo(
).get_redis()
if redis_conn and redis_conn != 'no': if redis_conn and redis_conn != 'no':
# redis_conn.lpush('push_ship_package_state', json.dumps( # redis_conn.lpush('push_ship_package_state', json.dumps(
# {'bl_id': bl_obj.id, 'ship_package_ids': ship_package_ids})) # {'bl_id': bl_obj.id, 'ship_package_ids': ship_package_ids}))
...@@ -266,6 +298,152 @@ class OrderController(http.Controller): ...@@ -266,6 +298,152 @@ class OrderController(http.Controller):
logging.info('res:%s' % res) logging.info('res:%s' % res)
return res return res
@http.route('/api/update/pro/big/package/tally/detail', type='json', auth='public', methods=['GET', 'POST'],
csrf=False)
def update_pro_big_package_tally_detail(self):
"""
修改尾程快递的理货信息
:return:
"""
kwargs = json.loads(request.httprequest.data)
pda_lang = kwargs.get('pda_lang') or 'zh'
res = {'state': 201, 'message': ''}
action_type = kwargs.get('action_type') or 'tally' # tally / handover
try:
logging.info('update_pro_big_package_tally_detail kw:%s' % kwargs)
if action_type and (kwargs.get('big_package_arr') or kwargs.get('ship_package_arr')):
ship_packages = []
big_package_exception_arr = {}
ship_package_exception_arr = {}
# 处理包裹信息
def process_packages(package_arr, package_type, ship_packages):
error_no_set = set() # 使用集合来存储错误信息
for package_item in package_arr:
file_str = 'big_package_no' if package_type == 'big' else (
'logistic_order_no' if package_type == 'ship' else 'pallet_number') # 大包号/物流订单号
package_no = package_item.get(file_str) # 包裹号
exception_cause_ids = package_item.get(
'exception_cause_ids') # 异常原因id数组
package_obj = request.env[f'cc.{package_type}.package'].sudo().search(
[(file_str, '=', package_no)]) # 小包/大包 1个
if package_obj:
if exception_cause_ids:
for excep_item in exception_cause_ids:
if package_type == 'ship':
if excep_item not in ship_package_exception_arr:
ship_package_exception_arr[excep_item] = [
]
ship_package_exception_arr[excep_item] += package_obj.ids
else:
if excep_item not in big_package_exception_arr:
big_package_exception_arr[excep_item] = [
]
big_package_exception_arr[excep_item] += package_obj.ids
package_obj.update_exception_info(
exception_cause_ids) # 修改异常信息
tally_time = package_item.get('tally_time')
if (action_type == 'tally' and package_item.get('tally_state') == 'checked_goods') or (
action_type == 'handover' and package_item.get(
'tally_state') == 'handover_completed'):
if package_type == 'ship':
ship_packages.append({
'id': [package_obj.id],
'bl_id': package_obj.bl_id.id,
'tally_time': tally_time})
else:
for package in package_obj:
if (
action_type == 'tally' and package.tally_state == 'unprocessed_goods') or (
action_type == 'handover' and package.tally_state in (
'unprocessed_goods', 'checked_goods')):
ship_packages.append({
'id': package.ship_package_ids.ids,
'bl_id': package.bl_id.id,
'tally_time': tally_time
}) # 小包
package_obj.update_big_package_info(action_type=action_type,
tally_state=package_item.get(
'tally_state'),
tally_user_id=package_item.get(
'tally_user_id'),
tally_time=tally_time) # 修改理货信息
else:
error_no_set.add(package_no)
return error_no_set
# 处理小包、大包
if kwargs.get('ship_package_arr'):
error_no_arr = process_packages(
kwargs['ship_package_arr'], 'ship', ship_packages)
if error_no_arr:
res['message'] = {
'en': 'Ship package number [%s] does not exist' % ','.join(error_no_arr),
'zh': '小包物流订单号[%s]不存在' % ','.join(error_no_arr)
}[pda_lang]
return res
if kwargs.get('big_package_arr'):
error_no_arr = process_packages(
kwargs['big_package_arr'], 'big', ship_packages)
if error_no_arr:
res['message'] = {
'en': 'Big package number [%s] does not exist' % ','.join(error_no_arr),
'zh': '大包号[%s]不存在' % ','.join(error_no_arr)
}[pda_lang]
return res
# 修改异常原因,发送异常邮件
lang = 'zh_CN' if pda_lang == 'zh' else 'en_US' # 语言
for exception_id, big_package in big_package_exception_arr.items():
if big_package:
big_wizard_obj = request.env[
'add.exception.info.wizard'].sudo().with_context({'active_id': big_package,
'active_name': 'cc.big.package',
'not_update_ex': True}).create_add_exception_wizard(
'big package', [exception_id], big_package_ids=big_package, send_email=True,
email_language=lang)
big_wizard_obj.confirm() # 发送邮件
for exception_id, ship_package in ship_package_exception_arr.items():
if ship_package:
ship_wizard_obj = request.env[
'add.exception.info.wizard'].sudo().with_context({'active_id': ship_package,
'active_name': 'cc.ship.package',
'not_update_ex': True}).create_add_exception_wizard(
'ship package', [exception_id], ship_package_ids=ship_package, send_email=True,
email_language=lang)
ship_wizard_obj.confirm() # 发送邮件
res['state'] = 200
logging.info(
'update_big_package_tally_detail ship_packages:%s' % len(ship_packages))
# 有小包 就更新小包状态和同步
if ship_packages:
redis_conn = request.env['common.common'].sudo().get_redis()
if redis_conn and redis_conn != 'no':
# redis_conn.lpush('push_ship_package_state', json.dumps(
# {'bl_id': bl_obj.id, 'ship_package_ids': ship_package_ids}))
bl_ids = [ship_package.get('bl_id') for ship_package in ship_packages]
logging.info('bl_ids:%s' % bl_ids)
redis_conn.lpush('mail_push_package_list', json.dumps(
{'ids': list(set(bl_ids)), 'ship_packages': str(ship_packages), 'action_type': action_type}))
else:
null_msg_dic = {
'en': 'The bill of lading number and action type must be provided; at least one of the following is required: big package data or ship package data.',
'zh': '类型必须提供;大包数据或小包数据至少需要提供一个。'
}
res['message'] = null_msg_dic[pda_lang]
except Exception as e:
logging.info('update_pro_big_package_tally_detail error:%s' % e)
exceptions_msg_dic = {
'en': 'System parsing error, the reason for the error is %s' % e,
'zh': '系统解析错误,错误原因是%s' % e
}
res['message'] = exceptions_msg_dic[pda_lang]
logging.info('res:%s' % res)
return res
@http.route('/api/exceptions/info', type='json', auth='public', methods=['GET', 'POST'], csrf=False) @http.route('/api/exceptions/info', type='json', auth='public', methods=['GET', 'POST'], csrf=False)
def exceptions_info(self): def exceptions_info(self):
""" """
...@@ -278,7 +456,7 @@ class OrderController(http.Controller): ...@@ -278,7 +456,7 @@ class OrderController(http.Controller):
logging.info('exceptions_info kwargs:%s' % kwargs) logging.info('exceptions_info kwargs:%s' % kwargs)
lang = 'zh_CN' if pda_lang == 'zh' else 'en_US' # 语言 lang = 'zh_CN' if pda_lang == 'zh' else 'en_US' # 语言
exception_obj = request.env['cc.exception.info'].sudo().with_context({'lang': lang}).search([]) # 包裹异常原因 exception_obj = request.env['cc.exception.info'].sudo().with_context({'lang': lang}).search([]) # 包裹异常原因
res['exception_arr'] = [excep.search_exception_info(pda_lang=pda_lang) for excep in exception_obj] res['exception_arr'] = [excep.search_exception_info() for excep in exception_obj]
res['state'] = 200 res['state'] = 200
except Exception as e: except Exception as e:
exceptions_msg_dic = { exceptions_msg_dic = {
...@@ -290,3 +468,84 @@ class OrderController(http.Controller): ...@@ -290,3 +468,84 @@ class OrderController(http.Controller):
pda_lang] # _('System parsing error, the reason for the error is %s', e) # 系统解析错误,错误原因是 pda_lang] # _('System parsing error, the reason for the error is %s', e) # 系统解析错误,错误原因是
logging.info('exceptions_info res:%s' % res) logging.info('exceptions_info res:%s' % res)
return res return res
@http.route('/api/last_mile/tally', type='json', auth='public', csrf=False)
def last_mile_tally(self):
"""
按尾程快递理货的接口,查询所有清关中的提单关联的大包,且大包状态为未理货的数量,按下一阶段服务商名称分组,下一个阶段服务商名称匹配尾程快递;传给PDA。
尾程快递名称,摆放区域,快递LOGO,大包件数
大包信息字段:大包号,尾程快递名称,尾程快递色值;
"""
kwargs = json.loads(request.httprequest.data)
pda_lang = kwargs.get('pda_lang') or 'zh'
res = {'state': 201, 'message': ''}
try:
logging.info('last_mile_tally kwargs:%s' % kwargs)
return self._get_last_mile_grouped('unprocessed_goods', pda_lang)
except Exception as e:
exceptions_msg_dic = {
'en': 'System parsing error, the reason for the error is %s' % e,
'zh': '系统解析错误,错误原因是%s' % e
}
logging.info('last_mile_tally error:%s' % e)
res['message'] = exceptions_msg_dic[
pda_lang] # _('System parsing error, the reason for the error is %s', e) # 系统解析错误,错误原因是
logging.info('last_mile_tally res:%s' % res)
return res
@http.route('/api/last_mile/delivery', type='json', auth='public', csrf=False)
def last_mile_delivery(self):
"""
按尾程快递交货接口,查询系统所有清关中的提单关联的大包,且大包状态为已理货的数量,按下一阶段服务商名称分组,下一个阶段服务商名称匹配尾程快递传给PDA。传输字段与理货的一致。
"""
kwargs = json.loads(request.httprequest.data)
pda_lang = kwargs.get('pda_lang') or 'zh'
res = {'state': 201, 'message': ''}
try:
logging.info('last_mile_delivery kwargs:%s' % kwargs)
return self._get_last_mile_grouped('checked_goods', pda_lang)
except Exception as e:
exceptions_msg_dic = {
'en': 'System parsing error, the reason for the error is %s' % e,
'zh': '系统解析错误,错误原因是%s' % e
}
logging.info('last_mile_delivery error:%s' % e)
res['message'] = exceptions_msg_dic[
pda_lang] # _('System parsing error, the reason for the error is %s', e) # 系统解析错误,错误原因是
logging.info('last_mile_delivery res:%s' % res)
return res
def _get_last_mile_grouped(self, tally_state, pda_lang):
lang = 'zh_CN' if pda_lang == 'zh' else 'en_US' # 语言
# 1. 查所有清关中提单
bls = request.env['cc.bl'].sudo().search([('state', '=', 'ccing')])
# 2. 查所有大包
big_packages = request.env['cc.big.package'].sudo().search([
('bl_id', 'in', bls.ids),
('tally_state', '=', tally_state)
])
# 3. 按"下一阶段服务商名称"分组
group_dict = {}
for pkg in big_packages:
provider = request.env['cc.last.mile.provider'].sudo().with_context({'lang': lang}).match_provider(
pkg.next_provider_name)
if not provider:
continue
key = provider.id
if key not in group_dict:
group_dict[key] = provider.search_pro_info() # 查询快递信息
group_dict[key]['count'] = 0
group_dict[key]['big_package_arr'] = []
group_dict[key]['ship_package_arr'] = []
group_dict[key]['count'] += 1
group_dict[key]['big_package_arr'].append(pkg.search_big_package_info(pda_lang=pda_lang, type=tally_state))
group_dict[key]['ship_package_arr'].extend(
[ship_package_item.search_ship_package_info(pda_lang=pda_lang) for ship_package_item in
pkg.ship_package_ids])
# 4. 返回
provider_info_arr = list(group_dict.values())
# 按服务商名称升序排序
provider_info_arr.sort(key=lambda x: x.get('name', ''))
return {'provider_info_arr': provider_info_arr, 'state': 200}
...@@ -6,6 +6,8 @@ from . import res_config_setting ...@@ -6,6 +6,8 @@ from . import res_config_setting
from . import ao_tt_api_log from . import ao_tt_api_log
from . import cc_node from . import cc_node
from . import cc_bill_loading from . import cc_bill_loading
from . import ir_attachment
from . import http
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import asyncio import asyncio
import json
import logging import logging
import ssl import ssl
from datetime import timedelta, datetime from datetime import timedelta, datetime
import json
import aiohttp import aiohttp
import certifi import certifi
import pytz import pytz
...@@ -280,7 +281,7 @@ class CcBl(models.Model): ...@@ -280,7 +281,7 @@ class CcBl(models.Model):
bl = self.env['cc.bl'].sudo().search( bl = self.env['cc.bl'].sudo().search(
[('bl_no', '=', master_waybill_no), ('create_date', '>=', date)], limit=1) [('bl_no', '=', master_waybill_no), ('create_date', '>=', date)], limit=1)
if bl: if bl:
# 根据kws中获取的"img_detail"中的"img_file_code和img_file_type,生成odoo的附件,并关联到cc.bl的bl_attachment_ids字段 # 根据kws中获取的"img_detail"中的"img_file_code"和img_file_type,生成odoo的附件,并关联到cc.bl的bl_attachment_ids字段
vals = {'customs_bl_no': kws.get('customs_waybill_id'), vals = {'customs_bl_no': kws.get('customs_waybill_id'),
'transport_tool_code': kws.get('transport_code'), 'transport_tool_code': kws.get('transport_code'),
'transport_tool_name': kws.get('transport_name'), 'transport_tool_name': kws.get('transport_name'),
...@@ -367,6 +368,7 @@ class CcBl(models.Model): ...@@ -367,6 +368,7 @@ class CcBl(models.Model):
""" """
根据小包的状态修改提单关务状态以及生成同步日志 根据小包的状态修改提单关务状态以及生成同步日志
:param package_state_obj:小包更新后的状态 :param package_state_obj:小包更新后的状态
:param user_obj:
:return: :return:
""" """
# 根据小包的状态找到对应的提单关务状态 # 根据小包的状态找到对应的提单关务状态
...@@ -540,7 +542,12 @@ class CcBl(models.Model): ...@@ -540,7 +542,12 @@ class CcBl(models.Model):
# 根据小包状态更新提单关务状态 # 根据小包状态更新提单关务状态
try: try:
if is_ok and item.ship_package_ids: if is_ok and item.ship_package_ids:
item.change_customs_state_by_ship_package(item.ship_package_ids[0].state, user_obj=user_obj) # 检查所有小包状态是否一致
states = set(item.ship_package_ids.mapped('state.id'))
if len(states) == 1:
item.change_customs_state_by_ship_package(item.ship_package_ids[0].state, user_obj=user_obj)
else:
logging.info('Not all ship package states are the same, skip updating customs state.')
except Exception as e: except Exception as e:
logging.info('change_customs_state_by_ship_package error:%s' % e) logging.info('change_customs_state_by_ship_package error:%s' % e)
return is_ok return is_ok
...@@ -606,19 +613,22 @@ class CcBl(models.Model): ...@@ -606,19 +613,22 @@ class CcBl(models.Model):
# package_order = self.env['cc.ship.package'].sudo().browse(package_id) # package_order = self.env['cc.ship.package'].sudo().browse(package_id)
if response_data['code'] != 0: if response_data['code'] != 0:
# package_order.is_sync = False # package_order.is_sync = False
update_false_arr.append(package_id) # 更新 is_sync为 False update_false_arr.append(package_id) # 更新 is_sync为 False
error_msg = response_data['msg'] error_msg = response_data['msg']
request_id = response_data['requestID'] request_id = response_data['requestID']
create_api_log_value_arr.append((tracking_no, utc_time, '小包状态轨迹回传:' + error_msg, False, data_text, request_id, '推出', utc_time)) create_api_log_value_arr.append((tracking_no, utc_time, '小包状态轨迹回传:' + error_msg, False,
data_text, request_id, '推出', utc_time))
is_ok = False is_ok = False
else: else:
# 回传成功 # 回传成功
update_true_arr.append(package_id) # 更新 is_sync为 True update_true_arr.append(package_id) # 更新 is_sync为 True
state_arr = package_node_result_dict.get(state, []) state_arr = package_node_result_dict.get(state, [])
tk_code = state_arr[1] if state_arr else '' tk_code = state_arr[1] if state_arr else ''
create_sync_log_value_arr.append((package_id, utc_time, 'Tiktok', tk_code, process_time, state_explain, user_id)) create_sync_log_value_arr.append(
(package_id, utc_time, 'Tiktok', tk_code, process_time, state_explain, user_id))
request_id = response_data['requestID'] request_id = response_data['requestID']
create_api_log_value_arr.append((tracking_no, utc_time, '', True, data_text, request_id, '推出', utc_time)) create_api_log_value_arr.append(
(tracking_no, utc_time, '', True, data_text, request_id, '推出', utc_time))
if update_false_arr: if update_false_arr:
update_false_ids = '(%s)' % str(update_false_arr)[1:-1] update_false_ids = '(%s)' % str(update_false_arr)[1:-1]
sql = "update cc_ship_package set is_sync=False where id in %s" % update_false_ids sql = "update cc_ship_package set is_sync=False where id in %s" % update_false_ids
...@@ -813,13 +823,18 @@ class CcBl(models.Model): ...@@ -813,13 +823,18 @@ class CcBl(models.Model):
if is_ok: if is_ok:
# 根据小包状态更新提单关务状态 # 根据小包状态更新提单关务状态
if self.ship_package_ids: if self.ship_package_ids:
self.change_customs_state_by_ship_package(self.ship_package_ids[0].state, user_obj=user_obj) # 检查所有小包状态是否一致
states = set(self.ship_package_ids.mapped('state.id'))
if len(states) == 1:
self.change_customs_state_by_ship_package(self.ship_package_ids[0].state, user_obj=user_obj)
else:
logging.info('Not all ship package states are the same, skip updating customs state.')
if is_ok: if is_ok:
return is_ok return is_ok
logging.warning(f"Attempt {i + 1}/{max_retries} failed. Retrying...") logging.warning(f"Attempt {i + 1}/{max_retries} failed. Retrying...")
return False return False
def mail_auto_push(self, mail_time=False, ship_packages=[], action_type='tally', mail_db_user='邮件接收', def mail_auto_push(self, mail_time=False, tally_ship_packages=[], action_type='tally', mail_db_user='邮件接收',
pda_db_user='pda'): pda_db_user='pda'):
self = self.with_context(dict(self._context, is_mail=True)) self = self.with_context(dict(self._context, is_mail=True))
for item in self: for item in self:
...@@ -831,14 +846,15 @@ class CcBl(models.Model): ...@@ -831,14 +846,15 @@ class CcBl(models.Model):
item.push_clear_customs_start(before_utc_time) item.push_clear_customs_start(before_utc_time)
user_obj = self.env['res.users'].search([('login', '=', mail_db_user)], limit=1) user_obj = self.env['res.users'].search([('login', '=', mail_db_user)], limit=1)
# 尝试调用 callback_track # 尝试调用 callback_track
if self.try_callback_track(user_obj=user_obj): if item.try_callback_track(user_obj=user_obj):
item.push_clear_customs_end(utc_time) item.push_clear_customs_end(utc_time)
# 再次尝试调用 callback_track # 再次尝试调用 callback_track
if not self.try_callback_track(user_obj=user_obj): if not item.try_callback_track(user_obj=user_obj):
logging.error(f"Failed to push item after {3} attempts.") logging.error(f"Failed to push item after {3} attempts.")
else: else:
logging.error(f"Failed to start process for item after {3} attempts.") logging.error(f"Failed to start process for item after {3} attempts.")
elif ship_packages: elif tally_ship_packages:
ship_packages = [pkg for pkg in tally_ship_packages if pkg.get('bl_id') == item.id]
user_obj = self.env['res.users'].search([('login', '=', pda_db_user)], limit=1) user_obj = self.env['res.users'].search([('login', '=', pda_db_user)], limit=1)
ship_package_ids = [ship_package_dict for sublist in [d['id'] for d in ship_packages] for ship_package_ids = [ship_package_dict for sublist in [d['id'] for d in ship_packages] for
ship_package_dict in sublist] ship_package_dict in sublist]
...@@ -914,7 +930,7 @@ class CcBl(models.Model): ...@@ -914,7 +930,7 @@ class CcBl(models.Model):
self.env.cr.execute(sql) self.env.cr.execute(sql)
self._cr.commit() # 提交事务 self._cr.commit() # 提交事务
self.try_callback_track(max_retries=2, ship_package_ids=ship_package_ids, item.try_callback_track(max_retries=2, ship_package_ids=ship_package_ids,
user_obj=user_obj) user_obj=user_obj)
# 理货或尾程交接的节点 # 理货或尾程交接的节点
# 预先获取所有状态节点 # 预先获取所有状态节点
...@@ -960,9 +976,8 @@ class CcBl(models.Model): ...@@ -960,9 +976,8 @@ class CcBl(models.Model):
self.env.cr.execute(sql) self.env.cr.execute(sql)
self._cr.commit() # 提交事务 self._cr.commit() # 提交事务
self.try_callback_track(max_retries=2, ship_package_ids=ship_package_ids, item.try_callback_track(max_retries=2, ship_package_ids=ship_package_ids,
user_obj=user_obj) user_obj=user_obj)
return True
except Exception as err: except Exception as err:
logging.error('fetch_mail_dlv--error:%s' % str(err)) logging.error('fetch_mail_dlv--error:%s' % str(err))
......
# -*- coding: utf-8 -*-
import logging
from datetime import datetime, timedelta
import time
import hashlib
from odoo import models
from odoo.http import request
__author__ = 'yubo.peng'
_logger = logging.getLogger(__name__)
class AuthenticationError(Exception):
pass
class Http(models.AbstractModel):
_inherit = 'ir.http'
@classmethod
def _auth_method_erp_token(cls):
# 从headers.environ中获取对方传过来的token,timestamp,加密的校验字符串
datas = request.httprequest.headers.environ
if 'HTTP_TOKEN' in datas and 'HTTP_TIMESTAMP' in datas and 'HTTP_CHECKSTR' in datas:
# 从系统参数中获取TOKEt和密钥
factory_secret = request.env['ir.config_parameter'].sudo().get_param('erp_token')
erp_secret_key = request.env['ir.config_parameter'].sudo().get_param('erp_secret_key')
# 从系统参数获取是否时间校验,-1为不校验,其他为校验
check_timeout = int(request.env['ir.config_parameter'].sudo().get_param('check_timeout') or 5)
if not factory_secret:
raise AuthenticationError('系统中未设置ERP TOKEN')
if datas['HTTP_TOKEN'] != factory_secret:
raise AuthenticationError('无效的token')
if check_timeout > 0:
post_time = int(datas['HTTP_TIMESTAMP'])
datetime_post = datetime.fromtimestamp(post_time)
datetime_now = datetime.now().replace(microsecond=0)
datetime_del = datetime_now + timedelta(seconds=check_timeout)
if datetime_post > datetime_del:
raise AuthenticationError('请求已过期')
# 获得sha1_str加密字符串
check_str = '%s%s%s' % (datas['HTTP_TOKEN'], datas['HTTP_TIMESTAMP'], erp_secret_key)
check_crm_str = hashlib.sha1(check_str.encode('utf-8')).hexdigest()
if check_crm_str.upper() != datas['HTTP_CHECKSTR'].upper():
raise AuthenticationError('数据校验不通过')
else:
raise AuthenticationError('请求参数中无token')
# -*- coding: utf-8 -*-
from odoo import models, api, _
from collections import defaultdict
from odoo.exceptions import AccessError
class IrAttachment(models.Model):
_inherit = "ir.attachment"
# override
@api.model
def check(self, mode, values=None):
""" Restricts the access to an ir.attachment, according to referred mode """
if self.env.is_superuser():
return True
# Always require an internal user (aka, employee) to access to a attachment
if not (self.env.is_admin() or self.env.user.has_group('base.group_user')):
raise AccessError(
_("Sorry, you are not allowed to access this document."))
# collect the records to check (by model)
model_ids = defaultdict(set) # {model_name: set(ids)}
if self:
# DLE P173: `test_01_portal_attachment`
self.env['ir.attachment'].flush(['res_model', 'res_id', 'create_uid', 'public', 'res_field'])
self._cr.execute('SELECT res_model, res_id, create_uid, public, res_field FROM ir_attachment WHERE id IN %s', [tuple(self.ids)])
for res_model, res_id, create_uid, public, res_field in self._cr.fetchall():
if public and mode == 'read':
continue
if not (res_model and res_id):
continue
model_ids[res_model].add(res_id)
if values and values.get('res_model') and values.get('res_id'):
model_ids[values['res_model']].add(values['res_id'])
# check access rights on the records
for res_model, res_ids in model_ids.items():
# ignore attachments that are not attached to a resource anymore
# when checking access rights (resource was deleted but attachment
# was not)
if res_model not in self.env:
continue
if res_model == 'res.users' and len(res_ids) == 1 and self.env.uid == list(res_ids)[0]:
# by default a user cannot write on itself, despite the list of writeable fields
# e.g. in the case of a user inserting an image into his image signature
# we need to bypass this check which would needlessly throw us away
continue
records = self.env[res_model].browse(res_ids).exists()
# For related models, check if we can write to the model, as unlinking
# and creating attachments can be seen as an update to the model
access_mode = 'write' if mode in ('create', 'unlink') else mode
records.check_access_rights(access_mode)
records.check_access_rule(access_mode)
@api.model
def read_as_sudo(self, domain=None, fields=None):
return self.sudo().search_read(domain, fields)
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
# 本地 # 本地
db_ip = "127.0.0.1" db_ip = "127.0.0.1"
db_port = "8069" db_port = "8069"
db_name = "hhccs_test" db_name = "hhccs324"
db_user = "admin" db_user = "admin"
db_password = "admin" db_password = "admin"
...@@ -13,13 +13,6 @@ redis_options = dict( ...@@ -13,13 +13,6 @@ redis_options = dict(
db=0 db=0
) )
postgresql_options = dict(
host="127.0.0.1",
port=5431,
database="hh_ccs_test",
user="odoo14",
password="qq166349",
)
# 测试 # 测试
# db_ip = "121.199.167.133" # db_ip = "121.199.167.133"
......
...@@ -37,7 +37,11 @@ class Order_dispose(object): ...@@ -37,7 +37,11 @@ class Order_dispose(object):
bl_obj = self.odoo_db.env['cc.bl'] bl_obj = self.odoo_db.env['cc.bl']
if action_type and not utc_time: if action_type and not utc_time:
bl_obj = self.pda_odoo_db.env['cc.bl'] bl_obj = self.pda_odoo_db.env['cc.bl']
bl_record = bl_obj.browse(data['id']) bl_ids = data.get('ids')
if bl_ids:
bl_record = bl_obj.browse(bl_ids)
else:
bl_record = bl_obj.browse(data['id'])
# utc_time = datetime.strptime(data['utc_time'], "%Y-%m-%d %H:%M:%S") # utc_time = datetime.strptime(data['utc_time'], "%Y-%m-%d %H:%M:%S")
utc_time = data.get('utc_time') utc_time = data.get('utc_time')
user_login = data.get('user_login') user_login = data.get('user_login')
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论