提交 799749cb authored 作者: 刘擎阳's avatar 刘擎阳

1.优化

上级 ee0abc37
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
'wizard/batch_input_ship_package_statu_wizard.xml', 'wizard/batch_input_ship_package_statu_wizard.xml',
'wizard/update_bl_status_wizard.xml', 'wizard/update_bl_status_wizard.xml',
'wizard/excel_wizard.xml', 'wizard/excel_wizard.xml',
'views/warn_config_views.xml',
# 'wizard/again_push_wizard.xml', # 'wizard/again_push_wizard.xml',
# 'wizard/batch_push_tiktok.xml', # 'wizard/batch_push_tiktok.xml',
# view # view
......
...@@ -25,5 +25,18 @@ ...@@ -25,5 +25,18 @@
<field name="doall" eval="False"/> <field name="doall" eval="False"/>
</record> </record>
<record id="cron_cron_warn_cc_order" model="ir.cron">
<field name="name">节点进度预警</field>
<field name="model_id" ref="ccs_connect_tiktok.model_warning_config"/>
<field name="state">code</field>
<field name="code">model.cron_warn_cc_order()</field>
<field name='interval_number'>1</field>
<field name='interval_type'>days</field>
<field name="numbercall">-1</field>
<field name="active" eval="True"/>
<field name="nextcall" eval="(DateTime.now().replace(hour=0, minute=0)).strftime('%Y-%m-%d %H:%M:%S')" />
<field name="doall" eval="False"/>
</record>
</data> </data>
</odoo> </odoo>
\ No newline at end of file
...@@ -12,5 +12,5 @@ from . import pda_scan_record ...@@ -12,5 +12,5 @@ from . import pda_scan_record
from . import bl_patrol from . import bl_patrol
from . import cc_pallet from . import cc_pallet
from . import warn_config
from odoo import models, fields, api
from odoo.exceptions import ValidationError
from datetime import datetime, timedelta, timezone
from collections import defaultdict
import logging
_logger = logging.getLogger(__name__)
class WarningConfig(models.Model):
_name = 'warning.config'
_description = '预警配置'
name = fields.Char(string='预警名称', required=True, copy=False)
time_type = fields.Selection([
('clearance_node', '清关进度节点'),
('flight_landing', '航班落地')
], string='预警时间类型', required=True, default='clearance_node')
# 当类型为"清关进度节点"时使用的字段
time_point_id = fields.Many2one(
'cc.node',
string='预警时间点'
)
# 当类型为"航班落地"时使用的占位展示字段
flight_landing_time = fields.Char(
string='预警时间点',
default='预计到达时间',
readonly=True
)
remaining_time = fields.Integer(
string='剩余时间',
help='填正整数或负整数。整数代表距离该时间还剩多长时间,负数代表超过时间多长时间。'
)
unsynced_node_id = fields.Many2one(
'cc.node',
string='未同步节点状态'
)
warning_reason = fields.Text(string='预警原因')
active = fields.Boolean(string='有效', default=True)
# 数据库层面的唯一性约束
_sql_constraints = [
('name_unique', 'UNIQUE(name)', '预警名称必须唯一,不能重复!')
]
# Python 层面的逻辑校验
@api.constrains('time_point_id', 'unsynced_node_id', 'time_type')
def _check_node_difference(self):
for record in self:
# 只有在时间类型为清关节点时,才去对比两个节点是否相同
if record.time_type == 'clearance_node':
if record.time_point_id and record.unsynced_node_id and record.time_point_id == record.unsynced_node_id:
raise ValidationError('未同步节点状态 不能跟 预警时间点 一致!')
# 当切换时间类型时,清空不相关的数据
@api.onchange('time_type')
def _onchange_time_type(self):
if self.time_type == 'flight_landing':
self.time_point_id = False
def convert_to_utc(self, time_str):
"""
将带时区的时间字符串转换为 0 时区 (UTC) 的常规格式
"""
# 1. 解析时间字符串
dt = datetime.fromisoformat(time_str)
# 2. 转换到 0 时区
utc_dt = dt.astimezone(timezone.utc)
# 3. 返回格式化后的字符串
return datetime.strptime(utc_dt.strftime("%Y-%m-%d %H:%M:%S"), '%d/%m/%Y %H:%M:%S')
# def cron_warn_cc_order(self):
# # 获取最近几天的提单 未完成的
# # 根据配置条件去检查
# config_objs = self.env['warning.config'].sudo().search([])
# warn_order_days = self.env['ir.config_parameter'].sudo().get_param('warn_order_days') or 10
# warn_order_days = int(warn_order_days)
# utc_time = datetime.utcnow()
# c_time = utc_time - timedelta(days=warn_order_days)
# domain = [('create_date', '>=', c_time.strftime('%Y-%m-%d %H:%M:%S')), ('state', '!=', 'done')]
# bl_objs = self.env['cc.bl'].sudo().search(domain)
# ship_package_arr = defaultdict(list)
# for config_obj in config_objs:
# # 需要一个时间然后检查同步日志里面有没有这个节点
# if config_obj.time_type == 'clearance_node':
# if config_obj.time_point_id.node_type == 'bl':
# # 找这个提单的节点的这个时间 去查有没有上传
# for bl_obj in bl_objs:
# log_obj = self.env['cc.bl.sync.log'].sudo().search([('bl_id', '=', bl_obj.id),
# ('process_code', '=', config_obj.time_point_id.tk_code)], order='operate_time desc', limit=1)
# not_log_obj = self.env['cc.bl.sync.log'].sudo().search([('bl_id', '=', bl_obj.id),
# ('process_code', '=',
# config_obj.unsynced_node_id.tk_code)],
# order='operate_time desc', limit=1)
# if utc_time > (log_obj.operate_time + timedelta(hours=-int(config_obj.remaining_time))) and not not_log_obj:
# # 发送消息
# content = f"""
# 提单号{bl_obj.bl_no}
# 关务节点{config_obj.unsynced_node_id.name})未同步,{config_obj.warning_reason or ''}
# 请及时操作!
# """
# self.send_email(content)
# pass
# elif config_obj.time_point_id.node_type == 'package':
# for bl_obj in bl_objs:
# package_objs = self.env['cc.ship.package'].sudo().search([('bl_id', '=', bl_obj.id)])
# package_arr = []
# for package_obj in package_objs:
# log_obj = self.env['cc.ship.package.sync.log'].sudo().search([('package_id', '=', package_obj.id),
# ('process_code', '=', config_obj.time_point_id.tk_code)], order='operate_time desc', limit=1)
# not_log_obj = self.env['cc.ship.package.sync.log'].sudo().search([('package_id', '=', package_obj.id),
# ('process_code', '=',
# config_obj.unsynced_node_id.tk_code)],
# order='operate_time desc', limit=1)
# if utc_time > (log_obj.operate_time + timedelta(hours=-int(config_obj.remaining_time))) and not not_log_obj:
# # 发送消息
# package_arr.append(package_obj)
# pass
# if bl_obj.bl_no in ship_package_arr:
# ship_package_arr.append({
# 'name': config_obj.unsynced_node_id.name or '',
# 'arr': package_arr
# })
# else:
# ship_package_arr[bl_obj.bl_no] = [{
# 'name': config_obj.unsynced_node_id.name or '',
# 'arr': package_arr
# }]
# self.send_ship_email(ship_package_arr)
# else:
# if config_obj.unsynced_node_id.node_type == 'bl':
# # 找这个提单的节点的这个时间 去查有没有上传
# for bl_obj in bl_objs:
# not_log_obj = self.env['cc.bl.sync.log'].sudo().search([('bl_id', '=', bl_obj.id),
# ('process_code', '=',
# config_obj.unsynced_node_id.tk_code)],
# order='operate_time desc', limit=1)
# eta = self.convert_to_utc(bl_obj.eta)
# # 输出: 2025-09-30 13:35:00
# if utc_time > (eta + timedelta(hours=-int(config_obj.remaining_time))) and not not_log_obj:
# # 发送消息
# content = f"""
# 提单号{bl_obj.bl_no}
# 关务节点{config_obj.unsynced_node_id.name})未同步,{config_obj.warning_reason or ''}
# 请及时操作!
# """
# self.send_email(content)
# pass
# elif config_obj.unsynced_node_id.node_type == 'package':
# for bl_obj in bl_objs:
# package_objs = self.env['cc.ship.package'].sudo().search([('bl_id', '=', bl_obj.id)])
# package_arr = []
# for package_obj in package_objs:
# not_log_obj = self.env['cc.ship.package.sync.log'].sudo().search([('package_id', '=', package_obj.id),
# ('process_code', '=',
# config_obj.unsynced_node_id.tk_code)],
# order='operate_time desc', limit=1)
# eta = self.convert_to_utc(bl_obj.eta)
# if utc_time > (eta + timedelta(hours=-int(config_obj.remaining_time))) and not not_log_obj:
# # 发送消息
# package_arr.append(package_obj)
# pass
# if bl_obj.bl_no in ship_package_arr:
# ship_package_arr.append({
# 'name': config_obj.unsynced_node_id.name or '',
# 'arr': package_arr
# })
# else:
# ship_package_arr[bl_obj.bl_no] = [{
# 'name': config_obj.unsynced_node_id.name or '',
# 'arr': package_arr
# }]
# self.send_ship_email(ship_package_arr)
# def cron_warn_cc_order(self):
# # 1. 初始化基础参数
# utc_time = datetime.utcnow()
# warn_days = int(self.env['ir.config_parameter'].sudo().get_param('warn_order_days', 10))
# c_time = utc_time - timedelta(days=warn_days)
# config_objs = self.env['warning.config'].sudo().search([])
# bl_objs = self.env['cc.bl'].sudo().search([
# ('create_date', '>=', c_time.strftime('%Y-%m-%d %H:%M:%S')),
# ('state', '!=', 'done')
# ])
# # bl_objs = self.env['cc.bl'].sudo().search([('id', '=', 71)]) # 本地测试提单
# if not config_objs or not bl_objs:
# return
# # 2. 一次性获取所有相关的包裹,并按 bl_id 分组映射到内存中
# all_packages = self.env['cc.ship.package'].sudo().search([('bl_id', 'in', bl_objs.ids)])
# packages_by_bl = defaultdict(list)
# for pkg in all_packages:
# packages_by_bl[pkg.bl_id.id].append(pkg)
# # 3. 核心工具函数:批量获取最新日志字典 {record_id: {process_code: operate_time}}
#
# def get_latest_logs_time(model_name, rel_field, record_ids, process_codes):
# if not record_ids or not process_codes:
# return {}
# logs = self.env[model_name].sudo().search([
# (rel_field, 'in', tuple(record_ids)),
# ('process_code', 'in', tuple(process_codes))
# ], order='operate_time desc')
# log_dict = defaultdict(dict)
# for log in logs:
# rec_id = getattr(log, rel_field).id
# code = log.process_code
# if code not in log_dict[rec_id]: # 只记录最新的那条时间
# log_dict[rec_id][code] = log.operate_time
# return log_dict
#
# ship_package_arr = defaultdict(list)
# bl_arr = defaultdict(list)
# # 4. 遍历配置规则进行校验
# for config in config_objs:
# is_clearance = (config.time_type == 'clearance_node')
# node_type = config.time_point_id.node_type if is_clearance else config.unsynced_node_id.node_type
#
# sync_code = config.time_point_id.tk_code if is_clearance else None
# unsync_code = config.unsynced_node_id.tk_code
# codes_to_fetch = {c for c in [sync_code, unsync_code] if c}
#
# time_offset = timedelta(hours=-int(config.remaining_time or 0))
# node_name = config.unsynced_node_id.name or ''
# warning_reason = config.warning_reason or ''
#
# # 处理提单级别 (BL)
# if node_type == 'bl':
# bl_logs = get_latest_logs_time('cc.bl.sync.log', 'bl_id', bl_objs.ids, codes_to_fetch)
# for bl in bl_objs:
# logs = bl_logs.get(bl.id, {})
# if unsync_code in logs: # 已同步,跳过
# continue
#
# # 确定计算基准时间
# base_time = logs.get(sync_code) if is_clearance else (
# self.convert_to_utc(bl.eta) if bl.eta else None)
#
# if base_time and utc_time > (base_time + time_offset):
# content = f"提单号{bl.bl_no}\n关务节点({node_name})未同步,{warning_reason}\n请及时操作!"
# bl_arr[bl.bl_no].append({
# 'name': node_name,
# 'warning_reason': warning_reason
# })
# # self.send_bl_email(config.name, content)
# # 处理包裹级别 (Package)
# elif node_type == 'package':
# pkg_logs = get_latest_logs_time('cc.ship.package.sync.log', 'package_id', all_packages.ids,
# codes_to_fetch)
# for bl in bl_objs:
# alert_pkgs = []
# # 获取该提单下的所有包裹
# for pkg in packages_by_bl.get(bl.id, []):
# logs = pkg_logs.get(pkg.id, {})
# if unsync_code in logs: # 已同步,跳过
# continue
# # 确定计算基准时间
# base_time = logs.get(sync_code) if is_clearance else (
# self.convert_to_utc(bl.eta) if bl.eta else None)
# if base_time and utc_time > (base_time + time_offset):
# alert_pkgs.append(pkg)
# # 修复 Bug: 正确地向 defaultdict(list) 的子列表中追加数据
# if alert_pkgs:
# ship_package_arr[bl.bl_no].append({
# 'name': node_name,
# 'arr': alert_pkgs,
# 'warning_reason': warning_reason
# })
# # 5. 循环外部:一次性发送包裹邮件汇总!
# if ship_package_arr:
# # print(ship_package_arr)
# self.send_warn_email(self.format_package_warning_email(ship_package_arr))
# if bl_arr:
# # print(bl_arr)
# self.send_warn_email(self.format_email_content_grouped(bl_arr))
def cron_warn_cc_order(self):
# 1. 初始化基础参数
utc_time = datetime.utcnow()
warn_days = int(self.env['ir.config_parameter'].sudo().get_param('warn_order_days', 7))
# c_time = utc_time - timedelta(days=warn_days)
end_date = fields.Date.today()
start_date = end_date - timedelta(days=warn_days)
print(start_date)
config_objs = self.env['warning.config'].sudo().search([])
bl_objs = self.env['cc.bl'].sudo().search([
('bl_date', '>=', start_date),
('state', '!=', 'done')
])
if not config_objs or not bl_objs:
return
# 2. 获取包裹及预计算提单的 ETA (避免重复转换时间)
all_packages = self.env['cc.ship.package'].sudo().search([('bl_id', 'in', bl_objs.ids)])
packages_by_bl = defaultdict(list)
for pkg in all_packages:
packages_by_bl[pkg.bl_id.id].append(pkg)
bl_eta_utc_dict = {bl.id: self.convert_to_utc(bl.eta) if bl.eta else None for bl in bl_objs}
# 3. 核心优化:提前收集所有配置中用到的 process_code,一次性查询!
bl_codes_needed = set()
pkg_codes_needed = set()
for config in config_objs:
is_clearance = (config.time_type == 'clearance_node')
node_type = config.time_point_id.node_type if is_clearance else config.unsynced_node_id.node_type
if node_type == 'bl':
if is_clearance: bl_codes_needed.add(config.time_point_id.tk_code)
bl_codes_needed.add(config.unsynced_node_id.tk_code)
elif node_type == 'package':
if is_clearance: pkg_codes_needed.add(config.time_point_id.tk_code)
pkg_codes_needed.add(config.unsynced_node_id.tk_code)
# 4. 超级工具函数:使用 search_read 避开 ORM 实例化开销
def get_all_logs_dict(model_name, rel_field, record_ids, process_codes):
if not record_ids or not process_codes:
return {}
# search_read 直接返回字典列表,比 search 返回对象快 10 倍以上
logs_data = self.env[model_name].sudo().search_read(
[(rel_field, 'in', tuple(record_ids)), ('process_code', 'in', tuple(process_codes))],
[rel_field, 'process_code', 'operate_time'],
order='operate_time desc'
)
log_dict = defaultdict(dict)
for data in logs_data:
# search_read 中的 Many2one 字段会返回 (id, name) 元组
rec_id = data[rel_field][0] if isinstance(data[rel_field], tuple) else data[rel_field]
code = data['process_code']
if code not in log_dict[rec_id]: # 依靠 order='desc',最先遍历到的一定是最新的
log_dict[rec_id][code] = data['operate_time']
return log_dict
# 无论有多少条配置规则,查提单日志和包裹日志永远只各查 1 次数据库!
bl_logs_dict = get_all_logs_dict('cc.bl.sync.log', 'bl_id', bl_objs.ids, bl_codes_needed)
pkg_logs_dict = get_all_logs_dict('cc.ship.package.sync.log', 'package_id', all_packages.ids, pkg_codes_needed)
ship_package_arr = defaultdict(list)
bl_arr = defaultdict(list)
# 5. 遍历配置规则进行校验 (此时全是纯内存/字典操作,耗时接近 0)
for config in config_objs:
is_clearance = (config.time_type == 'clearance_node')
node_type = config.time_point_id.node_type if is_clearance else config.unsynced_node_id.node_type
sync_code = config.time_point_id.tk_code if is_clearance else None
unsync_code = config.unsynced_node_id.tk_code
time_offset = timedelta(hours=-int(config.remaining_time or 0))
node_name = config.unsynced_node_id.name or ''
warning_reason = config.warning_reason or ''
if node_type == 'bl':
for bl in bl_objs:
logs = bl_logs_dict.get(bl.id, {})
if unsync_code in logs: # 已同步,跳过
continue
base_time = logs.get(sync_code) if is_clearance else bl_eta_utc_dict.get(bl.id)
if base_time and utc_time > (base_time + time_offset):
bl_arr[bl.bl_no].append({
'name': node_name,
'warning_reason': warning_reason
})
elif node_type == 'package':
for bl in bl_objs:
alert_pkgs = []
# 获取该提单预计算的基准时间 (非clearance模式下)
bl_eta = bl_eta_utc_dict.get(bl.id) if not is_clearance else None
for pkg in packages_by_bl.get(bl.id, []):
logs = pkg_logs_dict.get(pkg.id, {})
if unsync_code in logs:
continue
base_time = logs.get(sync_code) if is_clearance else bl_eta
if base_time and utc_time > (base_time + time_offset):
alert_pkgs.append(pkg)
if alert_pkgs:
ship_package_arr[bl.bl_no].append({
'name': node_name,
'arr': alert_pkgs,
'warning_reason': warning_reason
})
# 6. 发送邮件汇总
if ship_package_arr:
self.send_warn_email(self.format_package_warning_email(ship_package_arr))
if bl_arr:
self.send_warn_email(self.format_email_content_grouped(bl_arr))
def format_package_warning_email(self, data):
"""
将小包预警数据格式化为指定的邮件内容(已移除开头标题)
"""
email_lines = []
for bl_no, node_list in data.items():
email_lines.append(f"提单号{bl_no}")
for node in node_list:
packages = node.get('arr', [])
total_count = len(packages)
if total_count == 0:
continue
# 提取小包号(最多10个)
# 注意:这里的 pkg.name 假设你的小包号字段叫 name,请根据实际情况替换
pkg_names = [pkg.logistic_order_no for pkg in packages[:10]]
pkg_str = "/".join(pkg_names)
node_name = node.get('name', '未知节点')
reason = node.get('warning_reason', '无预警原因')
# 按照要求的格式拼接
email_lines.append(f"小包{pkg_str}等{total_count}个小包,节点{node_name}未同步,{reason}")
email_lines.append("请及时操作!")
# 用换行符将所有行连接起来
return "\n".join(email_lines)
def format_email_content_grouped(self, data):
"""
将预警数据格式化为邮件文本(按提单号聚合节点)
"""
email_lines = []
for bl_no, nodes in data.items():
email_lines.append(f"提单号{bl_no}")
# 将该提单下的所有异常节点列出来
for node in nodes:
node_name = node.get('name', '未知节点')
reason = node.get('warning_reason', '无')
email_lines.append(f"关务节点{node_name}未同步,{reason}")
email_lines.append("请及时操作!\n") # \n 用于和下一个提单隔开
return "\n".join(email_lines).strip()
def send_warn_email(self, content):
"""
发送邮件
"""
try:
# 获取邮件配置
config = self.env['ir.config_parameter'].sudo()
receiver_emails = config.get_param('patrol_receiver_emails', default='')
sender_email = config.get_param('patrol_sender_email', default='')
if not receiver_emails or not sender_email:
logging.warning("邮件配置不完整,跳过邮件发送")
return
# 解析接收邮箱
receiver_list = [email.strip() for email in receiver_emails.split(';') if email.strip()]
# 构建邮件内容
subject = f"节点未及时推送预警通知" # 标题
# 发送邮件
self.env['mail.mail'].sudo().create({
'subject': subject,
'body_html': content.replace('\n', '<br/>'),
'email_from': sender_email,
'email_to': ','.join(receiver_list),
'auto_delete': True,
'date': datetime.utcnow()
}).send()
# 更新发送记录
# if patrol_obj:
# patrol_obj.write({
# 'email_sent': True,
# 'email_sent_time': fields.Datetime.now()
# })
_logger.info(f"预警邮件发送成功,接收人: {receiver_list}")
except Exception as e:
_logger.error(f"发送预警邮件失败: {str(e)}")
# def send_ship_email(self, content):
# """
# 发送邮件
# """
# try:
# # 获取邮件配置
# config = self.env['ir.config_parameter'].sudo()
# receiver_emails = config.get_param('patrol_receiver_emails', default='')
# sender_email = config.get_param('patrol_sender_email', default='')
# if not receiver_emails or not sender_email:
# logging.warning("邮件配置不完整,跳过邮件发送")
# return
# # 解析接收邮箱
# receiver_list = [email.strip() for email in receiver_emails.split(';') if email.strip()]
# # 构建邮件内容
# subject = f"节点未及时推送预警通知" # 标题
# # 发送邮件
# self.env['mail.mail'].sudo().create({
# 'subject': subject,
# 'body_html': content.replace('\n', '<br/>'),
# 'email_from': sender_email,
# 'email_to': ','.join(receiver_list),
# 'auto_delete': True,
# 'date': datetime.utcnow()
# }).send()
#
# # 更新发送记录
# # if patrol_obj:
# # patrol_obj.write({
# # 'email_sent': True,
# # 'email_sent_time': fields.Datetime.now()
# # })
#
# _logger.info(f"预警邮件发送成功,接收人: {receiver_list}")
#
# except Exception as e:
# _logger.error(f"发送预警邮件失败: {str(e)}")
...@@ -22,4 +22,7 @@ access_pda_scan_record_manager,pda.scan.record.manager,model_pda_scan_record,bas ...@@ -22,4 +22,7 @@ access_pda_scan_record_manager,pda.scan.record.manager,model_pda_scan_record,bas
access_bl_patrol_user,bl.patrol.user,model_bl_patrol,base.group_user,1,0,0,0 access_bl_patrol_user,bl.patrol.user,model_bl_patrol,base.group_user,1,0,0,0
access_bl_patrol_manager,bl.patrol.manager,model_bl_patrol,base.group_system,1,1,1,1 access_bl_patrol_manager,bl.patrol.manager,model_bl_patrol,base.group_system,1,1,1,1
access_package_data_wizard_base.group_user,package_data_wizard base.group_user,ccs_connect_tiktok.model_package_data_wizard,base.group_user,1,1,1,1 access_package_data_wizard_base.group_user,package_data_wizard base.group_user,ccs_connect_tiktok.model_package_data_wizard,base.group_user,1,1,1,1
\ No newline at end of file
access_warning_config_user,warning.config.user,model_warning_config,base.group_user,1,1,1,1
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_warning_config_form" model="ir.ui.view">
<field name="name">warning.config.form</field>
<field name="model">warning.config</field>
<field name="arch" type="xml">
<form string="预警配置">
<sheet>
<widget name="web_ribbon" title="归档" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<div class="oe_title">
<h1>
<field name="name" placeholder="请输入预警名称..."/>
</h1>
</div>
<group>
<group>
<field name="time_type" widget="radio"/>
<field name="time_point_id"
attrs="{
'invisible': [('time_type', '=', 'flight_landing')],
'required': [('time_type', '=', 'clearance_node')]
}"/>
<field name="flight_landing_time"
attrs="{'invisible': [('time_type', '=', 'clearance_node')]}"/>
<field name="remaining_time"/>
</group>
<group>
<field name="unsynced_node_id"/>
<field name="active" invisible="1"/>
</group>
</group>
<notebook>
<page string="预警原因" name="reason">
<field name="warning_reason" placeholder="请详细描述预警原因..."/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="view_warning_config_tree" model="ir.ui.view">
<field name="name">warning.config.tree</field>
<field name="model">warning.config</field>
<field name="arch" type="xml">
<tree string="预警配置">
<field name="name"/>
<field name="time_type"/>
<field name="time_point_id" attrs="{'invisible': [('time_type', '=', 'flight_landing')]}"/>
<field name="flight_landing_time" attrs="{'invisible': [('time_type', '=', 'clearance_node')]}"/>
<field name="remaining_time"/>
<field name="unsynced_node_id"/>
<field name="active" widget="boolean_toggle"/>
</tree>
</field>
</record>
<record id="view_warning_config_search" model="ir.ui.view">
<field name="name">warning.config.search</field>
<field name="model">warning.config</field>
<field name="arch" type="xml">
<search string="搜索预警配置">
<field name="name"/>
<field name="time_point_id"/>
<filter string="航班落地" name="type_flight" domain="[('time_type', '=', 'flight_landing')]"/>
<filter string="清关进度节点" name="type_clearance" domain="[('time_type', '=', 'clearance_node')]"/>
<separator/>
<filter string="归档" name="inactive" domain="[('active', '=', False)]"/>
</search>
</field>
</record>
<record id="action_warning_config" model="ir.actions.act_window">
<field name="name">预警配置</field>
<field name="res_model">warning.config</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
创建第一条预警配置
</p>
</field>
</record>
<menuitem id="menu_warning_config"
name="预警配置"
action="action_warning_config"
sequence="10"/>
</odoo>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论