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

Merge branch 'release/2.8.0'

差异被折叠。
import random
from odoo import models, fields, api
......@@ -21,7 +20,7 @@ class CCExceptionInfo(models.Model):
# 异常编码增加唯一约束
_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:
......
......@@ -10,23 +10,70 @@ class CCLastMileProvider(models.Model):
def _check_matching_value(self):
for record in self:
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):
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) # 简称
tape_color_value = fields.Char(string='Tape Color Value') # 胶带色值
active = fields.Boolean('Active', default=True) # 有效☑️
matching_value = fields.Text(string='Matching Value') # 尾程服务商匹配值
placement_area = fields.Char('Placement Area') # 摆放区域,英文
def match_provider(self, provider_name):
"""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_value 包含 provider_name
matching_records = self.sudo().search([])
# 检查是否有记录的 matching_value 包含 provider_name(不区分大小写)
for record in matching_records:
if provider_name in record.matching_value.split('\n'):
return record # 返回找到的记录
if record.matching_value:
# 将匹配值转换为小写进行比较
matching_values = [value.lower() for value in record.matching_value.split('\n')]
if provider_name_lower in matching_values:
return record # 返回找到的记录
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):
_name = 'common.common'
_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):
"""获取Odoo时区的时间
Args:
......@@ -30,8 +40,10 @@ class CommonCommon(models.Model):
if not user_obj:
user_obj = self.env.user
user_tz = user_obj.tz or 'UTC'
timezone_offset = self.env['common.common'].sudo().get_time_zone(user_tz)
local_time = local_time + datetime.timedelta(hours=int(timezone_offset))
timezone_offset = self.env['common.common'].sudo(
).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
except Exception as e:
# 如果出现任何错误,返回UTC时间
......@@ -51,8 +63,10 @@ class CommonCommon(models.Model):
if not user_obj:
user_obj = self.env.user
user_tz = user_obj.tz or 'UTC'
timezone_offset = self.env['common.common'].sudo().get_time_zone(user_tz)
local_time = local_time + datetime.timedelta(hours=int(timezone_offset))
timezone_offset = self.env['common.common'].sudo(
).get_time_zone(user_tz)
local_time = local_time + \
datetime.timedelta(hours=int(timezone_offset))
local_tz = pytz.timezone(user_tz)
# 确保时间是本地时区
if local_time.tzinfo is None:
......
......@@ -12,7 +12,10 @@ class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
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
def get_values(self):
......@@ -24,10 +27,9 @@ class ResConfigSettings(models.TransientModel):
config = self.env['ir.config_parameter'].sudo()
before_min = config.get_param('before_min', default=10)
package_scan_min = config.get_param('package_scan_min', default=10)
values.update(
before_min=before_min,
package_scan_min=package_scan_min,
package_scan_min=package_scan_min
)
return values
......
......@@ -7,11 +7,13 @@
<sheet>
<group>
<group>
<field name="logo" widget="image" class="oe_avatar"/>
<field name="name"/> <!-- 快递名称 -->
<field name="abbreviation"/> <!-- 简称 -->
<field name="tape_color_value" widget="color" required="1"/> <!-- 胶带色值 -->
<field name="matching_value"
placeholder="Multiple entries can be made, one matching value per line"/> <!-- 尾程服务商匹配值 -->
<field name="placement_area"/>
</group>
<group>
<field name="active"/> <!-- 有效☑️ -->
......@@ -31,6 +33,7 @@
<field name="abbreviation"/> <!-- 简称 -->
<field name="tape_color_value"/> <!-- 胶带色值 -->
<field name="matching_value"/> <!-- 尾程服务商匹配值 -->
<field name="placement_area"/> <!-- 摆放区域 -->
<field name="active"/> <!-- 有效☑️ -->
</tree>
</field>
......@@ -43,6 +46,7 @@
<search string="Last Mile Provider">
<field name="name"/>
<field name="abbreviation"/>
<field name="placement_area"/>
</search>
</field>
</record>
......
......@@ -27,6 +27,11 @@
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_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">
<label for="package_scan_min"/>
<field name="package_scan_min"/>
......
from . import tt_controllers
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)
......@@ -6,6 +6,8 @@ from . import res_config_setting
from . import ao_tt_api_log
from . import cc_node
from . import cc_bill_loading
from . import ir_attachment
from . import http
# -*- coding: utf-8 -*-
import asyncio
import json
import logging
import ssl
from datetime import timedelta, datetime
import json
import aiohttp
import certifi
import pytz
......@@ -280,7 +281,7 @@ class CcBl(models.Model):
bl = self.env['cc.bl'].sudo().search(
[('bl_no', '=', master_waybill_no), ('create_date', '>=', date)], limit=1)
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'),
'transport_tool_code': kws.get('transport_code'),
'transport_tool_name': kws.get('transport_name'),
......@@ -367,6 +368,7 @@ class CcBl(models.Model):
"""
根据小包的状态修改提单关务状态以及生成同步日志
:param package_state_obj:小包更新后的状态
:param user_obj:
:return:
"""
# 根据小包的状态找到对应的提单关务状态
......@@ -540,7 +542,12 @@ class CcBl(models.Model):
# 根据小包状态更新提单关务状态
try:
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:
logging.info('change_customs_state_by_ship_package error:%s' % e)
return is_ok
......@@ -606,19 +613,22 @@ class CcBl(models.Model):
# package_order = self.env['cc.ship.package'].sudo().browse(package_id)
if response_data['code'] != 0:
# 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']
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
else:
# 回传成功
update_true_arr.append(package_id) # 更新 is_sync为 True
state_arr = package_node_result_dict.get(state, [])
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']
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:
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
......@@ -813,13 +823,18 @@ class CcBl(models.Model):
if is_ok:
# 根据小包状态更新提单关务状态
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:
return is_ok
logging.warning(f"Attempt {i + 1}/{max_retries} failed. Retrying...")
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'):
self = self.with_context(dict(self._context, is_mail=True))
for item in self:
......@@ -831,14 +846,15 @@ class CcBl(models.Model):
item.push_clear_customs_start(before_utc_time)
user_obj = self.env['res.users'].search([('login', '=', mail_db_user)], limit=1)
# 尝试调用 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)
# 再次尝试调用 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.")
else:
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)
ship_package_ids = [ship_package_dict for sublist in [d['id'] for d in ship_packages] for
ship_package_dict in sublist]
......@@ -914,7 +930,7 @@ class CcBl(models.Model):
self.env.cr.execute(sql)
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)
# 理货或尾程交接的节点
# 预先获取所有状态节点
......@@ -960,9 +976,8 @@ class CcBl(models.Model):
self.env.cr.execute(sql)
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)
return True
except Exception as 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 @@
# 本地
db_ip = "127.0.0.1"
db_port = "8069"
db_name = "hhccs_test"
db_name = "hhccs324"
db_user = "admin"
db_password = "admin"
......@@ -13,13 +13,6 @@ redis_options = dict(
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"
......
......@@ -37,7 +37,11 @@ class Order_dispose(object):
bl_obj = self.odoo_db.env['cc.bl']
if action_type and not utc_time:
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 = data.get('utc_time')
user_login = data.get('user_login')
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论