提交 a742ea14 authored 作者: 贺阳's avatar 贺阳

更新关务提单状态以及同步

上级 2656a7c1
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
'security/account_security.xml', 'security/account_security.xml',
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'wizard/batch_input_ship_package_statu_wizard.xml', 'wizard/batch_input_ship_package_statu_wizard.xml',
'wizard/update_bl_status_wizard.xml',
'wizard/export_bl_big_package_xlsx_wizard.xml', 'wizard/export_bl_big_package_xlsx_wizard.xml',
'wizard/associate_pallet_wizard_views.xml', 'wizard/associate_pallet_wizard_views.xml',
'wizard/add_exception_info_wizard_views.xml', 'wizard/add_exception_info_wizard_views.xml',
......
差异被折叠。
...@@ -5,7 +5,7 @@ from datetime import timedelta ...@@ -5,7 +5,7 @@ from datetime import timedelta
import pytz import pytz
from odoo import models, fields, api, _ from odoo import models, fields, api, _
from odoo.exceptions import UserError from odoo.exceptions import UserError, ValidationError
# 获取日志 # 获取日志
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
...@@ -590,6 +590,30 @@ class CcBL(models.Model): ...@@ -590,6 +590,30 @@ class CcBL(models.Model):
'context': {'active_id': self.ids, 'default_is_batch': True, 'default_bl_id': self.ids} 'context': {'active_id': self.ids, 'default_is_batch': True, 'default_bl_id': self.ids}
} }
def batch_update_bl_status_wizard(self):
"""批量更新提单状态"""
# 检查关务提单状态必须是同一个
customs_clearance_status_list = self.filtered(lambda x: x.customs_clearance_status.id).mapped(
'customs_clearance_status.id')
if len(customs_clearance_status_list) == 0:
# 请先配置默认的提单节点类型的清关节点
raise ValidationError(
_('Please configure the default customs clearance status of the bill of loading node type first.'))
if len(customs_clearance_status_list) > 1:
raise ValidationError(_('The customs clearance status of the selected bill of loading must be the same.'))
# 最近操作时间取最晚的一条提单状态操作时间。
last_process_time = \
self.filtered(lambda x: x.customs_clearance_status.id).mapped('process_time').sorted(reverse=True)[0]
return {
'name': _('Update the status of the bill of loading'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'update.bl.status.wizard',
'target': 'new',
'context': {'active_id': self.ids, 'default_is_batch': True, 'default_last_process_time': last_process_time,
'default_current_status': customs_clearance_status_list[0]}
}
@api.depends('big_package_ids', 'big_package_ids.tally_state', 'big_package_ids.is_cancel') @api.depends('big_package_ids', 'big_package_ids.tally_state', 'big_package_ids.is_cancel')
def cal_tally_big_package_qty(self): def cal_tally_big_package_qty(self):
""" """
...@@ -671,10 +695,14 @@ class CcBL(models.Model): ...@@ -671,10 +695,14 @@ class CcBL(models.Model):
cc_attachment_ids = fields.One2many('cc.clearance.file', 'bl_id', string='Clearance Files') cc_attachment_ids = fields.One2many('cc.clearance.file', 'bl_id', string='Clearance Files')
# 提单上新增字段:关务提单状态,用英文:关联节点的配置(cc.node),节点类型过滤提单的节点名称。 # 提单上新增字段:关务提单状态,用英文:关联节点的配置(cc.node),节点类型过滤提单的节点名称。
customs_clearance_status = fields.Many2one('cc.node', string='Customs Clearance Status', customs_clearance_status = fields.Many2one('cc.node', string='Customs Clearance Status', tracking=True,
default=lambda self: self.env['cc.node'].search(
[('node_type', '=', 'bl'), ('is_default', '=', True)], limit=1),
domain=[('node_type', '=', 'bl')]) domain=[('node_type', '=', 'bl')])
# 增加提单状态操作时间 # 增加关务提单状态操作时间
process_time = fields.Datetime(string='Process Time') process_time = fields.Datetime(string='Customs Clearance Status Process Time')
# 添加状态说明字段
state_explain = fields.Text('State Explain', help='State Explain')
# 增加一个can_cancel的方法,用于检查提单当前是否可以取消,返回True表示可以取消, False表示不可以取消,同时返回取消的原因 # 增加一个can_cancel的方法,用于检查提单当前是否可以取消,返回True表示可以取消, False表示不可以取消,同时返回取消的原因
def check_cancel(self): def check_cancel(self):
...@@ -689,6 +717,8 @@ class CcBL(models.Model): ...@@ -689,6 +717,8 @@ class CcBL(models.Model):
is_cancel = fields.Boolean(string='Is Cancel', default=False) is_cancel = fields.Boolean(string='Is Cancel', default=False)
# 取消原因 # 取消原因
cancel_reason = fields.Char(string='Cancel Reason') cancel_reason = fields.Char(string='Cancel Reason')
# 是否同步
is_bl_sync = fields.Boolean('Is Bill Of Loading Synchronized', default=False)
# 增加提单取消的方法,用于取消提单,取消提单时,需要检查提单是否可以取消,如果可以取消,则将提单的状态设置为取消,并记录取消原因, 同时取消提单下的所有包裹 # 增加提单取消的方法,用于取消提单,取消提单时,需要检查提单是否可以取消,如果可以取消,则将提单的状态设置为取消,并记录取消原因, 同时取消提单下的所有包裹
def action_cancel(self, cancel_reason=''): def action_cancel(self, cancel_reason=''):
......
...@@ -3,7 +3,7 @@ batch_input_ship_package_status_wizard_group_user,batch_input_ship_package_statu ...@@ -3,7 +3,7 @@ batch_input_ship_package_status_wizard_group_user,batch_input_ship_package_statu
export_bl_big_package_xlsx_wizard_group_user,export_bl_big_package_xlsx_wizard_group_user,ccs_base.model_export_bl_big_package_xlsx_wizard,base.group_user,1,1,1,1 export_bl_big_package_xlsx_wizard_group_user,export_bl_big_package_xlsx_wizard_group_user,ccs_base.model_export_bl_big_package_xlsx_wizard,base.group_user,1,1,1,1
associate_pallet_wizard_group_user,associate_pallet_wizard_group_user,ccs_base.model_associate_pallet_wizard,base.group_user,1,1,1,1 associate_pallet_wizard_group_user,associate_pallet_wizard_group_user,ccs_base.model_associate_pallet_wizard,base.group_user,1,1,1,1
add_exception_info_wizard_group_user,add_exception_info_wizard_group_user,ccs_base.model_add_exception_info_wizard,base.group_user,1,1,1,1 add_exception_info_wizard_group_user,add_exception_info_wizard_group_user,ccs_base.model_add_exception_info_wizard,base.group_user,1,1,1,1
update_bl_status_wizard_group_user,update_bl_status_wizard_group_user,ccs_base.model_update_bl_status_wizard,base.group_user,1,1,1,1
access_group_user_common_common,access_group_user_common_common,model_common_common,base.group_user,1,1,1,1 access_group_user_common_common,access_group_user_common_common,model_common_common,base.group_user,1,1,1,1
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
<tree string="Bill of Loading" decoration-warning="is_cancel==True"> <tree string="Bill of Loading" decoration-warning="is_cancel==True">
<field optional="show" name="state" string="Status" widget="badge" decoration-info="state=='draft'" <field optional="show" name="state" string="Status" widget="badge" decoration-info="state=='draft'"
decoration-primary="state=='ccing'" decoration-success="state=='done'"/> decoration-primary="state=='ccing'" decoration-success="state=='done'"/>
<field optional="show" name="customs_clearance_status" string="Customs Clearance Status"/>
<field optional="show" name="bl_no" string="Bill of Loading No."/> <field optional="show" name="bl_no" string="Bill of Loading No."/>
<field optional="show" name="bl_date" string="B/L Date"/> <field optional="show" name="bl_date" string="B/L Date"/>
<field optional="show" name="customer_id" string="Customer"/> <field optional="show" name="customer_id" string="Customer"/>
...@@ -50,14 +51,18 @@ ...@@ -50,14 +51,18 @@
<!-- # 为action_batch_input_ship_package_wizard添加一个按钮, 上下文中添加bl_id--> <!-- # 为action_batch_input_ship_package_wizard添加一个按钮, 上下文中添加bl_id-->
<button name="%(action_batch_input_ship_package_wizard)d" type="action" class="oe_highlight" <button name="%(action_batch_input_ship_package_wizard)d" type="action" class="oe_highlight"
string="Update Ship Package Status" string="Update Ship Package Status"
context="{'default_bl_id': active_id, 'active_id': id,'default_action_type':'小包'}"/> context="{'default_bl_id': active_id, 'active_id': id,}"/>
<button name="%(action_batch_input_ship_package_wizard)d" type="action" class="oe_highlight" <button name="%(action_batch_input_bl_status_wizard)d" type="action" class="oe_highlight"
string="Update BL Status" string="Update Bill Of Loading Status"
context="{'active_id': id,'default_action_type':'提单'}"/> context="{'active_id': id,'default_bl_id': active_id,
'default_last_process_time':process_time,'default_current_status':customs_clearance_status}"/>
<field name="state" widget="statusbar" options="{'clickable': '1'}"/> <field name="state" widget="statusbar" options="{'clickable': '1'}"/>
</header> </header>
<header>
<field name="customs_clearance_status" widget="statusbar"/>
</header>
<sheet> <sheet>
<div class="oe_button_box" name="button_box"> <div class="oe_button_box" name="button_box">
...@@ -121,6 +126,7 @@ ...@@ -121,6 +126,7 @@
<field name="eta" string="ETA"/> <field name="eta" string="ETA"/>
<field name="end_port_code" string="End Port"/> <field name="end_port_code" string="End Port"/>
<field name="etd" string="ETD"/> <field name="etd" string="ETD"/>
<field name="process_time" string="Customs Clearance Status Process Time"/>
</group> </group>
<group> <group>
<field name="billing_weight" string="Billing Weight"/> <field name="billing_weight" string="Billing Weight"/>
...@@ -408,4 +414,16 @@ ...@@ -408,4 +414,16 @@
</field> </field>
</record> </record>
<record id="batch_update_bl_status_server" model="ir.actions.server">
<field name="name">Update the status of the bl of loading</field>
<field name="model_id" ref="model_cc_bl"/>
<field name="binding_model_id" ref="model_cc_bl"/>
<field name="state">code</field>
<field name="code">
if records:
action = records.batch_update_bl_status_wizard()
</field>
</record>
</odoo> </odoo>
\ No newline at end of file
...@@ -4,4 +4,5 @@ from . import batch_input_ship_package_statu_wizard ...@@ -4,4 +4,5 @@ from . import batch_input_ship_package_statu_wizard
from . import export_bl_big_package_xlsx_wizard from . import export_bl_big_package_xlsx_wizard
from . import associate_pallet_wizard from . import associate_pallet_wizard
from . import add_exception_info_wizard from . import add_exception_info_wizard
from . import update_bl_status_wizard
...@@ -50,7 +50,7 @@ class BatchInputShipPackageStatusWizard(models.TransientModel): ...@@ -50,7 +50,7 @@ class BatchInputShipPackageStatusWizard(models.TransientModel):
# print(self.get_order()) # print(self.get_order())
return len(self.get_order()) return len(self.get_order())
bl_count = fields.Integer('Bl count', default=get_bl_count) bl_count = fields.Integer('Bill Of Loading Count', default=get_bl_count)
current_status = fields.Many2one('cc.node', 'Select Node') current_status = fields.Many2one('cc.node', 'Select Node')
next_code_ids = fields.Many2many('cc.node', 'node_next_node_wizard_rel', 'node_id', 'next_node_id', 'Next Node', next_code_ids = fields.Many2many('cc.node', 'node_next_node_wizard_rel', 'node_id', 'next_node_id', 'Next Node',
related='current_status.next_code_ids') related='current_status.next_code_ids')
...@@ -82,57 +82,55 @@ class BatchInputShipPackageStatusWizard(models.TransientModel): ...@@ -82,57 +82,55 @@ class BatchInputShipPackageStatusWizard(models.TransientModel):
state_explain = fields.Text('State Explain', help='State Explain') state_explain = fields.Text('State Explain', help='State Explain')
node_exception_reason_id = fields.Many2one('cc.node.exception.reason', 'Exception Reason', node_exception_reason_id = fields.Many2one('cc.node.exception.reason', 'Exception Reason',
domain="[('code_id', '=', update_status)]") domain="[('code_id', '=', update_status)]")
action_type = fields.Char(string='Action Type', default='小包')
# 批量更新小包状态 # 批量更新小包状态
def submit(self): def submit(self):
# 确认数据 # 确认数据
if not self.is_ok: if not self.is_ok:
raise ValidationError('Please confirm that the above data is correct.') # 请确认以上数据正确 raise ValidationError('Please confirm that the above data is correct.') # 请确认以上数据正确
if self.action_type == '小包':
parcels = self.get_process_package() parcels = self.get_process_package()
if not parcels: if not parcels:
raise ValidationError(_('No package to update found.')) # 没有找到要更新的小包 raise ValidationError(_('No package to update found.')) # 没有找到要更新的小包
# 1.若选择的更新节点为是当前节点【清关节点设置,是当前节点字段名称改为初始节点】,当更新节点为初始节点时,无需填写操作时间; # 1.若选择的更新节点为是当前节点【清关节点设置,是当前节点字段名称改为初始节点】,当更新节点为初始节点时,无需填写操作时间;
# if self.update_status and not self.update_status.is_default: # if self.update_status and not self.update_status.is_default:
# 2.若选择的更新节点为“选择节点”的后续节点(根据节点设置排序),则按照操作时间不能大于当前时间,且不能早于最近的操作时间。 # 2.若选择的更新节点为“选择节点”的后续节点(根据节点设置排序),则按照操作时间不能大于当前时间,且不能早于最近的操作时间。
# 3.若选择的“更新节点”为“选择节点”的前序节点(根据节点设置排序),则查找“选择节点”是否已有同步日志,若有,则操作时间不允许早于前序节点同步日志里的操作时间,且不能大于当前时间。若有多条,以同步时间最晚的一条为准。 # 3.若选择的“更新节点”为“选择节点”的前序节点(根据节点设置排序),则查找“选择节点”是否已有同步日志,若有,则操作时间不允许早于前序节点同步日志里的操作时间,且不能大于当前时间。若有多条,以同步时间最晚的一条为准。
# 4.若选择的“更新节点”和“选择节点”一致时,需检查该节点的前序节点是否有同步日志,若有,则操作时间不允许早于前序节点同步日志里的操作时间,且不能大于当前时间。同一节点若有多条同步日志,以同步时间最晚的一条为准。 # 4.若选择的“更新节点”和“选择节点”一致时,需检查该节点的前序节点是否有同步日志,若有,则操作时间不允许早于前序节点同步日志里的操作时间,且不能大于当前时间。同一节点若有多条同步日志,以同步时间最晚的一条为准。
# 判断异常状态是否选择了异常原因 # 判断异常状态是否选择了异常原因
reason_obj = self.env['cc.node.exception.reason'].search([('code_id', '=', self.update_status.id)]) reason_obj = self.env['cc.node.exception.reason'].search([('code_id', '=', self.update_status.id)])
if reason_obj and not self.node_exception_reason_id: if reason_obj and not self.node_exception_reason_id:
raise ValidationError(_('Please select the reason for the exception!')) # 请选择异常原因 raise ValidationError(_('Please select the reason for the exception!')) # 请选择异常原因
# 如果更新节点是 默认节点 同步的标志变为True # 如果更新节点是 默认节点 同步的标志变为True
is_sync = False is_sync = False
if self.update_status.is_default: if self.update_status.is_default:
is_sync = True is_sync = True
# 更新状态 # 更新状态
parcels.write( parcels.write(
{'state': self.update_status.id, 'node_exception_reason_id': self.node_exception_reason_id.id, {'state': self.update_status.id, 'node_exception_reason_id': self.node_exception_reason_id.id,
'process_time': self.process_time, 'state_explain': self.state_explain, 'is_sync': is_sync}) 'process_time': self.process_time, 'state_explain': self.state_explain, 'is_sync': is_sync})
# if parcels: # if parcels:
# where_sql = " where id={0}".format(parcels[0].id) if len( # where_sql = " where id={0}".format(parcels[0].id) if len(
# parcels) == 1 else " where id in {0}".format(tuple(parcels.ids)) # parcels) == 1 else " where id in {0}".format(tuple(parcels.ids))
# update_sql = """update cc_ship_package set node_exception_reason_id={0},process_time='{1}',state_explain='{2}',is_sync={3} {4}""".format( # update_sql = """update cc_ship_package set node_exception_reason_id={0},process_time='{1}',state_explain='{2}',is_sync={3} {4}""".format(
# self.node_exception_reason_id.id if self.node_exception_reason_id else False, # self.node_exception_reason_id.id if self.node_exception_reason_id else False,
# self.process_time, self.state_explain or '', is_sync, # self.process_time, self.state_explain or '', is_sync,
# where_sql) # where_sql)
# update_sql = update_sql.replace("'False'", "null").replace("False", "null") # update_sql = update_sql.replace("'False'", "null").replace("False", "null")
# self._cr.execute(update_sql) # self._cr.execute(update_sql)
# parcels.write({'state': self.update_status.id}) # parcels.write({'state': self.update_status.id})
# for parcel in parcels: # for parcel in parcels:
# parcel.message_post(body='%s改为%s' % (self.current_status.name, self.update_status.name)) # parcel.message_post(body='%s改为%s' % (self.current_status.name, self.update_status.name))
# 生成sns日志 # 生成sns日志
# self.bl_id.message_post(body='%s更新为%s' % (self.current_status.name or '', self.update_status.name or '')) # self.bl_id.message_post(body='%s更新为%s' % (self.current_status.name or '', self.update_status.name or ''))
# 跳转显示本次更新状态的小包 更新小包状态 # 跳转显示本次更新状态的小包 更新小包状态
return { return {
'name': _('Update the status of the small package'), 'name': _('Update the status of the small package'),
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
'res_model': 'cc.ship.package', 'res_model': 'cc.ship.package',
'view_mode': 'tree,form', 'view_mode': 'tree,form',
'domain': [('id', 'in', parcels.ids)], 'domain': [('id', 'in', parcels.ids)],
} }
def get_process_package(self): def get_process_package(self):
"""获取要更新的小包""" """获取要更新的小包"""
......
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, api, fields
from odoo.exceptions import ValidationError
# 定义一个批量更新提单状态的向导, 用于批量更新提单状态
# 包括以下字段,提单, 当前节点, 更新节点, 排除面单号, 排除状态
class UpdateBlStatusWizard(models.TransientModel):
_name = 'update.bl.status.wizard'
_description = 'Update the status of the bill of loading' # 更新提单状态向导
def get_order(self):
"""
得到单据
:return:
"""
order_id = self._context.get('active_id')
if type(order_id) != list:
order_id = [self._context.get('active_id')]
return self.env['cc.bl'].browse(order_id)
bl_id = fields.Many2one('cc.bl', 'Bill of Loading')
def get_bl_count(self):
# print(self.get_order())
return len(self.get_order())
bl_count = fields.Integer('Bill Of Loading Count', default=get_bl_count)
current_status = fields.Many2one('cc.node', 'Current Status')
update_status = fields.Many2one('cc.node', 'Update Node')
is_ok = fields.Boolean('Confirm Date is ok.', default=False)
process_time = fields.Datetime('Process Time')
last_process_time = fields.Datetime('Last Process Time', readonly=True, help='Bill Of Loading Last Process Time')
# 添加状态说明字段
state_explain = fields.Text('State Explain', help='State Explain')
is_batch = fields.Boolean('Is Batch', default=False)
# 批量更新小包状态
def submit(self):
bl_obj=self.get_order()
# 确认数据
if not self.is_ok:
raise ValidationError('Please confirm that the above data is correct.') # 请确认以上数据正确
# 1.若选择的更新节点为是当前节点【清关节点设置,是当前节点字段名称改为初始节点】,当更新节点为初始节点时,无需填写操作时间;
# if self.update_status and not self.update_status.is_default:
# 2.若选择的更新节点为“选择节点”的后续节点(根据节点设置排序),则按照操作时间不能大于当前时间,且不能早于最近的操作时间。
# 3.若选择的“更新节点”为“选择节点”的前序节点(根据节点设置排序),则查找“选择节点”是否已有同步日志,若有,则操作时间不允许早于前序节点同步日志里的操作时间,且不能大于当前时间。若有多条,以同步时间最晚的一条为准。
# 4.若选择的“更新节点”和“选择节点”一致时,需检查该节点的前序节点是否有同步日志,若有,则操作时间不允许早于前序节点同步日志里的操作时间,且不能大于当前时间。同一节点若有多条同步日志,以同步时间最晚的一条为准。
# 如果更新节点是 默认节点 同步的标志变为True
is_sync = False
if self.update_status.is_default:
is_sync = True
# 更新状态
bl_obj.write(
{'customs_clearance_status': self.update_status.id, 'process_time': self.process_time, 'state_explain': self.state_explain,
'is_bl_sync': is_sync})
<?xml version="1.0" encoding="utf-8"?>
<!-- © <2016> <heyang>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
<odoo>
<data>
<record id="view_update_bl_status_wizard" model="ir.ui.view">
<field name="name">view_update_bl_status_wizard</field>
<field name="model">update.bl.status.wizard</field>
<field name="arch" type="xml">
<form string="Update Bill Of Loading Status">
<sheet>
<group>
<field name="bl_count" attrs="{'invisible': [('is_batch', '=', False)]}" readonly="1"/>
<field name="bl_id" invisible="1"/>
<field name="current_status" readonly="1"/>
<field name="last_process_time"/>
<field name="update_status" required="1" domain="[('node_type','=','bl')]"/>
<field name="process_time" required="1" string="Customs Clearance Status Process Time"/>
<field name="state_explain" invisible="1"/>
<field name="is_batch" invisible="1"/>
</group>
<group>
<field name="is_ok"/>
</group>
<footer>
<button name="submit" type="object" string="Submit" class="oe_highlight"
attrs="{'invisible':[('is_ok','=',False)]}"/>
<button string="Close" special="cancel"/>
</footer>
</sheet>
</form>
</field>
</record>
<!--定义视图动作-->
<record model="ir.actions.act_window" id="action_batch_input_bl_status_wizard">
<field name="name">Update Bill Of Loading Status</field>
<field name="res_model">update.bl.status.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</odoo>
\ No newline at end of file
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
# 'data/data.xml', # 'data/data.xml',
# wizard # wizard
'wizard/batch_input_ship_package_statu_wizard.xml', 'wizard/batch_input_ship_package_statu_wizard.xml',
'wizard/update_bl_status_wizard.xml',
# 'wizard/again_push_wizard.xml', # 'wizard/again_push_wizard.xml',
# 'wizard/batch_push_tiktok.xml', # 'wizard/batch_push_tiktok.xml',
# view # view
......
...@@ -135,17 +135,6 @@ class CcShipPackage(models.Model): ...@@ -135,17 +135,6 @@ class CcShipPackage(models.Model):
# 增加同步日志纪录字段 # 增加同步日志纪录字段
sync_log_ids = fields.One2many('cc.ship.package.sync.log', 'package_id', 'Sync Logs') sync_log_ids = fields.One2many('cc.ship.package.sync.log', 'package_id', 'Sync Logs')
def is_next_code(self, next_state_id):
"""
判断更新的节点是否是 小包状态的下级节点
:param next_state_id:
:return:
"""
if self.state:
if next_state_id in self.state.next_code_ids.ids:
return True
return False
@api.model @api.model
def create(self, vals_list): def create(self, vals_list):
""" """
...@@ -257,23 +246,37 @@ class CcBl(models.Model): ...@@ -257,23 +246,37 @@ class CcBl(models.Model):
def _compute_process_time(self): def _compute_process_time(self):
for bl in self: for bl in self:
if bl.bl_sync_log_ids: if bl.bl_sync_log_ids:
bl.process_time = bl.bl_sync_log_ids.mapped('operate_time').sorted(reverse=True)[0] bl.process_time = sorted(bl.bl_sync_log_ids.mapped('operate_time'), reverse=True)[0]
else: else:
bl.process_time = False bl.process_time = False
# 增加未同步小包数量字段 # 增加未同步小包数量字段
unsync_package_count = fields.Integer('Unsync Package Count', compute='_compute_unsync_package_count', store=True) unsync_package_count = fields.Integer('Unsync Package Count', compute='_compute_unsync_package_count', store=True)
is_bl_sync = fields.Boolean('Is BL Synchronized', default=False)
# 关联提单同步日志 # 关联提单同步日志
bl_sync_log_ids = fields.One2many('cc.bl.sync.log', 'bl_id', string='BL 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)
# =============同步提单状态==================================
# 增加同步提单状态的方法 # 提单节点为提单已提货,对应的小包状态为已提货,当提单a下所有小包,都有已提货的同步日志时,自动改变提单的关务提单状态,为已提货。操作时间取小包已提货同步日志最晚的一条;
def action_sync_bl_status(self): def change_state_by_ship_package(self, state_code):
self.callback_track_bl() for item in self:
# 获取提单下所有小包
ship_packages = self.env['cc.ship.package'].search([('bl_id', '=', item.id), ('is_sync', '=', True)])
if ship_packages:
# 获取所有小包的已提货同步日志
sync_logs = ship_packages.mapped('sync_log_ids').filtered(lambda r: r.process_code == state_code)
if len(sync_logs) >= len(ship_packages):
item.customs_clearance_status = '已提货'
#同时检查已有关务提单节点的同步日志不再生成,若提单节点的前序节点未产生同步日志,则需为其前序节点产生同步日志,操作时间根据设置的前序节点间距时间取值;
# 检查提单的同步日志该节点是否已生成
if not item.bl_sync_log_ids.filtered(lambda r: r.process_code == state_code):
pass
item.process_time = sorted(sync_logs.mapped('operate_time'), reverse=True)[0] #需要优化
# =============同步提单状态==================================
# 定义一个方法, 获取提单,并回传提单状态 # 定义一个方法, 获取提单,并回传提单状态
def callback_track_bl(self): def callback_track_bl(self):
...@@ -291,23 +294,22 @@ class CcBl(models.Model): ...@@ -291,23 +294,22 @@ class CcBl(models.Model):
is_ok = True is_ok = True
tt_api_obj = self.env["ao.tt.api"].sudo() tt_api_obj = self.env["ao.tt.api"].sudo()
async def perform_requests(): async def bl_perform_requests():
ssl_context = ssl.create_default_context(cafile=certifi.where()) ssl_context = ssl.create_default_context(cafile=certifi.where())
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=ssl_context), async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=ssl_context),
timeout=aiohttp.ClientTimeout(total=60)) as session: timeout=aiohttp.ClientTimeout(total=60)) as session:
tasks = [] tasks = []
for index, bl in enumerate(bls): for index, bl in enumerate(bls):
if not bl.is_bl_sync and bl.state and bl.state.tk_code: if not bl.is_bl_sync and bl.customs_clearance_status and bl.customs_clearance_status.tk_code:
data = bl.get_bl_callback_track_data() data = bl.get_bl_callback_track_data()
tasks.append(tt_api_obj.async_bl_callback_track(session, data, bl.id)) tasks.append(tt_api_obj.async_bl_callback_track(session, data, bl.id))
responses = await asyncio.gather(*tasks) responses = await asyncio.gather(*tasks)
return responses return responses
# 在 Odoo 中运行异步任务 # 在 Odoo 中运行异步任务
responses = asyncio.run(perform_requests()) responses = asyncio.run(bl_perform_requests())
for response_item in responses: for response_item in responses:
response_data = response_item[0] response_data = response_item[0]
logging.info('bl response_data response:%s' % response_data)
data = response_item[1] data = response_item[1]
bl_id = response_item[2] bl_id = response_item[2]
bl_order = self.env['cc.bl'].sudo().browse(bl_id) bl_order = self.env['cc.bl'].sudo().browse(bl_id)
...@@ -326,7 +328,8 @@ class CcBl(models.Model): ...@@ -326,7 +328,8 @@ class CcBl(models.Model):
bl_order.is_bl_sync = True bl_order.is_bl_sync = True
self._cr.commit() # 提交事务 self._cr.commit() # 提交事务
self.env['cc.bl.sync.log'].sudo().create_sync_log( self.env['cc.bl.sync.log'].sudo().create_sync_log(
bl_order.id, 'Tiktok', bl_order.state.tk_code, bl_order.state.name, bl_order.state_explain, bl_order.id, 'Tiktok', bl_order.customs_clearance_status.tk_code,
bl_order.customs_clearance_status.name, bl_order.state_explain or '',
bl_order.process_time.strftime('%Y-%m-%d %H:%M:%S')) bl_order.process_time.strftime('%Y-%m-%d %H:%M:%S'))
request_id = response_data['requestID'] request_id = response_data['requestID']
self.env['ao.tt.api.log'].sudo().create_api_log( self.env['ao.tt.api.log'].sudo().create_api_log(
...@@ -339,13 +342,14 @@ class CcBl(models.Model): ...@@ -339,13 +342,14 @@ class CcBl(models.Model):
""" """
return { return {
"master_waybill_no": self.bl_no or '', # 主提运单号 "master_waybill_no": self.bl_no or '', # 主提运单号
"customs_waybill_no": self.customs_bl_no or '', # 关务提单号取海关装货单号 "customs_waybill_id": self.customs_bl_no or '', # 关务提单号取海关装货单号
"track_detail": [ "track_detail":
{ {
"operate_time": get_utc_time(self.process_time), # 事件发⽣时间,RFC3339格式(实操时间) "operate_time": get_rfc339_time(self.process_time), # "2025-03-19T09:28:00+00:00",
"waybill_status_code": self.state.tk_code, # 提单关务状态编码 # "#get_utc_time(self.process_time), # 事件发⽣时间,RFC3339格式(实操时间)
"waybill_status_code": self.customs_clearance_status.tk_code, # 提单关务状态编码
} }
]
} }
# =============同步小包状态================================== # =============同步小包状态==================================
...@@ -674,7 +678,7 @@ class CcBl(models.Model): ...@@ -674,7 +678,7 @@ class CcBl(models.Model):
# 提单节点同步日志 # 提单节点同步日志
class CcBlSyncLog(models.Model): class CcBlSyncLog(models.Model):
_name = 'cc.bl.sync.log' _name = 'cc.bl.sync.log'
_description = 'CC Bl Sync Log' _description = 'CC Bill Of Loading Sync Log'
# 定义模型字段 # 定义模型字段
# 提单对象 # 提单对象
...@@ -696,9 +700,9 @@ class CcBlSyncLog(models.Model): ...@@ -696,9 +700,9 @@ class CcBlSyncLog(models.Model):
# 添加一个新增日志的方法,传入小包ID,API客户,操作状态,操作备注,操作时间 # 添加一个新增日志的方法,传入小包ID,API客户,操作状态,操作备注,操作时间
@api.model @api.model
def create_sync_log(self, package_id, api_customer, process_code, progress_name, operate_remark, operate_time): def create_sync_log(self, bl_id, api_customer, process_code, progress_name, operate_remark, operate_time):
vals = { vals = {
'package_id': package_id, 'bl_id': bl_id,
'api_customer': api_customer, 'api_customer': api_customer,
'process_code': process_code, 'process_code': process_code,
'progress_name': progress_name, 'progress_name': progress_name,
......
import logging import logging
from datetime import datetime
from odoo import models, fields, api, tools from odoo import models, fields
# 继承节点对象.增加TK编码 # 继承节点对象.增加TK编码
...@@ -13,6 +12,17 @@ class CCNode(models.Model): ...@@ -13,6 +12,17 @@ class CCNode(models.Model):
interval_minutes = fields.Integer('Predecessor Node Interval (Minutes)', default=20, interval_minutes = fields.Integer('Predecessor Node Interval (Minutes)', default=20,
help='Default interval time between predecessor nodes in minutes.') # 前序节点间隔时间,默认20分钟 help='Default interval time between predecessor nodes in minutes.') # 前序节点间隔时间,默认20分钟
def is_next_code(self, current_state_obj, next_state_id):
"""
判断更新的节点是否是 小包状态的下级节点
:param next_state_id:
:return:
"""
if current_state_obj:
if next_state_id in current_state_obj.next_code_ids.ids:
return True
return False
def calculate_total_interval(self, next_node): def calculate_total_interval(self, next_node):
""" """
计算该节点到某个节点直接的间隔时间和 计算该节点到某个节点直接的间隔时间和
......
...@@ -126,7 +126,7 @@ class TT(models.Model): ...@@ -126,7 +126,7 @@ class TT(models.Model):
'app_key': app_key 'app_key': app_key
} }
request_url = tt_url + url request_url = tt_url + url
# logging.info('request_url: %s' % request_url) logging.info('request_url: %s' % request_url)
# logging.info('request_data: %s' % parameter) # logging.info('request_data: %s' % parameter)
for i in range(3): # 尝试最多3次 for i in range(3): # 尝试最多3次
try: try:
......
...@@ -7,10 +7,8 @@ access_cc_ship_package_sync_log_base.group_erp_manager,cc_ship_package_sync_log ...@@ -7,10 +7,8 @@ access_cc_ship_package_sync_log_base.group_erp_manager,cc_ship_package_sync_log
access_cc_ship_package_sync_log_ccs_base.group_clearance_of_customs_manager,cc_ship_package_sync_log ccs_base.group_clearance_of_customs_manager,ccs_connect_tiktok.model_cc_ship_package_sync_log,ccs_base.group_clearance_of_customs_manager,1,0,0,0 access_cc_ship_package_sync_log_ccs_base.group_clearance_of_customs_manager,cc_ship_package_sync_log ccs_base.group_clearance_of_customs_manager,ccs_connect_tiktok.model_cc_ship_package_sync_log,ccs_base.group_clearance_of_customs_manager,1,0,0,0
access_cc_ship_package_sync_log_ccs_base.group_clearance_of_customs_user,cc_ship_package_sync_log ccs_base.group_clearance_of_customs_user,ccs_connect_tiktok.model_cc_ship_package_sync_log,ccs_base.group_clearance_of_customs_user,1,0,0,0 access_cc_ship_package_sync_log_ccs_base.group_clearance_of_customs_user,cc_ship_package_sync_log ccs_base.group_clearance_of_customs_user,ccs_connect_tiktok.model_cc_ship_package_sync_log,ccs_base.group_clearance_of_customs_user,1,0,0,0
access_cc_bl_sync_log_base.group_user,cc_bl_sync_log base.group_user,ccs_connect_tiktok.model_cc_bl_sync_log,base.group_user,1,0,0,0 access_cc_bl_sync_log_base.group_user,cc_bl_sync_log base.group_user,ccs_connect_tiktok.model_cc_bl_sync_log,base.group_user,1,0,0,0
access_cc_bl_sync_log_base.group_erp_manager,cc_bl_sync_log base.group_erp_manager,ccs_connect_tiktok.model_cc_bl_sync_log,base.group_erp_manager,1,1,1,1 access_cc_bl_sync_log_base.group_erp_manager,cc_bl_sync_log base.group_erp_manager,ccs_connect_tiktok.model_cc_bl_sync_log,base.group_erp_manager,1,1,1,1
access_cc_bl_sync_log_ccs_base.group_clearance_of_customs_manager,cc_bl_sync_log ccs_base.group_clearance_of_customs_manager,ccs_connect_tiktok.model_cc_bl_sync_log,ccs_base.group_clearance_of_customs_manager,1,0,0,0 access_cc_bl_sync_log_ccs_base.group_clearance_of_customs_manager,cc_bl_sync_log ccs_base.group_clearance_of_customs_manager,ccs_connect_tiktok.model_cc_bl_sync_log,ccs_base.group_clearance_of_customs_manager,1,0,0,0
access_cc_bl_sync_log_ccs_base.group_clearance_of_customs_user,cc_bl_sync_log ccs_base.group_clearance_of_customs_user,ccs_connect_tiktok.model_cc_bl_sync_log,ccs_base.group_clearance_of_customs_user,1,0,0,0 access_cc_bl_sync_log_ccs_base.group_clearance_of_customs_user,cc_bl_sync_log ccs_base.group_clearance_of_customs_user,ccs_connect_tiktok.model_cc_bl_sync_log,ccs_base.group_clearance_of_customs_user,1,0,0,0
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
# ---------- CC Bl Sync Log ------------ # ---------- CC Bill Of Loading Sync Log ------------
<record model="ir.ui.view" id="tree_cc_bl_sync_log_view"> <record model="ir.ui.view" id="tree_cc_bl_sync_log_view">
<field name="name">tree.cc.bl.sync.log</field> <field name="name">tree.cc.bl.sync.log</field>
<field name="model">cc.bl.sync.log</field> <field name="model">cc.bl.sync.log</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="CC Bl Sync Log"> <tree string="CC Bill Of Loading Sync Log">
<field optional="show" name="bl_id" string="Bill of Loading"/> <field optional="hide" name="bl_id" string="Bill of Loading"/>
<field optional="show" name="api_customer" string="Api Customer"/> <field optional="show" name="api_customer" string="Api Customer"/>
<field optional="show" name="process_code" string="TK Process Code"/> <field optional="show" name="process_code" string="TK Process Code"/>
<field optional="show" name="progress_name" string="Progress Name"/> <field optional="show" name="progress_name" string="Progress Name"/>
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
<field name="name">form.cc.bl.sync.log</field> <field name="name">form.cc.bl.sync.log</field>
<field name="model">cc.bl.sync.log</field> <field name="model">cc.bl.sync.log</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="CC Bl Sync Log"> <form string="CC Bill Of Loading Sync Log">
<sheet> <sheet>
<group> <group>
<group> <group>
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
<field name="name">search.cc.bl.sync.log</field> <field name="name">search.cc.bl.sync.log</field>
<field name="model">cc.bl.sync.log</field> <field name="model">cc.bl.sync.log</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="CC Bl Sync Log"> <search string="CC Bill Of Loading Sync Log">
<field name="bl_id" string="Bill of Loading"/> <field name="bl_id" string="Bill of Loading"/>
<field name="api_customer" string="Api Customer"/> <field name="api_customer" string="Api Customer"/>
<field name="process_code" string="TK Process Code"/> <field name="process_code" string="TK Process Code"/>
...@@ -76,14 +76,14 @@ ...@@ -76,14 +76,14 @@
</record> </record>
<record model="ir.actions.act_window" id="action_cc_bl_sync_log"> <record model="ir.actions.act_window" id="action_cc_bl_sync_log">
<field name="name">CC Bl Sync Log</field> <field name="name">CC Bill Of Loading Sync Log</field>
<field name="res_model">cc.bl.sync.log</field> <field name="res_model">cc.bl.sync.log</field>
<field name="view_mode">tree</field> <field name="view_mode">tree</field>
<field name="domain">[]</field> <field name="domain">[]</field>
<field name="context">{}</field> <field name="context">{}</field>
<field name="help" type="html"> <field name="help" type="html">
<p class="o_view_nocontent_smiling_face"> <p class="o_view_nocontent_smiling_face">
[CC Bl Sync Log] Not yet! Click the Create button in the top left corner and the sofa is yours! [CC Bill Of Loading Sync Log] Not yet! Click the Create button in the top left corner and the sofa is yours!
</p> </p>
<p> <p>
</p> </p>
......
...@@ -9,7 +9,11 @@ ...@@ -9,7 +9,11 @@
<field name="inherit_id" ref="ccs_base.tree_cc_bl_view"/> <field name="inherit_id" ref="ccs_base.tree_cc_bl_view"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="state" position="after"> <field name="state" position="after">
<field name="unsync_package_count" string="UnSync" widget="badge" decoration-danger="1 == 1"/> <field name="unsync_package_count" string="UnSync Package Count" widget="badge"
decoration-danger="1 == 1"/>
</field>
<field name="customs_clearance_status" position="after">
<field name="is_bl_sync" string="Is Bill Of Loading Sync"/>
</field> </field>
</field> </field>
</record> </record>
...@@ -25,7 +29,7 @@ ...@@ -25,7 +29,7 @@
<button name="callback_track" string="Sync Package Status" type="object"/> <button name="callback_track" string="Sync Package Status" type="object"/>
<button name="batch_action_sync" string="Sync CC Attachment" type="object"/> <button name="batch_action_sync" string="Sync CC Attachment" type="object"/>
<!--增加同步提单状态的按钮--> <!--增加同步提单状态的按钮-->
<button name="action_sync_bl_status" string="Sync Bl Status" type="object"/> <button name="callback_track_bl" string="Sync Bill Of Loading Status" type="object"/>
</header> </header>
<button name="action_show_ship_package" position="replace"> <button name="action_show_ship_package" position="replace">
...@@ -50,6 +54,10 @@ ...@@ -50,6 +54,10 @@
<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"/>
<group invisible="1">
<field name="is_bl_sync" string="Is Sync" readonly="1"/>
<field name="state_explain" string="State Explain"/>
</group>
</page> </page>
</notebook> </notebook>
</field> </field>
...@@ -67,6 +75,18 @@ ...@@ -67,6 +75,18 @@
</field> </field>
</record> </record>
<record id="action_batch_sync_bl_status" model="ir.actions.server">
<field name="name">Batch Sync Bill Of Loading Status</field>
<field name="model_id" ref="model_cc_bl"/>
<field name="binding_model_id" ref="model_cc_bl"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">
if records:
records.callback_track_bl()
</field>
</record>
<record id="action_batch_update_package_status" model="ir.actions.server"> <record id="action_batch_update_package_status" model="ir.actions.server">
<field name="name">批量手动处理小包异常数据</field> <field name="name">批量手动处理小包异常数据</field>
......
...@@ -2,4 +2,5 @@ ...@@ -2,4 +2,5 @@
from . import batch_input_ship_package_statu_wizard from . import batch_input_ship_package_statu_wizard
from . import update_bl_status_wizard
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import logging
from odoo import models, api, fields, _ from odoo import models, api, fields, _
from odoo.exceptions import Warning, ValidationError from odoo.exceptions import ValidationError
class BatchInputShipPackageStatusWizard(models.TransientModel): class BatchInputShipPackageStatusWizard(models.TransientModel):
...@@ -129,7 +128,7 @@ class BatchInputShipPackageStatusWizard(models.TransientModel): ...@@ -129,7 +128,7 @@ class BatchInputShipPackageStatusWizard(models.TransientModel):
if parcels: if parcels:
error_package_arr = [] error_package_arr = []
for package_item in parcels: for package_item in parcels:
result = package_item.is_next_code(self.update_status.id) result = self.env['cc.node'].is_next_code(package_item.state, self.update_status.id)
if not result: if not result:
error_package_arr.append(package_item.tracking_no) error_package_arr.append(package_item.tracking_no)
if len(error_package_arr) > 0: if len(error_package_arr) > 0:
......
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, _
from odoo.exceptions import ValidationError
# 定义一个批量更新提单状态的向导, 用于批量更新提单状态
# 包括以下字段,提单, 当前节点, 更新节点, 排除面单号, 排除状态
class UpdateBlStatusWizard(models.TransientModel):
_inherit = 'update.bl.status.wizard'
_description = 'Update the status of the bill of loading' # 更新提单状态向导
is_skip_check = fields.Boolean('Skip Check', default=False)
def submit(self):
bl_objs = self.get_order()
if not self.is_skip_check:
# 1检查操作时间不能大于当前时间,不能小于 最晚操作时间
current_time = fields.Datetime.now()
if self.process_time and (self.process_time > current_time or (
self.last_process_time and self.process_time < self.last_process_time)):
raise ValidationError(
_('The operation time cannot be greater than the current time and cannot be less than the latest operation time!')) # 操作时间不能大于当前时间且不能小于最晚操作时间
# 2检查更新节点是否是 提单的状态的下一节点
if bl_objs:
error_bl_arr = []
for bl in bl_objs:
result = self.env['cc.node'].is_next_code(bl.customs_clearance_status, self.update_status.id)
if not result:
error_bl_arr.append(bl.bl_no)
if len(error_bl_arr) > 0:
raise ValidationError(
_('[%s] The update node is not the next node in the state of the bill of loading!') % ','.join(
error_bl_arr)) # 更新节点不是提单的状态的下一节点!
# 3检查提单%s是否存在还未推送或 提单已经推送过将更变更的状态
bl_id_arr = self.get_exception_bl() # 异常的提单
if len(bl_id_arr) > 0:
raise ValidationError(
_('Check if the bill of loading [%s] has a status that has not been pushed yet or if the bill of loading has already been pushed and will be updated!') % '\n'.join(
[bl.bl_no for bl in
self.env['cc.bl'].search([('id', 'in', bl_id_arr)])])) # # 提单%s是否存在还未推送或 提单已经推送过将更变更的状态
obj = super(UpdateBlStatusWizard, self).submit()
for bl_obj in bl_objs:
bl_obj.message_post(body=_('[%s]%sUpdate to[%s]%s') % (
self.current_status.tk_code or '', self.current_status.name or '',
self.update_status.tk_code or '',
self.update_status.name or ''))
return obj
def get_exception_bl(self):
"""
比如 已提货 --- 清关开始,选择了清关开始之后 把已提货状态的提单且更新日志明细包含了 清关开始 的编码的 提单显示出来
已提货的判断是否推送过
"""
item = self
bl_ids = []
bl_objs = self.get_order()
if bl_objs and not item.is_skip_check:
where_sql = " and bl_id={0}".format(bl_objs[0].id) if len(
bl_objs) == 1 else " and bl_id in {0}".format(tuple(bl_objs.ids))
current_status = item.current_status
if current_status and not current_status.is_default:
# 更新日志里没有 当前节点(不包括默认节点) 【已提货】的提单
select_sql = "select id from cc_bl_sync_log where process_code='{0}' {1}".format(
current_status.tk_code, where_sql)
self._cr.execute(select_sql)
sync_log_obj = self._cr.fetchall()
if len(sync_log_obj) <= 0:
bl_ids += bl_objs.ids
if item.update_status:
# 更新日志明细包含了 更新节点 【清关开始】 的 提单
select_sql = "select bl_id from cc_bl_sync_log where process_code='{0}' {1}".format(
item.update_status.tk_code, where_sql)
self._cr.execute(select_sql)
bl_ids += [r[0] for r in self._cr.fetchall()]
return bl_ids
<?xml version="1.0" encoding="utf-8"?>
<!-- © <2016> <heyang>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
<odoo>
<data>
<record id="view_update_bl_status_wizard" model="ir.ui.view">
<field name="name">view_update_bl_status_wizard</field>
<field name="model">update.bl.status.wizard</field>
<field name="inherit_id" ref="ccs_base.view_update_bl_status_wizard"/>
<field name="arch" type="xml">
<field name="bl_id" position="after">
<field name="is_skip_check"/>
</field>
</field>
</record>
</data>
</odoo>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论