!11921 Merge remote-tracking branch 'origin/dev' into dev
Merge pull request !11921 from 李鹏宇/dev
This commit is contained in:
@@ -3,7 +3,7 @@ import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
from odoo import http, api, SUPERUSER_ID
|
||||
from odoo import http, api, SUPERUSER_ID, fields
|
||||
from odoo.http import request, Response
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -20,16 +20,28 @@ class AiReportController(http.Controller):
|
||||
|
||||
def _get_report_by_token(self, token):
|
||||
"""根据 token 查找并验证报表"""
|
||||
db_name = request.env.cr.dbname if hasattr(request.env.cr, 'dbname') else 'unknown'
|
||||
_logger.info(
|
||||
'报表访问请求: token=%s, db=%s, uid=%s, remote=%s',
|
||||
token[:12], db_name, request.env.uid,
|
||||
request.httprequest.remote_addr if hasattr(request, 'httprequest') else 'unknown'
|
||||
)
|
||||
report = request.env['ai.report'].sudo().search([
|
||||
('access_token', '=', token),
|
||||
], limit=1)
|
||||
if not report:
|
||||
_logger.warning('报表不存在: token=%s', token[:12])
|
||||
_logger.warning(
|
||||
'报表不存在: token=%s, db=%s, 可能原因: token错误/数据库不匹配/记录已被删除',
|
||||
token[:12], db_name
|
||||
)
|
||||
return None, '报表不存在或已被删除'
|
||||
if not report.is_valid():
|
||||
_logger.warning('报表已过期: token=%s, expire=%s, active=%s',
|
||||
token[:12], report.expire_date, report.active)
|
||||
return None, '报表已过期或已停用'
|
||||
_logger.warning('报表已失效: token=%s, db=%s, expire=%s, active=%s',
|
||||
token[:12], db_name, report.expire_date, report.active)
|
||||
reason = '报表已过期' if (report.expire_date and report.expire_date < fields.Datetime.now()) else '报表已停用'
|
||||
return None, f'{reason},请联系分享者重新生成'
|
||||
_logger.info('报表验证通过: token=%s, report_id=%s, name=%s',
|
||||
token[:12], report.id, report.name)
|
||||
return report, None
|
||||
|
||||
@http.route('/ai/report/<string:token>', type='http', auth='none',
|
||||
@@ -83,22 +95,35 @@ class AiReportController(http.Controller):
|
||||
first = data_records[0]
|
||||
col_keys = {c.get('k', '') for c in config['columns']}
|
||||
data_keys = set(first.keys())
|
||||
# 排除 id 字段(Odoo search_read 总是返回 id)
|
||||
display_keys = data_keys - {'id'}
|
||||
missing = col_keys - data_keys
|
||||
extra = display_keys - col_keys
|
||||
if missing:
|
||||
_logger.warning(
|
||||
'模板 %s report_config 列 key 与数据不匹配: missing=%s, col_keys=%s, data_keys=%s',
|
||||
template.id, missing, col_keys, data_keys)
|
||||
# 从数据字段重建列
|
||||
fields = data.get('fields', list(data_keys - {'id'}))
|
||||
'模板 %s (id=%s) report_config 列 key 与数据不匹配: '
|
||||
'missing=%s, extra=%s, col_keys=%s, data_keys=%s',
|
||||
template.name, template.id, missing, extra, col_keys, data_keys)
|
||||
# 从数据字段重建列,但尝试保留模板中的 label 和 width
|
||||
old_col_map = {c.get('k', ''): c for c in config['columns']}
|
||||
labels = {}
|
||||
if report.field_labels:
|
||||
labels = json.loads(report.field_labels)
|
||||
elif template and template.field_labels:
|
||||
labels = json.loads(template.field_labels)
|
||||
fields = data.get('fields', list(display_keys))
|
||||
config['columns'] = [
|
||||
{'k': f, 'l': labels.get(f, f), 'w': 120}
|
||||
for f in fields
|
||||
{
|
||||
'k': f,
|
||||
'l': labels.get(f, old_col_map.get(f, {}).get('l', f)),
|
||||
'w': old_col_map.get(f, {}).get('w', 120),
|
||||
}
|
||||
for f in fields if f != 'id'
|
||||
]
|
||||
config['_column_mismatch'] = True # 标记列被重建,前端可用此提示
|
||||
_logger.info(
|
||||
'模板 %s: 列已从数据字段重建,共 %s 列', template.name, len(config['columns'])
|
||||
)
|
||||
|
||||
# 2d. 处理数据加载失败
|
||||
if not data.get('success'):
|
||||
|
||||
@@ -489,7 +489,11 @@ class AiConversation(models.Model):
|
||||
return result
|
||||
except Exception as e:
|
||||
_logger.exception('generate_report failed: %s', e)
|
||||
return {'success': True, 'url': '', 'message': f'报表生成完毕,请检查'} # 失败不报错
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'报表生成失败:{str(e)}',
|
||||
'message': f'报表生成失败,请稍后重试或联系管理员检查服务端日志。'
|
||||
}
|
||||
|
||||
def _call_chat_completion(self, provider, messages, tools=None):
|
||||
"""封装一次 HTTP 调用,返回 (assistant_message_dict, error_str)。
|
||||
|
||||
@@ -81,13 +81,23 @@ class AiReport(models.Model):
|
||||
if not base_url:
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url', '')
|
||||
# 如果 web.base.url 是内网地址,检查是否有配置公共 URL
|
||||
if 'localhost' in base_url or base_url.startswith('http://127.') or \
|
||||
base_url.startswith('http://10.') or base_url.startswith('http://172.') or \
|
||||
base_url.startswith('http://192.168'):
|
||||
_is_internal = (
|
||||
'localhost' in base_url or
|
||||
base_url.startswith('http://127.') or base_url.startswith('https://127.') or
|
||||
base_url.startswith('http://10.') or base_url.startswith('https://10.') or
|
||||
base_url.startswith('http://172.') or base_url.startswith('https://172.') or
|
||||
base_url.startswith('http://192.168') or base_url.startswith('https://192.168')
|
||||
)
|
||||
if _is_internal:
|
||||
public_url = self.env['ir.config_parameter'].sudo().get_param(
|
||||
'yuthon.public.url', '')
|
||||
if public_url:
|
||||
base_url = public_url
|
||||
else:
|
||||
_logger.warning(
|
||||
'web.base.url 是内网地址 %s,但未配置 yuthon.public.url,'
|
||||
'生成的报表链接外部可能无法访问', base_url
|
||||
)
|
||||
return f"{base_url}/ai/report/{self.access_token}"
|
||||
|
||||
@api.onchange('model_id')
|
||||
|
||||
@@ -78,19 +78,30 @@ class AiReportTemplate(models.Model):
|
||||
'filter_ids', 'default_visible')
|
||||
def _compute_report_config(self):
|
||||
for t in self:
|
||||
# 如果没有任何配置行,保留现有值(兼容旧数据)
|
||||
if not any([t.column_ids, t.card_ids, t.group_by_ids, t.filter_ids]):
|
||||
continue
|
||||
cfg = {}
|
||||
cols = []
|
||||
skipped_cols = 0
|
||||
for c in t.column_ids:
|
||||
fname = c.field_name or (c.field_id.name if c.field_id else '')
|
||||
flabel = c.label or fname
|
||||
if not fname:
|
||||
skipped_cols += 1
|
||||
continue
|
||||
cols.append({'k': fname, 'l': flabel, 'w': c.width or 120})
|
||||
if skipped_cols > 0:
|
||||
_logger.warning(
|
||||
'模板 %s (id=%s): %s 个列定义的字段名为空,已被跳过。'
|
||||
'请检查列定义中的字段名或字段选择。',
|
||||
t.name, t.id, skipped_cols
|
||||
)
|
||||
# 如果 column_ids 有数据但全部被跳过,需要重置 report_config
|
||||
if cols:
|
||||
cfg['columns'] = cols
|
||||
elif t.column_ids:
|
||||
# column_ids 存在但全部无效 → 清除旧的 report_config
|
||||
_logger.warning(
|
||||
'模板 %s (id=%s): 所有列定义无效,report_config 将被清空。', t.name, t.id
|
||||
)
|
||||
cards = []
|
||||
for c in t.card_ids:
|
||||
fname_c = c.field_name or (c.field_id.name if c.field_id else '')
|
||||
|
||||
Reference in New Issue
Block a user