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

Merge branch 'release/3.6.0'

...@@ -12,5 +12,31 @@ ...@@ -12,5 +12,31 @@
<field name="active" eval="False"/> <field name="active" eval="False"/>
</record> </record>
<!-- 自动获取尾程POD-->
<record id="cron_get_pod" model="ir.cron">
<field name="name">自动获取尾程POD</field>
<field name="model_id" ref="ccs_base.model_cc_bl"/>
<field name="state">code</field>
<field name="code">model.cron_get_pod()</field>
<field name='interval_number'>3</field>
<field name='interval_type'>hours</field>
<field name="nextcall" eval="(DateTime.now().replace(hour=0, minute=0) + timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')" />
<field name="numbercall">-1</field>
<field name="active" eval="False"/>
</record>
<!-- 清理向导生成的临时附件-->
<record id="cron_cleanup_temp_attachments" model="ir.cron">
<field name="name">清理向导临时附件</field>
<field name="model_id" ref="model_batch_get_pod_info_wizard"/>
<field name="state">code</field>
<field name="code">model.cron_cleanup_temp_attachments()</field>
<field name='interval_number'>1</field>
<field name='interval_type'>days</field>
<field name="nextcall" eval="(DateTime.now().replace(hour=0, minute=0) + timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')" />
<field name="numbercall">-1</field>
<field name="active" eval="True"/>
</record>
</data> </data>
</odoo> </odoo>
\ No newline at end of file
差异被折叠。
...@@ -556,7 +556,8 @@ class CcClearanceFile(models.Model): ...@@ -556,7 +556,8 @@ class CcClearanceFile(models.Model):
def search_clearance_file(self, bl_id, file_name): def search_clearance_file(self, bl_id, file_name):
"""搜索清关文件""" """搜索清关文件"""
return self.env['cc.clearance.file'].search([('bl_id','=',bl_id),('file_name','=',file_name)],limit=1) return self.env['cc.clearance.file'].search([('bl_id', '=', bl_id), ('file_name', '=', file_name)], limit=1)
# 创建一个业务对象,继承自models.Model, 用于管理业务数据.业务数据包括提单号、提单日期、提单总件数、提单总金额、所属客户、提单明细、清关进度明细、状态[待确认、清关中、已完成] # 创建一个业务对象,继承自models.Model, 用于管理业务数据.业务数据包括提单号、提单日期、提单总件数、提单总金额、所属客户、提单明细、清关进度明细、状态[待确认、清关中、已完成]
class CcBL(models.Model): class CcBL(models.Model):
...@@ -712,6 +713,26 @@ class CcBL(models.Model): ...@@ -712,6 +713,26 @@ class CcBL(models.Model):
# 添加状态说明字段 # 添加状态说明字段
state_explain = fields.Text('State Explain', help='State Explain') state_explain = fields.Text('State Explain', help='State Explain')
def cron_get_pod(self):
"""
状态为清关中且附件信息尾程交接POD(待大包数量和箱号),为空的提单,自动获取尾程POD信息
"""
fix_name = '尾程交接POD(待大包数量和箱号)'
bl_objs = self.env['cc.bl'].search(
[('state', '=', 'ccing'), ('cc_attachment_ids.file_name', '=', fix_name)])
if len(bl_objs) > 0:
line_objs = bl_objs.cc_attachment_ids.filtered(lambda attach: not attach.file and attach.file_name == fix_name)
if len(line_objs) > 0:
bl_objs = line_objs.mapped('bl_id')
logging.info('cron_get_pod bl_objs:%s,%s' % (len(bl_objs), ','.join([bl.bl_no for bl in bl_objs])))
wizard_obj = self.env['batch.get.pod.info.wizard'].sudo().with_context(active_id=bl_objs.ids,
is_skip_raise_error=True).create({
'sync_last_mile_pod': True,
'remove_specified_text': True,
'sync_match_node': True
})
wizard_obj.confirm()
# 增加一个can_cancel的方法,用于检查提单当前是否可以取消,返回True表示可以取消, False表示不可以取消,同时返回取消的原因 # 增加一个can_cancel的方法,用于检查提单当前是否可以取消,返回True表示可以取消, False表示不可以取消,同时返回取消的原因
def check_cancel(self): def check_cancel(self):
if self.is_cancel: if self.is_cancel:
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
# 导入日志 # 导入日志
import logging import logging
from odoo import models, fields from odoo import api, models, fields, _
from odoo.exceptions import ValidationError
# 获取日志 # 获取日志
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
...@@ -52,6 +53,31 @@ class CcNode(models.Model): ...@@ -52,6 +53,31 @@ class CcNode(models.Model):
package_state = fields.Many2one('cc.node', string='Corresponding to the status of the package', package_state = fields.Many2one('cc.node', string='Corresponding to the status of the package',
domain="[('node_type','=','package')]", index=True) # 对应小包状态 domain="[('node_type','=','package')]", index=True) # 对应小包状态
# 新增字段:尾程POD节点匹配,只有类型为小包的节点才能勾选
is_pod_node = fields.Boolean(string='Tail POD Node Match', default=False)
@api.constrains('is_pod_node', 'node_type')
def _check_pod_node(self):
"""
Constraints: Only package type nodes can be selected as POD nodes
Only one package type node can be selected as POD node
"""
for record in self:
# 只有小包类型节点才能勾选尾程POD节点匹配
if record.is_pod_node and record.node_type != 'package':
raise ValidationError(_("Only package type nodes can be selected as POD nodes"))
# 如果当前节点勾选了POD节点,检查是否已有其他小包节点勾选了
# Only one package type node can be selected as POD node
if record.is_pod_node and record.node_type == 'package':
existing_pod_nodes = self.search([
('is_pod_node', '=', True),
('node_type', '=', 'package'),
('id', '!=', record.id)
])
if existing_pod_nodes:
raise ValidationError(_("Only one package type node can be selected as POD node"))
def node_is_sync(self): def node_is_sync(self):
# 如果更新节点是 默认节点 同步的标志变为True # 如果更新节点是 默认节点 同步的标志变为True
is_sync = False is_sync = False
......
...@@ -5,15 +5,15 @@ import base64 ...@@ -5,15 +5,15 @@ import base64
import json import json
import logging import logging
import re import re
import tempfile
from datetime import datetime, timedelta from datetime import datetime, timedelta
from io import BytesIO from io import BytesIO
import pdfplumber import pdfplumber
import xlrd import xlrd
from aip.ocr import AipOcr
from odoo import models from odoo import models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
import tempfile
from aip.ocr import AipOcr
from pdf2image import convert_from_path from pdf2image import convert_from_path
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
...@@ -265,7 +265,8 @@ class OrderStateChangeRule(models.Model): ...@@ -265,7 +265,8 @@ class OrderStateChangeRule(models.Model):
ids = [i[0] for i in result] ids = [i[0] for i in result]
bl_objs = self.env['cc.bl'].sudo().search([('id', 'in', ids)]) if result else False bl_objs = self.env['cc.bl'].sudo().search([('id', 'in', ids)]) if result else False
if bl_objs and attachment_tuple_arr: if bl_objs and attachment_tuple_arr:
file_objs = self.env['cc.clearance.file'].sudo().search([('file_name', '=', '尾程交接POD(待大包数量和箱号)'), file_objs = self.env['cc.clearance.file'].sudo().search(
[('file_name', '=', '尾程交接POD(待大包数量和箱号)'),
('bl_id', 'in', bl_objs.ids)]) ('bl_id', 'in', bl_objs.ids)])
file_objs.unlink() file_objs.unlink()
for attachment_tuple in attachment_tuple_arr: for attachment_tuple in attachment_tuple_arr:
...@@ -362,7 +363,7 @@ class OrderStateChangeRule(models.Model): ...@@ -362,7 +363,7 @@ class OrderStateChangeRule(models.Model):
# poppler_path = r"E:\poppler-23.08.0\Library\bin" # poppler_path = r"E:\poppler-23.08.0\Library\bin"
# images = convert_from_path(pdf_path, poppler_path=poppler_path) # images = convert_from_path(pdf_path, poppler_path=poppler_path)
# 非本地代码 # 非本地代码
images = convert_from_path(pdf_path)#如果文件损坏的会报错,需要处理 images = convert_from_path(pdf_path) # 如果文件损坏的会报错,需要处理
# 保存每一页为图片文件 # 保存每一页为图片文件
for i, image in enumerate(images): for i, image in enumerate(images):
if i == 0: if i == 0:
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
<field name="file" string="PDF File" required="1" filename="attachment_name"/> <field name="file" string="PDF File" required="1" filename="attachment_name"/>
<field name="is_upload" readonly="1"/> <field name="is_upload" readonly="1"/>
<field name="upload_time"/> <field name="upload_time"/>
<field name="create_date" optional="show"/>
<button name="action_sync" string="SyncTo.." type="object" icon="fa-upload"/> <button name="action_sync" string="SyncTo.." type="object" icon="fa-upload"/>
<field name="attachment_name" invisible="1"/> <field name="attachment_name" invisible="1"/>
</tree> </tree>
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
<field optional="show" name="is_must" string="Is Must Node"/> <field optional="show" name="is_must" string="Is Must Node"/>
<field optional="show" name="is_done" string="Is Done Node"/> <field optional="show" name="is_done" string="Is Done Node"/>
<field optional="show" name="is_default" string="Is Current Node"/> <field optional="show" name="is_default" string="Is Current Node"/>
<field optional="show" name="is_pod_node" string="Tail POD Node Match" attrs="{'invisible': [('node_type', '!=', 'package')]}" />
</tree> </tree>
</field> </field>
</record> </record>
......
差异被折叠。
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -9,24 +9,61 @@ ...@@ -9,24 +9,61 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Batch Get POD Info"> <!-- 批量获取POD信息 --> <form string="Batch Get POD Info"> <!-- 批量获取POD信息 -->
<sheet> <sheet>
<!-- <group> -->
<group> <group>
<group> <!-- attrs="{'invisible': [('pdf_file', '!=', False)]}" -->
<field name="sync_last_mile_pod" widget="boolean_toggle"/> <field name="remove_specified_text" readonly="1" widget="boolean_toggle"/>
<field name="skip_ocr_direct_ai" readonly="0" widget="boolean_toggle"
attrs="{'invisible': [('pdf_file', '!=', False)]}"/>
</group> </group>
<group> <group attrs="{'invisible': ['|', ('pdf_file', '=', False), ('show_error_message', '=', False)]}">
<field name="remove_specified_text" widget="boolean_toggle"/> <field name="sync_successful_processed" widget="boolean_toggle"/>
</group> </group>
<group attrs="{'invisible': ['|', ('pdf_file', '=', False), ('sync_successful_processed', '=', False)]}">
<field name="sync_last_mile_pod" widget="boolean_toggle"/>
</group> </group>
<group attrs="{'invisible': ['|', ('pdf_file', '=', False), ('sync_successful_processed', '=', False)]}">
<field name="sync_match_node" widget="boolean_toggle"/>
</group>
<!-- </group> -->
<div class="alert alert-info" role="alert"> <div class="alert alert-info" role="alert">
<strong>Description:</strong> <!-- 说明: --> <strong>Description:</strong> <!-- 说明: -->
<ul> <ul>
<li><strong>Sync Last Mile POD:</strong> Synchronize POD (Proof of Delivery) attachment information with TK system, including big package quantities and container numbers</li> <!-- 同步尾程POD:向TK同步尾程交接POD(待大包数量和箱号)的附件信息 --> <li>
<li><strong>Remove Specified Text:</strong> Remove specified text (AGN, UCLINK LOGISITICS LTD) from PDF files</li> <!-- 涂抹指定文字:对PDF文件中的指定文字进行涂抹处理 --> <strong>Remove Specified Text:</strong>
Remove specified text (AGN, UCLINK LOGISITICS LTD) from PDF files
</li> <!-- 涂抹指定文字:对PDF文件中的指定文字进行涂抹处理 -->
<li attrs="{'invisible': [('sync_successful_processed', '=', False)]}">
<strong>Sync Last Mile POD:</strong>
Synchronize POD (Proof of Delivery) attachment information with TK system, including
big package quantities and container numbers
</li> <!-- 同步尾程POD:向TK同步尾程交接POD(待大包数量和箱号)的附件信息 -->
<li attrs="{'invisible': [('sync_successful_processed', '=', False)]}">
<strong>Sync Push Match Node:</strong>
Synchronize and push matched node information based on POD file, extract time from
red boxes as node operation time
</li> <!-- 同步推送匹配节点:根据POD文件获取对应的清关节点,提取红色框时间作为节点操作时间 -->
</ul> </ul>
</div> </div>
<div class="alert alert-danger" role="alert"
attrs="{'invisible': [('show_error_message', '=', False)]}">
<field name="show_error_message"/>
</div>
<div>
<field name="pdf_file" filename="pdf_filename" widget="pdf_viewer" readonly="1"
attrs="{'invisible': [('pdf_file', '=', False)]}"/>
</div>
<footer> <footer>
<button string="Confirm" type="object" name="confirm" class="btn-primary"/> <!-- 预览按钮:处理PDF并显示合并后的文件 -->
<button string="Preview" type="object" name="action_preview" class="btn-primary"
attrs="{'invisible': [('pdf_file', '!=', False)]}"/>
<!-- 确认按钮:使用已处理的文件数据进行回写和同步 -->
<!-- 如果有失败的文件(show_error_message不为空),需要勾选sync_successful_processed才显示;如果全部成功,直接显示 -->
<button string="Confirm" type="object" name="confirm" class="btn-primary"
attrs="{'invisible': ['|', ('pdf_file', '=', False), '&amp;', ('show_error_message', '!=', False), ('sync_successful_processed', '=', False)]}"/>
<button string="Close" special="cancel"/> <button string="Close" special="cancel"/>
</footer> </footer>
</sheet> </sheet>
......
差异被折叠。
import json
import os
from dashscope import MultiModalConversation
import dashscope
import base64
import requests
dashscope.base_http_api_url = 'https://dashscope.aliyuncs.com/api/v1'
image_path = "./图片识别2.png"
def download_image(image_url, save_path='output.png'):
try:
response = requests.get(image_url, stream=True, timeout=300) # 设置超时
response.raise_for_status() # 如果HTTP状态码不是200,则引发异常
with open(save_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
print(f"图像已成功下载到: {save_path}")
except requests.exceptions.RequestException as e:
print(f"图像下载失败: {e}")
with open(image_path, "rb") as image_file:
image_base64 = base64.b64encode(image_file.read()).decode('utf-8')
# 模型支持输入1-3张图片
messages = [
{
"role": "user",
"content": [
{"image": f"data:image/png;base64,{image_base64}"},
{"text": "将图片中的AGN UCLINK LOGISITICS LTD这一段文字抹去"}
]
}
]
# 新加坡和北京地域的API Key不同。获取API Key:https://help.aliyun.com/zh/model-studio/get-api-key
# 若没有配置环境变量,请用百炼 API Key 将下行替换为:api_key="sk-xxx"
# api_key = os.getenv("DASHSCOPE_API_KEY")
# 模型仅支持单轮对话,复用了多轮对话的接口
response = MultiModalConversation.call(
api_key='sk-e41914f0d9c94035a5ae1322e9a61fb1',
model="qwen-image-edit",
messages=messages,
stream=False,
watermark=False,
negative_prompt=" "
)
if response.status_code == 200:
# 如需查看完整响应,请取消下行注释
# print(json.dumps(response, ensure_ascii=False))
print("输出图像的URL:", response.output.choices[0].message.content[0]['image'])
image_url = response.output.choices[0].message.content[0]['image']
download_image(image_url, save_path='处理图片.png')
else:
print(f"HTTP返回码:{response.status_code}")
print(f"错误码:{response.code}")
print(f"错误信息:{response.message}")
print("请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code")
import base64
import mimetypes
from http import HTTPStatus
from urllib.parse import urlparse, unquote
from pathlib import PurePosixPath
import dashscope
import requests
from dashscope import ImageSynthesis
import os
# 以下为北京地域url,若使用新加坡地域的模型,需将url替换为:https://dashscope-intl.aliyuncs.com/api/v1
dashscope.base_http_api_url = 'https://dashscope.aliyuncs.com/api/v1'
# 若没有配置环境变量,请用百炼API Key将下行替换为:api_key="sk-xxx"
# 新加坡和北京地域的API Key不同。获取API Key:https://help.aliyun.com/zh/model-studio/get-api-key
api_key = 'sk-e41914f0d9c94035a5ae1322e9a61fb1'
# --- 输入图片:使用 Base64 编码 ---
# base64编码格式为 data:{MIME_type};base64,{base64_data}
def encode_file(file_path):
mime_type, _ = mimetypes.guess_type(file_path)
if not mime_type or not mime_type.startswith("image/"):
raise ValueError("不支持或无法识别的图像格式")
with open(file_path, "rb") as image_file:
encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
return f"data:{mime_type};base64,{encoded_string}"
"""
图像输入方式说明:
以下提供了三种图片输入方式,三选一即可
1. 使用公网URL - 适合已有公开可访问的图片
2. 使用本地文件 - 适合本地开发测试
3. 使用Base64编码 - 适合私有图片或需要加密传输的场景
"""
# 【方式一】使用公网图片 URL
# image_url_1 = "https://img.alicdn.com/imgextra/i4/O1CN01TlDlJe1LR9zso3xAC_!!6000000001295-2-tps-1104-1472.png"
# image_url_2 = "https://img.alicdn.com/imgextra/i4/O1CN01M9azZ41YdblclkU6Z_!!6000000003082-2-tps-1696-960.png"
# 【方式二】使用本地文件(支持绝对路径和相对路径)
# 格式要求:file:// + 文件路径
# 示例(绝对路径):
# image_url_1 = "file://" + "/path/to/your/image_1.png" # Linux/macOS
# image_url_2 = "file://" + "C:/path/to/your/image_2.png" # Windows
# 示例(相对路径):
# image_url_1 = "file://" + "./image_1.png" # 以实际路径为准
# image_url_2 = "file://" + "./image_1.png" # 以实际路径为准
# 【方式三】使用Base64编码的图片
image_url_1 = encode_file("./图片识别2.png") # 以实际路径为准
# image_url_2 = encode_file("./image_2.png") # 以实际路径为准
print('----sync call, please wait a moment----')
rsp = ImageSynthesis.call(api_key=api_key,
model="wan2.5-i2i-preview",
prompt="将图片中的 AGN 跟 UCLINK LOGISITICS LTD 这两段文字抹去, 确保其他的内容都保留,包括水印等",
images=[image_url_1],
negative_prompt="",
n=1,
watermark=False,
seed=12345)
print('response: %s' % rsp)
if rsp.status_code == HTTPStatus.OK:
# 在当前目录下保存图片
for result in rsp.output.results:
file_name = PurePosixPath(unquote(urlparse(result.url).path)).parts[-1]
with open('./%s' % file_name, 'wb+') as f:
f.write(requests.get(result.url).content)
else:
print('sync_call Failed, status_code: %s, code: %s, message: %s' %
(rsp.status_code, rsp.code, rsp.message))
\ No newline at end of file
...@@ -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-09-23 09:31+0000\n" "POT-Creation-Date: 2025-10-31 02:15+0000\n"
"PO-Revision-Date: 2025-09-23 17:35+0800\n" "PO-Revision-Date: 2025-10-31 10:16+0800\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: zh_CN\n" "Language: zh_CN\n"
...@@ -54,6 +54,11 @@ msgstr "" ...@@ -54,6 +54,11 @@ msgstr ""
msgid "Attachment" msgid "Attachment"
msgstr "附件" msgstr "附件"
#. module: ccs_connect_tiktok
#: model:ir.model,name:ccs_connect_tiktok.model_batch_get_pod_info_wizard
msgid "Batch Get POD Info Wizard"
msgstr "批量获取尾程POD向导"
#. module: ccs_connect_tiktok #. module: ccs_connect_tiktok
#: model:ir.actions.server,name:ccs_connect_tiktok.action_batch_sync_bl_status #: model:ir.actions.server,name:ccs_connect_tiktok.action_batch_sync_bl_status
msgid "Batch Sync Bill Of Loading Status" msgid "Batch Sync Bill Of Loading Status"
...@@ -396,14 +401,6 @@ msgstr "托盘" ...@@ -396,14 +401,6 @@ msgstr "托盘"
msgid "Pallet Handover" msgid "Pallet Handover"
msgstr "托盘交货" msgstr "托盘交货"
#. module: ccs_connect_tiktok
#. odoo-python
#: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0
#: model:ir.model.fields.selection,name:ccs_connect_tiktok.selection__pda_scan_record__operation__pallet_pickup
#, python-format
msgid "Pallet Pickup"
msgstr "托盘提货"
#. module: ccs_connect_tiktok #. module: ccs_connect_tiktok
#. odoo-python #. odoo-python
#: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0 #: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0
...@@ -442,6 +439,11 @@ msgstr "进度" ...@@ -442,6 +439,11 @@ msgstr "进度"
msgid "Progress Name" msgid "Progress Name"
msgstr "进度名称" msgstr "进度名称"
#. module: ccs_connect_tiktok
#: model_terms:ir.ui.view,arch_db:ccs_connect_tiktok.search_cc_bl_view
msgid "Push Failed"
msgstr "推送失败"
#. module: ccs_connect_tiktok #. module: ccs_connect_tiktok
#: model:ir.model,name:ccs_connect_tiktok.model_cc_ship_package #: model:ir.model,name:ccs_connect_tiktok.model_cc_ship_package
#: model:ir.model.fields,field_description:ccs_connect_tiktok.field_cc_ship_package_sync_log__package_id #: model:ir.model.fields,field_description:ccs_connect_tiktok.field_cc_ship_package_sync_log__package_id
...@@ -451,6 +453,11 @@ msgstr "进度名称" ...@@ -451,6 +453,11 @@ msgstr "进度名称"
msgid "Ship Package" msgid "Ship Package"
msgstr "小包" msgstr "小包"
#. module: ccs_connect_tiktok
#: model:ir.model.fields,field_description:ccs_connect_tiktok.field_cc_bl__push_remark
msgid "Ship Package Push Remark"
msgstr "小包推送备注"
#. module: ccs_connect_tiktok #. module: ccs_connect_tiktok
#: model:ir.model.fields,field_description:ccs_connect_tiktok.field_batch_input_ship_package_status_wizard__is_skip_check #: model:ir.model.fields,field_description:ccs_connect_tiktok.field_batch_input_ship_package_status_wizard__is_skip_check
#: model:ir.model.fields,field_description:ccs_connect_tiktok.field_update_bl_status_wizard__is_skip_check #: model:ir.model.fields,field_description:ccs_connect_tiktok.field_update_bl_status_wizard__is_skip_check
...@@ -565,14 +572,6 @@ msgstr "进度编码" ...@@ -565,14 +572,6 @@ msgstr "进度编码"
msgid "Tail Handover" msgid "Tail Handover"
msgstr "按尾程交货" msgstr "按尾程交货"
#. module: ccs_connect_tiktok
#. odoo-python
#: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0
#: model:ir.model.fields.selection,name:ccs_connect_tiktok.selection__pda_scan_record__operation__tail_pickup
#, python-format
msgid "Tail Pickup"
msgstr ""
#. module: ccs_connect_tiktok #. module: ccs_connect_tiktok
#. odoo-python #. odoo-python
#: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0 #: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0
......
...@@ -351,6 +351,7 @@ class CcBl(models.Model): ...@@ -351,6 +351,7 @@ class CcBl(models.Model):
bl_sync_log_ids = fields.One2many('cc.bl.sync.log', 'bl_id', string='Bill Of Loading Sync Logs') bl_sync_log_ids = fields.One2many('cc.bl.sync.log', 'bl_id', string='Bill Of Loading Sync Logs')
# 增加提单状态操作时间:取最新一条提单节点同步信息的操作时间 # 增加提单状态操作时间:取最新一条提单节点同步信息的操作时间
process_time = fields.Datetime(string='Process Time', compute='_compute_process_time', store=True) process_time = fields.Datetime(string='Process Time', compute='_compute_process_time', store=True)
push_remark = fields.Text('Ship Package Push Remark', default='')
def change_state_by_ship_package(self): def change_state_by_ship_package(self):
""" """
...@@ -457,7 +458,7 @@ class CcBl(models.Model): ...@@ -457,7 +458,7 @@ class CcBl(models.Model):
raise ValidationError( raise ValidationError(
_('The small package node or bill of lading node is not in the completed node, and the bill of lading cannot be changed to completed!')) # 小包节点或提单节点不在已完成节点,提单不能变为已完成! _('The small package node or bill of lading node is not in the completed node, and the bill of lading cannot be changed to completed!')) # 小包节点或提单节点不在已完成节点,提单不能变为已完成!
def done_func(self, is_email=False,**kwargs): def done_func(self, is_email=False, **kwargs):
""" """
变为已完成.先进行提单巡查,再进行提单状态变更 变为已完成.先进行提单巡查,再进行提单状态变更
""" """
...@@ -475,7 +476,7 @@ class CcBl(models.Model): ...@@ -475,7 +476,7 @@ class CcBl(models.Model):
content = self.get_patrol_email_content(result) content = self.get_patrol_email_content(result)
raise ValidationError(content) raise ValidationError(content)
if is_success or kwargs.get('exception_reason'): if is_success or kwargs.get('exception_reason'):
super(CcBl, self).done_func(is_email=is_email,**kwargs) super(CcBl, self).done_func(is_email=is_email, **kwargs)
def check_bl_patrol(self): def check_bl_patrol(self):
""" """
...@@ -994,7 +995,7 @@ class CcBl(models.Model): ...@@ -994,7 +995,7 @@ class CcBl(models.Model):
create_sync_log_value_arr = [] # 创建小包同步日志数据 create_sync_log_value_arr = [] # 创建小包同步日志数据
create_api_log_value_arr = [] # 创建api_log日志数据 create_api_log_value_arr = [] # 创建api_log日志数据
utc_time = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') utc_time = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
user_id = self.env.user.id user_id = user_obj.id if user_obj else self.env.user.id
for response_item in responses: for response_item in responses:
response_data = response_item[0] response_data = response_item[0]
logging.info('ship response_data:%s' % response_data) logging.info('ship response_data:%s' % response_data)
...@@ -1226,7 +1227,7 @@ class CcBl(models.Model): ...@@ -1226,7 +1227,7 @@ class CcBl(models.Model):
return False return False
def mail_auto_push(self, mail_time=False, tally_ship_packages=[], action_type='tally', mail_db_user='邮件接收', def mail_auto_push(self, mail_time=False, tally_ship_packages=[], action_type='tally', mail_db_user='邮件接收',
pda_db_user='pda'): pda_db_user='pda', pod_node_id=False):
self = self.with_context(dict(self._context, is_mail=True)) self = self.with_context(dict(self._context, is_mail=True))
for item in self: for item in self:
try: try:
...@@ -1249,14 +1250,7 @@ class CcBl(models.Model): ...@@ -1249,14 +1250,7 @@ class CcBl(models.Model):
user_obj = self.env['res.users'].search([('login', '=', pda_db_user)], limit=1) user_obj = self.env['res.users'].search([('login', '=', pda_db_user)], limit=1)
ship_package_ids = [ship_package_dict for sublist in [d['id'] for d in ship_packages] for ship_package_ids = [ship_package_dict for sublist in [d['id'] for d in ship_packages] for
ship_package_dict in sublist] ship_package_dict in sublist]
tally_state = 'checked_goods' if action_type == 'tally' else ( node_obj = False
'picked_up' if action_type == 'pickup' else 'handover_completed')
# 后续节点
node_obj = self.env['cc.node'].sudo().search([
('node_type', '=', 'package'),
('tally_state', '=', tally_state) # 检查理货或尾程交接的节点,根据排序进行升序
], order='seq asc')
if node_obj:
all_ship_package_obj = self.env['cc.ship.package'].search( all_ship_package_obj = self.env['cc.ship.package'].search(
[('id', 'in', ship_package_ids)]) # 所有小包 [('id', 'in', ship_package_ids)]) # 所有小包
# 预先获取所有同步日志 - 批量查询 # 预先获取所有同步日志 - 批量查询
...@@ -1279,8 +1273,24 @@ class CcBl(models.Model): ...@@ -1279,8 +1273,24 @@ class CcBl(models.Model):
for single_id in package['id']: for single_id in package['id']:
ship_packages_dict[single_id] = package['tally_time'] ship_packages_dict[single_id] = package['tally_time']
if action_type == 'push_match_node' and pod_node_id:
# 尾程匹配的节点包括前序节点
node_obj = self.env['cc.node'].sudo().search([('id', '=', pod_node_id)])
else:
tally_state = 'checked_goods' if action_type == 'tally' else (
'picked_up' if action_type == 'pickup' else 'handover_completed')
# 检查理货或尾程交接的节点,根据排序进行升序
node_obj = self.env['cc.node'].sudo().search([
('node_type', '=', 'package'),
('tally_state', '=', tally_state)
], order='seq asc')
if not node_obj:
return True
# 前序节点 理货或尾程交接之前没有生成的节点 # 前序节点 理货或尾程交接之前没有生成的节点
before_node_obj = node_obj[0].get_before_node() before_node_obj = node_obj[0].get_before_node()
if pod_node_id:
# 合并前序节点和当前节点,去重
before_node_obj = before_node_obj | node_obj[0]
# 理货或尾程交接之前没有生成的节点 # 理货或尾程交接之前没有生成的节点
for before_node in before_node_obj: for before_node in before_node_obj:
before_minutes = before_node.calculate_total_interval(node_obj[0]) before_minutes = before_node.calculate_total_interval(node_obj[0])
...@@ -1292,10 +1302,11 @@ class CcBl(models.Model): ...@@ -1292,10 +1302,11 @@ class CcBl(models.Model):
package_id, set()): package_id, set()):
tally_time = ship_packages_dict.get(package_id) tally_time = ship_packages_dict.get(package_id)
if tally_time: if tally_time:
operation_time = ( tally_time = datetime.strptime(tally_time, '%Y-%m-%d %H:%M:%S')
datetime.strptime(tally_time, '%Y-%m-%d %H:%M:%S') - timedelta( operation_time = tally_time if pod_node_id and pod_node_id == before_node.id else ((
tally_time - timedelta(
minutes=before_minutes)) if tally_time else fields.Datetime.now() - timedelta( minutes=before_minutes)) if tally_time else fields.Datetime.now() - timedelta(
minutes=before_minutes) minutes=before_minutes))
update_data.append(( update_data.append((
package_id, package_id,
before_node.id, before_node.id,
...@@ -1307,7 +1318,8 @@ class CcBl(models.Model): ...@@ -1307,7 +1318,8 @@ class CcBl(models.Model):
if update_data: if update_data:
# 构建批量更新SQL # 构建批量更新SQL
values_str = ','.join( values_str = ','.join(
self.env.cr.mogrify("(%s,%s,%s,%s,%s)", row).decode('utf-8') for row in update_data) self.env.cr.mogrify("(%s,%s,%s,%s,%s)", row).decode('utf-8') for row in
update_data)
sql = """ sql = """
UPDATE cc_ship_package AS t SET UPDATE cc_ship_package AS t SET
state = c.state, state = c.state,
...@@ -1325,6 +1337,8 @@ class CcBl(models.Model): ...@@ -1325,6 +1337,8 @@ class CcBl(models.Model):
item.try_callback_track(max_retries=2, ship_package_ids=ship_package_ids, item.try_callback_track(max_retries=2, ship_package_ids=ship_package_ids,
user_obj=user_obj) user_obj=user_obj)
# 理货或尾程交接的节点 # 理货或尾程交接的节点
# pda需要生成后续节点
if action_type != 'push_match_node':
# 预先获取所有状态节点 # 预先获取所有状态节点
all_state_nodes = self.env['cc.node'].sudo().search([ all_state_nodes = self.env['cc.node'].sudo().search([
('node_type', '=', 'package') ('node_type', '=', 'package')
......
...@@ -53,6 +53,9 @@ ...@@ -53,6 +53,9 @@
</div> </div>
</button> </button>
</button> </button>
<field name="process_time" position="after">
<field name="push_remark" readonly="1" attrs="{'invisible': [('push_remark', '=', '')]}"/>
</field>
<notebook position="inside"> <notebook position="inside">
<page string="Sync Log"> <page string="Sync Log">
<field name="bl_sync_log_ids" widget="one2many_list"/> <field name="bl_sync_log_ids" widget="one2many_list"/>
...@@ -65,6 +68,20 @@ ...@@ -65,6 +68,20 @@
</field> </field>
</record> </record>
<record model="ir.ui.view" id="search_cc_bl_view">
<field name="name">search.cc.bl</field>
<field name="model">cc.bl</field>
<field name="inherit_id" ref="ccs_base.search_cc_bl_view"/>
<field name="arch" type="xml">
<filter name="filter_state_not_finished" position="after">
<separator/>
<filter string="Push Failed" name="filter_push_failed" domain="[('push_remark', '!=', '')]"/>
</filter>
</field>
</record>
<record id="action_batch_sync_package_status" model="ir.actions.server"> <record id="action_batch_sync_package_status" model="ir.actions.server">
<field name="name">Batch Sync Package Status</field> <field name="name">Batch Sync Package Status</field>
<field name="model_id" ref="model_cc_bl"/> <field name="model_id" ref="model_cc_bl"/>
......
...@@ -3,4 +3,5 @@ ...@@ -3,4 +3,5 @@
from . import batch_input_ship_package_statu_wizard from . import batch_input_ship_package_statu_wizard
from . import update_bl_status_wizard from . import update_bl_status_wizard
from . import batch_get_pod_info_wizard
...@@ -8,12 +8,12 @@ import redis ...@@ -8,12 +8,12 @@ import redis
import config import config
# 默认字符gbk # 默认字符gbk
# logging.basicConfig(filename='./push_data_logger.log', level=logging.INFO) logging.basicConfig(filename='./push_data_logger.log', level=logging.INFO)
# 设置文件字符为utf-8 # 设置文件字符为utf-8
logging.basicConfig(handlers=[logging.FileHandler('logs/mail_push.log', 'a', 'utf-8')], # logging.basicConfig(handlers=[logging.FileHandler('logs/mail_push.log', 'a', 'utf-8')],
format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO) # format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO)
class Order_dispose(object): class Order_dispose(object):
...@@ -42,10 +42,12 @@ class Order_dispose(object): ...@@ -42,10 +42,12 @@ class Order_dispose(object):
bl_record = bl_obj.browse(bl_ids) bl_record = bl_obj.browse(bl_ids)
else: else:
bl_record = bl_obj.browse(data['id']) bl_record = bl_obj.browse(data['id'])
# utc_time = datetime.strptime(data['utc_time'], "%Y-%m-%d %H:%M:%S")
utc_time = data.get('utc_time') utc_time = data.get('utc_time')
user_login = data.get('user_login') user_login = data.get('user_login')
bl_record.mail_auto_push(utc_time, ship_packages, action_type, user_login, config.pda_db_user) logging.info('user_login: %s', user_login)
pod_node_id = data.get('pod_node_id')
bl_record.mail_auto_push(utc_time, ship_packages, action_type, user_login, user_login or config.pda_db_user,
pod_node_id=pod_node_id)
except Exception as ex: except Exception as ex:
logging.error('mail_auto_push error:%s' % str(ex)) logging.error('mail_auto_push error:%s' % str(ex))
return res_data return res_data
...@@ -54,7 +56,7 @@ class Order_dispose(object): ...@@ -54,7 +56,7 @@ class Order_dispose(object):
try: try:
pool = redis.ConnectionPool(**config.redis_options) pool = redis.ConnectionPool(**config.redis_options)
r = redis.Redis(connection_pool=pool) r = redis.Redis(connection_pool=pool)
logging.info(u'redis连接成功') logging.info(u'redis connection success')
Order_dispose = Order_dispose() Order_dispose = Order_dispose()
while 1: while 1:
try: try:
...@@ -65,4 +67,4 @@ try: ...@@ -65,4 +67,4 @@ try:
logging.error(e) logging.error(e)
continue continue
except Exception as e: except Exception as e:
logging.error("登录失败:%s" % e) logging.error("login failed:%s" % e)
...@@ -14,10 +14,10 @@ pytesseract ...@@ -14,10 +14,10 @@ pytesseract
# 2. 安装OpenCV: pip install opencv-python # 2. 安装OpenCV: pip install opencv-python
# Linux系统: # Linux系统:
# 1. 更新包列表: sudo apt update # 1. 更新包列表: apt update
# 2. 安装Tesseract OCR: sudo apt install tesseract-ocr # 2. 安装Tesseract OCR: sudo apt install tesseract-ocr
# 3. 安装英语语言包: sudo apt install tesseract-ocr-eng # 3. 安装英语语言包: apt install tesseract-ocr-eng
# 4. 安装OpenCV系统依赖: sudo apt install libopencv-dev python3-opencv # 4. 安装OpenCV系统依赖: apt install libopencv-dev python3-opencv
# 5. 安装OpenCV Python包: pip install opencv-python-headless # 5. 安装OpenCV Python包: pip install opencv-python-headless
# macOS系统: # macOS系统:
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论