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

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

2、预览和确认的优化
上级 ee754777
......@@ -3,6 +3,9 @@
import json
import logging
from datetime import timedelta
import base64
import io
import zipfile
import pytz
from odoo import models, fields, api, _
......@@ -737,6 +740,45 @@ class CcBL(models.Model):
'sync_match_node': True
})
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表示不可以取消,同时返回取消的原因
def check_cancel(self):
......@@ -1179,7 +1221,7 @@ class CcBL(models.Model):
'view_mode': 'form',
'res_model': 'batch.get.pod.info.wizard',
'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',
}
......@@ -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, 用于管理业务数据.业务数据包括提单号、清关节点(业务对象)、进度日期、进度描述、更新人
class CcProgress(models.Model):
# 模型名称
......
......@@ -329,7 +329,7 @@
<field name="res_model">cc.bl</field>
<field name="view_mode">tree,form,pivot,graph,calendar</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="help" type="html">
<p>
......@@ -337,52 +337,12 @@
</field>
</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">
<field name="name">TEMU Bill of Loading</field>
<field name="res_model">cc.bl</field>
<field name="view_mode">tree,form,pivot,graph,calendar</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_temu_cc_bl_view"/> -->
<field name="context">{'search_default_filter_state_not_finished':1}</field>
......@@ -468,9 +428,9 @@
</record>
<!-- 批量创建temu提单 -->
<record id="bl_create_temu_bl_server_action" model="ir.actions.server">
<field name="name">Batch Create BL</field>
<!-- 下载POD -->
<record id="bl_download_pod_server_action" model="ir.actions.server">
<field name="name">Batch Download POD</field>
<field name="model_id" ref="model_cc_bl"/>
<field name="binding_model_id" ref="model_cc_bl"/>
<field name="state">code</field>
......@@ -478,7 +438,7 @@
<field name="groups_id" eval="[(4, ref('ccs_base.group_clearance_of_customs_user'))]"/>
<field name="code">
if records:
action = records.action_batch_get_pod_info()
action = records.action_batch_download_pod()
</field>
</record>
......
......@@ -112,25 +112,25 @@ class BatchGetPodInfoWizard(models.TransientModel):
pdf_filename = fields.Char(string='PDF文件名称')
processed_files_data = fields.Text(string='已处理的文件数据', help='存储已处理的文件信息(JSON格式)')
def _get_bill_numbers(self):
if self.action_type == '获取尾程POD信息':
bl_objs = self.get_order()
bill_numbers = [self.env['common.common'].sudo().process_match_str(bl.bl_no) for bl in bl_objs]
else:
bill_numbers = [self.env['common.common'].sudo().process_match_str(bl_no) for bl_no in
self.bl_numbers.splitlines()]
_logger.info(f"开始预览操作,提单数量: {len(bill_numbers)}")
def _get_bill_numbers(self, bl_objs):
# if self.action_type == '获取尾程POD信息':
# bl_objs = self.get_order()
# bill_numbers = [self.env['common.common'].sudo().process_match_str(bl.bl_no) for bl in bl_objs]
# else:
# bill_numbers = [self.env['common.common'].sudo().process_match_str(bl_no) for bl_no in
# self.bl_numbers.splitlines()]
_logger.info(f"开始预览操作,提单数量: {len(bl_objs)}")
# 调用接口获取提单pdf文件
pdf_file_arr = self._get_pdf_file_arr(bill_numbers)
pdf_file_arr = self._get_pdf_file_arr(bl_objs)
# 处理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 = []
matched_bl_ids = [f['bl_no'] for f in processed_files if f.get('bl_no')]
for bl_no in bill_numbers:
if bl_no not in matched_bl_ids:
error_bl.append(bl_no)
matched_bl_ids = [f['bl'].id for f in processed_files if f.get('bl')]
for bl in bl_objs:
if bl.id not in matched_bl_ids:
error_bl.append(bl.id)
if error_bl:
logging.info('%s个提单无法找到release note文件' % len(error_bl))
if not self._context.get('is_skip_raise_error'):
......@@ -144,12 +144,9 @@ class BatchGetPodInfoWizard(models.TransientModel):
"""
action_type = self.action_type
try:
bl_objs = False
if action_type == '获取尾程POD信息':
bl_objs = self.get_order()
bl_objs = self.get_bl_objs() # 获取提单
# 处理PDF文件,匹配提单对象
processed_files = self._get_bill_numbers()
processed_files = self._get_bill_numbers(bl_objs)
# 如果启用了涂抹文字,进行处理
if self.remove_specified_text and processed_files:
processed_files = self._remove_specified_text(processed_files, debug_mode=False)
......@@ -158,15 +155,15 @@ class BatchGetPodInfoWizard(models.TransientModel):
successful_files = []
failed_files = []
for file_info in processed_files:
if file_info.get('bl'):
bl = file_info.get('bl')
if file_info.get('bl_no'):
bl_no = file_info.get('bl_no')
file_data = file_info.get('file_data', '')
# 检查处理是否失败(通过processing_failed标记或错误消息)
processing_failed = file_info.get('processing_failed', False)
has_error = False
if 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
# 如果处理失败或者有错误,则认为失败
......@@ -261,6 +258,47 @@ class BatchGetPodInfoWizard(models.TransientModel):
'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):
"""
Confirm operation # 确认操作
......@@ -269,47 +307,54 @@ class BatchGetPodInfoWizard(models.TransientModel):
start_time = time.time()
self.show_error_message = False
action_type = self.action_type
bl_objs = False
if action_type == '获取尾程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
if self.processed_files_data:
processed_files = self._deserialize_processed_files(self.processed_files_data)
_logger.info(f"使用已处理的文件数据,共 {len(processed_files)} 个文件")
# 检查文件数据是否完整
valid_files = []
for file_info in processed_files:
if file_info.get('file_data'):
valid_files.append(file_info)
else:
_logger.warning(f"提单 {file_info.get('bl', {}).get('bl_no', 'Unknown')} 的文件数据为空")
processed_files = valid_files
_logger.info(f"有效文件数量: {len(processed_files)}")
bl_objs = self.get_bl_objs()
if action_type == '获取尾程POD信息' or (action_type == '创建temu提单' and self.get_last_mile_pod):
# 优先使用已处理的文件数据(预览时已处理)
processed_files = None
if self.processed_files_data:
processed_files = self._deserialize_processed_files(self.processed_files_data)
_logger.info(f"使用已处理的文件数据,共 {len(processed_files)} 个文件")
# 检查文件数据是否完整
valid_files = []
for file_info in processed_files:
if file_info.get('file_data'):
valid_files.append(file_info)
else:
_logger.warning(f"提单 {file_info.get('bl', {}).get('bl_no', 'Unknown')} 的文件数据为空")
processed_files = valid_files
_logger.info(f"有效文件数量: {len(processed_files)}")
# 如果没有已处理的数据,则执行处理流程
if not processed_files:
# 处理PDF文件,匹配提单对象
bl_objs = self.get_bl_objs() # 获取提单
processed_files = self._get_bill_numbers(bl_objs)
# 如果启用了涂抹文字,进行处理
if self.remove_specified_text and processed_files:
processed_files = self._remove_specified_text(processed_files, debug_mode=False)
# 合并PDF并保存到pdf_file字段
self._merge_pdf_files(processed_files)
# 如果有处理后的文件,序列化存储
if processed_files:
self.processed_files_data = self._serialize_processed_files(processed_files)
# 跳转到本向导的form视图(显示合并后的PDF)
return {
'type': 'ir.actions.act_window',
'res_model': 'batch.get.pod.info.wizard',
'name': _('Batch Get POD Info') if action_type == '获取尾程POD信息' else _(
'Create TEMU Bill of Loading'),
'view_mode': 'form',
'res_id': self.id,
'target': 'new',
'context': {'active_id': bl_objs.ids, }
}
# 如果没有已处理的数据,则执行处理流程
if not processed_files:
# 处理PDF文件,匹配提单对象
processed_files = self._get_bill_numbers()
# 如果启用了涂抹文字,进行处理
if self.remove_specified_text and processed_files:
processed_files = self._remove_specified_text(processed_files, debug_mode=False)
# 合并PDF并保存到pdf_file字段
self._merge_pdf_files(processed_files)
# 如果有处理后的文件,序列化存储
if processed_files:
self.processed_files_data = self._serialize_processed_files(processed_files)
# 跳转到本向导的form视图(显示合并后的PDF)
# 检查是否有文字清除失败的错误
if self.show_error_message and any(
'仍存在目标文字' in str(self.show_error_message) or '未完全清除文字' in str(
self.show_error_message)):
_logger.error(f"检测到文字清除失败,停止处理: {self.show_error_message}")
return {
'type': 'ir.actions.act_window',
'res_model': 'batch.get.pod.info.wizard',
......@@ -318,65 +363,50 @@ class BatchGetPodInfoWizard(models.TransientModel):
'view_mode': 'form',
'res_id': self.id,
'target': 'new',
'context': {'active_id': bl_objs.ids, }
'context': {'default_show_error_message': self.show_error_message,
'active_id': bl_objs.ids if bl_objs else False}
}
# 检查是否有文字清除失败的错误
if self.show_error_message and any(
'仍存在目标文字' in str(self.show_error_message) or '未完全清除文字' in str(self.show_error_message)):
_logger.error(f"检测到文字清除失败,停止处理: {self.show_error_message}")
return {
'type': 'ir.actions.act_window',
'res_model': 'batch.get.pod.info.wizard',
'name': _('Batch Get POD Info') if action_type == '获取尾程POD信息' else _(
'Create TEMU Bill of Loading'),
'view_mode': 'form',
'res_id': self.id,
'target': 'new',
'context': {'default_show_error_message': self.show_error_message,
'active_id': bl_objs.ids if bl_objs else False}
}
# 只处理成功涂抹的提单
# 直接根据processed_files中的processing_failed标志筛选成功处理的文件,无需从文本解析
successful_processed_files = []
if processed_files:
for file_info in processed_files:
# 检查是否有bl对象和有文件数据
if not file_info.get('bl'):
continue
file_data = file_info.get('file_data', '')
# 检查处理是否失败(通过processing_failed标记)
processing_failed = file_info.get('processing_failed', False)
# 如果有文件数据且未标记为失败,则认为处理成功
if file_data and not processing_failed:
successful_processed_files.append(file_info)
_logger.info(f"从{len(processed_files)}个文件中筛选出{len(successful_processed_files)}个成功处理的文件")
else:
_logger.warning("没有找到已处理的文件数据")
# 只处理成功涂抹的提单
# 直接根据processed_files中的processing_failed标志筛选成功处理的文件,无需从文本解析
successful_processed_files = []
if processed_files:
for file_info in processed_files:
# 检查是否有bl对象和有文件数据
if not file_info.get('bl'):
continue
bl = file_info['bl']
file_data = file_info.get('file_data', '')
# 检查处理是否失败(通过processing_failed标记)
processing_failed = file_info.get('processing_failed', False)
# 如果有文件数据且未标记为失败,则认为处理成功
if file_data and not processing_failed:
successful_processed_files.append(file_info)
_logger.info(f"从{len(processed_files)}个文件中筛选出{len(successful_processed_files)}个成功处理的文件")
else:
_logger.warning("没有找到已处理的文件数据")
# 回写到附件信息
if successful_processed_files and ((self.sync_last_mile_pod or self.sync_match_node) or self.get_last_mile_pod):
# 回写PDF文件到清关文件
self._write_pdf_file(successful_processed_files)
# 再同步和回写
if action_type == '获取尾程POD信息':
if self.sync_last_mile_pod and successful_processed_files:
self._sync_last_mile_pod(successful_processed_files)
# 同步推送匹配节点
if self.sync_match_node and successful_processed_files:
# 且需先对比小包当前节点的操作时间是否小于提取时间(同时区对比)若大于则不能推送,
# 若需补推节点,则需判断提取时间-写入节点(不取写入第一个节点)的前序间隔时间是否大于小包当前节点的操作时间。
# 若不满足以上条件,则不执行生成和自动推送节点,并在小包上新增推送备注(新增该字段)回写备注信息:获取尾程POD,自动推送节点失败,有风险产生倒挂。请手动操作205-10-20 10:20:20(获取时间)
valid_files = self._validate_node_push_conditions(successful_processed_files)
# raise ValidationError('通知')
if valid_files:
self.get_date_sync_match_node(valid_files)
else:
_logger.info(f"没有满足条件的文件,不执行生成和自动推送节点")
# 回写到附件信息
if successful_processed_files and (
(self.sync_last_mile_pod or self.sync_match_node) or self.get_last_mile_pod):
# 回写PDF文件到清关文件
self._write_pdf_file(successful_processed_files)
# 再同步和回写
if action_type == '获取尾程POD信息':
if self.sync_last_mile_pod and successful_processed_files:
self._sync_last_mile_pod(successful_processed_files)
# 同步推送匹配节点
if self.sync_match_node and successful_processed_files:
# 且需先对比小包当前节点的操作时间是否小于提取时间(同时区对比)若大于则不能推送,
# 若需补推节点,则需判断提取时间-写入节点(不取写入第一个节点)的前序间隔时间是否大于小包当前节点的操作时间。
# 若不满足以上条件,则不执行生成和自动推送节点,并在小包上新增推送备注(新增该字段)回写备注信息:获取尾程POD,自动推送节点失败,有风险产生倒挂。请手动操作205-10-20 10:20:20(获取时间)
valid_files = self._validate_node_push_conditions(successful_processed_files)
# raise ValidationError('通知')
if valid_files:
self.get_date_sync_match_node(valid_files)
else:
_logger.info(f"没有满足条件的文件,不执行生成和自动推送节点")
# 清理所有临时文件(包括数据库记录和物理文件),不能删,不然回写的时候没有文件了
self._cleanup_temp_attachments(bl_objs)
......@@ -395,6 +425,16 @@ class BatchGetPodInfoWizard(models.TransientModel):
'context': {'default_show_error_message': self.show_error_message,
'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):
"""
......@@ -405,10 +445,11 @@ class BatchGetPodInfoWizard(models.TransientModel):
return processed_files
# 写一个方法调接口获取提单pdf文件
def _get_pdf_file_arr(self, bill_numbers):
def _get_pdf_file_arr(self, bl_objs):
"""
从API获取PDF文件
"""
bill_numbers = [self.env['common.common'].sudo().process_match_str(bl.bl_no) for bl in bl_objs]
# 调用API获取PDF文件
api_url = self.env['ir.config_parameter'].sudo().get_param('last_mile_pod_api_url',
'http://172.104.52.150:7002')
......@@ -530,7 +571,7 @@ class BatchGetPodInfoWizard(models.TransientModel):
# 过滤有效的PDF文件
valid_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)
if not valid_files:
......@@ -541,12 +582,13 @@ class BatchGetPodInfoWizard(models.TransientModel):
if len(valid_files) == 1:
file_info = valid_files[0]
bl = file_info['bl']
bl_no = bl.bl_no
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')
pdf_filename = f"POD文件_{bl.bl_no}_{timestamp}.pdf"
pdf_filename = f"POD文件_{bl_no}_{timestamp}.pdf"
# 直接保存到字段
self.write({
......@@ -573,11 +615,10 @@ class BatchGetPodInfoWizard(models.TransientModel):
for file_info in batch_files:
bl = file_info['bl']
bl_no = bl.bl_no
file_data = file_info['file_data']
bl_numbers.append(bl.bl_no)
bl_numbers.append(bl_no)
source_pdf = None
pdf_binary = None
try:
# 将base64数据转换为二进制
pdf_binary = base64.b64decode(file_data)
......@@ -588,17 +629,15 @@ class BatchGetPodInfoWizard(models.TransientModel):
# 将源PDF的所有页面插入到合并的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:
_logger.error(f"合并提单 {bl.bl_no} 的PDF失败: {str(e)}")
_logger.error(f"合并提单 {bl_no} 的PDF失败: {str(e)}")
continue
finally:
# 立即释放资源
if source_pdf:
source_pdf.close()
source_pdf = None
pdf_binary = None
gc.collect() # 强制垃圾回收
# 每批处理完后,保存到临时文件并释放内存
......@@ -661,13 +700,12 @@ class BatchGetPodInfoWizard(models.TransientModel):
except Exception as 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 # 根据文件名匹配提单并返回处理后的数组
:param pdf_file_arr: PDF文件数组 [{'bill_number':'', 'filename':'', 'file_data':''}]
:return: 处理后的数组 [{'bl': bl_obj, 'file_name': 'xxx.pdf', 'file_data': 'xxx', 'matched': True/False}]
"""
bl_obj = self.get_order() # 获取当前选中的提单对象
processed_files = []
for bl in bl_obj:
select_bl_no = self.env['common.common'].sudo().process_match_str(bl.bl_no)
......@@ -854,10 +892,10 @@ class BatchGetPodInfoWizard(models.TransientModel):
skip_ocr = self.skip_ocr_direct_ai # 是否跳过OCR直接使用AI
for file_info in processed_files:
if not file_info['bl']:
if not file_info['bl_no']:
updated_files.append(file_info)
continue
bl = file_info['bl']
bl_no = file_info['bl_no']
file_data = file_info['file_data']
processed_file_data = file_data # 默认使用原始数据
processing_failed = False # 标记处理是否失败
......@@ -868,15 +906,15 @@ class BatchGetPodInfoWizard(models.TransientModel):
# 先提取文本用于后续同步节点功能(如果需要的话)
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处理
if skip_ocr:
_logger.info(f"提单 {bl.bl_no} 跳过OCR,直接使用AI处理")
_logger.info(f"提单 {bl_no} 跳过OCR,直接使用AI处理")
try:
ai_processed_pdf = self._process_pdf_with_ai_image_edit(
pdf_data=pdf_binary,
bl_no=bl.bl_no
bl_no=bl_no
)
if ai_processed_pdf:
processed_file_data = base64.b64encode(ai_processed_pdf).decode('utf-8')
......@@ -884,50 +922,50 @@ class BatchGetPodInfoWizard(models.TransientModel):
# 检查是否还存在目标文字
final_check_pdf = base64.b64decode(processed_file_data)
text_still_exists, final_found_texts = self._check_target_texts_exist(final_check_pdf,
bl.bl_no)
bl_no)
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)
error_messages.append(error_msg)
# 不更新文件数据,保持原始状态
processed_file_data = file_data
processing_failed = True
else:
_logger.info(f"提单 {bl.bl_no} AI处理成功,目标文字已清除")
_logger.info(f"提单 {bl_no} AI处理成功,目标文字已清除")
else:
error_msg = f"提单 {bl.bl_no} AI处理失败"
error_msg = f"提单 {bl_no} AI处理失败"
_logger.error(error_msg)
error_messages.append(error_msg)
processing_failed = True
except Exception as e:
_logger.error(f"提单 {bl.bl_no} AI处理异常: {str(e)}")
error_msg = f"提单 {bl.bl_no} AI处理异常: {str(e)}"
_logger.error(f"提单 {bl_no} AI处理异常: {str(e)}")
error_msg = f"提单 {bl_no} AI处理异常: {str(e)}"
error_messages.append(error_msg)
processing_failed = True
else:
# 原有逻辑:先用OCR处理,如果还存在则用AI处理
# 第一步:使用OCR方法处理PDF
_logger.info(f"提单 {bl.bl_no} 开始OCR处理")
_logger.info(f"提单 {bl_no} 开始OCR处理")
try:
processed_pdf = self._process_pdf_with_ocr(
pdf_data=pdf_binary,
bl_no=bl.bl_no,
bl_no=bl_no,
debug_mode=debug_mode
)
if processed_pdf:
processed_file_data = base64.b64encode(processed_pdf).decode('utf-8')
# 第二步:检查是否还存在目标文字
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}")
if text_exists:
# 第三步:如果还存在,使用AI图片编辑处理
_logger.info(f"提单 {bl.bl_no} OCR处理后仍存在目标文字,使用AI图片编辑处理")
_logger.info(f"提单 {bl_no} OCR处理后仍存在目标文字,使用AI图片编辑处理")
try:
ai_processed_pdf = self._process_pdf_with_ai_image_edit(
pdf_data=pdf_for_check,
bl_no=bl.bl_no
bl_no=bl_no
)
if ai_processed_pdf:
processed_file_data = base64.b64encode(ai_processed_pdf).decode('utf-8')
......@@ -935,51 +973,51 @@ class BatchGetPodInfoWizard(models.TransientModel):
# 第四步:再次检查是否还存在目标文字
final_check_pdf = base64.b64decode(processed_file_data)
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:
# 第五步:如果仍然存在,记录错误信息并停止处理
error_msg = f"提单 {bl.bl_no} 经过系统处理后仍存在目标文字: {', '.join(final_found_texts)},请取消该提单操作,手动处理"
error_msg = f"提单 {bl_no} 经过系统处理后仍存在目标文字: {', '.join(final_found_texts)},请取消该提单操作,手动处理"
_logger.error(error_msg)
error_messages.append(error_msg)
# 不更新文件数据,保持原始状态
processed_file_data = file_data
processing_failed = True
else:
_logger.warning(f"提单 {bl.bl_no} AI处理失败,检查OCR处理结果")
_logger.warning(f"提单 {bl_no} AI处理失败,检查OCR处理结果")
# AI处理失败,检查OCR结果是否真的清除了目标文字
ocr_check_pdf = base64.b64decode(processed_file_data)
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:
error_msg = f"提单 {bl.bl_no} 经过系统处理后仍存在目标文字: {', '.join(ocr_found_texts)},请取消该提单操作,手动处理"
error_msg = f"提单 {bl_no} 经过系统处理后仍存在目标文字: {', '.join(ocr_found_texts)},请取消该提单操作,手动处理"
error_messages.append(error_msg)
# 不更新文件数据,保持原始状态
processed_file_data = file_data
processing_failed = True
else:
_logger.info(f"提单 {bl.bl_no} OCR处理成功,目标文字已清除")
_logger.info(f"提单 {bl_no} OCR处理成功,目标文字已清除")
except Exception as e:
_logger.error(f"提单 {bl.bl_no} AI处理异常: {str(e)}")
_logger.error(f"提单 {bl_no} AI处理异常: {str(e)}")
# AI处理失败,使用OCR结果,但需要检查
final_check_pdf = base64.b64decode(processed_file_data)
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:
error_msg = f"提单 {bl.bl_no} 经过系统处理后仍存在目标文字: {', '.join(final_found_texts)},请取消该提单操作,手动处理"
error_msg = f"提单 {bl_no} 经过系统处理后仍存在目标文字: {', '.join(final_found_texts)},请取消该提单操作,手动处理"
error_messages.append(error_msg)
# 不更新文件数据,保持原始状态
processed_file_data = file_data
processing_failed = True
else:
_logger.info(f"提单 {bl.bl_no} OCR处理成功,目标文字已清除")
_logger.info(f"提单 {bl_no} OCR处理成功,目标文字已清除")
else:
_logger.warning(f"提单 {bl.bl_no} OCR处理失败")
error_messages.append(f"提单 {bl.bl_no} OCR处理失败")
_logger.warning(f"提单 {bl_no} OCR处理失败")
error_messages.append(f"提单 {bl_no} OCR处理失败")
processing_failed = True
except Exception as e:
_logger.error(f"提单 {bl.bl_no} OCR处理异常: {str(e)}")
error_messages.append(f"提单 {bl.bl_no} OCR处理异常: {str(e)}")
_logger.error(f"提单 {bl_no} OCR处理异常: {str(e)}")
error_messages.append(f"提单 {bl_no} OCR处理异常: {str(e)}")
processing_failed = True
# 更新文件信息,使用处理后的PDF数据
......@@ -2526,8 +2564,6 @@ class BatchGetPodInfoWizard(models.TransientModel):
temp_attachments = self.env['ir.attachment'].sudo().browse(attachment_ids)
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')}之前创建的临时附件,开始清理")
# 删除物理文件
......
......@@ -131,7 +131,9 @@
<!-- 确认按钮:使用已处理的文件数据进行回写 -->
<!-- 如果有失败的文件(show_error_message不为空),需要勾选generate_successful_processed才显示;如果全部成功,直接显示 -->
<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"/>
</footer>
</sheet>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论