提交 91014d40 authored 作者: 贺阳's avatar 贺阳

1、将选中的提单以提单号命名下载尾程交接POD文件,若文件有多个则以提单-1/-2进行命名下载,若选择多个提单,则将所有文件打包成zip,zip命名则以POD进行命名

2、预览和确认的优化
上级 ee754777
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
import json import json
import logging import logging
from datetime import timedelta from datetime import timedelta
import base64
import io
import zipfile
import pytz import pytz
from odoo import models, fields, api, _ from odoo import models, fields, api, _
...@@ -737,6 +740,45 @@ class CcBL(models.Model): ...@@ -737,6 +740,45 @@ class CcBL(models.Model):
'sync_match_node': True 'sync_match_node': True
}) })
wizard_obj.confirm() wizard_obj.confirm()
def _get_pod_files(self):
fix_name = '尾程交接POD(待大包数量和箱号)'
res = []
for bl in self:
files = self.env['cc.clearance.file'].sudo().search([('bl_id', '=', bl.id), ('file_name', '=', fix_name), ('file', '!=', False)])
if not files:
continue
total = len(files)
idx = 0
for f in files:
idx += 1
base = bl.bl_no or str(bl.id)
ext = 'pdf'
if f.attachment_name and '.' in f.attachment_name:
ext = f.attachment_name.split('.')[-1]
name = f"{base}.{ext}" if total == 1 else f"{base}-{idx}.{ext}"
res.append((name, f.file))
return res
def action_download_pod(self):
files = self._get_pod_files()
if not files:
return {'type': 'ir.actions.act_window_close'}
if len(self) == 1 and len(files) == 1:
name, data_b64 = files[0]
att = self.env['ir.attachment'].sudo().create({'name': name, 'datas': data_b64, 'res_model': 'cc.bl', 'res_id': self.id, 'mimetype': 'application/pdf'})
url = f"/web/content/ir.attachment/{att.id}/datas/{att.name}?download=true"
return {'type': 'ir.actions.act_url', 'url': url, 'target': 'self'}
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w', compression=zipfile.ZIP_DEFLATED) as zf:
for name, data_b64 in files:
zf.writestr(name, base64.b64decode(data_b64))
datas = base64.b64encode(buf.getvalue())
att = self.env['ir.attachment'].sudo().create({'name': 'POD.zip', 'datas': datas, 'res_model': 'cc.bl', 'res_id': self[0].id, 'mimetype': 'application/zip'})
url = f"/web/content/ir.attachment/{att.id}/datas/{att.name}?download=true"
return {'type': 'ir.actions.act_url', 'url': url, 'target': 'self'}
def action_package_pod(self, ids):
return self.browse(ids).action_download_pod()
# 增加一个can_cancel的方法,用于检查提单当前是否可以取消,返回True表示可以取消, False表示不可以取消,同时返回取消的原因 # 增加一个can_cancel的方法,用于检查提单当前是否可以取消,返回True表示可以取消, False表示不可以取消,同时返回取消的原因
def check_cancel(self): def check_cancel(self):
...@@ -1179,7 +1221,7 @@ class CcBL(models.Model): ...@@ -1179,7 +1221,7 @@ class CcBL(models.Model):
'view_mode': 'form', 'view_mode': 'form',
'res_model': 'batch.get.pod.info.wizard', 'res_model': 'batch.get.pod.info.wizard',
'context': {'active_id': self.ids, 'default_action_type': '获取尾程POD信息'}, 'context': {'active_id': self.ids, 'default_action_type': '获取尾程POD信息'},
'view_id': self.env.ref('ccs_base.view_batch_get_pod_info_wizard_form').id, 'views': [[self.env.ref('ccs_base.view_batch_get_pod_info_wizard_form').id, "form"]],
'target': 'new', 'target': 'new',
} }
...@@ -1198,6 +1240,22 @@ class CcBL(models.Model): ...@@ -1198,6 +1240,22 @@ class CcBL(models.Model):
} }
def action_batch_download_pod(self):
"""
将选中的提单以提单号命名下载尾程交接POD文件,若文件有多个则以提单-1/-2进行命名下载,若选择多个提单,则将所有文件打包成zip,zip命名则以POD进行命名
"""
# 检查是否有选中的提单
if not self:
raise UserError(_('Please select at least one bill of loading.'))
# 检查是否有尾程交接POD文件
fix_name = '尾程交接POD(待大包数量和箱号)'
has_files = self.env['cc.clearance.file'].search_count([('bl_id', 'in', self.ids), ('file_name', '=', fix_name), ('file', '!=', False)])
if not has_files:
raise UserError(_('Please configure the tail-end handover POD file of the bill of loading first.'))
return self.action_download_pod()
# 增加一个清关进度的业务对象,继承自models.Model, 用于管理业务数据.业务数据包括提单号、清关节点(业务对象)、进度日期、进度描述、更新人 # 增加一个清关进度的业务对象,继承自models.Model, 用于管理业务数据.业务数据包括提单号、清关节点(业务对象)、进度日期、进度描述、更新人
class CcProgress(models.Model): class CcProgress(models.Model):
# 模型名称 # 模型名称
......
...@@ -329,7 +329,7 @@ ...@@ -329,7 +329,7 @@
<field name="res_model">cc.bl</field> <field name="res_model">cc.bl</field>
<field name="view_mode">tree,form,pivot,graph,calendar</field> <field name="view_mode">tree,form,pivot,graph,calendar</field>
<field name="domain">[('bl_type','=','tk')]</field> <field name="domain">[('bl_type','=','tk')]</field>
<field name="context">{'search_default_filter_state_not_finished':1}</field> <field name="context">{'search_default_filter_state_not_finished':1,'default_bl_type':'tk'}</field>
<field name="view_id" ref="tree_cc_bl_view"/> <field name="view_id" ref="tree_cc_bl_view"/>
<field name="help" type="html"> <field name="help" type="html">
<p> <p>
...@@ -337,52 +337,12 @@ ...@@ -337,52 +337,12 @@
</field> </field>
</record> </record>
<!--
<record model="ir.ui.view" id="tree_temu_cc_bl_view">
<field name="name">tree.temu.cc.bl</field>
<field name="model">cc.bl</field>
<field name="arch" type="xml">
<tree string="Temu Bill of Loading" decoration-warning="is_cancel==True" js_class="cc_temu_bl_tree">
<field optional="show" name="state" string="Status" widget="badge" decoration-info="state=='draft'"
decoration-primary="state=='ccing'" decoration-success="state=='done'"/>
<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_date" string="B/L Date"/>
<field optional="show" name="last_mile_provider_ids" string="Last Mile Providers"
widget="many2many_tags"/>
<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="customs_bl_no" string="Customs Bill of Loading No."/>
<field optional="hide" name="big_package_sell_country" string="Sell Country"/>
<field optional="hide" name="billing_weight" string="Billing Weight"/>
<field optional="hide" name="actual_weight" string="Actual Weight"/>
<field optional="hide" name="big_package_qty" string="Big Package"/>
<field optional="hide" name="bl_total_big_qty" string="Receive Big Package"/>
<field optional="hide" name="bl_ship_package_qty" string="Receive Ship Package"/>
<field optional="hide" name="bl_total_qty" string="Receive Goods Qty"/>
<field optional="show" name="bl_total_amount" string="Total Amount"/>
<field optional="show" name="cc_company_id" string="CC Company"/>
<field optional="show" name="cc_country_id" string="CC Country"/>
<field optional="show" name="cc_deadline" string="CC Deadline"/>
<field optional="show" name="trade_type" string="Trade Type"/>
<field optional="hide" name="transport_tool_code" string="Transport Tool Code"/>
<field optional="hide" name="transport_tool_name" string="Transport Tool Name"/>
<field optional="show" name="start_port_code" string="Start Port Code"/>
<field optional="show" name="eta" string="ETA"/>
<field optional="show" name="end_port_code" string="End Port Code"/>
<field optional="show" name="etd" string="ETD"/>
<field optional="show" name="is_cancel" string="Is Cancel"/>
<field optional="hide" name="cancel_reason" string="Cancel Reason"/>
<field optional="show" name="create_date"/>
</tree>
</field>
</record> -->
<record model="ir.actions.act_window" id="action_temu_bl"> <record model="ir.actions.act_window" id="action_temu_bl">
<field name="name">TEMU Bill of Loading</field> <field name="name">TEMU Bill of Loading</field>
<field name="res_model">cc.bl</field> <field name="res_model">cc.bl</field>
<field name="view_mode">tree,form,pivot,graph,calendar</field> <field name="view_mode">tree,form,pivot,graph,calendar</field>
<field name="domain">[('bl_type','=','temu')]</field> <field name="domain">[('bl_type','=','temu')]</field>
<field name="context">{'default_bl_type':'temu'}</field>
<field name="view_id" ref="tree_cc_bl_view"/> <field name="view_id" ref="tree_cc_bl_view"/>
<!-- <field name="view_id" ref="tree_temu_cc_bl_view"/> --> <!-- <field name="view_id" ref="tree_temu_cc_bl_view"/> -->
<field name="context">{'search_default_filter_state_not_finished':1}</field> <field name="context">{'search_default_filter_state_not_finished':1}</field>
...@@ -468,9 +428,9 @@ ...@@ -468,9 +428,9 @@
</record> </record>
<!-- 批量创建temu提单 --> <!-- 下载POD -->
<record id="bl_create_temu_bl_server_action" model="ir.actions.server"> <record id="bl_download_pod_server_action" model="ir.actions.server">
<field name="name">Batch Create BL</field> <field name="name">Batch Download POD</field>
<field name="model_id" ref="model_cc_bl"/> <field name="model_id" ref="model_cc_bl"/>
<field name="binding_model_id" ref="model_cc_bl"/> <field name="binding_model_id" ref="model_cc_bl"/>
<field name="state">code</field> <field name="state">code</field>
...@@ -478,7 +438,7 @@ ...@@ -478,7 +438,7 @@
<field name="groups_id" eval="[(4, ref('ccs_base.group_clearance_of_customs_user'))]"/> <field name="groups_id" eval="[(4, ref('ccs_base.group_clearance_of_customs_user'))]"/>
<field name="code"> <field name="code">
if records: if records:
action = records.action_batch_get_pod_info() action = records.action_batch_download_pod()
</field> </field>
</record> </record>
......
...@@ -112,25 +112,25 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -112,25 +112,25 @@ class BatchGetPodInfoWizard(models.TransientModel):
pdf_filename = fields.Char(string='PDF文件名称') pdf_filename = fields.Char(string='PDF文件名称')
processed_files_data = fields.Text(string='已处理的文件数据', help='存储已处理的文件信息(JSON格式)') processed_files_data = fields.Text(string='已处理的文件数据', help='存储已处理的文件信息(JSON格式)')
def _get_bill_numbers(self): def _get_bill_numbers(self, bl_objs):
if self.action_type == '获取尾程POD信息': # if self.action_type == '获取尾程POD信息':
bl_objs = self.get_order() # bl_objs = self.get_order()
bill_numbers = [self.env['common.common'].sudo().process_match_str(bl.bl_no) for bl in bl_objs] # bill_numbers = [self.env['common.common'].sudo().process_match_str(bl.bl_no) for bl in bl_objs]
else: # else:
bill_numbers = [self.env['common.common'].sudo().process_match_str(bl_no) for bl_no in # bill_numbers = [self.env['common.common'].sudo().process_match_str(bl_no) for bl_no in
self.bl_numbers.splitlines()] # self.bl_numbers.splitlines()]
_logger.info(f"开始预览操作,提单数量: {len(bill_numbers)}") _logger.info(f"开始预览操作,提单数量: {len(bl_objs)}")
# 调用接口获取提单pdf文件 # 调用接口获取提单pdf文件
pdf_file_arr = self._get_pdf_file_arr(bill_numbers) pdf_file_arr = self._get_pdf_file_arr(bl_objs)
# 处理PDF文件,匹配提单对象 # 处理PDF文件,匹配提单对象
processed_files = self._match_bl_by_file_name(pdf_file_arr) processed_files = self._match_bl_by_file_name(pdf_file_arr, bl_objs)
# 把没有匹配到文件的进行提示 # 把没有匹配到文件的进行提示
error_bl = [] error_bl = []
matched_bl_ids = [f['bl_no'] for f in processed_files if f.get('bl_no')] matched_bl_ids = [f['bl'].id for f in processed_files if f.get('bl')]
for bl_no in bill_numbers: for bl in bl_objs:
if bl_no not in matched_bl_ids: if bl.id not in matched_bl_ids:
error_bl.append(bl_no) error_bl.append(bl.id)
if error_bl: if error_bl:
logging.info('%s个提单无法找到release note文件' % len(error_bl)) logging.info('%s个提单无法找到release note文件' % len(error_bl))
if not self._context.get('is_skip_raise_error'): if not self._context.get('is_skip_raise_error'):
...@@ -144,12 +144,9 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -144,12 +144,9 @@ class BatchGetPodInfoWizard(models.TransientModel):
""" """
action_type = self.action_type action_type = self.action_type
try: try:
bl_objs = False bl_objs = self.get_bl_objs() # 获取提单
if action_type == '获取尾程POD信息':
bl_objs = self.get_order()
# 处理PDF文件,匹配提单对象 # 处理PDF文件,匹配提单对象
processed_files = self._get_bill_numbers() processed_files = self._get_bill_numbers(bl_objs)
# 如果启用了涂抹文字,进行处理 # 如果启用了涂抹文字,进行处理
if self.remove_specified_text and processed_files: if self.remove_specified_text and processed_files:
processed_files = self._remove_specified_text(processed_files, debug_mode=False) processed_files = self._remove_specified_text(processed_files, debug_mode=False)
...@@ -158,15 +155,15 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -158,15 +155,15 @@ class BatchGetPodInfoWizard(models.TransientModel):
successful_files = [] successful_files = []
failed_files = [] failed_files = []
for file_info in processed_files: for file_info in processed_files:
if file_info.get('bl'): if file_info.get('bl_no'):
bl = file_info.get('bl') bl_no = file_info.get('bl_no')
file_data = file_info.get('file_data', '') file_data = file_info.get('file_data', '')
# 检查处理是否失败(通过processing_failed标记或错误消息) # 检查处理是否失败(通过processing_failed标记或错误消息)
processing_failed = file_info.get('processing_failed', False) processing_failed = file_info.get('processing_failed', False)
has_error = False has_error = False
if self.show_error_message: if self.show_error_message:
error_msg = str(self.show_error_message) error_msg = str(self.show_error_message)
if bl and bl.bl_no in error_msg: if bl_no in error_msg:
has_error = True has_error = True
# 如果处理失败或者有错误,则认为失败 # 如果处理失败或者有错误,则认为失败
...@@ -261,6 +258,47 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -261,6 +258,47 @@ class BatchGetPodInfoWizard(models.TransientModel):
'context': {'active_id': self._context.get('active_id', [])} 'context': {'active_id': self._context.get('active_id', [])}
} }
def get_bl_objs(self):
if self.action_type == '获取尾程POD信息':
bl_objs = self.get_order()
else:
raw_lines = [i.strip() for i in self.bl_numbers.splitlines() if i.strip()]
normalized_list = [self.env['common.common'].sudo().process_match_str(i) for i in raw_lines]
norm_to_raw = {}
for raw, norm in zip(raw_lines, normalized_list):
if norm not in norm_to_raw:
norm_to_raw[norm] = raw
normalized = sorted(set(normalized_list))
exist_set = set()
if normalized:
self._cr.execute(
"select UPPER(REPLACE(REPLACE(REPLACE(bl_no,' ', ''), '-', ''), '/', '')) from cc_bl where UPPER(REPLACE(REPLACE(REPLACE(bl_no,' ', ''), '-', ''), '/', '')) in %s",
(tuple(normalized),))
exist_set.update([r[0] for r in self._cr.fetchall()])
self._cr.execute(
"select UPPER(REPLACE(REPLACE(REPLACE(transfer_bl_no,' ', ''), '-', ''), '/', '')) from cc_bl where transfer_bl_no is not null and UPPER(REPLACE(REPLACE(REPLACE(transfer_bl_no,' ', ''), '-', ''), '/', '')) in %s",
(tuple(normalized),))
exist_set.update([r[0] for r in self._cr.fetchall()])
non_exist_norm = [n for n in normalized if n not in exist_set]
create_vals = [{
'customer_id': self.partner_id.id,
'bl_type': 'temu',
'bl_no': norm_to_raw[n]
} for n in non_exist_norm]
new_bl = self.env['cc.bl'].create(create_vals) if create_vals else self.env['cc.bl']
ids = []
if normalized:
self._cr.execute(
"select id from cc_bl where UPPER(REPLACE(REPLACE(REPLACE(bl_no,' ', ''), '-', ''), '/', '')) in %s",
(tuple(normalized),))
ids += [r[0] for r in self._cr.fetchall()]
self._cr.execute(
"select id from cc_bl where transfer_bl_no is not null and UPPER(REPLACE(REPLACE(REPLACE(transfer_bl_no,' ', ''), '-', ''), '/', '')) in %s",
(tuple(normalized),))
ids += [r[0] for r in self._cr.fetchall()]
bl_objs = self.env['cc.bl'].browse(list(set(ids))) | new_bl
return bl_objs
def confirm(self): def confirm(self):
""" """
Confirm operation # 确认操作 Confirm operation # 确认操作
...@@ -269,18 +307,8 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -269,18 +307,8 @@ class BatchGetPodInfoWizard(models.TransientModel):
start_time = time.time() start_time = time.time()
self.show_error_message = False self.show_error_message = False
action_type = self.action_type action_type = self.action_type
bl_objs = False bl_objs = self.get_bl_objs()
if action_type == '获取尾程POD信息': if action_type == '获取尾程POD信息' or (action_type == '创建temu提单' and self.get_last_mile_pod):
bl_objs = self.get_order()
else:
# 根据提单号和客户生成temu提单
bl_objs = self.env['cc.bl'].create([{
'customer_id': self.partner_id.id,
'bl_type': 'temu',
'bl_no': bl_no
} for bl_no in self.bl_numbers.splitlines()])
_logger.info(f"%s提单开始执行批量获取POD信息操作" % len(bl_objs))
# 优先使用已处理的文件数据(预览时已处理) # 优先使用已处理的文件数据(预览时已处理)
processed_files = None processed_files = None
if self.processed_files_data: if self.processed_files_data:
...@@ -300,7 +328,8 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -300,7 +328,8 @@ class BatchGetPodInfoWizard(models.TransientModel):
# 如果没有已处理的数据,则执行处理流程 # 如果没有已处理的数据,则执行处理流程
if not processed_files: if not processed_files:
# 处理PDF文件,匹配提单对象 # 处理PDF文件,匹配提单对象
processed_files = self._get_bill_numbers() bl_objs = self.get_bl_objs() # 获取提单
processed_files = self._get_bill_numbers(bl_objs)
# 如果启用了涂抹文字,进行处理 # 如果启用了涂抹文字,进行处理
if self.remove_specified_text and processed_files: if self.remove_specified_text and processed_files:
processed_files = self._remove_specified_text(processed_files, debug_mode=False) processed_files = self._remove_specified_text(processed_files, debug_mode=False)
...@@ -323,7 +352,8 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -323,7 +352,8 @@ class BatchGetPodInfoWizard(models.TransientModel):
# 检查是否有文字清除失败的错误 # 检查是否有文字清除失败的错误
if self.show_error_message and any( if self.show_error_message and any(
'仍存在目标文字' in str(self.show_error_message) or '未完全清除文字' in str(self.show_error_message)): '仍存在目标文字' in str(self.show_error_message) or '未完全清除文字' in str(
self.show_error_message)):
_logger.error(f"检测到文字清除失败,停止处理: {self.show_error_message}") _logger.error(f"检测到文字清除失败,停止处理: {self.show_error_message}")
return { return {
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
...@@ -345,7 +375,6 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -345,7 +375,6 @@ class BatchGetPodInfoWizard(models.TransientModel):
# 检查是否有bl对象和有文件数据 # 检查是否有bl对象和有文件数据
if not file_info.get('bl'): if not file_info.get('bl'):
continue continue
bl = file_info['bl']
file_data = file_info.get('file_data', '') file_data = file_info.get('file_data', '')
# 检查处理是否失败(通过processing_failed标记) # 检查处理是否失败(通过processing_failed标记)
processing_failed = file_info.get('processing_failed', False) processing_failed = file_info.get('processing_failed', False)
...@@ -357,7 +386,8 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -357,7 +386,8 @@ class BatchGetPodInfoWizard(models.TransientModel):
_logger.warning("没有找到已处理的文件数据") _logger.warning("没有找到已处理的文件数据")
# 回写到附件信息 # 回写到附件信息
if successful_processed_files and ((self.sync_last_mile_pod or self.sync_match_node) or self.get_last_mile_pod): if successful_processed_files and (
(self.sync_last_mile_pod or self.sync_match_node) or self.get_last_mile_pod):
# 回写PDF文件到清关文件 # 回写PDF文件到清关文件
self._write_pdf_file(successful_processed_files) self._write_pdf_file(successful_processed_files)
...@@ -395,6 +425,16 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -395,6 +425,16 @@ class BatchGetPodInfoWizard(models.TransientModel):
'context': {'default_show_error_message': self.show_error_message, 'context': {'default_show_error_message': self.show_error_message,
'active_id': bl_objs.ids if bl_objs else False} 'active_id': bl_objs.ids if bl_objs else False}
} }
if action_type == '创建TEMU提单':
return {
'type': 'ir.actions.act_window',
'res_model': 'cc.bl',
'name': _('TEMU Bill of Loading'),
'view_mode': 'list,form',
'domian': [('bl_type', '=', 'temu')],
'context': {'default_bl_type': 'temu'},
'target': 'new',
}
def _validate_node_push_conditions(self, processed_files): def _validate_node_push_conditions(self, processed_files):
""" """
...@@ -405,10 +445,11 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -405,10 +445,11 @@ class BatchGetPodInfoWizard(models.TransientModel):
return processed_files return processed_files
# 写一个方法调接口获取提单pdf文件 # 写一个方法调接口获取提单pdf文件
def _get_pdf_file_arr(self, bill_numbers): def _get_pdf_file_arr(self, bl_objs):
""" """
从API获取PDF文件 从API获取PDF文件
""" """
bill_numbers = [self.env['common.common'].sudo().process_match_str(bl.bl_no) for bl in bl_objs]
# 调用API获取PDF文件 # 调用API获取PDF文件
api_url = self.env['ir.config_parameter'].sudo().get_param('last_mile_pod_api_url', api_url = self.env['ir.config_parameter'].sudo().get_param('last_mile_pod_api_url',
'http://172.104.52.150:7002') 'http://172.104.52.150:7002')
...@@ -530,7 +571,7 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -530,7 +571,7 @@ class BatchGetPodInfoWizard(models.TransientModel):
# 过滤有效的PDF文件 # 过滤有效的PDF文件
valid_files = [] valid_files = []
for file_info in processed_files: for file_info in processed_files:
if file_info.get('bl') and file_info.get('file_data'): if file_info.get('bl_no') and file_info.get('file_data'):
valid_files.append(file_info) valid_files.append(file_info)
if not valid_files: if not valid_files:
...@@ -541,12 +582,13 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -541,12 +582,13 @@ class BatchGetPodInfoWizard(models.TransientModel):
if len(valid_files) == 1: if len(valid_files) == 1:
file_info = valid_files[0] file_info = valid_files[0]
bl = file_info['bl'] bl = file_info['bl']
bl_no = bl.bl_no
file_data = file_info['file_data'] file_data = file_info['file_data']
file_name = file_info.get('file_name', f"{bl.bl_no}.pdf") file_name = file_info.get('file_name', f"{bl_no}.pdf")
# 生成文件名(包含提单号和日期) # 生成文件名(包含提单号和日期)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
pdf_filename = f"POD文件_{bl.bl_no}_{timestamp}.pdf" pdf_filename = f"POD文件_{bl_no}_{timestamp}.pdf"
# 直接保存到字段 # 直接保存到字段
self.write({ self.write({
...@@ -573,11 +615,10 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -573,11 +615,10 @@ class BatchGetPodInfoWizard(models.TransientModel):
for file_info in batch_files: for file_info in batch_files:
bl = file_info['bl'] bl = file_info['bl']
bl_no = bl.bl_no
file_data = file_info['file_data'] file_data = file_info['file_data']
bl_numbers.append(bl.bl_no) bl_numbers.append(bl_no)
source_pdf = None source_pdf = None
pdf_binary = None
try: try:
# 将base64数据转换为二进制 # 将base64数据转换为二进制
pdf_binary = base64.b64decode(file_data) pdf_binary = base64.b64decode(file_data)
...@@ -588,17 +629,15 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -588,17 +629,15 @@ class BatchGetPodInfoWizard(models.TransientModel):
# 将源PDF的所有页面插入到合并的PDF中 # 将源PDF的所有页面插入到合并的PDF中
merged_pdf.insert_pdf(source_pdf) merged_pdf.insert_pdf(source_pdf)
_logger.info(f"已添加提单 {bl.bl_no} 的PDF到合并文档({len(source_pdf)} 页)") _logger.info(f"已添加提单 {bl_no} 的PDF到合并文档({len(source_pdf)} 页)")
except Exception as e: except Exception as e:
_logger.error(f"合并提单 {bl.bl_no} 的PDF失败: {str(e)}") _logger.error(f"合并提单 {bl_no} 的PDF失败: {str(e)}")
continue continue
finally: finally:
# 立即释放资源 # 立即释放资源
if source_pdf: if source_pdf:
source_pdf.close() source_pdf.close()
source_pdf = None
pdf_binary = None
gc.collect() # 强制垃圾回收 gc.collect() # 强制垃圾回收
# 每批处理完后,保存到临时文件并释放内存 # 每批处理完后,保存到临时文件并释放内存
...@@ -661,13 +700,12 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -661,13 +700,12 @@ class BatchGetPodInfoWizard(models.TransientModel):
except Exception as e: except Exception as e:
_logger.warning(f"删除临时文件失败: {str(e)}") _logger.warning(f"删除临时文件失败: {str(e)}")
def _match_bl_by_file_name(self, pdf_file_arr): def _match_bl_by_file_name(self, pdf_file_arr, bl_obj):
""" """
Match BL by file name and return processed array # 根据文件名匹配提单并返回处理后的数组 Match BL by file name and return processed array # 根据文件名匹配提单并返回处理后的数组
:param pdf_file_arr: PDF文件数组 [{'bill_number':'', 'filename':'', 'file_data':''}] :param pdf_file_arr: PDF文件数组 [{'bill_number':'', 'filename':'', 'file_data':''}]
:return: 处理后的数组 [{'bl': bl_obj, 'file_name': 'xxx.pdf', 'file_data': 'xxx', 'matched': True/False}] :return: 处理后的数组 [{'bl': bl_obj, 'file_name': 'xxx.pdf', 'file_data': 'xxx', 'matched': True/False}]
""" """
bl_obj = self.get_order() # 获取当前选中的提单对象
processed_files = [] processed_files = []
for bl in bl_obj: for bl in bl_obj:
select_bl_no = self.env['common.common'].sudo().process_match_str(bl.bl_no) select_bl_no = self.env['common.common'].sudo().process_match_str(bl.bl_no)
...@@ -854,10 +892,10 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -854,10 +892,10 @@ class BatchGetPodInfoWizard(models.TransientModel):
skip_ocr = self.skip_ocr_direct_ai # 是否跳过OCR直接使用AI skip_ocr = self.skip_ocr_direct_ai # 是否跳过OCR直接使用AI
for file_info in processed_files: for file_info in processed_files:
if not file_info['bl']: if not file_info['bl_no']:
updated_files.append(file_info) updated_files.append(file_info)
continue continue
bl = file_info['bl'] bl_no = file_info['bl_no']
file_data = file_info['file_data'] file_data = file_info['file_data']
processed_file_data = file_data # 默认使用原始数据 processed_file_data = file_data # 默认使用原始数据
processing_failed = False # 标记处理是否失败 processing_failed = False # 标记处理是否失败
...@@ -868,15 +906,15 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -868,15 +906,15 @@ class BatchGetPodInfoWizard(models.TransientModel):
# 先提取文本用于后续同步节点功能(如果需要的话) # 先提取文本用于后续同步节点功能(如果需要的话)
if 'ocr_texts' not in file_info: if 'ocr_texts' not in file_info:
file_info['ocr_texts'] = self._extract_text_from_pdf_with_ocr(pdf_binary, bl.bl_no) file_info['ocr_texts'] = self._extract_text_from_pdf_with_ocr(pdf_binary, bl_no)
# 如果跳过OCR,直接使用AI处理 # 如果跳过OCR,直接使用AI处理
if skip_ocr: if skip_ocr:
_logger.info(f"提单 {bl.bl_no} 跳过OCR,直接使用AI处理") _logger.info(f"提单 {bl_no} 跳过OCR,直接使用AI处理")
try: try:
ai_processed_pdf = self._process_pdf_with_ai_image_edit( ai_processed_pdf = self._process_pdf_with_ai_image_edit(
pdf_data=pdf_binary, pdf_data=pdf_binary,
bl_no=bl.bl_no bl_no=bl_no
) )
if ai_processed_pdf: if ai_processed_pdf:
processed_file_data = base64.b64encode(ai_processed_pdf).decode('utf-8') processed_file_data = base64.b64encode(ai_processed_pdf).decode('utf-8')
...@@ -884,50 +922,50 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -884,50 +922,50 @@ class BatchGetPodInfoWizard(models.TransientModel):
# 检查是否还存在目标文字 # 检查是否还存在目标文字
final_check_pdf = base64.b64decode(processed_file_data) final_check_pdf = base64.b64decode(processed_file_data)
text_still_exists, final_found_texts = self._check_target_texts_exist(final_check_pdf, text_still_exists, final_found_texts = self._check_target_texts_exist(final_check_pdf,
bl.bl_no) bl_no)
if text_still_exists: if text_still_exists:
error_msg = f"提单 {bl.bl_no} 经过AI处理后仍存在目标文字: {', '.join(final_found_texts)},请取消该提单操作,手动处理" error_msg = f"提单 {bl_no} 经过AI处理后仍存在目标文字: {', '.join(final_found_texts)},请取消该提单操作,手动处理"
_logger.error(error_msg) _logger.error(error_msg)
error_messages.append(error_msg) error_messages.append(error_msg)
# 不更新文件数据,保持原始状态 # 不更新文件数据,保持原始状态
processed_file_data = file_data processed_file_data = file_data
processing_failed = True processing_failed = True
else: else:
_logger.info(f"提单 {bl.bl_no} AI处理成功,目标文字已清除") _logger.info(f"提单 {bl_no} AI处理成功,目标文字已清除")
else: else:
error_msg = f"提单 {bl.bl_no} AI处理失败" error_msg = f"提单 {bl_no} AI处理失败"
_logger.error(error_msg) _logger.error(error_msg)
error_messages.append(error_msg) error_messages.append(error_msg)
processing_failed = True processing_failed = True
except Exception as e: except Exception as e:
_logger.error(f"提单 {bl.bl_no} AI处理异常: {str(e)}") _logger.error(f"提单 {bl_no} AI处理异常: {str(e)}")
error_msg = f"提单 {bl.bl_no} AI处理异常: {str(e)}" error_msg = f"提单 {bl_no} AI处理异常: {str(e)}"
error_messages.append(error_msg) error_messages.append(error_msg)
processing_failed = True processing_failed = True
else: else:
# 原有逻辑:先用OCR处理,如果还存在则用AI处理 # 原有逻辑:先用OCR处理,如果还存在则用AI处理
# 第一步:使用OCR方法处理PDF # 第一步:使用OCR方法处理PDF
_logger.info(f"提单 {bl.bl_no} 开始OCR处理") _logger.info(f"提单 {bl_no} 开始OCR处理")
try: try:
processed_pdf = self._process_pdf_with_ocr( processed_pdf = self._process_pdf_with_ocr(
pdf_data=pdf_binary, pdf_data=pdf_binary,
bl_no=bl.bl_no, bl_no=bl_no,
debug_mode=debug_mode debug_mode=debug_mode
) )
if processed_pdf: if processed_pdf:
processed_file_data = base64.b64encode(processed_pdf).decode('utf-8') processed_file_data = base64.b64encode(processed_pdf).decode('utf-8')
# 第二步:检查是否还存在目标文字 # 第二步:检查是否还存在目标文字
pdf_for_check = base64.b64decode(processed_file_data) pdf_for_check = base64.b64decode(processed_file_data)
text_exists, found_texts = self._check_target_texts_exist(pdf_for_check, bl.bl_no) text_exists, found_texts = self._check_target_texts_exist(pdf_for_check, bl_no)
logging.info(f"ocr处理之后的text_exists: {text_exists}") logging.info(f"ocr处理之后的text_exists: {text_exists}")
if text_exists: if text_exists:
# 第三步:如果还存在,使用AI图片编辑处理 # 第三步:如果还存在,使用AI图片编辑处理
_logger.info(f"提单 {bl.bl_no} OCR处理后仍存在目标文字,使用AI图片编辑处理") _logger.info(f"提单 {bl_no} OCR处理后仍存在目标文字,使用AI图片编辑处理")
try: try:
ai_processed_pdf = self._process_pdf_with_ai_image_edit( ai_processed_pdf = self._process_pdf_with_ai_image_edit(
pdf_data=pdf_for_check, pdf_data=pdf_for_check,
bl_no=bl.bl_no bl_no=bl_no
) )
if ai_processed_pdf: if ai_processed_pdf:
processed_file_data = base64.b64encode(ai_processed_pdf).decode('utf-8') processed_file_data = base64.b64encode(ai_processed_pdf).decode('utf-8')
...@@ -935,51 +973,51 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -935,51 +973,51 @@ class BatchGetPodInfoWizard(models.TransientModel):
# 第四步:再次检查是否还存在目标文字 # 第四步:再次检查是否还存在目标文字
final_check_pdf = base64.b64decode(processed_file_data) final_check_pdf = base64.b64decode(processed_file_data)
text_still_exists, final_found_texts = self._check_target_texts_exist( text_still_exists, final_found_texts = self._check_target_texts_exist(
final_check_pdf, bl.bl_no) final_check_pdf, bl_no)
if text_still_exists: if text_still_exists:
# 第五步:如果仍然存在,记录错误信息并停止处理 # 第五步:如果仍然存在,记录错误信息并停止处理
error_msg = f"提单 {bl.bl_no} 经过系统处理后仍存在目标文字: {', '.join(final_found_texts)},请取消该提单操作,手动处理" error_msg = f"提单 {bl_no} 经过系统处理后仍存在目标文字: {', '.join(final_found_texts)},请取消该提单操作,手动处理"
_logger.error(error_msg) _logger.error(error_msg)
error_messages.append(error_msg) error_messages.append(error_msg)
# 不更新文件数据,保持原始状态 # 不更新文件数据,保持原始状态
processed_file_data = file_data processed_file_data = file_data
processing_failed = True processing_failed = True
else: else:
_logger.warning(f"提单 {bl.bl_no} AI处理失败,检查OCR处理结果") _logger.warning(f"提单 {bl_no} AI处理失败,检查OCR处理结果")
# AI处理失败,检查OCR结果是否真的清除了目标文字 # AI处理失败,检查OCR结果是否真的清除了目标文字
ocr_check_pdf = base64.b64decode(processed_file_data) ocr_check_pdf = base64.b64decode(processed_file_data)
text_still_exists, ocr_found_texts = self._check_target_texts_exist( text_still_exists, ocr_found_texts = self._check_target_texts_exist(
ocr_check_pdf, bl.bl_no) ocr_check_pdf, bl_no)
if text_still_exists: if text_still_exists:
error_msg = f"提单 {bl.bl_no} 经过系统处理后仍存在目标文字: {', '.join(ocr_found_texts)},请取消该提单操作,手动处理" error_msg = f"提单 {bl_no} 经过系统处理后仍存在目标文字: {', '.join(ocr_found_texts)},请取消该提单操作,手动处理"
error_messages.append(error_msg) error_messages.append(error_msg)
# 不更新文件数据,保持原始状态 # 不更新文件数据,保持原始状态
processed_file_data = file_data processed_file_data = file_data
processing_failed = True processing_failed = True
else: else:
_logger.info(f"提单 {bl.bl_no} OCR处理成功,目标文字已清除") _logger.info(f"提单 {bl_no} OCR处理成功,目标文字已清除")
except Exception as e: except Exception as e:
_logger.error(f"提单 {bl.bl_no} AI处理异常: {str(e)}") _logger.error(f"提单 {bl_no} AI处理异常: {str(e)}")
# AI处理失败,使用OCR结果,但需要检查 # AI处理失败,使用OCR结果,但需要检查
final_check_pdf = base64.b64decode(processed_file_data) final_check_pdf = base64.b64decode(processed_file_data)
text_still_exists, final_found_texts = self._check_target_texts_exist( text_still_exists, final_found_texts = self._check_target_texts_exist(
final_check_pdf, bl.bl_no) final_check_pdf, bl_no)
if text_still_exists: if text_still_exists:
error_msg = f"提单 {bl.bl_no} 经过系统处理后仍存在目标文字: {', '.join(final_found_texts)},请取消该提单操作,手动处理" error_msg = f"提单 {bl_no} 经过系统处理后仍存在目标文字: {', '.join(final_found_texts)},请取消该提单操作,手动处理"
error_messages.append(error_msg) error_messages.append(error_msg)
# 不更新文件数据,保持原始状态 # 不更新文件数据,保持原始状态
processed_file_data = file_data processed_file_data = file_data
processing_failed = True processing_failed = True
else: else:
_logger.info(f"提单 {bl.bl_no} OCR处理成功,目标文字已清除") _logger.info(f"提单 {bl_no} OCR处理成功,目标文字已清除")
else: else:
_logger.warning(f"提单 {bl.bl_no} OCR处理失败") _logger.warning(f"提单 {bl_no} OCR处理失败")
error_messages.append(f"提单 {bl.bl_no} OCR处理失败") error_messages.append(f"提单 {bl_no} OCR处理失败")
processing_failed = True processing_failed = True
except Exception as e: except Exception as e:
_logger.error(f"提单 {bl.bl_no} OCR处理异常: {str(e)}") _logger.error(f"提单 {bl_no} OCR处理异常: {str(e)}")
error_messages.append(f"提单 {bl.bl_no} OCR处理异常: {str(e)}") error_messages.append(f"提单 {bl_no} OCR处理异常: {str(e)}")
processing_failed = True processing_failed = True
# 更新文件信息,使用处理后的PDF数据 # 更新文件信息,使用处理后的PDF数据
...@@ -2526,8 +2564,6 @@ class BatchGetPodInfoWizard(models.TransientModel): ...@@ -2526,8 +2564,6 @@ class BatchGetPodInfoWizard(models.TransientModel):
temp_attachments = self.env['ir.attachment'].sudo().browse(attachment_ids) temp_attachments = self.env['ir.attachment'].sudo().browse(attachment_ids)
attachment_count = len(temp_attachments) attachment_count = len(temp_attachments)
attachment_names = [att.name for att in temp_attachments]
_logger.info(f"找到 {attachment_count} 个{one_day_ago.strftime('%Y-%m-%d')}之前创建的临时附件,开始清理") _logger.info(f"找到 {attachment_count} 个{one_day_ago.strftime('%Y-%m-%d')}之前创建的临时附件,开始清理")
# 删除物理文件 # 删除物理文件
......
...@@ -131,7 +131,9 @@ ...@@ -131,7 +131,9 @@
<!-- 确认按钮:使用已处理的文件数据进行回写 --> <!-- 确认按钮:使用已处理的文件数据进行回写 -->
<!-- 如果有失败的文件(show_error_message不为空),需要勾选generate_successful_processed才显示;如果全部成功,直接显示 --> <!-- 如果有失败的文件(show_error_message不为空),需要勾选generate_successful_processed才显示;如果全部成功,直接显示 -->
<button string="Confirm" type="object" name="confirm" class="btn-primary" <button string="Confirm" type="object" name="confirm" class="btn-primary"
attrs="{'invisible': ['|', ('get_last_mile_pod','=',True), '|', '|', ('pdf_file', '=', False), ('show_error_message', '!=', False), ('generate_successful_processed', '=', False)]}"/> attrs="{'invisible': [('get_last_mile_pod','=',True)]}"/>
<button string="Confirm" type="object" name="confirm" class="btn-primary"
attrs="{'invisible': ['|', ('pdf_file', '=', False), '&amp;', ('show_error_message', '!=', False), ('generate_successful_processed', '=', False)]}"/>
<button string="Close" special="cancel"/> <button string="Close" special="cancel"/>
</footer> </footer>
</sheet> </sheet>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论