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

Merge branch 'release/2.9.0'

...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
'wizard/batch_input_ship_package_statu_wizard.xml', 'wizard/batch_input_ship_package_statu_wizard.xml',
'wizard/update_bl_status_wizard.xml', 'wizard/update_bl_status_wizard.xml',
'wizard/export_bl_big_package_xlsx_wizard.xml', 'wizard/export_bl_big_package_xlsx_wizard.xml',
'wizard/batch_update_transfer_bl_no_wizard_view.xml',
'wizard/associate_pallet_wizard_views.xml', 'wizard/associate_pallet_wizard_views.xml',
'wizard/add_exception_info_wizard_views.xml', 'wizard/add_exception_info_wizard_views.xml',
'wizard/email_template.xml', 'wizard/email_template.xml',
...@@ -57,9 +58,11 @@ ...@@ -57,9 +58,11 @@
'ccs_base/static/css/base.scss', 'ccs_base/static/css/base.scss',
], ],
'web.assets_backend': [ 'web.assets_backend': [
'ccs_base/static/src/mixins/*.js', 'ccs_base/static/src/mixins/link_pallet.js',
'ccs_base/static/src/views/*.js', 'ccs_base/static/src/mixins/link_transfer_bl_no.js',
'ccs_base/static/src/views/*.xml', 'ccs_base/static/src/views/big_package_list_controller.js',
'ccs_base/static/src/views/bl_list_controller.js',
'ccs_base/static/src/views/list.xml',
], ],
}, },
'license': 'AGPL-3', 'license': 'AGPL-3',
......
...@@ -133,8 +133,8 @@ class ExportBlAndPackageXlsx(http.Controller): ...@@ -133,8 +133,8 @@ class ExportBlAndPackageXlsx(http.Controller):
return sheet1 return sheet1
# 运单导出包裹清关数据 每个运单导出一个文件 生成压缩包 # 运单导出包裹清关数据 每个运单导出一个文件 生成压缩包
@http.route(['/export/bl/package/xls/<string:arr>/<string:select_type>'], type='http', auth="public") @http.route(['/export/bl/package/xls/<string:arr>/<string:select_type>/<string:file_name_type>'], type='http', auth="public")
def export_bl_package_xls(self, arr, select_type): def export_bl_package_xls(self, arr, select_type, file_name_type):
""" """
是否分大包,如果不分,就是提单一个文件,命名 提单号;如果分,一个提单一个大包一个文件,命名 提单号➕大包号 是否分大包,如果不分,就是提单一个文件,命名 提单号;如果分,一个提单一个大包一个文件,命名 提单号➕大包号
""" """
...@@ -144,6 +144,7 @@ class ExportBlAndPackageXlsx(http.Controller): ...@@ -144,6 +144,7 @@ class ExportBlAndPackageXlsx(http.Controller):
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
if select_type == 'yes': if select_type == 'yes':
for bl_item in bl_obj: for bl_item in bl_obj:
order_no = bl_item.bl_no if file_name_type == 'bl_no' else bl_item.transfer_bl_no
for bag_item in bl_item.big_package_ids.filtered(lambda pack: not pack.is_cancel): for bag_item in bl_item.big_package_ids.filtered(lambda pack: not pack.is_cancel):
if len(bag_item.mapped('ship_package_ids').mapped('good_ids').filtered( if len(bag_item.mapped('ship_package_ids').mapped('good_ids').filtered(
lambda good: not good.is_cancel)) > 0: lambda good: not good.is_cancel)) > 0:
...@@ -152,9 +153,10 @@ class ExportBlAndPackageXlsx(http.Controller): ...@@ -152,9 +153,10 @@ class ExportBlAndPackageXlsx(http.Controller):
excel_file = io.BytesIO() excel_file = io.BytesIO()
worksheet.save(excel_file) worksheet.save(excel_file)
excel_file.seek(0) excel_file.seek(0)
zip_file.writestr(f"{bl_item.bl_no}-{bag_item.big_package_no}.xls", excel_file.read()) # 压缩 zip_file.writestr(f"{order_no}-{bag_item.big_package_no}.xls", excel_file.read()) # 压缩
else: else:
for bl_item in bl_obj: for bl_item in bl_obj:
order_no = bl_item.bl_no if file_name_type == 'bl_no' else bl_item.transfer_bl_no
big_bag_obj = bl_item.big_package_ids.filtered(lambda pack: not pack.is_cancel) big_bag_obj = bl_item.big_package_ids.filtered(lambda pack: not pack.is_cancel)
if len(big_bag_obj.mapped('ship_package_ids').mapped('good_ids').filtered( if len(big_bag_obj.mapped('ship_package_ids').mapped('good_ids').filtered(
lambda good: not good.is_cancel)) > 0: lambda good: not good.is_cancel)) > 0:
...@@ -163,7 +165,7 @@ class ExportBlAndPackageXlsx(http.Controller): ...@@ -163,7 +165,7 @@ class ExportBlAndPackageXlsx(http.Controller):
excel_file = io.BytesIO() excel_file = io.BytesIO()
worksheet.save(excel_file) worksheet.save(excel_file)
excel_file.seek(0) excel_file.seek(0)
zip_file.writestr(f"{bl_item.bl_no}.xls", excel_file.read()) # 压缩 zip_file.writestr(f"{order_no}.xls", excel_file.read()) # 压缩
zip_buffer.seek(0) zip_buffer.seek(0)
headers = [ headers = [
('Content-Type', 'application/x-zip-compressed'), ('Content-Type', 'application/x-zip-compressed'),
...@@ -172,19 +174,20 @@ class ExportBlAndPackageXlsx(http.Controller): ...@@ -172,19 +174,20 @@ class ExportBlAndPackageXlsx(http.Controller):
response = http.request.make_response(zip_buffer.getvalue(), headers=headers) response = http.request.make_response(zip_buffer.getvalue(), headers=headers)
return response return response
@http.route(['/export/flight_png/xls/<string:arr>'], type='http', auth="public") @http.route(['/export/flight_png/xls/<string:arr>/<string:file_name_type>'], type='http', auth="public")
def export_flight_png_xls(self, arr): def export_flight_png_xls(self, arr, file_name_type):
arr = json.loads(arr) arr = json.loads(arr)
flight_objs = http.request.env['cc.bl'].sudo().search([('id', 'in', arr)], order='id') flight_objs = http.request.env['cc.bl'].sudo().search([('id', 'in', arr)], order='id')
zip_buffer = io.BytesIO() zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
for flight in flight_objs: for flight in flight_objs:
order_no = flight.bl_no if file_name_type == 'bl_no' else flight.transfer_bl_no
bl_attachment_objs = flight.bl_attachment_ids bl_attachment_objs = flight.bl_attachment_ids
for bl_attachment_obj in bl_attachment_objs: for bl_attachment_obj in bl_attachment_objs:
# 获取图片数据 # 获取图片数据
image_data = base64.b64decode(bl_attachment_obj.datas) image_data = base64.b64decode(bl_attachment_obj.datas)
# 创建文件名,前缀为单据号 # 创建文件名,前缀为单据号
file_name = f"{flight.bl_no}.png" file_name = f"{order_no}.png"
# 将图片添加到 ZIP 文件 # 将图片添加到 ZIP 文件
zip_file.writestr(file_name, image_data) zip_file.writestr(file_name, image_data)
zip_buffer.seek(0) zip_buffer.seek(0)
......
...@@ -6,8 +6,8 @@ msgid "" ...@@ -6,8 +6,8 @@ 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-06-09 07:48+0000\n" "POT-Creation-Date: 2025-07-15 02:39+0000\n"
"PO-Revision-Date: 2025-06-09 15:53+0800\n" "PO-Revision-Date: 2025-07-15 10:45+0800\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: zh_CN\n" "Language: zh_CN\n"
...@@ -143,6 +143,19 @@ msgid "" ...@@ -143,6 +143,19 @@ msgid ""
" " " "
msgstr "" msgstr ""
#. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.view_batch_update_transfer_bl_no_wizard
msgid ""
"<span class=\"label label-warning\">\n"
" If there is abnormal data, please download "
"the error file, fix the data as prompted, and re-import!\n"
" </span>"
msgstr ""
"<span class=\"label label-warning\">\n"
" 如果数据异常,请下载错误文件,按提示修正数"
"据,然后重新导入!\n"
" </span>"
#. module: ccs_base #. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_bl_view #: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_bl_view
msgid "<span class=\"o_stat_text\">Big Packages</span>" msgid "<span class=\"o_stat_text\">Big Packages</span>"
...@@ -172,6 +185,18 @@ msgstr "<span class=\"o_stat_text\">小包</span>" ...@@ -172,6 +185,18 @@ msgstr "<span class=\"o_stat_text\">小包</span>"
msgid "<span class=\"o_stat_text\">Tally Big Packages</span>" msgid "<span class=\"o_stat_text\">Tally Big Packages</span>"
msgstr "<span class=\"o_stat_text\">已理货大包</span>" msgstr "<span class=\"o_stat_text\">已理货大包</span>"
#. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.view_batch_update_transfer_bl_no_wizard
msgid ""
"<span style=\"color:red;font-size:15px;\">Tip: Please fill in the "
"information strictly according to the template, otherwise the system will "
"not recognize it.\n"
" </span>"
msgstr ""
"<span style=\"color:red;font-size:15px;\">提示:请务必按照模板填写信息,否则"
"系统无法识别。\n"
" </span>"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,help:ccs_base.field_batch_input_ship_package_status_wizard__exclude_tracking_no #: model:ir.model.fields,help:ccs_base.field_batch_input_ship_package_status_wizard__exclude_tracking_no
#: model:ir.model.fields,help:ccs_base.field_batch_input_ship_package_status_wizard__select_tracking_no #: model:ir.model.fields,help:ccs_base.field_batch_input_ship_package_status_wizard__select_tracking_no
...@@ -215,6 +240,7 @@ msgstr "" ...@@ -215,6 +240,7 @@ msgstr ""
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard__action_type #: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard__action_type
#: model:ir.model.fields,field_description:ccs_base.field_export_bl_big_package_xlsx_wizard__action_type
msgid "Action Type" msgid "Action Type"
msgstr "动作类型" msgstr "动作类型"
...@@ -456,10 +482,33 @@ msgid "B/L File" ...@@ -456,10 +482,33 @@ msgid "B/L File"
msgstr "提货单文件" msgstr "提货单文件"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_batch_update_transfer_bl_no_wizard__report_file_ids
msgid "B/L List"
msgstr "提单列表"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#: model:ir.model.fields,field_description:ccs_base.field_cc_bl__bl_no #: model:ir.model.fields,field_description:ccs_base.field_cc_bl__bl_no
#: model:ir.model.fields.selection,name:ccs_base.selection__export_bl_big_package_xlsx_wizard__file_name_type__bl_no
#, python-format
msgid "B/L No" msgid "B/L No"
msgstr "提单号" msgstr "提单号"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#, python-format
msgid "B/L No and Transfer B/L No are duplicated"
msgstr "提单号和转单号重复"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#, python-format
msgid "B/L No is required"
msgstr "提单号必填"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_bl__bl_attachment_ids #: model:ir.model.fields,field_description:ccs_base.field_cc_bl__bl_attachment_ids
msgid "B\\L Attachments" msgid "B\\L Attachments"
...@@ -482,6 +531,16 @@ msgstr "批量" ...@@ -482,6 +531,16 @@ msgstr "批量"
msgid "Batch Add Package Exception Information" msgid "Batch Add Package Exception Information"
msgstr "批量添加异常信息" msgstr "批量添加异常信息"
#. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.view_batch_update_transfer_bl_no_wizard
msgid "Batch Link Transfer B/L No"
msgstr "批量关联转单号"
#. module: ccs_base
#: model:ir.model,name:ccs_base.model_batch_update_transfer_bl_no_wizard
msgid "Batch Update Transfer B/L No Wizard"
msgstr "批量关联转单号向导"
#. module: ccs_base #. module: ccs_base
#: model:ir.model,name:ccs_base.model_batch_input_ship_package_status_wizard #: model:ir.model,name:ccs_base.model_batch_input_ship_package_status_wizard
msgid "Batch Update the status of the small package" msgid "Batch Update the status of the small package"
...@@ -877,6 +936,7 @@ msgstr "清关文件" ...@@ -877,6 +936,7 @@ msgstr "清关文件"
#: model_terms:ir.ui.view,arch_db:ccs_base.view_add_exception_info_wizard #: model_terms:ir.ui.view,arch_db:ccs_base.view_add_exception_info_wizard
#: model_terms:ir.ui.view,arch_db:ccs_base.view_associate_pallet_wizard #: model_terms:ir.ui.view,arch_db:ccs_base.view_associate_pallet_wizard
#: model_terms:ir.ui.view,arch_db:ccs_base.view_batch_input_ship_package_wizard #: model_terms:ir.ui.view,arch_db:ccs_base.view_batch_input_ship_package_wizard
#: model_terms:ir.ui.view,arch_db:ccs_base.view_batch_update_transfer_bl_no_wizard
#: model_terms:ir.ui.view,arch_db:ccs_base.view_export_bl_big_package_xlsx_wizard #: model_terms:ir.ui.view,arch_db:ccs_base.view_export_bl_big_package_xlsx_wizard
#: model_terms:ir.ui.view,arch_db:ccs_base.view_update_bl_status_wizard #: model_terms:ir.ui.view,arch_db:ccs_base.view_update_bl_status_wizard
msgid "Close" msgid "Close"
...@@ -954,6 +1014,7 @@ msgstr "快递名称" ...@@ -954,6 +1014,7 @@ msgstr "快递名称"
#: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard__create_uid #: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard__create_uid
#: model:ir.model.fields,field_description:ccs_base.field_associate_pallet_wizard__create_uid #: model:ir.model.fields,field_description:ccs_base.field_associate_pallet_wizard__create_uid
#: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard__create_uid #: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard__create_uid
#: model:ir.model.fields,field_description:ccs_base.field_batch_update_transfer_bl_no_wizard__create_uid
#: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__create_uid #: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__create_uid
#: model:ir.model.fields,field_description:ccs_base.field_cc_bl__create_uid #: model:ir.model.fields,field_description:ccs_base.field_cc_bl__create_uid
#: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__create_uid #: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__create_uid
...@@ -982,6 +1043,7 @@ msgstr "创建人" ...@@ -982,6 +1043,7 @@ msgstr "创建人"
#: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard__create_date #: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard__create_date
#: model:ir.model.fields,field_description:ccs_base.field_associate_pallet_wizard__create_date #: model:ir.model.fields,field_description:ccs_base.field_associate_pallet_wizard__create_date
#: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard__create_date #: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard__create_date
#: model:ir.model.fields,field_description:ccs_base.field_batch_update_transfer_bl_no_wizard__create_date
#: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__create_date #: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__create_date
#: model:ir.model.fields,field_description:ccs_base.field_cc_bl__create_date #: model:ir.model.fields,field_description:ccs_base.field_cc_bl__create_date
#: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__create_date #: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__create_date
...@@ -1170,6 +1232,7 @@ msgstr "消费者地址" ...@@ -1170,6 +1232,7 @@ msgstr "消费者地址"
#: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard__display_name #: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard__display_name
#: model:ir.model.fields,field_description:ccs_base.field_associate_pallet_wizard__display_name #: model:ir.model.fields,field_description:ccs_base.field_associate_pallet_wizard__display_name
#: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard__display_name #: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard__display_name
#: model:ir.model.fields,field_description:ccs_base.field_batch_update_transfer_bl_no_wizard__display_name
#: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__display_name #: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__display_name
#: model:ir.model.fields,field_description:ccs_base.field_cc_bl__display_name #: model:ir.model.fields,field_description:ccs_base.field_cc_bl__display_name
#: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__display_name #: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__display_name
...@@ -1200,6 +1263,11 @@ msgstr "显示名称" ...@@ -1200,6 +1263,11 @@ msgstr "显示名称"
msgid "Done" msgid "Done"
msgstr "完成" msgstr "完成"
#. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.view_batch_update_transfer_bl_no_wizard
msgid "Download Template"
msgstr "下载模版"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields.selection,name:ccs_base.selection__cc_bl__state__draft #: model:ir.model.fields.selection,name:ccs_base.selection__cc_bl__state__draft
#: model_terms:ir.ui.view,arch_db:ccs_base.search_cc_bl_view #: model_terms:ir.ui.view,arch_db:ccs_base.search_cc_bl_view
...@@ -1265,6 +1333,21 @@ msgstr "目的地港口代码" ...@@ -1265,6 +1333,21 @@ msgstr "目的地港口代码"
msgid "English" msgid "English"
msgstr "英文" msgstr "英文"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#: model:ir.model.fields,field_description:ccs_base.field_batch_update_transfer_bl_no_wizard__error_file_ids
#, python-format
msgid "Error Data"
msgstr "异常数据"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#, python-format
msgid "Error Reason"
msgstr ""
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_exception_info__exception_code #: model:ir.model.fields,field_description:ccs_base.field_cc_exception_info__exception_code
msgid "Exception Code" msgid "Exception Code"
...@@ -1312,6 +1395,13 @@ msgstr "异常类型" ...@@ -1312,6 +1395,13 @@ msgstr "异常类型"
msgid "Exclude TrackingNo" msgid "Exclude TrackingNo"
msgstr "排除的追踪号" msgstr "排除的追踪号"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/models/cc_bill_loading.py:0
#, python-format
msgid "Export Customs Clearance Bill of Lading"
msgstr "导出清关清单文件"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_history_package_good__export_hs_code #: model:ir.model.fields,field_description:ccs_base.field_cc_history_package_good__export_hs_code
#: model:ir.model.fields,field_description:ccs_base.field_cc_package_good__export_hs_code #: model:ir.model.fields,field_description:ccs_base.field_cc_package_good__export_hs_code
...@@ -1346,6 +1436,7 @@ msgid "File" ...@@ -1346,6 +1436,7 @@ msgid "File"
msgstr "文件" msgstr "文件"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_batch_update_transfer_bl_no_wizard__file_name
#: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__attachment_name #: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__attachment_name
#: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__file_name #: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__file_name
#: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_clearance_file_view #: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_clearance_file_view
...@@ -1354,6 +1445,18 @@ msgstr "文件" ...@@ -1354,6 +1445,18 @@ msgstr "文件"
msgid "File Name" msgid "File Name"
msgstr "文件名" msgstr "文件名"
#. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_export_bl_big_package_xlsx_wizard__file_name_type
msgid "File Name Type"
msgstr "文件名称"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#, python-format
msgid "File error, the content order should be: %s"
msgstr "文件错误,内容顺序为:%s"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__message_follower_ids #: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__message_follower_ids
#: model:ir.model.fields,field_description:ccs_base.field_cc_bl__message_follower_ids #: model:ir.model.fields,field_description:ccs_base.field_cc_bl__message_follower_ids
...@@ -1537,6 +1640,7 @@ msgstr "历史小包" ...@@ -1537,6 +1640,7 @@ msgstr "历史小包"
#: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard__id #: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard__id
#: model:ir.model.fields,field_description:ccs_base.field_associate_pallet_wizard__id #: model:ir.model.fields,field_description:ccs_base.field_associate_pallet_wizard__id
#: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard__id #: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard__id
#: model:ir.model.fields,field_description:ccs_base.field_batch_update_transfer_bl_no_wizard__id
#: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__id #: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__id
#: model:ir.model.fields,field_description:ccs_base.field_cc_bl__id #: model:ir.model.fields,field_description:ccs_base.field_cc_bl__id
#: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__id #: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__id
...@@ -1614,6 +1718,11 @@ msgstr "" ...@@ -1614,6 +1718,11 @@ msgstr ""
msgid "If checked, some messages have a delivery error." msgid "If checked, some messages have a delivery error."
msgstr "" msgstr ""
#. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.view_batch_update_transfer_bl_no_wizard
msgid "Import"
msgstr "导入"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_history_package_good__import_hs_code #: model:ir.model.fields,field_description:ccs_base.field_cc_history_package_good__import_hs_code
#: model:ir.model.fields,field_description:ccs_base.field_cc_package_good__import_hs_code #: model:ir.model.fields,field_description:ccs_base.field_cc_package_good__import_hs_code
...@@ -1850,6 +1959,7 @@ msgstr "尾程服务商" ...@@ -1850,6 +1959,7 @@ msgstr "尾程服务商"
#: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard____last_update #: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard____last_update
#: model:ir.model.fields,field_description:ccs_base.field_associate_pallet_wizard____last_update #: model:ir.model.fields,field_description:ccs_base.field_associate_pallet_wizard____last_update
#: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard____last_update #: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard____last_update
#: model:ir.model.fields,field_description:ccs_base.field_batch_update_transfer_bl_no_wizard____last_update
#: model:ir.model.fields,field_description:ccs_base.field_cc_big_package____last_update #: model:ir.model.fields,field_description:ccs_base.field_cc_big_package____last_update
#: model:ir.model.fields,field_description:ccs_base.field_cc_bl____last_update #: model:ir.model.fields,field_description:ccs_base.field_cc_bl____last_update
#: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file____last_update #: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file____last_update
...@@ -1884,6 +1994,7 @@ msgstr "最近操作时间" ...@@ -1884,6 +1994,7 @@ msgstr "最近操作时间"
#: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard__write_uid #: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard__write_uid
#: model:ir.model.fields,field_description:ccs_base.field_associate_pallet_wizard__write_uid #: model:ir.model.fields,field_description:ccs_base.field_associate_pallet_wizard__write_uid
#: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard__write_uid #: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard__write_uid
#: model:ir.model.fields,field_description:ccs_base.field_batch_update_transfer_bl_no_wizard__write_uid
#: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__write_uid #: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__write_uid
#: model:ir.model.fields,field_description:ccs_base.field_cc_bl__write_uid #: model:ir.model.fields,field_description:ccs_base.field_cc_bl__write_uid
#: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__write_uid #: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__write_uid
...@@ -1912,6 +2023,7 @@ msgstr "最后更新人" ...@@ -1912,6 +2023,7 @@ msgstr "最后更新人"
#: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard__write_date #: model:ir.model.fields,field_description:ccs_base.field_add_exception_info_wizard__write_date
#: model:ir.model.fields,field_description:ccs_base.field_associate_pallet_wizard__write_date #: model:ir.model.fields,field_description:ccs_base.field_associate_pallet_wizard__write_date
#: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard__write_date #: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard__write_date
#: model:ir.model.fields,field_description:ccs_base.field_batch_update_transfer_bl_no_wizard__write_date
#: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__write_date #: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__write_date
#: model:ir.model.fields,field_description:ccs_base.field_cc_bl__write_date #: model:ir.model.fields,field_description:ccs_base.field_cc_bl__write_date
#: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__write_date #: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__write_date
...@@ -1971,6 +2083,15 @@ msgstr "链接" ...@@ -1971,6 +2083,15 @@ msgstr "链接"
msgid "Link Pallet" msgid "Link Pallet"
msgstr "关联托盘" msgstr "关联托盘"
#. module: ccs_base
#. odoo-javascript
#. odoo-python
#: code:addons/ccs_base/models/cc_bill_loading.py:0
#: code:addons/ccs_base/static/src/views/list.xml:0
#, python-format
msgid "Link Transfer B/L No"
msgstr "关联转单号"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_progress__loading_type #: model:ir.model.fields,field_description:ccs_base.field_cc_progress__loading_type
msgid "Loading Type" msgid "Loading Type"
...@@ -2264,6 +2385,13 @@ msgstr "" ...@@ -2264,6 +2385,13 @@ msgstr ""
msgid "ONLINE SELLING PLACE" msgid "ONLINE SELLING PLACE"
msgstr "网上销售网站" msgstr "网上销售网站"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#, python-format
msgid "Only excel files can be uploaded!"
msgstr "只能上传excel文件!"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_history_package_sync_log__operate_remark #: model:ir.model.fields,field_description:ccs_base.field_cc_history_package_sync_log__operate_remark
#: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_history_package_sync_log_view #: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_history_package_sync_log_view
...@@ -2411,10 +2539,18 @@ msgstr "摆放区域" ...@@ -2411,10 +2539,18 @@ msgstr "摆放区域"
#. odoo-python #. odoo-python
#: code:addons/ccs_base/models/cc_last_mile_provider.py:0 #: code:addons/ccs_base/models/cc_last_mile_provider.py:0
#, python-format #, python-format
msgid "" msgid "Placement Area must be unique !"
"Placement Area must be unique !"
msgstr "摆放区域必须唯一!" msgstr "摆放区域必须唯一!"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#, python-format
msgid ""
"Please check if the import file and content are correct, and import "
"according to the template file!"
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
...@@ -2431,6 +2567,13 @@ msgstr "请先配置默认的提单节点类型的清关节点" ...@@ -2431,6 +2567,13 @@ msgstr "请先配置默认的提单节点类型的清关节点"
msgid "Please select the reason for the exception!" msgid "Please select the reason for the exception!"
msgstr "请选择异常原因!" msgstr "请选择异常原因!"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#, python-format
msgid "Please upload the B/L data file to be imported first!"
msgstr "请先上传需要更新的提单数据文件!"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard__process_time #: model:ir.model.fields,field_description:ccs_base.field_batch_input_ship_package_status_wizard__process_time
#: model:ir.model.fields,field_description:ccs_base.field_cc_bl__process_time #: model:ir.model.fields,field_description:ccs_base.field_cc_bl__process_time
...@@ -3053,7 +3196,7 @@ msgstr "同步日志" ...@@ -3053,7 +3196,7 @@ msgstr "同步日志"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_history_ship_package__sync_log_ids #: model:ir.model.fields,field_description:ccs_base.field_cc_history_ship_package__sync_log_ids
msgid "Sync Logs" msgid "Sync Logs"
msgstr "" msgstr "同步日志"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__upload_time #: model:ir.model.fields,field_description:ccs_base.field_cc_clearance_file__upload_time
...@@ -3184,6 +3327,11 @@ msgstr "商品号必须唯一。" ...@@ -3184,6 +3327,11 @@ msgstr "商品号必须唯一。"
msgid "The Logistic Order No must be unique." msgid "The Logistic Order No must be unique."
msgstr "物流订单号必须唯一。" msgstr "物流订单号必须唯一。"
#. module: ccs_base
#: model:ir.model.constraint,message:ccs_base.constraint_cc_bl_transfer_bl_no_uniq
msgid "The Transfer B/L No. 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
...@@ -3235,6 +3383,20 @@ msgstr "托盘号只能输入数字!" ...@@ -3235,6 +3383,20 @@ msgstr "托盘号只能输入数字!"
msgid "The usage date cannot be later than the current date!" msgid "The usage date cannot be later than the current date!"
msgstr "使用日期不能大于当前日期!" msgstr "使用日期不能大于当前日期!"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#, python-format
msgid "This B/L No does not exist in the system"
msgstr "提单号在系统不存在"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#, python-format
msgid "This Transfer B/L No already exists"
msgstr "转单号已存在"
#. module: ccs_base #. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.search_cc_big_package_view #: model_terms:ir.ui.view,arch_db:ccs_base.search_cc_big_package_view
#: model_terms:ir.ui.view,arch_db:ccs_base.search_cc_history_big_package_view #: model_terms:ir.ui.view,arch_db:ccs_base.search_cc_history_big_package_view
...@@ -3337,6 +3499,41 @@ msgstr "交易单号" ...@@ -3337,6 +3499,41 @@ msgstr "交易单号"
msgid "Trade Type" msgid "Trade Type"
msgstr "交易类型" msgstr "交易类型"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#: model:ir.model.fields,field_description:ccs_base.field_cc_bl__transfer_bl_no
#: model:ir.model.fields.selection,name:ccs_base.selection__export_bl_big_package_xlsx_wizard__file_name_type__transfer_bl_no
#, python-format
msgid "Transfer B/L No"
msgstr "转单号"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/wizard/batch_update_transfer_bl_no_wizard.py:0
#, python-format
msgid "Transfer B/L No is required"
msgstr "转单号必填"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/models/cc_bill_loading.py:0
#, python-format
msgid "Transfer B/L No. cannot be the same as B/L No."
msgstr "转单号不能与提单号相同。"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/models/cc_bill_loading.py:0
#, python-format
msgid "Transfer B/L No. cannot be the same as B/L No. or Transfer B/L No."
msgstr "转单号不能与提单号或转单号相同。"
#. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.tree_cc_bl_view
msgid "Transfer Bill of Loading No."
msgstr "转单号"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_bl__transport_tool_code #: model:ir.model.fields,field_description:ccs_base.field_cc_bl__transport_tool_code
#: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_bl_view #: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_bl_view
...@@ -3536,13 +3733,6 @@ msgstr "昨日提单" ...@@ -3536,13 +3733,6 @@ msgstr "昨日提单"
msgid "Yesterday's ship package" msgid "Yesterday's ship package"
msgstr "昨日小包" msgstr "昨日小包"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/models/cc_bill_loading.py:0
#, python-format
msgid "You have no expense to report"
msgstr ""
#. module: ccs_base #. module: ccs_base
#: model_terms:ir.actions.act_window,help:ccs_base.action_cc_big_package #: model_terms:ir.actions.act_window,help:ccs_base.action_cc_big_package
msgid "" msgid ""
......
# 导入odoo # 导入odoo
# 导入日志 # 导入日志
import logging
from datetime import timedelta, datetime
import json import json
import logging
from datetime import timedelta
import pytz import pytz
from odoo import models, fields, api, _ from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
...@@ -118,14 +119,6 @@ class CcBigPackage(models.Model): ...@@ -118,14 +119,6 @@ class CcBigPackage(models.Model):
'target': 'new', 'target': 'new',
'context': {}, 'context': {},
} }
# if there ere no records selected, then select all draft expenses for the user
expenses = self.env['hr.expense'].search(
[('state', '=', 'draft'), ('sheet_id', '=', False), ('employee_id', '=', self.env.user.employee_id.id)])
if not expenses:
raise UserError(_('You have no expense to report'))
else:
return expenses.action_submit_expenses()
def action_cc_big_package(self): def action_cc_big_package(self):
""" """
...@@ -344,8 +337,8 @@ class CcShipPackage(models.Model): ...@@ -344,8 +337,8 @@ class CcShipPackage(models.Model):
receiver_email = fields.Char(string='Receiver EMAIL', index=True) receiver_email = fields.Char(string='Receiver EMAIL', index=True)
# 电话号码 # 电话号码
receiver_phone = fields.Char(string='Receiver PHONE', index=True) receiver_phone = fields.Char(string='Receiver PHONE', index=True)
#消费者地址 # 消费者地址
receiver_detailed_address = fields.Char(string='Detailed Address',index=True) receiver_detailed_address = fields.Char(string='Detailed Address', index=True)
# 毛重 # 毛重
gross_weight = fields.Float(string='GROSS WEIGHT') gross_weight = fields.Float(string='GROSS WEIGHT')
# 重量单位 # 重量单位
...@@ -569,52 +562,26 @@ class CcBL(models.Model): ...@@ -569,52 +562,26 @@ class CcBL(models.Model):
# 定义模型字段 # 定义模型字段
# 限制提单号唯一 # 限制提单号唯一
_sql_constraints = [ _sql_constraints = [
('bl_no_uniq', 'unique(bl_no)', 'The Bill of Loading No. must be unique.') ('bl_no_uniq', 'unique(bl_no)', 'The Bill of Loading No. must be unique.'),
('transfer_bl_no_uniq', 'unique(transfer_bl_no)', 'The Transfer B/L No. must be unique.')
] ]
# 导出清关提单文件 @api.constrains('transfer_bl_no', 'bl_no')
def export_bl_attachment_png(self): def _check_transfer_bl_no_not_same_as_bl_no(self):
arr = [item.id for item in self] """Check that Transfer B/L No. is not the same as B/L No, and not the same as any other B/L No in the system."""
return { for record in self:
'type': 'ir.actions.act_url', if record.transfer_bl_no:
'url': '/export/flight_png/xls/%s' % arr, # 1. 不能与本单提单号重复
'target': 'new', bl_no = self.env['common.common'].sudo().process_match_str(record.bl_no)
} transfer_bl_no = self.env['common.common'].sudo().process_match_str(record.transfer_bl_no)
if bl_no and transfer_bl_no == bl_no:
def batch_input_ship_package_status_wizard(self): raise ValidationError(_('Transfer B/L No. cannot be the same as B/L No.')) # 转单号不能与提单号重复
"""批量更新小包状态""" # 2. 不能与其他单据的提单号重复
return { other = self.env['cc.bl'].deal_bl_no_and_transfer_bl_no(transfer_bl_no,
'name': _('Update the status of the small package'), domain=[('id', '!=', record.id)])
'type': 'ir.actions.act_window', if other:
'view_mode': 'form', raise ValidationError(
'res_model': 'batch.input.ship.package.status.wizard', _('Transfer B/L No. cannot be the same as B/L No. or Transfer B/L No.')) # 转单号不能与提单号或转单号重复
'target': 'new',
'context': {'active_id': self.ids, 'default_is_batch': True, 'default_bl_id': self.ids}
}
def batch_update_bl_status_wizard(self):
"""批量更新提单状态"""
# 检查关务提单状态必须是同一个
customs_clearance_status_list = self.filtered(lambda x: x.customs_clearance_status.id).mapped(
'customs_clearance_status.id')
if len(customs_clearance_status_list) == 0:
# 请先配置默认的提单节点类型的清关节点
raise ValidationError(
_('Please configure the default customs clearance status of the bill of loading node type first.'))
if len(customs_clearance_status_list) > 1:
raise ValidationError(_('The customs clearance status of the selected bill of loading must be the same.'))
# 最近操作时间取最晚的一条提单状态操作时间。
last_process_time = \
sorted(self.filtered(lambda x: x.customs_clearance_status.id).mapped('process_time'), reverse=True)[0]
return {
'name': _('Update the status of the bill of loading'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'update.bl.status.wizard',
'target': 'new',
'context': {'active_id': self.ids, 'default_is_batch': True, 'default_last_process_time': last_process_time,
'default_current_status': customs_clearance_status_list[0]}
}
@api.depends('big_package_ids', 'big_package_ids.tally_state', 'big_package_ids.is_cancel') @api.depends('big_package_ids', 'big_package_ids.tally_state', 'big_package_ids.is_cancel')
def cal_tally_big_package_qty(self): def cal_tally_big_package_qty(self):
...@@ -653,6 +620,8 @@ class CcBL(models.Model): ...@@ -653,6 +620,8 @@ class CcBL(models.Model):
is_history = fields.Boolean('历史单据', default=False) is_history = fields.Boolean('历史单据', default=False)
# 提单号 # 提单号
bl_no = fields.Char(string='B/L No', index=True) bl_no = fields.Char(string='B/L No', index=True)
# 转单号
transfer_bl_no = fields.Char(string='Transfer B/L No', index=True)
# 关务提单号 # 关务提单号
customs_bl_no = fields.Char(string='Customs B/L No') customs_bl_no = fields.Char(string='Customs B/L No')
# 贸易方式 # 贸易方式
...@@ -750,31 +719,6 @@ class CcBL(models.Model): ...@@ -750,31 +719,6 @@ class CcBL(models.Model):
else: else:
raise UserError(reason) raise UserError(reason)
def action_batch_export_package(self):
"""
导出报关文件
"""
# error_order = []
# for item in order_obj:
# # item.ship_package_ids
# parcel_ids = item.big_package_ids.mapped('ship_package_ids')
# if not parcel_ids:
# error_order.append(item.bl_no)
# continue
# good_parcels = parcel_ids.mapped('good_ids')
# if len(good_parcels) == 0:
# error_order.append(item.bl_no)
# if error_order:
# raise ValidationError(f"{','.join(error_order)},没有任何商品信息可导出报关数据")
return {
'name': _('Export customs declaration documents'), # 导出报关文件
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'export.bl.big.package.xlsx.wizard',
'target': 'new',
'context': {'active_id': self.ids, },
}
# 添加计算方法,根据bl_line_ids计算bl_total_line,bl_total_qty,bl_total_amount # 添加计算方法,根据bl_line_ids计算bl_total_line,bl_total_qty,bl_total_amount
@api.depends('ship_package_ids', 'big_package_ids', 'good_ids', 'ship_package_ids.is_cancel', @api.depends('ship_package_ids', 'big_package_ids', 'good_ids', 'ship_package_ids.is_cancel',
'big_package_ids.is_cancel', 'good_ids.is_cancel', 'ship_package_ids.total_value') 'big_package_ids.is_cancel', 'good_ids.is_cancel', 'ship_package_ids.total_value')
...@@ -804,6 +748,60 @@ class CcBL(models.Model): ...@@ -804,6 +748,60 @@ class CcBL(models.Model):
state = fields.Selection([('draft', 'Draft'), ('ccing', 'CCing'), ('done', 'Done')], string='Status', state = fields.Selection([('draft', 'Draft'), ('ccing', 'CCing'), ('done', 'Done')], string='Status',
default='draft') default='draft')
# 增加清关截止日期
cc_deadline = fields.Date(string='CC Deadline', default=fields.Date.today() + timedelta(days=5))
# 增加当前清关进度和进度日期
cc_progress = fields.Char(string='Current CC Progress')
cc_progress_date = fields.Date(string='Current CC Progress Date')
# 增加清关公司, 关联到res.partner
cc_company_id = fields.Many2one('res.partner', string='CC Company')
# 定义清关国家,关联到国家字段
cc_country_id = fields.Many2one('res.country', string='CC Country')
def push_clear_customs_start(self, utc_time):
# 创建向导
push_node_obj = self.env['cc.node'].sudo().search(
[('node_type', '=', 'package'), ('tk_code', '=', 'cb_imcustoms_start')], limit=1)
node_obj = self.env['cc.node'].sudo().search([('node_type', '=', 'package'), ('seq', '<', push_node_obj.seq)],
order='seq desc',
limit=1)
vals = {
'bl_id': self.id,
'bl_count': 1,
'current_status': node_obj.id,
'update_status': push_node_obj.id,
'process_time': utc_time,
'is_ok': True
}
wizard_obj = self.env['batch.input.ship.package.status.wizard'].sudo().create(vals)
wizard_obj.change_ship_package_ids()
wizard_obj = wizard_obj.with_context(dict(self._context, active_id=self.id))
wizard_obj.submit()
def push_clear_customs_end(self, utc_time):
# 创建向导
push_node_obj = self.env['cc.node'].sudo().search(
[('node_type', '=', 'package'), ('tk_code', '=', 'cb_imcustoms_finished')], limit=1)
node_obj = self.env['cc.node'].sudo().search([('node_type', '=', 'package'), ('seq', '<', push_node_obj.seq)],
order='seq desc',
limit=1)
vals = {
'bl_id': self.id,
'bl_count': 1,
'current_status': node_obj.id,
'update_status': push_node_obj.id,
'process_time': utc_time,
'is_ok': True
}
wizard_obj = self.env['batch.input.ship.package.status.wizard'].sudo().create(vals)
wizard_obj.change_ship_package_ids()
wizard_obj = wizard_obj.with_context(dict(self._context, active_id=self.id))
# print(wizard_obj.get_order())
wizard_obj.submit()
def ccing_func(self): def ccing_func(self):
""" """
变为清关中 变为清关中
...@@ -881,59 +879,119 @@ class CcBL(models.Model): ...@@ -881,59 +879,119 @@ class CcBL(models.Model):
'domain': [('bl_line_id', 'in', ids), ('is_cancel', '=', False)], 'domain': [('bl_line_id', 'in', ids), ('is_cancel', '=', False)],
} }
# 增加清关截止日期 def deal_bl_no(self, bl_no, state_arr=[]):
cc_deadline = fields.Date(string='CC Deadline', default=fields.Date.today() + timedelta(days=5)) """
处理提单号:去掉杠和空格,并转换为小写
:param bl_no:
:return:
"""
processed_bl_no = self.env['common.common'].sudo().process_match_str(bl_no)
# 查询所有提单并处理它们的 bl_no
domain = [('state', 'in', state_arr)] if state_arr else []
all_bl_obj = self.env['cc.bl'].sudo().search(domain)
bl_obj = all_bl_obj.filtered(
lambda r: r.bl_no and self.env['common.common'].sudo().process_match_str(r.bl_no) == processed_bl_no) # 提单
return bl_obj
def deal_bl_no_and_transfer_bl_no(self, bl_no, state_arr=[], domain=[]):
"""
优先匹配提单号,匹配不到则匹配转单号
匹配规则与提单号匹配规则一致:去掉杠和空格,转换为小写
:param bl_no:
:param state_arr:
:param domain: 额外的查询条件
:return:
"""
processed_bl_no = self.env['common.common'].sudo().process_match_str(bl_no)
# 构建查询条件
search_domain = domain.copy() if domain else []
if state_arr:
search_domain.append(('state', 'in', state_arr))
all_bl_obj = self.env['cc.bl'].sudo().search(search_domain)
# 增加当前清关进度和进度日期 # 优先匹配提单号
cc_progress = fields.Char(string='Current CC Progress') bl_obj = all_bl_obj.filtered(
cc_progress_date = fields.Date(string='Current CC Progress Date') lambda r: r.bl_no and self.env['common.common'].sudo().process_match_str(r.bl_no) == processed_bl_no)
# 增加清关公司, 关联到res.partner # 如果提单号匹配不到,则匹配转单号
cc_company_id = fields.Many2one('res.partner', string='CC Company') if not bl_obj:
bl_obj = all_bl_obj.filtered(
lambda r: r.transfer_bl_no and self.env['common.common'].sudo().process_match_str(
r.transfer_bl_no) == processed_bl_no)
# 定义清关国家,关联到国家字段 return bl_obj
cc_country_id = fields.Many2one('res.country', string='CC Country')
def push_clear_customs_start(self, utc_time): def action_batch_export_package(self):
# 创建向导 """
push_node_obj = self.env['cc.node'].sudo().search( 导出报关文件
[('node_type', '=', 'package'), ('tk_code', '=', 'cb_imcustoms_start')], limit=1) """
node_obj = self.env['cc.node'].sudo().search([('node_type', '=', 'package'), ('seq', '<', push_node_obj.seq)], return {
order='seq desc', 'name': _('Export customs declaration documents'), # 导出报关文件
limit=1) 'type': 'ir.actions.act_window',
vals = { 'view_mode': 'form',
'bl_id': self.id, 'res_model': 'export.bl.big.package.xlsx.wizard',
'bl_count': 1, 'target': 'new',
'current_status': node_obj.id, 'context': {'active_id': self.ids, 'default_action_type': '报关文件'},
'update_status': push_node_obj.id,
'process_time': utc_time,
'is_ok': True
} }
wizard_obj = self.env['batch.input.ship.package.status.wizard'].sudo().create(vals)
wizard_obj.change_ship_package_ids()
wizard_obj = wizard_obj.with_context(dict(self._context, active_id=self.id))
wizard_obj.submit()
def push_clear_customs_end(self, utc_time): # 导出清关提单文件
# 创建向导 def action_export_bl_attachment_png(self):
push_node_obj = self.env['cc.node'].sudo().search( return {
[('node_type', '=', 'package'), ('tk_code', '=', 'cb_imcustoms_finished')], limit=1) 'name': _('Export Customs Clearance Bill of Lading'), # 导出清关提单文件
node_obj = self.env['cc.node'].sudo().search([('node_type', '=', 'package'), ('seq', '<', push_node_obj.seq)], 'type': 'ir.actions.act_window',
order='seq desc', 'view_mode': 'form',
limit=1) 'res_model': 'export.bl.big.package.xlsx.wizard',
vals = { 'target': 'new',
'bl_id': self.id, 'context': {'active_id': self.ids, 'default_action_type': '清关提单文件'},
'bl_count': 1, }
'current_status': node_obj.id,
'update_status': push_node_obj.id, def batch_input_ship_package_status_wizard(self):
'process_time': utc_time, """批量更新小包状态"""
'is_ok': True return {
'name': _('Update the status of the small package'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'batch.input.ship.package.status.wizard',
'target': 'new',
'context': {'active_id': self.ids, 'default_is_batch': True, 'default_bl_id': self.ids}
}
def action_link_transfer_bl_no(self):
"""
批量关联转单号
"""
return {
'name': _('Link Transfer B/L No'),
'type': 'ir.actions.act_window',
'views': [[False, "form"]],
'res_model': 'batch.update.transfer.bl.no.wizard',
'target': 'new',
'context': {}
}
def batch_update_bl_status_wizard(self):
"""批量更新提单状态"""
# 检查关务提单状态必须是同一个
customs_clearance_status_list = self.filtered(lambda x: x.customs_clearance_status.id).mapped(
'customs_clearance_status.id')
if len(customs_clearance_status_list) == 0:
# 请先配置默认的提单节点类型的清关节点
raise ValidationError(
_('Please configure the default customs clearance status of the bill of loading node type first.'))
if len(customs_clearance_status_list) > 1:
raise ValidationError(_('The customs clearance status of the selected bill of loading must be the same.'))
# 最近操作时间取最晚的一条提单状态操作时间。
last_process_time = \
sorted(self.filtered(lambda x: x.customs_clearance_status.id).mapped('process_time'), reverse=True)[0]
return {
'name': _('Update the status of the bill of loading'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'update.bl.status.wizard',
'target': 'new',
'context': {'active_id': self.ids, 'default_is_batch': True, 'default_last_process_time': last_process_time,
'default_current_status': customs_clearance_status_list[0]}
} }
wizard_obj = self.env['batch.input.ship.package.status.wizard'].sudo().create(vals)
wizard_obj.change_ship_package_ids()
wizard_obj = wizard_obj.with_context(dict(self._context, active_id=self.id))
# print(wizard_obj.get_order())
wizard_obj.submit()
# 增加一个清关进度的业务对象,继承自models.Model, 用于管理业务数据.业务数据包括提单号、清关节点(业务对象)、进度日期、进度描述、更新人 # 增加一个清关进度的业务对象,继承自models.Model, 用于管理业务数据.业务数据包括提单号、清关节点(业务对象)、进度日期、进度描述、更新人
......
...@@ -16,6 +16,32 @@ class CommonCommon(models.Model): ...@@ -16,6 +16,32 @@ class CommonCommon(models.Model):
_name = 'common.common' _name = 'common.common'
_description = u'公用基础类' _description = u'公用基础类'
def process_num(self, input_str):
"""
处理导入
:param input_str:
:return:
"""
if input_str:
return str(input_str).replace('\\', '-').replace('、、', '').replace('&',
' ').replace(
'>',
'').replace('<',
'').replace(
'?', '').replace('!', '')
return input_str
def process_str(self, input_str):
"""
处理导入的字符串
:param input_str:
:return:
"""
if input_str:
return self.process_num(str(input_str)).replace('.0', '').strip()
return input_str
# 去杠去空格转大写 # 去杠去空格转大写
def process_match_str(self, input_str): def process_match_str(self, input_str):
......
...@@ -4,6 +4,7 @@ export_bl_big_package_xlsx_wizard_group_user,export_bl_big_package_xlsx_wizard_g ...@@ -4,6 +4,7 @@ export_bl_big_package_xlsx_wizard_group_user,export_bl_big_package_xlsx_wizard_g
associate_pallet_wizard_group_user,associate_pallet_wizard_group_user,ccs_base.model_associate_pallet_wizard,base.group_user,1,1,1,1 associate_pallet_wizard_group_user,associate_pallet_wizard_group_user,ccs_base.model_associate_pallet_wizard,base.group_user,1,1,1,1
add_exception_info_wizard_group_user,add_exception_info_wizard_group_user,ccs_base.model_add_exception_info_wizard,base.group_user,1,1,1,1 add_exception_info_wizard_group_user,add_exception_info_wizard_group_user,ccs_base.model_add_exception_info_wizard,base.group_user,1,1,1,1
update_bl_status_wizard_group_user,update_bl_status_wizard_group_user,ccs_base.model_update_bl_status_wizard,base.group_user,1,1,1,1 update_bl_status_wizard_group_user,update_bl_status_wizard_group_user,ccs_base.model_update_bl_status_wizard,base.group_user,1,1,1,1
batch_update_transfer_bl_no_wizard_group_user,batch_update_transfer_bl_no_wizard_group_user,ccs_base.model_batch_update_transfer_bl_no_wizard,base.group_user,1,1,1,1
access_group_user_common_common,access_group_user_common_common,model_common_common,base.group_user,1,1,1,1 access_group_user_common_common,access_group_user_common_common,model_common_common,base.group_user,1,1,1,1
......
...@@ -13,18 +13,8 @@ export const BigPackageLinkPallet = { ...@@ -13,18 +13,8 @@ export const BigPackageLinkPallet = {
this.http = useService('http'); this.http = useService('http');
this.fileInput = useRef('fileInput'); this.fileInput = useRef('fileInput');
this.root = useRef("root"); this.root = useRef("root");
// this.isBigPackage = this.model.rootParams.resModel === "cc.big.package";
// this.is_link = this.user.hasGroup("ccs_base.group_clearance_of_customs_manager");
// console.log('ccs isBigPackage:' + this.isBigPackage)
// console.log('ccs is_link:' + this.is_link)
}, },
// displayLink() {
// console.log('ccs flag:' + this.isBigPackage && this.is_link)
// // 是大包的对象以及有清关清理的权限才显示按钮
// return this.isBigPackage && this.is_link;
// },
async onLinkPalletClick() { async onLinkPalletClick() {
// 点击按钮弹出关联托盘的向导 // 点击按钮弹出关联托盘的向导
const records = this.model.root.selection; const records = this.model.root.selection;
......
/** @odoo-module */
import {useService} from '@web/core/utils/hooks';
const {useRef, useEffect, useState} = owl;
export const LinkTransferBlNo = {
setup() {
this._super();
this.actionService = useService('action');
this.notification = useService('notification');
this.orm = useService('orm');
this.http = useService('http');
this.fileInput = useRef('fileInput');
this.root = useRef("root");
},
async onTransferBlNoClick() {
// 点击按钮弹出批量关联转单号的向导
const records = this.model.root.selection;
const recordIds = records.map((a) => a.resId);
const action = await this.orm.call('cc.bl', 'action_link_transfer_bl_no', [recordIds]);
this.actionService.doAction(action);
},
};
/** @odoo-module */
import {BigPackageLinkPallet} from '../mixins/link_pallet';
import {registry} from '@web/core/registry';
import {patch} from '@web/core/utils/patch';
import {useService} from '@web/core/utils/hooks';
import {listView} from "@web/views/list/list_view";
import {ListController} from "@web/views/list/list_controller";
const {onWillStart} = owl;
export class BigPackageListController extends ListController {
setup() {
super.setup();
console.log('----------引用成功')
this.orm = useService('orm');
this.actionService = useService('action');
this.rpc = useService("rpc");
this.user = useService("user");
this.isBigPackage = this.model.rootParams.resModel === "cc.big.package";
console.log('ccs isBigPackage:' + this.isBigPackage)
onWillStart(async () => {
this.is_link = await this.user.hasGroup("ccs_base.group_clearance_of_customs_manager");
console.log('ccs is_link:' + this.is_link)
});
}
displayLink() {
console.log('ccs flag:' + this.isBigPackage && this.is_link)
// 是大包的对象以及有清关清理的权限才显示按钮
return this.isBigPackage && this.is_link;
}
displayTransferBlNo() {
// 大包页面永远不显示“关联转单号”按钮
return false;
}
}
patch(BigPackageListController.prototype, 'big_package_list_controller_link_pallet', BigPackageLinkPallet);
registry.category('views').add('cc_big_package_tree', {
...listView,
buttonTemplate: 'ccs_base.ListButtons',
Controller: BigPackageListController
});
\ No newline at end of file
/** @odoo-module */
import {LinkTransferBlNo} from '../mixins/link_transfer_bl_no';
import {registry} from '@web/core/registry';
import {patch} from '@web/core/utils/patch';
import {useService} from '@web/core/utils/hooks';
import {listView} from "@web/views/list/list_view";
import {ListController} from "@web/views/list/list_controller";
const {onWillStart} = owl;
export class BlListController extends ListController {
setup() {
super.setup();
console.log('----------引用成功')
this.orm = useService('orm');
this.actionService = useService('action');
this.rpc = useService("rpc");
this.user = useService("user");
this.isBl = this.model.rootParams.resModel === "cc.bl";
console.log('ccs isBl:' + this.isBl)
onWillStart(async () => {
this.can_link_transfer_bl_no = await this.user.hasGroup("ccs_base.group_clearance_of_customs_user");
console.log('ccs can_link_transfer_bl_no:' + this.can_link_transfer_bl_no)
});
}
displayLink() {
// 提单页面永远不显示“关联托盘”按钮
return false;
}
displayTransferBlNo() {
console.log('ccs flag:' + this.isBl && this.can_link_transfer_bl_no)
// 是提单的对象以及有清关用户的权限才显示按钮
return this.isBl && this.can_link_transfer_bl_no;
}
}
patch(BlListController.prototype, 'link_transfer_bl_no', LinkTransferBlNo);
registry.category('views').add('cc_bl_tree', {
...listView,
buttonTemplate: 'ccs_base.ListButtons',
Controller: BlListController
});
\ No newline at end of file
/** @odoo-module */ /** @odoo-module */
import {LinkTransferBlNo} from '../mixins/link_transfer_bl_no';
import {BigPackageLinkPallet} from '../mixins/link_pallet'; import {BigPackageLinkPallet} from '../mixins/link_pallet';
import {registry} from '@web/core/registry'; import {registry} from '@web/core/registry';
...@@ -10,7 +11,7 @@ import {ListController} from "@web/views/list/list_controller"; ...@@ -10,7 +11,7 @@ import {ListController} from "@web/views/list/list_controller";
const {onWillStart} = owl; const {onWillStart} = owl;
export class BigPackageListController extends ListController { export class BlListController extends ListController {
setup() { setup() {
super.setup(); super.setup();
console.log('----------引用成功') console.log('----------引用成功')
...@@ -18,14 +19,24 @@ export class BigPackageListController extends ListController { ...@@ -18,14 +19,24 @@ export class BigPackageListController extends ListController {
this.actionService = useService('action'); this.actionService = useService('action');
this.rpc = useService("rpc"); this.rpc = useService("rpc");
this.user = useService("user"); this.user = useService("user");
this.isBl = this.model.rootParams.resModel === "cc.bl";
this.isBigPackage = this.model.rootParams.resModel === "cc.big.package"; this.isBigPackage = this.model.rootParams.resModel === "cc.big.package";
console.log('ccs isBigPackage:' + this.isBigPackage) console.log('ccs isBl:' + this.isBl)
onWillStart(async () => { onWillStart(async () => {
this.can_link_transfer_bl_no = await this.user.hasGroup("ccs_base.group_clearance_of_customs_user");
this.is_link = await this.user.hasGroup("ccs_base.group_clearance_of_customs_manager"); this.is_link = await this.user.hasGroup("ccs_base.group_clearance_of_customs_manager");
console.log('ccs can_link_transfer_bl_no:' + this.can_link_transfer_bl_no)
console.log('ccs is_link:' + this.is_link) console.log('ccs is_link:' + this.is_link)
}); });
} }
displayTransferBlNo() {
console.log('ccs flag:' + this.isBl && this.can_link_transfer_bl_no)
// 是提单的对象以及有清关用户的权限才显示按钮
return this.isBl && this.can_link_transfer_bl_no;
}
displayLink() { displayLink() {
console.log('ccs flag:' + this.isBigPackage && this.is_link) console.log('ccs flag:' + this.isBigPackage && this.is_link)
// 是大包的对象以及有清关清理的权限才显示按钮 // 是大包的对象以及有清关清理的权限才显示按钮
...@@ -34,11 +45,19 @@ export class BigPackageListController extends ListController { ...@@ -34,11 +45,19 @@ export class BigPackageListController extends ListController {
} }
patch(BlListController.prototype, 'link_transfer_bl_no', LinkTransferBlNo);
registry.category('views').add('cc_bl_tree', {
...listView,
buttonTemplate: 'ccs_base.ListButtons',
Controller: BlListController
});
patch(BigPackageListController.prototype, 'big_package_list_controller_link_pallet', BigPackageLinkPallet); patch(BigPackageListController.prototype, 'big_package_list_controller_link_pallet', BigPackageLinkPallet);
registry.category('views').add('cc_big_package_tree', { registry.category('views').add('cc_big_package_tree', {
...listView, ...listView,
buttonTemplate: 'ccs_base.ListButtons', buttonTemplate: 'ccs_base.ListButtons',
Controller: BigPackageListController Controller: BigPackageListController
}); });
\ No newline at end of file
...@@ -7,5 +7,10 @@ ...@@ -7,5 +7,10 @@
Link Pallet Link Pallet
</button> </button>
</xpath> </xpath>
<xpath expr="//button[hasclass('o_list_button_add')]" position="after">
<button t-if="displayTransferBlNo()" type="button" class="d-none d-md-inline o_button_transfer_bl_no btn btn-primary mx-1" t-on-click.prevent="onTransferBlNoClick">
Link Transfer B/L No
</button>
</xpath>
</t> </t>
</templates> </templates>
...@@ -7,12 +7,13 @@ ...@@ -7,12 +7,13 @@
<field name="name">tree.cc.bl</field> <field name="name">tree.cc.bl</field>
<field name="model">cc.bl</field> <field name="model">cc.bl</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Bill of Loading" decoration-warning="is_cancel==True"> <tree string="Bill of Loading" decoration-warning="is_cancel==True" js_class="cc_bl_tree">
<field optional="show" name="state" string="Status" widget="badge" decoration-info="state=='draft'" <field optional="show" name="state" string="Status" widget="badge" decoration-info="state=='draft'"
decoration-primary="state=='ccing'" decoration-success="state=='done'"/> decoration-primary="state=='ccing'" decoration-success="state=='done'"/>
<field optional="show" name="customs_clearance_status" string="Customs Clearance Status"/> <field optional="show" name="customs_clearance_status" string="Customs Clearance Status"/>
<field optional="show" name="bl_no" string="Bill of Loading No."/> <field optional="show" name="bl_no" string="Bill of Loading No."/>
<field optional="show" name="bl_date" string="B/L Date"/> <field optional="show" name="bl_date" string="B/L Date"/>
<field optional="hide" name="transfer_bl_no" string="Transfer Bill of Loading No."/>
<field optional="show" name="customer_id" string="Customer"/> <field optional="show" name="customer_id" string="Customer"/>
<field optional="show" name="customs_bl_no" string="Customs Bill of Loading No."/> <field optional="show" name="customs_bl_no" string="Customs Bill of Loading No."/>
<field optional="hide" name="big_package_sell_country" string="Sell Country"/> <field optional="hide" name="big_package_sell_country" string="Sell Country"/>
...@@ -120,6 +121,7 @@ ...@@ -120,6 +121,7 @@
<group> <group>
<group> <group>
<field name="bl_date" string="B/L Date"/> <field name="bl_date" string="B/L Date"/>
<field name="transfer_bl_no"/>
<field name="customer_id" string="Customer"/> <field name="customer_id" string="Customer"/>
<field name="customs_bl_no" string="Customs Bill of Loading No."/> <field name="customs_bl_no" string="Customs Bill of Loading No."/>
<field name="big_package_sell_country" string="Sell Country"/> <field name="big_package_sell_country" string="Sell Country"/>
...@@ -163,7 +165,7 @@ ...@@ -163,7 +165,7 @@
<!-- </page>--> <!-- </page>-->
<page string="Attachments"> <page string="Attachments">
<group> <group>
<field name="bl_attachment_ids" string="B/L Attachments" widget="many2many_binary" <field name="bl_attachment_ids" string="B/L Attachments" widget="many2many_attachment_preview"
readonly="1"/> readonly="1"/>
<field name="cc_attachment_ids" string="CC Attachments"/> <field name="cc_attachment_ids" string="CC Attachments"/>
</group> </group>
...@@ -405,7 +407,7 @@ ...@@ -405,7 +407,7 @@
<field name="state">code</field> <field name="state">code</field>
<field name="code"> <field name="code">
if records: if records:
action = records.export_bl_attachment_png() action = records.action_export_bl_attachment_png()
</field> </field>
</record> </record>
......
...@@ -146,7 +146,7 @@ ...@@ -146,7 +146,7 @@
</page> </page>
<page string="Invoice Attachments"> <page string="Invoice Attachments">
<field name="invoice_attachment_ids" string="Invoice Attachments" <field name="invoice_attachment_ids" string="Invoice Attachments"
widget="many2many_binary"/> widget="many2many_attachment_preview"/>
</page> </page>
<page string="Sender Info"> <page string="Sender Info">
<group> <group>
......
...@@ -149,7 +149,7 @@ ...@@ -149,7 +149,7 @@
</page> </page>
<page string="Invoice Attachments"> <page string="Invoice Attachments">
<field name="invoice_attachment_ids" string="Invoice Attachments" <field name="invoice_attachment_ids" string="Invoice Attachments"
widget="many2many_binary"/> widget="many2many_attachment_preview"/>
</page> </page>
<page string="Sender Info"> <page string="Sender Info">
<group> <group>
......
...@@ -5,4 +5,5 @@ from . import export_bl_big_package_xlsx_wizard ...@@ -5,4 +5,5 @@ from . import export_bl_big_package_xlsx_wizard
from . import associate_pallet_wizard from . import associate_pallet_wizard
from . import add_exception_info_wizard from . import add_exception_info_wizard
from . import update_bl_status_wizard from . import update_bl_status_wizard
from . import batch_update_transfer_bl_no_wizard
import base64
import io
from odoo import models, fields, _
from odoo.exceptions import ValidationError
try:
import xlrd
import xlwt
except ImportError:
xlrd = None
xlwt = None
class BatchUpdateTransferBlNoWizard(models.TransientModel):
_name = 'batch.update.transfer.bl.no.wizard'
_description = 'Batch Update Transfer B/L No Wizard'
report_file_ids = fields.Many2many('ir.attachment', 'batch_update_transfer_bl_no_attachment_rel',
string='B/L List') # 提单清单
error_file_ids = fields.Many2many('ir.attachment', 'batch_update_transfer_bl_no_error_attachment_rel', 'wizard_id',
'error_file_id',
string='Error Data') # 异常数据
file_name = fields.Char('File Name') # 文件名称
def get_file_path(self, report_file_ids):
"""
Get the content of the excel file
:return:
"""
if report_file_ids.name[-3:] == 'xls' or report_file_ids.name[-4:] == 'xlsx':
data = False
report_path = report_file_ids._full_path(report_file_ids.store_fname)
if report_file_ids.name[-3:] == 'xls':
data = xlrd.open_workbook(report_path, formatting_info=True)
elif report_file_ids.name[-4:] == 'xlsx':
data = xlrd.open_workbook(report_path)
return data
else:
raise ValidationError(_('Only excel files can be uploaded!')) # 只能上传excel文件!
def check_import_template(self, report_file_ids):
"""
Check if the template header is correct
:return:
"""
if self.report_file_ids:
try:
data = self.get_file_path(report_file_ids[0])
first_table = data.sheets()[0]
excel_header = first_table.row_values(0)
except Exception as e:
raise ValidationError(
_('Please check if the import file and content are correct, and import according to the template file!')) # 请检查导入文件和内容是否正确,请根据模板文件导入!
header = [_('B/L No'), _('Transfer B/L No')]
str_header = ','.join(header)
if not excel_header == header:
raise ValidationError(_('File error, the content order should be: %s') % str_header) # 文件错误,内容顺序为:%s
def init_importfile(self):
"""
Initialize the imported file and convert it into a unified array format
:return:
"""
common_obj = self.env['common.common'].sudo()
data = self.get_file_path(self.report_file_ids[0])
order_list = []
first_table = data.sheets()[0]
for i in range(1, first_table.nrows):
line = first_table.row_values(i)
bl_no = common_obj.process_str(line[0]) # 提单号
transfer_bl_no = common_obj.process_str(line[1]) # 转单号
order_list.append({
'bl_no': bl_no, # 提单号
'bl_id': False, # 提单对象
'transfer_bl_no': transfer_bl_no, # 转单号
'error_remark': '' # 错误信息
})
return order_list
def check_list_import(self, order_list):
"""
Check normal and abnormal data
:param order_list:
:return:
"""
error_list = [] # 异常数据
pass_list = [] # 正常数据
# Collect all B/L No and Transfer B/L No
all_bl_no = set(item['bl_no'] for item in order_list if item.get('bl_no'))
all_transfer_bl_no = set(item['transfer_bl_no'] for item in order_list if item.get('transfer_bl_no'))
is_error = False
for order_item in order_list:
cause_arr = []
bl_no = self.env['common.common'].sudo().process_match_str(order_item.get('bl_no'))
transfer_bl_no = self.env['common.common'].sudo().process_match_str(order_item.get('transfer_bl_no'))
if not bl_no:
cause_arr.append(_('B/L No is required')) # 提单号必填
else:
bl_obj = self.env['cc.bl'].sudo().deal_bl_no(bl_no)
if bl_obj:
order_item['bl_id'] = bl_obj.id
else:
cause_arr.append(_('This B/L No does not exist in the system')) # 该提单号在系统不存在
if not transfer_bl_no:
cause_arr.append(_('Transfer B/L No is required')) # 转单号必填
else:
if bl_no == transfer_bl_no:
cause_arr.append(_('Transfer B/L No. cannot be the same as B/L No.')) # 转单号不能与提单号重复
# Check if Transfer B/L No already exists in the system
bl_obj = self.env['cc.bl'].sudo().deal_bl_no_and_transfer_bl_no(transfer_bl_no)
if bl_obj and self.env['common.common'].sudo().process_match_str(bl_obj.bl_no) != bl_no:
cause_arr.append(
_('Transfer B/L No. cannot be the same as B/L No. or Transfer B/L No.')) # 转单号不能与提单号或转单号重复
# Check if any B/L No equals any Transfer B/L No, or vice versa
if (bl_no and bl_no in all_transfer_bl_no) or (transfer_bl_no and transfer_bl_no in all_bl_no):
cause_arr.append(_('B/L No and Transfer B/L No are duplicated')) # 提单号和转单号有重复
if len(cause_arr) > 0:
is_error = True
order_item['error_remark'] = ','.join(cause_arr)
error_list.append(order_item)
else:
pass_list.append(order_item)
error_list.append(order_item)
return pass_list, error_list, is_error
def error_export(self, error_msg):
"""
Export error records
:return:
"""
# Generate excel file for error data
wb = xlwt.Workbook(encoding='utf-8')
# Initialize style
style = xlwt.XFStyle()
style.num_format_str = 'yyyy/mm/dd'
# Create font for style
font = xlwt.Font()
font.name = 'Times New Roman'
font.bold = True
style.font = font
ws = wb.add_sheet('sheet1', cell_overwrite_ok=True)
header = [_('B/L No'), _('Transfer B/L No'), _('Error Reason')] # ['提单号', '转单号', '异常原因']
col_index = 0
for title in header:
ws.write(0, col_index, title, style)
ws.col(len(header) - col_index - 1).width = 256 * 20 # around 256 pixels
col_index += 1
index = 0
for item in error_msg:
ws.write(1 + index, 0, item['bl_no'])
ws.write(1 + index, 1, item['transfer_bl_no'])
ws.write(1 + index, 2, item['error_remark'])
index += 1
buf = io.BytesIO()
wb.save(buf)
buf.seek(0)
data = base64.encodebytes(buf.read())
buf.close()
self.file_name = '%s.%s' % (_('Error Data'), "xls")
# 将base64编码的字符串保存到Odoo的binary字段
attachment = self.env['ir.attachment'].sudo().create({
'datas': data,
'type': 'binary',
'name': self.file_name,
'store_fname': self.file_name,
'res_model': self._name, # 用 wizard 的模型名
'res_id': self.id, # 关联到当前 wizard 记录
})
self.error_file_ids = [(6, 0, attachment.ids)]
return {
'type': 'ir.actions.act_window',
'name': _('Error Data'),
'res_model': self._name,
'view_mode': 'form',
'res_id': self.id,
'context': {'default_error_file_ids': [(6, 0, attachment.ids)],
'default_report_file_ids': [(6, 0, self.report_file_ids.ids)]},
'target': 'new',
}
def submit(self):
report_file_ids = self.report_file_ids
if report_file_ids and len(report_file_ids) > 0:
if len(report_file_ids) > 1:
raise ValidationError(u'Only one template file can be uploaded at a time!') # 一次只能上传一个模板文件!
self.check_import_template(report_file_ids) # Check template header
# Convert to array
order_list = self.init_importfile()
# Check normal and error data
pass_list, error_list, is_error = self.check_list_import(order_list)
# Export error data
if len(error_list) > 0 and is_error:
return self.error_export(error_list)
if len(pass_list) > 0 and not is_error:
for item in pass_list:
item.pop('error_remark') if item.get('error_remark') or item.get(
'error_remark') == '' else False
if item.get('bl_id'):
self.env['cc.bl'].sudo().browse(item['bl_id']).write({'transfer_bl_no': item['transfer_bl_no']})
else:
raise ValidationError(
_('Please upload the B/L data file to be imported first!')) # 请先上传需要更新的提单数据文件!
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Batch Link Transfer B/L No Wizard View -->
<record id="view_batch_update_transfer_bl_no_wizard" model="ir.ui.view">
<field name="name">batch.update.transfer.bl.no.wizard.form</field>
<field name="model">batch.update.transfer.bl.no.wizard</field>
<field name="arch" type="xml">
<form string="Batch Link Transfer B/L No"> <!-- 批量关联转单号 -->
<group>
<field name="report_file_ids" widget="many2many_attachment_preview"/>
<label for="error_file_ids" attrs="{'invisible': [('error_file_ids', '=', [])]}"/>
<div attrs="{'invisible': [('error_file_ids', '=', [])]}">
<field name="error_file_ids" widget="many2many_attachment_preview" readonly="1"
attrs="{'invisible': [('error_file_ids', '=', [])]}"/>
<br/>
<!-- 有异常数据时,请下载异常数据文件,根据提示处理好数据,再导入! -->
<span class="label label-warning">
If there is abnormal data, please download the error file, fix the data as prompted, and re-import!
</span>
</div>
</group>
<group>
<!-- 模板下载 -->
<a href="/ccs_base/static/template/transfer_bl_no_template.xlsx?v=20250715001">
Download Template
</a>
<!-- 提示:请务必按照模板填写信息,否则系统无法识别。 -->
<span style="color:red;font-size:15px;">Tip: Please fill in the information strictly according to the template, otherwise the system will not recognize it.
</span>
</group>
<footer>
<button name="submit" type="object"
string="Import" class="oe_highlight"/> <!-- 导入 -->
<button special="cancel" string="Close"/> <!-- 关闭 -->
</footer>
</form>
</field>
</record>
</data>
</odoo>
\ No newline at end of file
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# 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, fields
from odoo.exceptions import Warning, ValidationError
class ExportBlBigPackageXlsxWizard(models.TransientModel): class ExportBlBigPackageXlsxWizard(models.TransientModel):
...@@ -20,7 +19,12 @@ class ExportBlBigPackageXlsxWizard(models.TransientModel): ...@@ -20,7 +19,12 @@ class ExportBlBigPackageXlsxWizard(models.TransientModel):
return self.env['cc.bl'].sudo().browse(order_id) return self.env['cc.bl'].sudo().browse(order_id)
# 增加选择类型字段, 是否分大包导出 # 增加选择类型字段, 是否分大包导出
select_type = fields.Selection([('yes', 'YES'), ('no', 'NO')], string='Whether to export in Big packages') select_type = fields.Selection([('yes', 'YES'), ('no', 'NO')], string='Whether to export in Big packages',
default='no')
# 增加文件命名类型字段。导出的表格命名,可勾选。默认为提单号命名(现在就是按提单号命名),若勾选了转单号,则按转单号命名
file_name_type = fields.Selection([('bl_no', 'B/L No'), ('transfer_bl_no', 'Transfer B/L No')],
string='File Name Type', default='bl_no')
action_type = fields.Char(string='Action Type', default='报关文件')
def submit(self): def submit(self):
""" """
...@@ -28,8 +32,15 @@ class ExportBlBigPackageXlsxWizard(models.TransientModel): ...@@ -28,8 +32,15 @@ class ExportBlBigPackageXlsxWizard(models.TransientModel):
""" """
order_obj = self.get_order() order_obj = self.get_order()
arr = [item.id for item in order_obj] arr = [item.id for item in order_obj]
return { if self.action_type == '报关文件':
'type': 'ir.actions.act_url', return {
'url': '/export/bl/package/xls/%s/%s' % (arr, self.select_type), 'type': 'ir.actions.act_url',
'target': 'new', 'url': '/export/bl/package/xls/%s/%s/%s' % (arr, self.select_type, self.file_name_type),
} 'target': 'new',
}
else:
return {
'type': 'ir.actions.act_url',
'url': '/export/flight_png/xls/%s/%s' % (arr, self.file_name_type),
'target': 'new',
}
...@@ -12,8 +12,10 @@ ...@@ -12,8 +12,10 @@
<form string="导出报关文件"> <form string="导出报关文件">
<sheet> <sheet>
<group> <group>
<field name="select_type" required="1"/> <field name="select_type"
attrs="{'invisible': [('action_type', '!=', '报关文件')], 'required': [('action_type', '=', '报关文件')]}"/>
<field name="file_name_type" required="1"/>
<field name="action_type" invisible="1"/>
</group> </group>
<footer> <footer>
<button name="submit" type="object" string="Submit" class="oe_highlight"/> <button name="submit" type="object" string="Submit" class="oe_highlight"/>
......
...@@ -102,8 +102,8 @@ class OrderController(http.Controller): ...@@ -102,8 +102,8 @@ 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_obj = request.env['cc.bl'].sudo().deal_bl_no_and_transfer_bl_no(
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( res['bl_info'] = bl_obj.search_bl_info(
...@@ -149,8 +149,8 @@ class OrderController(http.Controller): ...@@ -149,8 +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_obj = request.env['cc.bl'].sudo().deal_bl_no_and_transfer_bl_no(
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 = {}
......
...@@ -799,20 +799,6 @@ class CcBl(models.Model): ...@@ -799,20 +799,6 @@ class CcBl(models.Model):
return [{'pallet_number': k, 'pallet_usage_time': v} for k, v in pallet_info.items()] return [{'pallet_number': k, 'pallet_usage_time': v} for k, v in pallet_info.items()]
def deal_bl_no(self, bl_no, state_arr=[]):
"""
处理提单号:去掉杠和空格,并转换为小写
:param bl_no:
:return:
"""
processed_bl_no = bl_no.replace('-', '').replace(' ', '').lower()
# 查询所有提单并处理它们的 bl_no
domain = [('state', 'in', state_arr)] if state_arr else []
all_bl_obj = self.env['cc.bl'].sudo().search(domain)
bl_obj = all_bl_obj.filtered(
lambda r: r.bl_no.replace('-', '').replace(' ', '').lower() == processed_bl_no) # 提单
return bl_obj
def try_callback_track(self, max_retries=3, ship_package_ids=[], user_obj=False): def try_callback_track(self, max_retries=3, ship_package_ids=[], user_obj=False):
""" 封装的重试逻辑 """ """ 封装的重试逻辑 """
for i in range(max_retries): for i in range(max_retries):
......
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
Many2Many Attachment Preview
============================
This Module will help to preview the attachments in Many2Many fields.
Configuration
=============
* No additional configurations needed
Company
-------
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__
License
=======
GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3)
(https://www.gnu.org/licenses/agpl-3.0-standalone.html)
Credits
-------
* Developer: (V16) Ajith V,
(V15) Shonima, Contact: odoo@cybrosys.com
Contacts
--------
* Mail Contact : odoo@cybrosys.com
* Website : https://cybrosys.com
Bug Tracker
-----------
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your
issue has already been reported.
Maintainer
==========
.. image:: https://cybrosys.com/images/logo.png
:target: https://cybrosys.com
This module is maintained by Cybrosys Technologies.
For support and more information, please visit `Our Website <https://cybrosys.com/>`__
Further information
===================
HTML Description: `<static/description/index.html>`__
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: Ajith(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from . import models
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: Ajith(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
{
'name': "Many2Many Attachment Preview",
'version': '16.0.1.0.0',
'summary': """Preview the attachment in Many2Many field""",
'description': """"This module helps you to preview the attachments in
Many2Many fields.""",
'category': 'Uncategorized',
'author': 'Cybrosys Techno Solutions',
'company': 'Cybrosys Techno Solutions',
'maintainer': 'Cybrosys Techno Solutions',
'website': "http://www.cybrosys.com",
'depends': ['base', 'sale_management'],
'data': ['views/sale_order_views.xml'],
'assets': {
'web.assets_backend': [
'many2many_attachment_preview/static/src/js/attachment_preview.js',
'many2many_attachment_preview/static/src/xml/attachment_preview.xml',
'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3.5.6/dist/jquery.fancybox.min.css',
'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3.5.6/dist/jquery.fancybox.min.js'
],
},
'license': 'AGPL-3',
'images': ['static/description/banner.png'],
'installable': True,
'auto_install': False,
'application': False,
}
## Module <many2many_attachment_preview>
#### 29.08.2024
#### Version 16.0.1.0.0
#### ADD
- Initial Commit for Many2Many Attachment Preview
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: Ajith(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from . import sale_order
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: Ajith(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import fields, models
class SaleOrder(models.Model):
"""Inherited sale. order class to add a new field for attachments"""
_inherit = 'sale.order'
attachment_ids = fields.Many2many(
comodel_name='ir.attachment',
string="Attachments", help="Add Multiple attachment file")
<div style="background-color: #714B67; min-height: 600px; width: 100%; padding: 15px; position: relative;">
<!-- TITLE BAR -->
<div class="d-flex align-items-center justify-content-between"
style="border-bottom: 1px solid #875A7B; padding: 15px; display: flex; justify-content: space-between; align-items: center;">
<img src="./assets/misc/cybrosys-logo.png" width="42" height="42"
style="width: 42px; height: 42px;"/>
<div>
<div style="color: #7C7BAD; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;"
class="mr-2">
<i class="fa fa-check mr-1"></i>Community
</div>
<div style="color: #875A7B; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;"
class="mr-2">
<i class="fa fa-check mr-1"></i>Enterprise
</div>
<div style="color: #017E84; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;"
class="mr-2">
<i class="fa fa-check mr-1"></i>Odoo.sh
</div>
</div>
</div>
<!-- END OF TITLE BAR -->
<!-- APP HERO -->
<div class="container">
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
<h1 style="color: #FFFFFF; font-weight: bolder; font-size: 50px; text-align: center; margin-top: 50px;">
Many2Many Attachment Preview</h1>
<p style="color:#FFFFFF; padding: 8px 15px; text-align: center; font-size: 24px;">
Preview the Attachments.</p>
<!-- END OF APP HERO -->
<img src="./assets/screenshots/hero.gif"
style="width: 75%; height: auto; position: absolute; margin-left: auto; margin-right: auto; top: 45%; left: 12%; right: auto;"/>
</div></div></div>
</div>
<!-- NAVIGATION SECTION -->
<div class="d-flex align-items-center"
style="border-bottom: 2px solid #714B67; padding: 15px 0px; margin-top: 300px;">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="./assets/misc/compass.png"/>
</div>
<h2 class="mt-2"
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
Explore This
Module</h2>
</div>
<div class="row my-4" style="font-family: 'Montserrat', sans-serif;">
<div class="col-sm-12 col-md-6 my-3">
<a href="#overview">
<div class="d-flex justify-content-between align-items-center"
style="background-color: #f5f5f5; padding: 30px; width: 100%;">
<div>
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Overview</span>
<span
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">Learn
more about this
module</span>
</div>
<img src="./assets/misc/right-arrow.png" width="36"
height="36"/>
</div>
</a>
</div>
<div class="col-sm-12 col-md-6 my-3">
<a href="#features">
<div class="d-flex justify-content-between align-items-center"
style="background-color: #f5f5f5; padding: 30px; width: 100%;">
<div>
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Features</span>
<span
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">View
features of this
module</span>
</div>
<img src="./assets/misc/right-arrow.png" width="36"
height="36"/>
</div>
</a>
</div>
<div class="col-sm-12 col-md-6 my-3">
<a href="#screenshots">
<div class="d-flex justify-content-between align-items-center"
style="background-color: #f5f5f5; padding: 30px; width: 100%;">
<div>
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Screenshots</span>
<span
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">View
screenshots for this
module</span>
</div>
<img src="./assets/misc/right-arrow.png" width="36"
height="36"/>
</div>
</a>
</div>
</div>
<!-- END OF NAVIGATION SECTION -->
<!-- OVERVIEW SECTION -->
<div class="d-flex align-items-center"
style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="overview">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="./assets/misc/pie-chart.png"/>
</div>
<h2 class="mt-2"
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
Overview
</h2>
</div>
<div class="row"
style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;">
<div class="col-sm-12 py-4">
This module helps you preview of the attachments (like pdf, png, jpg
etc.) which are uploaded in a Many2Many field.
</div>
</div>
<!-- END OF OVERVIEW SECTION -->
<!-- FEATURES SECTION -->
<div class="d-flex align-items-center"
style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="features">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="./assets/misc/features.png"/>
</div>
<h2 class="mt-2"
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
Features
</h2>
</div>
<div class="row"
style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;">
<div class="col-sm-12 col-md-6">
<div class="d-flex align-items-center"
style="margin-top: 40px; margin-bottom: 40px">
<img src="./assets/misc/check-box.png" class="mr-2"/>
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Community &amp; Enterprise Support</span>
<p
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;">
Available in Odoo 16.0 Community and Enterprise.</p>
</div>
<div class="d-flex align-items-center"
style="margin-top: 30px; margin-bottom: 30px">
<img src="./assets/misc/check-box.png" class="mr-2"/>
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> Available pdf, png, jpg (image formats) etc.</span>
<p
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;">
Available pdf, png, jpg (image formats) etc.</p>
</div>
</div>
</div>
<!-- END OF FEATURES SECTION -->
<!-- SCREENSHOTS SECTION -->
<div class="d-flex align-items-center"
style="border-bottom: 2px solid #714B67; padding: 15px 0px;"
id="screenshots">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="./assets/misc/pictures.png"/>
</div>
<h2 class="mt-2"
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
Screenshots
</h2>
</div>
<div class="row">
<div class="col-sm-12">
<div style="display: block; margin: 30px auto;">
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">
Can add multiple attachments</h3>
<img src="./assets/screenshots/1.png"
class="img-thumbnail">
</div>
<div style="display: block; margin: 30px auto;">
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">
Clicking on attachment,we can preview the document.</h3>
<img src="./assets/screenshots/2.png"
class="img-thumbnail">
</div>
</div>
</div>
<!-- END OF SCREENSHOTS SECTION -->
<!-- RELATED PRODUCTS -->
<div class="d-flex align-items-center"
style="border-bottom: 2px solid #714B67; padding: 15px 0px;">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="./assets/misc/categories.png"/>
</div>
<h2 class="mt-2"
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
Related
Products
</h2>
</div>
<div class="row">
<div class="col-sm-12">
<div id="demo1" class="row carousel slide" data-ride="carousel">
<!-- The slideshow -->
<div class="carousel-inner" style="padding: 30px;">
<div class="carousel-item" style="min-height: 198.656px;">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/16.0/export_stockinfo_xls/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-radius: 0px;"
src="./assets/modules/export_image.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/16.0/custom_gantt_view/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-radius: 0px;"
src="./assets/modules/gantt_image.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/16.0/sales_credit_limit/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-radius: 0px;"
src="./assets/modules/credit_image.png">
</div>
</a>
</div>
</div>
<div class="carousel-item active"
style="min-height: 198.656px;">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/16.0/base_account_budget/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-radius: 0px;"
src="./assets/modules/budget_image.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/16.0/product_to_quotation/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-radius: 0px;"
src="./assets/modules/quotation_image.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/16.0/employee_documents_expiry/">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-radius: 0px;"
src="./assets/modules/employee_image.png">
</div>
</a>
</div>
</div>
</div>
<!-- Left and right controls -->
<a class="carousel-control-prev" href="#demo1" data-slide="prev"
style="width:35px; color:#000"> <span
class="carousel-control-prev-icon"><i
class="fa fa-chevron-left"
style="font-size:24px"></i></span>
</a> <a class="carousel-control-next" href="#demo1"
data-slide="next" style="width:35px; color:#000">
<span class="carousel-control-next-icon"><i
class="fa fa-chevron-right"
style="font-size:24px"></i></span>
</a>
</div>
</div>
</div>
<!-- END OF RELATED PRODUCTS -->
<!-- OUR SERVICES -->
<div class="d-flex align-items-center"
style="border-bottom: 2px solid #714B67; padding: 15px 0px;">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="./assets/misc/star.png"/>
</div>
<h2 class="mt-2"
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
Our Services
</h2>
</div>
<div class="container my-5">
<div class="row">
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #1dd1a1 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/cogs.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Customization</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #ff6b6b !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/wrench.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Implementation</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #6462CD !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/lifebuoy.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Support</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #ffa801 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/user.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Hire
Odoo
Developer</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #54a0ff !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/puzzle.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Integration</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #6d7680 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/update.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Migration</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #786fa6 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/consultation.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Consultancy</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #f8a5c2 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/training.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Implementation</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #e6be26 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/license.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Licensing Consultancy</h6>
</div>
</div>
</div>
<!-- END OF END OF OUR SERVICES -->
<!-- OUR INDUSTRIES -->
<div class="d-flex align-items-center"
style="border-bottom: 2px solid #714B67; padding: 15px 0px;">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="./assets/misc/corporate.png"/>
</div>
<h2 class="mt-2"
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
Our
Industries
</h2>
</div>
<div class="container my-5">
<div class="row">
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/trading-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Trading
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Easily procure
and
sell your products</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/pos-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
POS
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Easy
configuration
and convivial experience</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/education-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Education
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
A platform for
educational management</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/manufacturing-black.png"
class="img-responsive mb-3" height="48px"
width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Manufacturing
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Plan, track and
schedule your operations</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/ecom-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
E-commerce &amp; Website
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Mobile
friendly,
awe-inspiring product pages</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/service-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Service Management
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Keep track of
services and invoice</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/restaurant-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Restaurant
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Run your bar or
restaurant methodically</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/hotel-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Hotel Management
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
An
all-inclusive
hotel management application</p>
</div>
</div>
</div>
</div>
<!-- END OF END OF OUR INDUSTRIES -->
<!-- SUPPORT -->
<div class="d-flex align-items-center"
style="border-bottom: 2px solid #714B67; padding: 15px 0px;">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="./assets/misc/customer-support.png"/>
</div>
<h2 class="mt-2"
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
Support
</h2>
</div>
<div class="container mt-5">
<div class="row">
<div class="col-sm-12 col-md-6">
<div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;">
<div class="mr-4"
style="background-color: #714B67; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;">
<img src="./assets/misc/support.png" height="48" width="48"
style="width: 42px; height: 42px;"/>
</div>
<div>
<h4>Need Help?</h4>
<p style="line-height: 100%;">Got questions or need help?
Get in touch.</p>
<a href="mailto:odoo@cybrosys.com">
<p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;">
odoo@cybrosys.com</p>
</a>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6">
<div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;">
<div class="mr-4"
style="background-color: #2AC44D; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;">
<img src="./assets/misc/whatsapp.png" height="52" width="52"
style="width: 52px; height: 52px;"/>
</div>
<div>
<h4>WhatsApp</h4>
<p style="line-height: 100%;">Say hi to us on WhatsApp!</p>
<a href="https://api.whatsapp.com/send?phone=918606827707">
<p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;">
+91 86068
27707</p>
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 my-5 d-flex justify-content-center align-items-center">
<img src="./assets/misc/logo.png" width="144" height="31"
style="width:144px; height: 31px; margin-top: 40px;"/>
</div>
</div>
</div>
<!-- END OF SUPPORT -->
\ No newline at end of file
/** @odoo-module **/
import {registry} from "@web/core/registry";
import {Component, useState} from "@odoo/owl";
import {FileInput} from "@web/core/file_input/file_input";
import {standardFieldProps} from "@web/views/fields/standard_field_props";
import {useService} from "@web/core/utils/hooks";
import {useX2ManyCrud} from "@web/views/fields/relational_utils";
/**
* The Many2ManyAttachmentPreview component is designed to manage the preview and handling
* of many2many fields that contain file attachments. It allows users to upload, preview,
* and remove files associated with a record.
*
* @class
* @extends Component
*
* @prop {Object} props - The props object includes all the standard field properties,
* as well as additional properties such as acceptedFileExtensions,
* className, and numberOfFiles.
* @prop {String} props.acceptedFileExtensions - (Optional) A string defining the accepted file
* extensions for uploads.
* @prop {String} props.className - (Optional) A string defining any additional CSS classes
* to be applied.
* @prop {Number} props.numberOfFiles - (Optional) A number representing the maximum number of
* files allowed.
*
* @setup
* @method setup - Initializes services and state for the component, including ORM service,
* notification service, and operations for managing many2many CRUD.
*
* @state {Object} state - Contains component state, including a flag for internal logic handling.
*
* @getter uploadText - Retrieves the label for the upload button, typically the field's label.
*
* @getter files - Returns a list of files associated with the record, including their IDs
* and other relevant data.
*
* @method getUrl(id) - Constructs the URL to access a file attachment by its ID.
* @param {Number} id - The ID of the file attachment.
* @returns {String} - The URL for the file attachment.
*
* @method getExtension(file) - Extracts the file extension from a file's name.
* @param {Object} file - The file object.
* @returns {String} - The file extension.
*
* @method onFileUploaded(files) - Handles the logic after files are uploaded,
* including error handling and saving the records.
* @param {Array} files - An array of uploaded files.
* @returns {Promise<void>}
*
* @method onFileRemove(deleteId) - Handles the logic for removing a file by its ID,
* including updating the records.
* @param {Number} deleteId - The ID of the file to be removed.
* @returns {Promise<void>}
*
* @supportedTypes - Specifies that this component supports "many2many" fields.
*
* @fieldsToFetch - Defines the fields to be fetched from the related records, such as
* 'name' and 'mimetype'.
*
* @registry.category("fields").add("many2many_attachment_preview", Many2ManyAttachmentPreview)
* - Registers the component in the Odoo registry.
*/
export class Many2ManyAttachmentPreview extends Component {
static template = 'many2many_attachment_preview.Many2ManyImageField'
static components = {
FileInput,
};
static props = {
...standardFieldProps,
acceptedFileExtensions: {type: String, optional: true},
className: {type: String, optional: true},
numberOfFiles: {type: Number, optional: true},
};
setup() {
this.orm = useService("orm");
this.notification = useService("notification");
this.operations = useX2ManyCrud(() => this.props.value, true);
this.state = useState({
flag: false,
});
}
get uploadText() {
return this.props.record.fields[this.props.name].string;
}
get files() {
return this.props.record.data[this.props.name].records.map((record) => {
return {
...record.data,
id: record.resId,
};
});
}
getUrl(id) {
return "/web/content/ir.attachment/" + id + "/datas";
}
getExtension(file) {
return file.name.replace(/^.*\./, "");
}
async onFileUploaded(files) {
for (const file of files) {
if (file.error) {
return this.notification.add(file.error, {
title: this.env._t("Uploading error"),
type: "danger",
});
}
await this.operations.saveRecord([file.id]);
}
}
async onFileRemove(deleteId) {
const record = this.props.value.records.find((record) => record.data.id === deleteId);
this.operations.removeRecord(record);
}
}
Many2ManyAttachmentPreview.supportedTypes = ["many2many"];
Many2ManyAttachmentPreview.fieldsToFetch = {
name: {
type: 'char'
},
mimetype: {
type: 'char'
},
}
registry.category("fields").add("many2many_attachment_preview", Many2ManyAttachmentPreview)
<template>
<!--
This template defines a Many2ManyImageField component used to display a list of image attachments
with an option to upload additional files. The component utilizes the Odoo Owl framework.
Structure:
- The outermost `div` has a dynamic class that includes `oe_fileupload` and optionally
a custom class if provided in `props.className`.
- Inside this `div`, there's a container `div` with the class `o_attachments o_attachments_widget`
which holds the list of attached files.
- The files are iterated over using `t-foreach`, where each `file` is rendered using the
`many2many_attachment_preview.image_preview` template.
- If the `readonly` property is not set, an upload section is rendered, allowing the user to
attach more files. The upload button triggers the `FileInput` component with the following attributes:
- `acceptedFileExtensions`: Defines the types of files allowed for upload.
- `multiUpload`: Enables multiple file uploads at once.
- `onUpload.bind`: Binds the `onFileUploaded` method for handling the upload process.
- `resModel`: Specifies the model associated with the uploaded files.
- `resId`: Specifies the record ID. If no ID is provided, it defaults to 0.
- The upload button has a tooltip "Attach" and displays a paperclip icon with a label and upload text.
-->
<t t-name="many2many_attachment_preview.Many2ManyImageField" owl="1">
<div t-attf-class="oe_fileupload {{ props.className ? props.className : ''}}"
aria-atomic="true">
<div class="o_attachments o_attachments_widget">
<t t-foreach="files" t-as="file" t-key="file_index">
<t t-call="many2many_attachment_preview.image_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 o_attach_wiget"
data-tooltip="Attach">
<span class="fa fa-paperclip" aria-label="Attach"/>
<t t-esc="uploadText"/>
</button>
</FileInput>
</div>
</div>
</t>
<!--
This template defines the `image_preview` component used to display individual file attachments
in a many-to-many relationship field within Odoo. It handles various file types such as images,
PDFs, and videos, and provides functionalities for viewing, downloading, and deleting files.
Structure:
- A variable `editable` is set based on the `readonly` property from `props`, determining whether the file is editable.
- The outer `div` uses dynamic classes based on the `editable` state and upload status to provide appropriate styling.
- The inner `div` wraps the file attachment and displays the content based on the file extension:
- For image files (`png`, `jpg`, `jpeg`), an image preview is displayed. The image is wrapped in a link that uses Fancybox to allow zooming.
- For PDF files, the link is set to open in an iframe using Fancybox.
- For video files (`mkv`), the link is configured to play the video in an HTML5 video player using Fancybox.
- The `caption` section displays the file name and extension. Clicking the file name triggers a download.
- If the file is editable, a progress bar is shown during the upload process, and a delete button is provided to remove the file.
- Once the file is uploaded, a checkmark icon indicates successful upload.
- The delete button allows the user to remove the file, triggering the `onFileRemove` function with the file's ID.
-->
<t t-name="many2many_attachment_preview.image_preview" owl="1">
<t t-set="editable" t-value="!props.readonly"/>
<div t-attf-class="o_attachment_widget o_attachment_many2many #{ editable ? 'o_attachment_editable' : '' } #{upload ? 'o_attachment_uploading' : ''}"
t-att-title="file.name">
<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-if="ext=='png' or ext=='jpg' or ext=='jpeg'">
<div class="o_image_box float-start"
t-att-data-tooltip="'Download ' + file.name">
<a t-att-href="getUrl(file.id)"
aria-label="Download"
style="cursor: zoom-in;" data-fancybox="gallery"
data-options="Toolbar">
<span class="o_image o_hover"
t-att-data-mimetype="file.mimetype"
t-att-data-ext="ext" role="img"
t-attf-data-src="/web/content/{{file.id}}"/>
</a>
</div>
</t>
<t t-if="ext=='pdf'">
<div class="o_image_box float-start"
t-att-data-tooltip="'Download ' + file.name">
<a t-att-href="getUrl(file.id)"
aria-label="Download"
style="cursor: zoom-in;" data-fancybox=""
data-type="iframe">
<span class="o_image o_hover"
t-att-data-mimetype="file.mimetype"
t-att-data-ext="ext" role="img"
t-attf-data-src="/web/content/{{file.id}}"/>
</a>
</div>
</t>
<t t-if="ext=='mkv'">
<div class="o_image_box float-start"
t-att-data-tooltip="'Download ' + file.name">
<a t-att-href="getUrl(file.id)"
aria-label="Download"
style="cursor: zoom-in;" data-fancybox=""
data-type="html5video" data-width="640"
data-height="360">
<span class="o_image o_hover"
t-att-data-mimetype="file.mimetype"
t-att-data-ext="ext" role="img"
t-attf-data-src="/web/content/{{file.id}}"/>
</a>
</div>
</t>
<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>
</div>
</t>
</template>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!--
This XML file inherits the sale order form view to add a new field,
`attachment_ids`, which uses the `many2many_attachment_preview` widget.
The new field is placed right after the `payment_term_id` field.
This customization allows users to preview attachments directly in the
sale order form.
-->
<record id="view_order_form" model="ir.ui.view">
<field name="name">
sale.order.view.form.inherit.many2many.attachment.preview
</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<field name="payment_term_id" position="after">
<field name="attachment_ids" widget="many2many_attachment_preview"/>
</field>
</field>
</record>
</odoo>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论