提交 39d19b53 authored 作者: 贺阳's avatar 贺阳

1、把面单号的唯一约束改为物流订单号的唯一约束

2、根据物流订单号判断系统是否已存在,如果存在的话,判断是否已关联提单,如果关联提单且提单没有取消就不进行处理(包裹和包裹下的商品都不修改); 如果不存在,把包裹下的商品明细删除,创建此次推送的商品,同时修改包裹的数据 3、提单已存在,状态是草稿是要传的是update才会修改,提单不存在(包括已取消的提单)要传的是Create才会创建
上级 352656c8
...@@ -2,3 +2,5 @@ from . import res_partner ...@@ -2,3 +2,5 @@ from . import res_partner
from . import cc_node from . import cc_node
from . import cc_bill_loading from . import cc_bill_loading
from . import cc_customs_declaration_order from . import cc_customs_declaration_order
from . import cc_node_exception_reason
...@@ -195,9 +195,9 @@ class CcShipPackage(models.Model): ...@@ -195,9 +195,9 @@ class CcShipPackage(models.Model):
# 物流订单号 # 物流订单号
logistic_order_no = fields.Char(string='Logistic Order No', index=True) logistic_order_no = fields.Char(string='Logistic Order No', index=True)
# 按tracking_no,唯一 # 按logistic_order_no,唯一
_sql_constraints = [ _sql_constraints = [
('tracking_no_uniq', 'unique(tracking_no)', 'The Tracking No must be unique.') ('logistic_order_no_uniq', 'unique(logistic_order_no)', 'The Logistic Order No must be unique.')
] ]
# 运单号(面单号) # 运单号(面单号)
...@@ -320,7 +320,7 @@ class CcShipPackage(models.Model): ...@@ -320,7 +320,7 @@ class CcShipPackage(models.Model):
# 进度状态, 包括已提货、 小包查验、海关放行、小包出库、小包入库、清关失败,包裹交接 # 进度状态, 包括已提货、 小包查验、海关放行、小包出库、小包入库、清关失败,包裹交接
state = fields.Many2one('cc.node', string='Progress state',domain="[('node_type', '=', 'package')]", state = fields.Many2one('cc.node', string='Progress state',domain="[('node_type', '=', 'package')]",
default=lambda self: self.env['cc.node'].search([('node_type','=','package'), ('is_default', '=', True)], limit=1)) default=lambda self: self.env['cc.node'].search([('node_type','=','package'), ('is_default', '=', True)], limit=1))
node_exception_reason_id = fields.Many2one('cc.node.exception.reason', 'Exception Reason')
process_time = fields.Datetime('Process Time(UTC)') process_time = fields.Datetime('Process Time(UTC)')
......
# 导入odoo
import base64
import xlrd
from odoo import models, fields, api, _
# 导入日志
import logging
from odoo.exceptions import UserError
# 获取日志
_logger = logging.getLogger(__name__)
# 增加清关节点对象,继承自models.Model, 用于管理业务数据.业务数据包括节点名称、节点描述、节点顺序、是否必须节点
class CcNodeExceptionReason(models.Model):
_name = 'cc.node.exception.reason'
_description = 'CC Node Exception Reason'
code_id = fields.Many2one('cc.node', 'Node', index=True)
name = fields.Char(string='Name', translate=True)
desc = fields.Char(string='Description', translate=True)
...@@ -3,6 +3,14 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink ...@@ -3,6 +3,14 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_cc_node_base.group_user,cc_node base.group_user,ccs_base.model_cc_node,base.group_user,1,0,0,0 access_cc_node_base.group_user,cc_node base.group_user,ccs_base.model_cc_node,base.group_user,1,0,0,0
access_cc_node_base.group_erp_manager,cc_node base.group_erp_manager,ccs_base.model_cc_node,base.group_erp_manager,1,1,1,1 access_cc_node_base.group_erp_manager,cc_node base.group_erp_manager,ccs_base.model_cc_node,base.group_erp_manager,1,1,1,1
access_cc_node_exception_reason_base.group_user,cc_node_exception_reason base.group_user,ccs_base.model_cc_node_exception_reason,base.group_user,1,0,0,0
access_cc_node_exception_reason_base.group_erp_manager,cc_node_exception_reason base.group_erp_manager,ccs_base.model_cc_node_exception_reason,base.group_erp_manager,1,1,1,1
access_cc_bl_base.group_user,cc_bl base.group_user,ccs_base.model_cc_bl,base.group_user,1,0,0,0 access_cc_bl_base.group_user,cc_bl base.group_user,ccs_base.model_cc_bl,base.group_user,1,0,0,0
access_cc_bl_base.group_erp_manager,cc_bl base.group_erp_manager,ccs_base.model_cc_bl,base.group_erp_manager,1,1,1,1 access_cc_bl_base.group_erp_manager,cc_bl base.group_erp_manager,ccs_base.model_cc_bl,base.group_erp_manager,1,1,1,1
access_cc_bl_ccs_base.group_clearance_of_customs_manager,cc_bl ccs_base.group_clearance_of_customs_manager,ccs_base.model_cc_bl,ccs_base.group_clearance_of_customs_manager,1,1,1,1 access_cc_bl_ccs_base.group_clearance_of_customs_manager,cc_bl ccs_base.group_clearance_of_customs_manager,ccs_base.model_cc_bl,ccs_base.group_clearance_of_customs_manager,1,1,1,1
......
...@@ -106,7 +106,8 @@ ...@@ -106,7 +106,8 @@
<field name="internal_account_number" string="Internal Account Number"/> <field name="internal_account_number" string="Internal Account Number"/>
<field name="logistic_order_no" string="Logistic Order No"/> <field name="logistic_order_no" string="Logistic Order No"/>
<field name="buyer_region" string="Buyer Region"/> <field name="buyer_region" string="Buyer Region"/>
<field name="node_exception_reason_id" readonly="1"
attrs="{'invisible':[('node_exception_reason_id','=',False)]}"/>
</group> </group>
<group string="CC info"> <group string="CC info">
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, api, fields from odoo import models, api, fields
from odoo.exceptions import Warning,ValidationError from odoo.exceptions import Warning, ValidationError
PUSH_TYPE = [ PUSH_TYPE = [
# ('轨迹揽收', '轨迹揽收'), # ('轨迹揽收', '轨迹揽收'),
...@@ -47,6 +47,8 @@ class BatchInputShipPackageStatusWizard(models.TransientModel): ...@@ -47,6 +47,8 @@ class BatchInputShipPackageStatusWizard(models.TransientModel):
process_time = fields.Datetime('Process Time(UTC)') process_time = fields.Datetime('Process Time(UTC)')
# 添加状态说明字段 # 添加状态说明字段
state_explain = fields.Text('State Explain', help='State Explain') state_explain = fields.Text('State Explain', help='State Explain')
node_exception_reason_id = fields.Many2one('cc.node.exception.reason', 'Exception Reason',
domain="[('code_id', '=', update_status)]")
# 批量更新小包状态 # 批量更新小包状态
def submit(self): def submit(self):
...@@ -57,9 +59,15 @@ class BatchInputShipPackageStatusWizard(models.TransientModel): ...@@ -57,9 +59,15 @@ class BatchInputShipPackageStatusWizard(models.TransientModel):
if not parcels: if not parcels:
raise ValidationError('没有找到要更新的小包.') raise ValidationError('没有找到要更新的小包.')
# 判断异常状态是否选择了异常原因
reason_obj = self.env['cc.node.exception.reason'].search([('code_id', '=', self.update_status.id)])
if reason_obj and not self.node_exception_reason_id:
raise ValidationError('请选择异常原因!')
# 更新状态 # 更新状态
parcels.write( parcels.write(
{'state': self.update_status.id, 'process_time': self.process_time, 'state_explain': self.state_explain}) {'state': self.update_status.id, 'node_exception_reason_id': self.node_exception_reason_id.id,
'process_time': self.process_time, 'state_explain': self.state_explain})
# 跳转显示本次更新状态的小包 # 跳转显示本次更新状态的小包
return { return {
...@@ -89,6 +97,6 @@ class BatchInputShipPackageStatusWizard(models.TransientModel): ...@@ -89,6 +97,6 @@ class BatchInputShipPackageStatusWizard(models.TransientModel):
if self.exclude_tracking_no: if self.exclude_tracking_no:
exclude_waybill_no_list = self.exclude_tracking_no.split('\n') exclude_waybill_no_list = self.exclude_tracking_no.split('\n')
exclude_waybill_no_list = [i.strip() for i in exclude_waybill_no_list if i.strip()] exclude_waybill_no_list = [i.strip() for i in exclude_waybill_no_list if i.strip()]
parcels = parcels.filtered(lambda r: r.waybill_no not in exclude_waybill_no_list) parcels = parcels.filtered(lambda r: r.tracking_no not in exclude_waybill_no_list)
# 排除状态 # 排除状态
return parcels return parcels
...@@ -21,14 +21,14 @@ ...@@ -21,14 +21,14 @@
<separator/> <separator/>
<field name="update_status" required="1" domain="[('node_type','=','package')]"/> <field name="update_status" required="1" domain="[('node_type','=','package')]"/>
<field name="process_time" required="1" string="Process Time(UTC)"/> <field name="process_time" required="1" string="Process Time(UTC)"/>
<field name="node_exception_reason_id" options="{'no_create':True}"/>
<field name="state_explain"/> <field name="state_explain"/>
</group> </group>
<group> <group>
<field name="is_ok"/> <field name="is_ok"/>
</group> </group>
<footer> <footer>
<button name="submit" type="object" string="Submit" class="oe_highlight" attrs="{'invisible':[('is_ok','=','false')]}"/> <button name="submit" type="object" string="Submit" class="oe_highlight" attrs="{'invisible':[('is_ok','=',False)]}"/>
<button string="Close" special="cancel"/> <button string="Close" special="cancel"/>
</footer> </footer>
</sheet> </sheet>
......
...@@ -112,9 +112,12 @@ class TTApi(http.Controller): ...@@ -112,9 +112,12 @@ class TTApi(http.Controller):
for package in packages: for package in packages:
try: try:
tracking_no = package.get('tracking_no') # 根据物流订单号判断是否已存在,已存在的话就更新
logistic_order_no = package.get('provider_order_id')
ship_pachage_obj = request.env['cc.ship.package'].sudo().search( ship_pachage_obj = request.env['cc.ship.package'].sudo().search(
[('tracking_no', '=', tracking_no)]) [('logistic_order_no', '=', logistic_order_no)])
if (ship_pachage_obj and (not ship_pachage_obj.bl_id or
ship_pachage_obj.bl_id.is_cancel)) or not ship_pachage_obj:
ship_package = dict(logistic_order_no=package.get('provider_order_id'), ship_package = dict(logistic_order_no=package.get('provider_order_id'),
tracking_no=package.get('tracking_no'), tracking_no=package.get('tracking_no'),
customer_ref=package.get('declaretion_bill_id'), customer_ref=package.get('declaretion_bill_id'),
...@@ -124,7 +127,8 @@ class TTApi(http.Controller): ...@@ -124,7 +127,8 @@ class TTApi(http.Controller):
trade_no=package.get('order_no'), trade_no=package.get('order_no'),
# 需要将时间搓转换为时间 # 需要将时间搓转换为时间
operation_time=fields.Datetime.from_string(datetime.fromtimestamp( operation_time=fields.Datetime.from_string(datetime.fromtimestamp(
int(package.get('operate_time')) / 1000).strftime('%Y-%m-%d %H:%M:%S')), int(package.get('operate_time')) / 1000).strftime(
'%Y-%m-%d %H:%M:%S')),
big_package_no=package.get('big_bag_no'), big_package_no=package.get('big_bag_no'),
container_no=package.get('container_no'), container_no=package.get('container_no'),
buyer_region=package.get('buyer_region'), buyer_region=package.get('buyer_region'),
...@@ -132,11 +136,15 @@ class TTApi(http.Controller): ...@@ -132,11 +136,15 @@ class TTApi(http.Controller):
sender_name=package.get('sender_info').get('name'), sender_name=package.get('sender_info').get('name'),
sender_vat_no=package.get('sender_info').get('shipping_tax_id'), sender_vat_no=package.get('sender_info').get('shipping_tax_id'),
sender_phone=package.get('sender_info').get('phone'), sender_phone=package.get('sender_info').get('phone'),
sender_country=package.get('sender_info').get('address').get('address_l0'), sender_country=package.get('sender_info').get('address').get(
sender_state=package.get('sender_info').get('address').get('address_l1'), 'address_l0'),
sender_state=package.get('sender_info').get('address').get(
'address_l1'),
sender_city=package.get('sender_info').get('address').get('address_l2'), sender_city=package.get('sender_info').get('address').get('address_l2'),
sender_add_1=package.get('sender_info').get('address').get('address_l3'), sender_add_1=package.get('sender_info').get('address').get(
sender_add_2=package.get('sender_info').get('address').get('address_l4'), 'address_l3'),
sender_add_2=package.get('sender_info').get('address').get(
'address_l4'),
sender_add_3=package.get('sender_info').get('address').get('details'), sender_add_3=package.get('sender_info').get('address').get('details'),
sender_postcode=package.get('sender_info').get('postcode'), sender_postcode=package.get('sender_info').get('postcode'),
receiver_name=package.get('receiver_info').get('name'), receiver_name=package.get('receiver_info').get('name'),
...@@ -148,7 +156,8 @@ class TTApi(http.Controller): ...@@ -148,7 +156,8 @@ class TTApi(http.Controller):
'address_l1'), 'address_l1'),
receiver_add_3=package.get('receiver_info').get('address').get( receiver_add_3=package.get('receiver_info').get('address').get(
'address_l2'), 'address_l2'),
receiver_city=package.get('receiver_info').get('address').get('address_l3'), receiver_city=package.get('receiver_info').get('address').get(
'address_l3'),
receiver_county=package.get('receiver_info').get('address').get( receiver_county=package.get('receiver_info').get('address').get(
'address_l4'), 'address_l4'),
receiver_vat_no=package.get('receiver_info').get('tax_id'), receiver_vat_no=package.get('receiver_info').get('tax_id'),
...@@ -189,7 +198,7 @@ class TTApi(http.Controller): ...@@ -189,7 +198,7 @@ class TTApi(http.Controller):
item_link=item.get('item_url'), item_link=item.get('item_url'),
item_tax_status=item.get('tax_mark')) item_tax_status=item.get('tax_mark'))
_logger.info('package_good:%s' % package_good) _logger.info('package_good:%s' % package_good)
if not package_good_obj and package_good: if package_good:
package_vals.append((0, 0, package_good)) package_vals.append((0, 0, package_good))
_logger.info('package_vals:%s' % package_vals) _logger.info('package_vals:%s' % package_vals)
if package_vals and len(package_vals) > 0: if package_vals and len(package_vals) > 0:
...@@ -198,9 +207,10 @@ class TTApi(http.Controller): ...@@ -198,9 +207,10 @@ class TTApi(http.Controller):
# 用ship_package生成cc.ship.package # 用ship_package生成cc.ship.package
request.env['cc.ship.package'].sudo().create(ship_package) request.env['cc.ship.package'].sudo().create(ship_package)
else: else:
for good in ship_pachage_obj.good_ids:
good.unlink()
ship_pachage_obj.write(ship_package) ship_pachage_obj.write(ship_package)
request._cr.commit() request._cr.commit()
except Exception as e_package: except Exception as e_package:
res['data']['all_result'] = False res['data']['all_result'] = False
res['data']['failed_provider_order_ids'].append(package.get('provider_order_id')) res['data']['failed_provider_order_ids'].append(package.get('provider_order_id'))
...@@ -239,10 +249,6 @@ class TTApi(http.Controller): ...@@ -239,10 +249,6 @@ class TTApi(http.Controller):
kws = json.loads(param_json) kws = json.loads(param_json)
# 分析mawb_info中的内容,生成cc.bl和cc.big.package,并与cc.ship.package进行关联 # 分析mawb_info中的内容,生成cc.bl和cc.big.package,并与cc.ship.package进行关联
mawb_info = kws.get('mwb_info') mawb_info = kws.get('mwb_info')
# 检查提单是否已经存在,且未被取消,如果存在则不再生成
bl = request.env['cc.bl'].sudo().search(
[('bl_no', '=', kws.get('master_waybill_no')), ('is_cancel', '=', False)], limit=1)
if not bl:
# 生成cc.bl # 生成cc.bl
bl_vals = dict(bl_no=kws.get('master_waybill_no'), bl_vals = dict(bl_no=kws.get('master_waybill_no'),
customs_bl_no=kws.get('customs_waybill_id'), customs_bl_no=kws.get('customs_waybill_id'),
...@@ -259,9 +265,16 @@ class TTApi(http.Controller): ...@@ -259,9 +265,16 @@ class TTApi(http.Controller):
etd=mawb_info.get('etd'), etd=mawb_info.get('etd'),
eta=mawb_info.get('eta'), eta=mawb_info.get('eta'),
customer_id=request.env["ir.config_parameter"].sudo().get_param( customer_id=request.env["ir.config_parameter"].sudo().get_param(
'tt_customer_id') or False) # 增加客户信息 'tt_customer_id') or False)
# 检查提单是否已经存在,且未被取消,如果存在则不再生成
bl = request.env['cc.bl'].sudo().search(
[('bl_no', '=', kws.get('master_waybill_no')), ('is_cancel', '=', False)], limit=1)
if not bl:
if kws.get('declare_type') == 'Create':
bl = request.env['cc.bl'].sudo().create(bl_vals) bl = request.env['cc.bl'].sudo().create(bl_vals)
else:
if kws.get('declare_type') == 'Update' and bl.state == 'draft':
bl.write(bl_vals)
# 生成cc.big.package # 生成cc.big.package
big_bag_list = kws.get('big_bag_list') big_bag_list = kws.get('big_bag_list')
if big_bag_list and len(big_bag_list) > 0: if big_bag_list and len(big_bag_list) > 0:
......
...@@ -30,7 +30,7 @@ def get_utc_time(local_time=None): ...@@ -30,7 +30,7 @@ def get_utc_time(local_time=None):
# 将本地时间转换为UTC时间 # 将本地时间转换为UTC时间
utc_time = local_time.astimezone(pytz.utc) utc_time = local_time.astimezone(pytz.utc)
# 格式化为RFC 3339格式 # 格式化为RFC 3339格式
#rfc3339_time = utc_time.isoformat(timespec='seconds') # rfc3339_time = utc_time.isoformat(timespec='seconds')
return utc_time.strftime('%Y-%m-%d %H:%M:%S') return utc_time.strftime('%Y-%m-%d %H:%M:%S')
...@@ -127,6 +127,16 @@ class CcShipPackage(models.Model): ...@@ -127,6 +127,16 @@ class CcShipPackage(models.Model):
# 增加同步日志纪录字段 # 增加同步日志纪录字段
sync_log_ids = fields.One2many('cc.ship.package.sync.log', 'package_id', 'Sync Logs') sync_log_ids = fields.One2many('cc.ship.package.sync.log', 'package_id', 'Sync Logs')
@api.model
def create(self, vals_list):
"""
第一个节点的时候 默认已同步
"""
obj = super(CcShipPackage, self).create(vals_list)
if obj.state.is_default:
obj.is_sync = True
return obj
def action_sync(self): def action_sync(self):
for record in self: for record in self:
record.is_sync = True record.is_sync = True
...@@ -146,6 +156,7 @@ class CcShipPackage(models.Model): ...@@ -146,6 +156,7 @@ class CcShipPackage(models.Model):
"time_zone": "UTC+0", "time_zone": "UTC+0",
"action_code": self.state.tk_code, "action_code": self.state.tk_code,
"operation_desc": self.state.desc, "operation_desc": self.state.desc,
"reason_code": self.node_exception_reason_id.name or "" # 异常原因
} }
] ]
} }
...@@ -157,6 +168,7 @@ class CcShipPackage(models.Model): ...@@ -157,6 +168,7 @@ class CcShipPackage(models.Model):
tt_api_obj = self.env["ao.tt.api"].sudo() tt_api_obj = self.env["ao.tt.api"].sudo()
response = tt_api_obj.callback_track(data) response = tt_api_obj.callback_track(data)
response_data = response.json() response_data = response.json()
logging.info('callback_track response:%s' % response)
if response_data['code'] != 0: if response_data['code'] != 0:
# 清关文件回传错误 # 清关文件回传错误
self.is_sync = False self.is_sync = False
...@@ -178,6 +190,7 @@ class CcShipPackage(models.Model): ...@@ -178,6 +190,7 @@ class CcShipPackage(models.Model):
self.env['ao.tt.api.log'].create_api_log(self.tracking_no or '', '', '', 0, request_id, source='推出') self.env['ao.tt.api.log'].create_api_log(self.tracking_no or '', '', '', 0, request_id, source='推出')
return '' return ''
# 继承提单对象 # 继承提单对象
class CcBl(models.Model): class CcBl(models.Model):
_inherit = 'cc.bl' _inherit = 'cc.bl'
...@@ -195,12 +208,9 @@ class CcBl(models.Model): ...@@ -195,12 +208,9 @@ class CcBl(models.Model):
else: else:
record.unsync_package_count = 0 record.unsync_package_count = 0
# 定义一个方法, 获取提单下的所有未同步的小包,并回传小包状态 # 定义一个方法, 获取提单下的所有未同步的小包,并回传小包状态
def callback_track(self): def callback_track(self):
ship_packages = self.env['cc.ship.package'].search([('bl_id', '=', self.id), ('is_sync', '=', False)]) ship_packages = self.env['cc.ship.package'].search([('bl_id', '=', self.id), ('is_sync', '=', False)])
for package in ship_packages: for package in ship_packages:
package.callback_track() package.callback_track()
return True return True
...@@ -50,6 +50,7 @@ class TT(models.Model): ...@@ -50,6 +50,7 @@ class TT(models.Model):
timestamp = int(time.time()) timestamp = int(time.time())
sign = self.generate_sign(timestamp, push_data) sign = self.generate_sign(timestamp, push_data)
response = self.get_response(url, sign, timestamp, push_data) response = self.get_response(url, sign, timestamp, push_data)
logging.info('callback_track response:%s' % response)
return response return response
def package_invoice_query(self, push_data): def package_invoice_query(self, push_data):
......
# -*- coding: utf-8 -*-
{
'name': 'Attachment Preview',
'version': '16.0.5',
'sequence': 1,
'category': 'Services/Tools',
'summary': """This module adds a new widget, "many2many_attachment_preview", which enables the user to view attachments without downloading them.""",
'description': """ User can preview a document without downloading. """,
'author': 'Odox Softhub',
'price': 15,
'currency': 'USD',
'website': 'https://www.odoxsofthub.com',
'support': 'support@odoxsofthub.com',
'license': 'LGPL-3',
'assets': {
'web.assets_backend': [
'odx_m2m_attachment_preview/static/src/js/widget.js',
'odx_m2m_attachment_preview/static/src/scss/style.scss',
'odx_m2m_attachment_preview/static/src/xml/widget_view.xml',
],
},
'installable': True,
'application': True,
'auto_install': False,
'images': ['static/description/thumbnail.gif'],
}
<section class="oe_container">
<section class="oe_spaced mw-100 card module-index_mobile_main_section">
<div class="container shadow-sm px-5 text-center module-index_mobile_main_section"
style="padding-right:0px !important;padding-left:0px !important;">
<section class="oe_container mb-4 mt-4">
<div class="row">
<div class="col-lg-12 d-flex flex-column justify-content-center">
<h1 class="text-center"
style="font-family:sans-serif; font-weight: 800; color:#0a1e2c;">
Attachment Preview</h1>
<hr style="background-color: #C5982C; border: 2px solid #C5982C; width: 100px;" ;>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12" style="padding: 0px 0px 0px 0px !important;">
<h3 class="panel-title" style="font-size: 18px;line-height: 32px;color: #333333;">
Features
</h3>
<p>
<ul style="font-size:20px;text-align:left;">
<br>
<li>
Added new widget 'many2many_attachment_preview'.
</li>
<li>
User can preview a document without downloading.
</li>
</ul>
</p>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12" style="padding: 0px 0px 0px 0px !important;">
<h3 class="panel-title" style="font-size: 18px;line-height: 32px;color: #333333;">
Look how it to use.
</h3>
<div class="oe_demo oe_picture oe_screenshot mobile-index-img-div img-m2m-field"
style="max-height:100% !important;">
<img class="index-image" src="m2m_py.png" style="height:100%;">
</div>
<div class="oe_demo oe_picture oe_screenshot mobile-index-img-div img-m2m-field"
style="max-height:100% !important;">
<img class="index-image" src="m2m_xml.png" style="height:100%;">
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12" style="padding: 0px 0px 0px 0px !important;">
<h3 class="panel-title" style="font-size: 18px;line-height: 32px;color: #333333;">
Preview will be showing on the screen.
</h3>
<div class="oe_demo oe_picture oe_screenshot mobile-index-img-div"
style="max-height:100% !important;">
<img class="index-image" src="m2m_widget.gif" style="height:100%;">
</div>
<br/>
<div class="oe_demo oe_picture oe_screenshot mobile-index-img-div"
style="max-height:100% !important;">
<img class="index-image" src="next_button.png" style="height:100%;">
</div>
</div>
</div>
</section>
<section class="oe_container" style="padding: 2rem 3rem 1rem;margin-top:5px;">
<section class="oe_container mb-4 mt-4">
<div class="panel panel-primary">
<a href="https://apps.odoo.com/apps/modules/browse?search=odx" target="_blank">
<div class="panel-heading">
<h3 class="panel-title" style="font-size: 26px;line-height: 32px;color: ##0f0b0b;">
Other Apps
</h3>
</div>
</a>
</div>
</section>
</section>
<section class="container" style="margin: 5rem auto 2rem; background-color: #fff !important;">
<div class="row" style="max-width:1540px;">
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center mb-4">
<hr class="position-absolute"
style="border: 1px solid #c4c6cc !important; width: 40% !important; z-index: 0 !important;margin-top:40px;">
<h2
style="font-size: 26px;background-color: #fff !important; z-index: 1 !important; padding: 0 1rem !important;">
Help & Support</h2>
</div>
</div>
<div class="row d-flex justify-content-center align-items-center"
style="max-width:1540px; margin: 0 auto 2rem auto;">
<div class="col-lg-12" style="padding: 0rem 3rem 2rem; border-radius: 10px;">
<div class="row mt-4 d-flex justify-content-center align-items-center">
<div class="col-lg-4">
<a href="mailto:support@odoxsofthub.com<" target="_blank"
class="btn btn-block mb-2 deep_hover"
style="text-decoration: none; background-color: #4d4d4d; color: #C5982C; border-radius: 4px;"><i
class="fa fa-envelope mr-2"></i>support@odoxsofthub.com</a>
</div>
</div>
</div>
</div>
</section>
<section class="oe_container" style="padding: 2rem 3rem 1rem; background-color: #fff !important;">
<div class="row" style="max-width:1540px; margin: 0 auto; margin-right: 3rem; ">
<div class="col-lg-12 d-flex justify-content-center align-items-center">
<a href="https://www.odoxsofthub.com" target="_blank">
<img src="logo.png" alt="Odoxsofthub.com">
</a>
</div>
</div>
</section>
</div>
</section>
</section>
/** @odoo-module **/
import { Many2ManyBinaryField } from "@web/views/fields/many2many_binary/many2many_binary_field";
import { registry } from "@web/core/registry";
import { useX2ManyCrud } from "@web/views/fields/relational_utils";
import { useService } from "@web/core/utils/hooks";
import { patch } from "web.utils";
patch(Many2ManyBinaryField.prototype, "odx_m2m_attachment_preview", {
setup() {
this.orm = useService("orm");
this.notification = useService("notification");
this.operations = useX2ManyCrud(() => this.props.value, true);
},
onFilePreview: function (fileID) {
var fileClass = '.attachment-preview-'+ fileID;
var clickedElement = $(fileClass);
var mimetype = clickedElement.attr('data-mimetype');
var fileURL = clickedElement.attr('data-url');
if (mimetype == 'image/jpeg') { this._previewImage(fileID) }
else if (mimetype == 'image/jpg') { this._previewImage(fileID)}
else if (mimetype == 'video/mp4') { this._previewVideo(fileID) }
else if (mimetype == 'image/png') { this._previewImage(fileID) }
else if (mimetype == 'application/pdf') { this._previewPDF(fileID) }
else { this._downloadFile(fileURL) }
},
_downloadFile: function (fileURL) {
var a = document.createElement('a'); a.href = fileURL; a.download = fileURL; a.style.display = 'none'; document.body.appendChild(a); a.click(); document.body.removeChild(a);
},
_previewImage: function (fileID){
var imgName = '';
var modalHtml = '<div class="modal-img"><div class="row"><div class="col"></div><div style="text-align:right;" class="col"><span style="margin-left:auto;"><a id="OrderImgDownloadLink" href="/web/content/' + fileID + '" download="/web/content/'+imgName+'"><i class="fa fa-fw fa-download" style="color:#ffffffdb !important;" role="img" aria-label="Download"></i><span style="color:#ffffffdb !important;">Download</span></a><a style="color:#ffffffdb !important;margin-left:15px;" class="close-img">&times;</a></span></div></div><div class="row modal-img-m2m" style="height:90%;"><img class="preview-img-order" id="m2m-zoom-image" src="/web/content/' + fileID + '"/></div><div class="row m2m-zoom-buttons"><div class="col-12" style="text-align:center;"><button id="m2m-prev-btn" title="Previous"><i class="fa fa-arrow-left"/></button><button title="Zoom In" id="m2m-zoom-in"><i class="fa fa-fw fa-plus" role="img" aria-label="Zoom In"></i></button><button title="Zoom Out" id="m2m-zoom-out"><i class="fa fa-fw fa-minus" role="img" aria-label="Zoom Out"></i></button><a href="/web/content/' + fileID + '" download="/web/content/'+imgName+'"><button title="Download" id="m2m-download-btn"><i class="fa fa-fw fa-download" role="img" aria-label="Download"></i></button></a><a target="_blank" href="/web/content/' + fileID + '"><button title="Open in New Tab" id="m2m-download-btn"><i class="fa fa-fw fa-external-link" role="img" aria-label="Open in New Tab"></i></button></a><button id="m2m-next-btn" title="Next" ><i class="fa fa-arrow-right"/></button></div></div></div>';
$('body').append(modalHtml);
$('.modal-img').show();
$('.close-img').click(function() { $('.modal-img').remove(); });
$('.modal-img').click(function(event) { if ($(event.target).hasClass('modal-img-m2m')) { $('.modal-img').remove(); } });
var zoomValue = 100;
var zoomIncrement = 10;
var minZoom = 50;
var maxZoom = 200;
var self = this;
var zoomImage = document.getElementById('m2m-zoom-image');
var zoomInButton = document.getElementById('m2m-zoom-in');
var zoomOutButton = document.getElementById('m2m-zoom-out');
var nextButton = document.getElementById('m2m-next-btn');
var prevButton = document.getElementById('m2m-prev-btn');
function updateZoomLevel() { zoomImage.style.transform = 'scale(' + (zoomValue / 100) + ')'; }
function handleZoomIn() { if (zoomValue < maxZoom) { zoomValue += zoomIncrement; updateZoomLevel(); } }
function handleZoomOut() { if (zoomValue > minZoom) { zoomValue -= zoomIncrement; updateZoomLevel(); } }
zoomInButton.addEventListener('click', handleZoomIn);
zoomOutButton.addEventListener('click', handleZoomOut);
nextButton.addEventListener('click', handlenextButton);
prevButton.addEventListener('click', handleprevButton);
function handlenextButton(){
var records= self.props.value.records;
var recordIds = records.map(record => record.data.id)
var len_record_id = recordIds.length
var index = recordIds.indexOf(fileID);
var nextID = recordIds[index + 1];
$('.modal-img').remove();
if (nextID){
self.onFilePreview(nextID);
}
}
function handleprevButton(){
var records= self.props.value.records;
var recordIds = records.map(record => record.data.id)
var index = recordIds.indexOf(fileID);
var PrevID = recordIds[index - 1];
$('.modal-img').remove();
if (PrevID){
self.onFilePreview(PrevID);
}
}
},
_previewVideo: function (fileID){
var imgName = '';
var modalHtml = '<div class="modal-img"><div class="row"><div class="col"></div><div style="text-align:right;" class="col"><span style="margin-left:auto;"><a id="OrderImgDownloadLink" href="/web/content/' + fileID + '" download="/web/content/'+imgName+'"><i class="fa fa-fw fa-download" style="color:#ffffffdb !important;" role="img" aria-label="Download"></i><span style="color:#ffffffdb !important;">Download</span></a><a style="color:#ffffffdb !important;margin-left:15px;" class="close-img">&times;</a></span></div></div><div class="row modal-img-m2m" style="height:90%;"><video class="odx_video"><source src="/web/content/' + fileID + '" /></video></div><div class="row m2m-zoom-buttons"><div class="col-12" style="text-align:center;"><button id="m2m-prev-btn" title="Previous" ><i class="fa fa-arrow-left"/></button> <button title="Play/Pause" id="m2m-play-pause"><i class="fa fa-fw" id="icon_ply_pause" role="img" aria-label="Play/Pause"></i></button><button title="Backward 5 seconds" id="m2m-backward"><i class="fa fa-fw fa-backward" role="img" aria-label="Backward 5 seconds"></i></button><button title="Forward 5 seconds" id="m2m-forward"><i class="fa fa-fw fa-fast-forward" role="img" aria-label="Forward 5 seconds"></i></button><a href="/web/content/' + fileID + '" download="/web/content/'+imgName+'"><button title="Download" id="m2m-download-btn"><i class="fa fa-fw fa-download" role="img" aria-label="Download"></i></button></a><a target="_blank" href="/web/content/' + fileID + '"><button title="Open in New Tab" id="m2m-download-btn"><i class="fa fa-fw fa-external-link" role="img" aria-label="Open in New Tab"></i></button></a><button id="m2m-next-btn" title="Next" ><i class="fa fa-arrow-right"/></button></div></div></div>';
var self = this;
$(document).ready(function(){
var video = document.querySelector('video');
video.play();
var playPauseButton = document.getElementById('m2m-play-pause');
var forwardButton = document.getElementById('m2m-forward');
var backwardButton = document.getElementById('m2m-backward');
var nextButton = document.getElementById('m2m-next-btn');
var prevButton = document.getElementById('m2m-prev-btn');
playPauseButton.addEventListener('click', function() {
var playPause = document.getElementById('icon_ply_pause');
if (video.paused) {
video.play();
playPause.classList.remove('fa-play');
playPause.classList.add('fa-pause');
} else {
video.pause();
playPause.classList.remove('fa-pause');
playPause.classList.add('fa-play');
}
});
forwardButton.addEventListener('click', function() {
video.currentTime += 5;
});
backwardButton.addEventListener('click', function() {
video.currentTime -= 5;
});
nextButton.addEventListener('click', function() {
var records= self.props.value.records;
var recordIds = records.map(record => record.data.id)
var index = recordIds.indexOf(fileID);
var nextID = recordIds[index + 1];
$('.modal-img').remove();
if (nextID){
self.onFilePreview(nextID);
}
});
prevButton.addEventListener('click', function() {
var records= self.props.value.records;
var recordIds = records.map(record => record.data.id)
var index = recordIds.indexOf(fileID);
var PrevID = recordIds[index - 1];
$('.modal-img').remove();
if (PrevID){
self.onFilePreview(PrevID);
}
});
});
$('body').append(modalHtml);
$('.modal-img').show();
$('.close-img').click(function() { $('.modal-img').remove(); });
$('.modal-img').click(function(event) { if ($(event.target).hasClass('modal-img-m2m')) { $('.modal-img').remove(); } });
$(document).ready(function(){
var playPauseButton = document.getElementById('icon_ply_pause');
var video = document.querySelector('video');
video.play();
playPauseButton.classList.add('fa-pause');
});
},
_previewPDF: function (fileSrc) {
var fileName = '';
var currentPage = 1;
var totalPageCount = 0;
var modalHtml = '<div class="modal-pdf modal-img">' +
'<div class="row">' +
'<div style="text-align:center;" class="col-12">' +
'<span id="pageCount" style="line-height:40px;color:#ffffffdb !important;">'+totalPageCount+' Page</span>' +
'<span style="float:right;">' +
'<a id="OrderPdfDownloadLink" href="/web/content/' + fileSrc + '" download="/web/content/' + fileName + '">' +
'<i class="fa fa-fw fa-download" style="color:#ffffffdb !important;" role="img" aria-label="Download"></i>' +
'<span style="color:#ffffffdb !important;">Download</span></a>' +
'<a style="color:#ffffffdb !important;margin-left:15px;" class="close-pdf">&times;</a></span></div></div>' +
'<div class="pdf-container">' +
'<div id="pdfCanvasContainer"></div>' +
'</div>' +
'<div style="position:fixed;bottom:30px;" class="row m2m-zoom-buttons"><div class="col-12" style="text-align:center;"><button id="m2m-prev-btn" title="Previous" ><i class="fa fa-arrow-left"/></button><button title="Zoom In" id="m2m-zoom-in"><i class="fa fa-fw fa-plus" role="img" aria-label="Zoom In"></i></button><button title="Zoom Out" id="m2m-zoom-out"><i class="fa fa-fw fa-minus" role="img" aria-label="Zoom Out"></i></button><a href="/web/content/' + fileSrc + '" download="/web/content/'+fileSrc+'"><button title="Download" id="m2m-download-btn"><i class="fa fa-fw fa-download" role="img" aria-label="Download"></i></button></a><button title="Print" id="m2m-print-pdf"><i class="fa fa-fw fa-print" role="img" aria-label="Print"></i></button><a href="/web/content/' + fileSrc + '" target="_blank"><button title="Open in New Tab" id="m2m-download-btn"><i class="fa fa-fw fa-external-link" role="img" aria-label="Open in New Tab"></i></button></a><button id="m2m-next-btn" title="Next"><i class="fa fa-arrow-right"/></button></div></div></div>';
$('body').append(modalHtml);
$('.modal-pdf').show();
var pdfjsLib = window['pdfjs-dist/build/pdf'];
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.worker.min.js';
var pdfCanvasContainer = document.getElementById('pdfCanvasContainer');
pdfjsLib.getDocument('/web/content/' + fileSrc)
.promise
.then(pdf => {
totalPageCount = pdf.numPages;
var renderPage = function(pageNum) {
pdf.getPage(pageNum)
.then(page => {
var viewport = page.getViewport({ scale: 1 });
var canvas = document.createElement('canvas');
canvas.className = 'pdf-page-canvas';
pdfCanvasContainer.appendChild(canvas);
var context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
page.render({ canvasContext: context, viewport: viewport })
.promise
.then(() => {
currentPage = pageNum;
$('#pageCount').text(totalPageCount + ' Page');
pdfCanvasContainer.appendChild(document.createElement('br'));
if (pageNum < totalPageCount) { renderPage(pageNum + 1); }
})
.catch(error => {
console.error('Error rendering page:', error);
});
})
.catch(error => {
console.error('Error getting page:', error);
});
};
renderPage(1);
})
.catch(error => {
console.error('Error:', error);
});
$('.close-pdf').click(function () { $('.modal-pdf').remove(); });
$('.modal-pdf').click(function (event) { if ($(event.target).hasClass('modal-pdf')) { $('.modal-pdf').remove(); } });
$(document).click(function (event) { if (!$(event.target).closest('#pdfCanvas').length && (event.target.tagName == 'DIV')) { $('.modal-pdf').remove(); } });
var zoomValue = 100;
var zoomIncrement = 10;
var minZoom = 50;
var marTop = 10;
var maxZoom = 200;
var self = this;
var zoomImage = document.getElementById('pdfCanvasContainer');
var zoomInButton = document.getElementById('m2m-zoom-in');
var zoomOutButton = document.getElementById('m2m-zoom-out');
var printButton = document.getElementById('m2m-print-pdf');
var nextButton = document.getElementById('m2m-next-btn');
var prevButton = document.getElementById('m2m-prev-btn');
function updateZoomLevel() { zoomImage.style.transform = 'scale(' + (zoomValue / 100) + ')'; }
function handleZoomIn() { if (zoomValue < maxZoom) { marTop = marTop + 10; zoomValue += zoomIncrement; updateZoomLevel(); zoomImage.style.marginTop = marTop.toString() +'%'; } }
function handleZoomOut() { if (zoomValue > minZoom) { marTop = marTop - 10; zoomValue -= zoomIncrement; updateZoomLevel(); zoomImage.style.marginTop = marTop.toString() +'%'; } }
zoomInButton.addEventListener('click', handleZoomIn);
zoomOutButton.addEventListener('click', handleZoomOut);
nextButton.addEventListener('click', handlenextButton);
prevButton.addEventListener('click', handleprevButton);
printButton.addEventListener('click', printPDF);
function printPDF() { var url = '/web/content/' + fileSrc; const iframe = document.createElement('iframe'); iframe.src = url; iframe.style.display = 'none'; document.body.appendChild(iframe); iframe.onload = () => { iframe.contentWindow.focus(); iframe.contentWindow.print(); }; }
function handlenextButton() {
var records= self.props.value.records;
var recordIds = records.map(record => record.data.id)
var index = recordIds.indexOf(fileSrc);
var nextID = recordIds[index + 1];
$('.modal-img').remove();
if (nextID){
self.onFilePreview(nextID);
}
}
function handleprevButton(){
var records= self.props.value.records;
var recordIds = records.map(record => record.data.id)
var index = recordIds.indexOf(fileSrc);
var PrevID = recordIds[index - 1];
$('.modal-img').remove();
if (PrevID){
self.onFilePreview(PrevID);
}
}
}
})
Many2ManyBinaryField.template = "odx_m2m_attachment_preview.Many2ManyBinaryField";
registry.category("fields").add("many2many_attachment_preview", Many2ManyBinaryField);
.modal-img {display: none;position: fixed;z-index: 2000;left: 0;top: 0;width: 100%;height: 100%;overflow: auto;background-color: rgba(0,0,0,0.9);}
.modal-img img {width:auto;height:auto;max-width: 70%;max-height: 70%;margin: auto;display: block;}
.modal-img .row{padding:0 40px;}
.close-img:hover {color:#fff !important;}
.close-img {margin-left:auto;vertical-align:middle;font-size: 30px;font-weight: bold;color: grey !important;cursor: pointer;}
#OrderPdfDownloadLink{margin-right:10px;}
.m2m-zoom-buttons{width:100%;position: absolute;bottom: 10px;left: 10px;}
#m2m-zoom-in,#m2m-download-btn,#m2m-print-pdf,#m2m-zoom-out,#m2m-forward,#m2m-backward,#m2m-play-pause,#m2m-next-btn,#m2m-prev-btn {width:40px;height:40px;font-size:15px;margin-right:5px;background-color: var(--AttachmentViewer_toolbarButton-background-color, #343a40);color: #fff;border: none;}
.m2m-widget-image{width:100px;cursor:pointer;}
.close-pdf{margin-left:auto;vertical-align:middle;font-size: 30px;font-weight: bold;color: grey !important;cursor: pointer;}
.pdf-container{display:flex;justify-content:center;align-items:center;}
.m2m-image-container{position: relative;display: inline-block;width: auto !important;padding: 2px 5px;}
.m2m-attach-delete-btn{color: red;margin-left: 5px;font-size: 15px;opacity: 0;transition: opacity 0.3s;cursor: pointer;}
.m2m-image-container:hover .m2m-attach-delete-btn { opacity: 1; }
.odx_video {
width: 100%;
height: 100%;
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="odx_m2m_attachment_preview.Many2ManyBinaryField" owl="1">
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.min.js"></script>
<div t-attf-class="oe_fileupload {{props.className ? props.className : ''}}" aria-atomic="true">
<div class="o_attachments">
<t t-foreach="files" t-as="file" t-key="file_index">
<t t-call="odx_m2m_attachment_preview.attachment_preview"/>
</t>
</div>
<div t-if="!props.readonly" class="oe_add">
<FileInput
acceptedFileExtensions="props.acceptedFileExtensions"
multiUpload="true"
onUpload.bind="onFileUploaded"
resModel="props.record.resModel"
resId="props.record.data.id or 0"
>
<button class="btn btn-secondary o_attach" data-tooltip="Attach">
<span class="fa fa-paperclip" aria-label="Attach"/>
<t t-esc="props.uploadText"/>
</button>
</FileInput>
</div>
</div>
</t>
<t t-name="odx_m2m_attachment_preview.attachment_preview" owl="1">
<t t-set="editable" t-value="!props.readonly"/>
<div t-attf-class="o_attachment o_attachment_many2many #{ editable ? 'o_attachment_editable' : '' } #{upload ? 'o_attachment_uploading' : ''}"
t-att-title="file.name">
<div class="o_attachment_wrap">
<t t-set="ext" t-value="getExtension(file)"/>
<t t-set="fileID" t-value="file.id"/>
<div class="o_image_box float-start" t-att-data-tooltip="'Download ' + file.name" t-att-data-id="file.id">
<!-- <a t-att-href="getUrl(file.id)" aria-label="Download">-->
<t t-if="file.mimetype.startsWith('image')">
<span t-attf-class="o_image o_hover attachment-preview-{{file.id}}"
t-on-click="() => {this.onFilePreview(file.id)}" t-attf-data-url="getUrl(file.id)"
t-att-data-mimetype="file.mimetype" t-att-data-ext="ext" role="img"
t-attf-style='background-image: url("/web/content/{{file.id}}");'/>
</t>
<t t-else="">
<span t-attf-class="o_image o_hover attachment-preview-{{file.id}}"
t-on-click="() => {this.onFilePreview(file.id)}" t-attf-data-url="getUrl(file.id)"
t-att-data-mimetype="file.mimetype" t-att-data-ext="ext" role="img"/>
</t>
<!-- </a>-->
</div>
<div class="caption">
<a class="ml4" t-att-data-tooltip="'Download ' + file.name" t-att-href="getUrl(file.id)"><t t-esc='file.name'/></a>
</div>
<div class="caption small">
<a class="ml4 small text-uppercase" t-att-href="getUrl(file.id)"><b><t t-esc='ext'/></b></a>
<div t-if="editable" class="progress o_attachment_progress_bar">
<div class="progress-bar progress-bar-striped active" style="width: 100%">Uploading</div>
</div>
</div>
<div class="o_attachment_uploaded"><i class="text-success fa fa-check" role="img" aria-label="Uploaded" title="Uploaded"/></div>
<div t-if="editable" class="o_attachment_delete" t-on-click.stop="() => this.onFileRemove(file.id)"><span class="text-white" role="img" aria-label="Delete" title="Delete">×</span></div>
</div>
</div>
</t>
</templates>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论