修复文档生成异常。修复模板输入字段关联异常。
This commit is contained in:
parent
d3afba9191
commit
7cbe4b29b7
175
app.py
175
app.py
@ -835,6 +835,180 @@ def get_document_by_task():
|
|||||||
return error_response(3001, f"文档生成失败: {str(e)}")
|
return error_response(3001, f"文档生成失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/template-field-manager')
|
||||||
|
def template_field_manager():
|
||||||
|
"""返回模板字段关联管理页面"""
|
||||||
|
return send_from_directory('static', 'template_field_manager.html')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/template-field-relations', methods=['GET'])
|
||||||
|
def get_template_field_relations():
|
||||||
|
"""
|
||||||
|
获取所有模板和字段的关联关系
|
||||||
|
用于模板字段关联管理页面
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
conn = document_service.get_connection()
|
||||||
|
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 获取所有启用的模板
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id, name, template_code
|
||||||
|
FROM f_polic_file_config
|
||||||
|
WHERE tenant_id = %s AND state = 1
|
||||||
|
ORDER BY name
|
||||||
|
""", (document_service.tenant_id,))
|
||||||
|
templates = cursor.fetchall()
|
||||||
|
|
||||||
|
# 获取所有启用的输入字段
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id, name, filed_code, field_type
|
||||||
|
FROM f_polic_field
|
||||||
|
WHERE tenant_id = %s AND field_type = 1 AND state = 1
|
||||||
|
ORDER BY name
|
||||||
|
""", (document_service.tenant_id,))
|
||||||
|
input_fields = cursor.fetchall()
|
||||||
|
|
||||||
|
# 获取所有启用的输出字段
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id, name, filed_code, field_type
|
||||||
|
FROM f_polic_field
|
||||||
|
WHERE tenant_id = %s AND field_type = 2 AND state = 1
|
||||||
|
ORDER BY name
|
||||||
|
""", (document_service.tenant_id,))
|
||||||
|
output_fields = cursor.fetchall()
|
||||||
|
|
||||||
|
# 获取现有的关联关系
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT file_id, filed_id
|
||||||
|
FROM f_polic_file_field
|
||||||
|
WHERE tenant_id = %s AND state = 1
|
||||||
|
""", (document_service.tenant_id,))
|
||||||
|
relations = cursor.fetchall()
|
||||||
|
|
||||||
|
# 构建关联关系映射 (file_id -> list of filed_id)
|
||||||
|
# 注意:JSON不支持set,所以转换为list
|
||||||
|
relation_map = {}
|
||||||
|
for rel in relations:
|
||||||
|
file_id = rel['file_id']
|
||||||
|
filed_id = rel['filed_id']
|
||||||
|
if file_id not in relation_map:
|
||||||
|
relation_map[file_id] = []
|
||||||
|
relation_map[file_id].append(filed_id)
|
||||||
|
|
||||||
|
return success_response({
|
||||||
|
'templates': templates,
|
||||||
|
'input_fields': input_fields,
|
||||||
|
'output_fields': output_fields,
|
||||||
|
'relations': relation_map
|
||||||
|
})
|
||||||
|
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return error_response(500, f"获取关联关系失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/template-field-relations', methods=['POST'])
|
||||||
|
def save_template_field_relations():
|
||||||
|
"""
|
||||||
|
保存模板和字段的关联关系
|
||||||
|
请求体格式: {
|
||||||
|
"template_id": 123,
|
||||||
|
"input_field_ids": [1, 2, 3],
|
||||||
|
"output_field_ids": [4, 5, 6]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return error_response(400, "请求参数不能为空")
|
||||||
|
|
||||||
|
template_id = data.get('template_id')
|
||||||
|
input_field_ids = data.get('input_field_ids', [])
|
||||||
|
output_field_ids = data.get('output_field_ids', [])
|
||||||
|
|
||||||
|
if not template_id:
|
||||||
|
return error_response(400, "template_id参数不能为空")
|
||||||
|
|
||||||
|
conn = document_service.get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 验证模板是否存在
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id FROM f_polic_file_config
|
||||||
|
WHERE id = %s AND tenant_id = %s AND state = 1
|
||||||
|
""", (template_id, document_service.tenant_id))
|
||||||
|
if not cursor.fetchone():
|
||||||
|
return error_response(400, f"模板ID {template_id} 不存在或未启用")
|
||||||
|
|
||||||
|
# 合并所有字段ID
|
||||||
|
all_field_ids = set(input_field_ids + output_field_ids)
|
||||||
|
|
||||||
|
# 验证字段是否存在
|
||||||
|
if all_field_ids:
|
||||||
|
placeholders = ','.join(['%s'] * len(all_field_ids))
|
||||||
|
cursor.execute(f"""
|
||||||
|
SELECT id FROM f_polic_field
|
||||||
|
WHERE id IN ({placeholders}) AND tenant_id = %s AND state = 1
|
||||||
|
""", list(all_field_ids) + [document_service.tenant_id])
|
||||||
|
existing_field_ids = {row[0] for row in cursor.fetchall()}
|
||||||
|
invalid_field_ids = all_field_ids - existing_field_ids
|
||||||
|
if invalid_field_ids:
|
||||||
|
return error_response(400, f"字段ID {list(invalid_field_ids)} 不存在或未启用")
|
||||||
|
|
||||||
|
# 删除该模板的所有现有关联关系
|
||||||
|
cursor.execute("""
|
||||||
|
DELETE FROM f_polic_file_field
|
||||||
|
WHERE file_id = %s AND tenant_id = %s
|
||||||
|
""", (template_id, document_service.tenant_id))
|
||||||
|
|
||||||
|
# 插入新的关联关系
|
||||||
|
current_time = datetime.now()
|
||||||
|
created_by = 655162080928945152 # 默认创建者ID
|
||||||
|
|
||||||
|
if all_field_ids:
|
||||||
|
insert_sql = """
|
||||||
|
INSERT INTO f_polic_file_field
|
||||||
|
(tenant_id, file_id, filed_id, created_time, created_by, updated_time, updated_by, state)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, %s, %s, 1)
|
||||||
|
"""
|
||||||
|
for field_id in all_field_ids:
|
||||||
|
cursor.execute(insert_sql, (
|
||||||
|
document_service.tenant_id,
|
||||||
|
template_id,
|
||||||
|
field_id,
|
||||||
|
current_time,
|
||||||
|
created_by,
|
||||||
|
current_time,
|
||||||
|
created_by
|
||||||
|
))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
return success_response({
|
||||||
|
'template_id': template_id,
|
||||||
|
'input_field_count': len(input_field_ids),
|
||||||
|
'output_field_count': len(output_field_ids),
|
||||||
|
'total_field_count': len(all_field_ids)
|
||||||
|
}, "保存成功")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
conn.rollback()
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return error_response(500, f"保存关联关系失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# 确保static目录存在
|
# 确保static目录存在
|
||||||
os.makedirs('static', exist_ok=True)
|
os.makedirs('static', exist_ok=True)
|
||||||
@ -844,6 +1018,7 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
print(f"服务启动在 http://localhost:{port}")
|
print(f"服务启动在 http://localhost:{port}")
|
||||||
print(f"测试页面: http://localhost:{port}/")
|
print(f"测试页面: http://localhost:{port}/")
|
||||||
|
print(f"模板字段管理页面: http://localhost:{port}/template-field-manager")
|
||||||
print(f"Swagger API文档: http://localhost:{port}/api-docs")
|
print(f"Swagger API文档: http://localhost:{port}/api-docs")
|
||||||
|
|
||||||
app.run(host='0.0.0.0', port=port, debug=debug)
|
app.run(host='0.0.0.0', port=port, debug=debug)
|
||||||
|
|||||||
496
check_file_field_relations_comprehensive.py
Normal file
496
check_file_field_relations_comprehensive.py
Normal file
@ -0,0 +1,496 @@
|
|||||||
|
"""
|
||||||
|
全面检查 f_polic_file_field 表的关联关系
|
||||||
|
重点检查输入字段(field_type=1)和输出字段(field_type=2)与模板的关联情况
|
||||||
|
"""
|
||||||
|
import pymysql
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
# 设置输出编码为UTF-8
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
import io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||||
|
|
||||||
|
# 数据库连接配置
|
||||||
|
DB_CONFIG = {
|
||||||
|
'host': os.getenv('DB_HOST', '152.136.177.240'),
|
||||||
|
'port': int(os.getenv('DB_PORT', 5012)),
|
||||||
|
'user': os.getenv('DB_USER', 'finyx'),
|
||||||
|
'password': os.getenv('DB_PASSWORD', '6QsGK6MpePZDE57Z'),
|
||||||
|
'database': os.getenv('DB_NAME', 'finyx'),
|
||||||
|
'charset': 'utf8mb4'
|
||||||
|
}
|
||||||
|
|
||||||
|
TENANT_ID = 615873064429507639
|
||||||
|
|
||||||
|
|
||||||
|
def check_all_templates_field_relations(conn) -> Dict:
|
||||||
|
"""检查所有模板的字段关联情况"""
|
||||||
|
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("1. 检查所有模板的字段关联情况")
|
||||||
|
print("="*80)
|
||||||
|
|
||||||
|
# 获取所有启用的模板
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id, name, template_code
|
||||||
|
FROM f_polic_file_config
|
||||||
|
WHERE tenant_id = %s AND state = 1
|
||||||
|
ORDER BY name
|
||||||
|
""", (TENANT_ID,))
|
||||||
|
templates = cursor.fetchall()
|
||||||
|
|
||||||
|
# 获取每个模板关联的字段(按类型分组)
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT
|
||||||
|
fc.id AS template_id,
|
||||||
|
fc.name AS template_name,
|
||||||
|
f.id AS field_id,
|
||||||
|
f.name AS field_name,
|
||||||
|
f.filed_code AS field_code,
|
||||||
|
f.field_type
|
||||||
|
FROM f_polic_file_config fc
|
||||||
|
INNER JOIN f_polic_file_field fff ON fc.id = fff.file_id AND fc.tenant_id = fff.tenant_id
|
||||||
|
INNER JOIN f_polic_field f ON fff.filed_id = f.id AND fff.tenant_id = f.tenant_id
|
||||||
|
WHERE fc.tenant_id = %s
|
||||||
|
AND fc.state = 1
|
||||||
|
AND fff.state = 1
|
||||||
|
AND f.state = 1
|
||||||
|
ORDER BY fc.name, f.field_type, f.name
|
||||||
|
""", (TENANT_ID,))
|
||||||
|
relations = cursor.fetchall()
|
||||||
|
|
||||||
|
# 按模板分组统计
|
||||||
|
template_stats = {}
|
||||||
|
for template in templates:
|
||||||
|
template_stats[template['id']] = {
|
||||||
|
'template_id': template['id'],
|
||||||
|
'template_name': template['name'],
|
||||||
|
'template_code': template.get('template_code'),
|
||||||
|
'input_fields': [],
|
||||||
|
'output_fields': [],
|
||||||
|
'input_count': 0,
|
||||||
|
'output_count': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 填充字段信息
|
||||||
|
for rel in relations:
|
||||||
|
template_id = rel['template_id']
|
||||||
|
if template_id in template_stats:
|
||||||
|
field_info = {
|
||||||
|
'field_id': rel['field_id'],
|
||||||
|
'field_name': rel['field_name'],
|
||||||
|
'field_code': rel['field_code'],
|
||||||
|
'field_type': rel['field_type']
|
||||||
|
}
|
||||||
|
if rel['field_type'] == 1:
|
||||||
|
template_stats[template_id]['input_fields'].append(field_info)
|
||||||
|
template_stats[template_id]['input_count'] += 1
|
||||||
|
elif rel['field_type'] == 2:
|
||||||
|
template_stats[template_id]['output_fields'].append(field_info)
|
||||||
|
template_stats[template_id]['output_count'] += 1
|
||||||
|
|
||||||
|
# 打印统计信息
|
||||||
|
print(f"\n共找到 {len(templates)} 个启用的模板\n")
|
||||||
|
|
||||||
|
templates_without_input = []
|
||||||
|
templates_without_output = []
|
||||||
|
templates_without_any = []
|
||||||
|
|
||||||
|
for template_id, stats in template_stats.items():
|
||||||
|
status = []
|
||||||
|
if stats['input_count'] == 0:
|
||||||
|
status.append("缺少输入字段")
|
||||||
|
templates_without_input.append(stats)
|
||||||
|
if stats['output_count'] == 0:
|
||||||
|
status.append("缺少输出字段")
|
||||||
|
templates_without_output.append(stats)
|
||||||
|
if stats['input_count'] == 0 and stats['output_count'] == 0:
|
||||||
|
templates_without_any.append(stats)
|
||||||
|
|
||||||
|
status_str = " | ".join(status) if status else "[OK] 正常"
|
||||||
|
print(f" {stats['template_name']} (ID: {stats['template_id']})")
|
||||||
|
print(f" 输入字段: {stats['input_count']} 个 | 输出字段: {stats['output_count']} 个 | {status_str}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'template_stats': template_stats,
|
||||||
|
'templates_without_input': templates_without_input,
|
||||||
|
'templates_without_output': templates_without_output,
|
||||||
|
'templates_without_any': templates_without_any
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def check_input_field_relations_detail(conn) -> Dict:
|
||||||
|
"""详细检查输入字段的关联情况"""
|
||||||
|
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("2. 详细检查输入字段的关联情况")
|
||||||
|
print("="*80)
|
||||||
|
|
||||||
|
# 获取所有启用的输入字段
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id, name, filed_code
|
||||||
|
FROM f_polic_field
|
||||||
|
WHERE tenant_id = %s AND field_type = 1 AND state = 1
|
||||||
|
ORDER BY name
|
||||||
|
""", (TENANT_ID,))
|
||||||
|
input_fields = cursor.fetchall()
|
||||||
|
|
||||||
|
# 获取每个输入字段关联的模板
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT
|
||||||
|
f.id AS field_id,
|
||||||
|
f.name AS field_name,
|
||||||
|
f.filed_code AS field_code,
|
||||||
|
fc.id AS template_id,
|
||||||
|
fc.name AS template_name
|
||||||
|
FROM f_polic_field f
|
||||||
|
INNER JOIN f_polic_file_field fff ON f.id = fff.filed_id AND f.tenant_id = fff.tenant_id
|
||||||
|
INNER JOIN f_polic_file_config fc ON fff.file_id = fc.id AND fff.tenant_id = fc.tenant_id
|
||||||
|
WHERE f.tenant_id = %s
|
||||||
|
AND f.field_type = 1
|
||||||
|
AND f.state = 1
|
||||||
|
AND fff.state = 1
|
||||||
|
AND fc.state = 1
|
||||||
|
ORDER BY f.name, fc.name
|
||||||
|
""", (TENANT_ID,))
|
||||||
|
input_field_relations = cursor.fetchall()
|
||||||
|
|
||||||
|
# 按字段分组
|
||||||
|
field_template_map = defaultdict(list)
|
||||||
|
for rel in input_field_relations:
|
||||||
|
field_template_map[rel['field_id']].append({
|
||||||
|
'template_id': rel['template_id'],
|
||||||
|
'template_name': rel['template_name']
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"\n共找到 {len(input_fields)} 个启用的输入字段\n")
|
||||||
|
|
||||||
|
fields_without_relations = []
|
||||||
|
fields_with_relations = []
|
||||||
|
|
||||||
|
for field in input_fields:
|
||||||
|
field_id = field['id']
|
||||||
|
templates = field_template_map.get(field_id, [])
|
||||||
|
|
||||||
|
if not templates:
|
||||||
|
fields_without_relations.append(field)
|
||||||
|
print(f" [WARN] {field['name']} ({field['filed_code']}) - 未关联任何模板")
|
||||||
|
else:
|
||||||
|
fields_with_relations.append({
|
||||||
|
'field': field,
|
||||||
|
'templates': templates
|
||||||
|
})
|
||||||
|
print(f" [OK] {field['name']} ({field['filed_code']}) - 关联了 {len(templates)} 个模板:")
|
||||||
|
for template in templates:
|
||||||
|
print(f" - {template['template_name']} (ID: {template['template_id']})")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'input_fields': input_fields,
|
||||||
|
'fields_without_relations': fields_without_relations,
|
||||||
|
'fields_with_relations': fields_with_relations
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def check_output_field_relations_detail(conn) -> Dict:
|
||||||
|
"""详细检查输出字段的关联情况"""
|
||||||
|
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("3. 详细检查输出字段的关联情况")
|
||||||
|
print("="*80)
|
||||||
|
|
||||||
|
# 获取所有启用的输出字段
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id, name, filed_code
|
||||||
|
FROM f_polic_field
|
||||||
|
WHERE tenant_id = %s AND field_type = 2 AND state = 1
|
||||||
|
ORDER BY name
|
||||||
|
""", (TENANT_ID,))
|
||||||
|
output_fields = cursor.fetchall()
|
||||||
|
|
||||||
|
# 获取每个输出字段关联的模板
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT
|
||||||
|
f.id AS field_id,
|
||||||
|
f.name AS field_name,
|
||||||
|
f.filed_code AS field_code,
|
||||||
|
fc.id AS template_id,
|
||||||
|
fc.name AS template_name
|
||||||
|
FROM f_polic_field f
|
||||||
|
INNER JOIN f_polic_file_field fff ON f.id = fff.filed_id AND f.tenant_id = fff.tenant_id
|
||||||
|
INNER JOIN f_polic_file_config fc ON fff.file_id = fc.id AND fff.tenant_id = fc.tenant_id
|
||||||
|
WHERE f.tenant_id = %s
|
||||||
|
AND f.field_type = 2
|
||||||
|
AND f.state = 1
|
||||||
|
AND fff.state = 1
|
||||||
|
AND fc.state = 1
|
||||||
|
ORDER BY f.name, fc.name
|
||||||
|
""", (TENANT_ID,))
|
||||||
|
output_field_relations = cursor.fetchall()
|
||||||
|
|
||||||
|
# 按字段分组
|
||||||
|
field_template_map = defaultdict(list)
|
||||||
|
for rel in output_field_relations:
|
||||||
|
field_template_map[rel['field_id']].append({
|
||||||
|
'template_id': rel['template_id'],
|
||||||
|
'template_name': rel['template_name']
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"\n共找到 {len(output_fields)} 个启用的输出字段\n")
|
||||||
|
|
||||||
|
fields_without_relations = []
|
||||||
|
fields_with_relations = []
|
||||||
|
|
||||||
|
for field in output_fields:
|
||||||
|
field_id = field['id']
|
||||||
|
templates = field_template_map.get(field_id, [])
|
||||||
|
|
||||||
|
if not templates:
|
||||||
|
fields_without_relations.append(field)
|
||||||
|
print(f" [WARN] {field['name']} ({field['filed_code']}) - 未关联任何模板")
|
||||||
|
else:
|
||||||
|
fields_with_relations.append({
|
||||||
|
'field': field,
|
||||||
|
'templates': templates
|
||||||
|
})
|
||||||
|
if len(templates) <= 5:
|
||||||
|
print(f" [OK] {field['name']} ({field['filed_code']}) - 关联了 {len(templates)} 个模板:")
|
||||||
|
for template in templates:
|
||||||
|
print(f" - {template['template_name']} (ID: {template['template_id']})")
|
||||||
|
else:
|
||||||
|
print(f" [OK] {field['name']} ({field['filed_code']}) - 关联了 {len(templates)} 个模板")
|
||||||
|
for template in templates[:3]:
|
||||||
|
print(f" - {template['template_name']} (ID: {template['template_id']})")
|
||||||
|
print(f" ... 还有 {len(templates) - 3} 个模板")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'output_fields': output_fields,
|
||||||
|
'fields_without_relations': fields_without_relations,
|
||||||
|
'fields_with_relations': fields_with_relations
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def check_invalid_relations(conn) -> Dict:
|
||||||
|
"""检查无效的关联关系"""
|
||||||
|
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("4. 检查无效的关联关系")
|
||||||
|
print("="*80)
|
||||||
|
|
||||||
|
# 检查关联到不存在的 file_id
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT fff.id, fff.file_id, fff.filed_id, fff.tenant_id
|
||||||
|
FROM f_polic_file_field fff
|
||||||
|
LEFT JOIN f_polic_file_config fc ON fff.file_id = fc.id AND fff.tenant_id = fc.tenant_id
|
||||||
|
WHERE fff.tenant_id = %s AND fc.id IS NULL
|
||||||
|
""", (TENANT_ID,))
|
||||||
|
invalid_file_relations = cursor.fetchall()
|
||||||
|
|
||||||
|
# 检查关联到不存在的 filed_id
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT fff.id, fff.file_id, fff.filed_id, fff.tenant_id
|
||||||
|
FROM f_polic_file_field fff
|
||||||
|
LEFT JOIN f_polic_field f ON fff.filed_id = f.id AND fff.tenant_id = f.tenant_id
|
||||||
|
WHERE fff.tenant_id = %s AND f.id IS NULL
|
||||||
|
""", (TENANT_ID,))
|
||||||
|
invalid_field_relations = cursor.fetchall()
|
||||||
|
|
||||||
|
print(f"\n关联到不存在的 file_id: {len(invalid_file_relations)} 条")
|
||||||
|
if invalid_file_relations:
|
||||||
|
print(" 详情:")
|
||||||
|
for rel in invalid_file_relations[:10]:
|
||||||
|
print(f" - 关联ID: {rel['id']}, file_id: {rel['file_id']}, filed_id: {rel['filed_id']}")
|
||||||
|
if len(invalid_file_relations) > 10:
|
||||||
|
print(f" ... 还有 {len(invalid_file_relations) - 10} 条")
|
||||||
|
else:
|
||||||
|
print(" [OK] 没有无效的 file_id 关联")
|
||||||
|
|
||||||
|
print(f"\n关联到不存在的 filed_id: {len(invalid_field_relations)} 条")
|
||||||
|
if invalid_field_relations:
|
||||||
|
print(" 详情:")
|
||||||
|
for rel in invalid_field_relations[:10]:
|
||||||
|
print(f" - 关联ID: {rel['id']}, file_id: {rel['file_id']}, filed_id: {rel['filed_id']}")
|
||||||
|
if len(invalid_field_relations) > 10:
|
||||||
|
print(f" ... 还有 {len(invalid_field_relations) - 10} 条")
|
||||||
|
else:
|
||||||
|
print(" [OK] 没有无效的 filed_id 关联")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'invalid_file_relations': invalid_file_relations,
|
||||||
|
'invalid_field_relations': invalid_field_relations
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_summary_statistics(conn) -> Dict:
|
||||||
|
"""获取汇总统计信息"""
|
||||||
|
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("5. 汇总统计信息")
|
||||||
|
print("="*80)
|
||||||
|
|
||||||
|
# 总关联数
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT COUNT(*) as total
|
||||||
|
FROM f_polic_file_field
|
||||||
|
WHERE tenant_id = %s AND state = 1
|
||||||
|
""", (TENANT_ID,))
|
||||||
|
total_relations = cursor.fetchone()['total']
|
||||||
|
|
||||||
|
# 输入字段关联数
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT COUNT(*) as total
|
||||||
|
FROM f_polic_file_field fff
|
||||||
|
INNER JOIN f_polic_field f ON fff.filed_id = f.id AND fff.tenant_id = f.tenant_id
|
||||||
|
WHERE fff.tenant_id = %s
|
||||||
|
AND fff.state = 1
|
||||||
|
AND f.state = 1
|
||||||
|
AND f.field_type = 1
|
||||||
|
""", (TENANT_ID,))
|
||||||
|
input_relations = cursor.fetchone()['total']
|
||||||
|
|
||||||
|
# 输出字段关联数
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT COUNT(*) as total
|
||||||
|
FROM f_polic_file_field fff
|
||||||
|
INNER JOIN f_polic_field f ON fff.filed_id = f.id AND fff.tenant_id = f.tenant_id
|
||||||
|
WHERE fff.tenant_id = %s
|
||||||
|
AND fff.state = 1
|
||||||
|
AND f.state = 1
|
||||||
|
AND f.field_type = 2
|
||||||
|
""", (TENANT_ID,))
|
||||||
|
output_relations = cursor.fetchone()['total']
|
||||||
|
|
||||||
|
# 关联的模板数
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT COUNT(DISTINCT file_id) as total
|
||||||
|
FROM f_polic_file_field
|
||||||
|
WHERE tenant_id = %s AND state = 1
|
||||||
|
""", (TENANT_ID,))
|
||||||
|
related_templates = cursor.fetchone()['total']
|
||||||
|
|
||||||
|
# 关联的输入字段数
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT COUNT(DISTINCT fff.filed_id) as total
|
||||||
|
FROM f_polic_file_field fff
|
||||||
|
INNER JOIN f_polic_field f ON fff.filed_id = f.id AND fff.tenant_id = f.tenant_id
|
||||||
|
WHERE fff.tenant_id = %s
|
||||||
|
AND fff.state = 1
|
||||||
|
AND f.state = 1
|
||||||
|
AND f.field_type = 1
|
||||||
|
""", (TENANT_ID,))
|
||||||
|
related_input_fields = cursor.fetchone()['total']
|
||||||
|
|
||||||
|
# 关联的输出字段数
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT COUNT(DISTINCT fff.filed_id) as total
|
||||||
|
FROM f_polic_file_field fff
|
||||||
|
INNER JOIN f_polic_field f ON fff.filed_id = f.id AND fff.tenant_id = f.tenant_id
|
||||||
|
WHERE fff.tenant_id = %s
|
||||||
|
AND fff.state = 1
|
||||||
|
AND f.state = 1
|
||||||
|
AND f.field_type = 2
|
||||||
|
""", (TENANT_ID,))
|
||||||
|
related_output_fields = cursor.fetchone()['total']
|
||||||
|
|
||||||
|
print(f"\n总关联数: {total_relations}")
|
||||||
|
print(f" 输入字段关联数: {input_relations}")
|
||||||
|
print(f" 输出字段关联数: {output_relations}")
|
||||||
|
print(f"\n关联的模板数: {related_templates}")
|
||||||
|
print(f"关联的输入字段数: {related_input_fields}")
|
||||||
|
print(f"关联的输出字段数: {related_output_fields}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'total_relations': total_relations,
|
||||||
|
'input_relations': input_relations,
|
||||||
|
'output_relations': output_relations,
|
||||||
|
'related_templates': related_templates,
|
||||||
|
'related_input_fields': related_input_fields,
|
||||||
|
'related_output_fields': related_output_fields
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
print("="*80)
|
||||||
|
print("全面检查 f_polic_file_field 表的关联关系")
|
||||||
|
print("="*80)
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = pymysql.connect(**DB_CONFIG)
|
||||||
|
print("[OK] 数据库连接成功\n")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] 数据库连接失败: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. 检查所有模板的字段关联情况
|
||||||
|
template_result = check_all_templates_field_relations(conn)
|
||||||
|
|
||||||
|
# 2. 详细检查输入字段的关联情况
|
||||||
|
input_result = check_input_field_relations_detail(conn)
|
||||||
|
|
||||||
|
# 3. 详细检查输出字段的关联情况
|
||||||
|
output_result = check_output_field_relations_detail(conn)
|
||||||
|
|
||||||
|
# 4. 检查无效的关联关系
|
||||||
|
invalid_result = check_invalid_relations(conn)
|
||||||
|
|
||||||
|
# 5. 获取汇总统计信息
|
||||||
|
stats = get_summary_statistics(conn)
|
||||||
|
|
||||||
|
# 总结
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("检查总结")
|
||||||
|
print("="*80)
|
||||||
|
|
||||||
|
issues = []
|
||||||
|
|
||||||
|
if len(template_result['templates_without_input']) > 0:
|
||||||
|
issues.append(f"[WARN] {len(template_result['templates_without_input'])} 个模板缺少输入字段关联")
|
||||||
|
|
||||||
|
if len(template_result['templates_without_output']) > 0:
|
||||||
|
issues.append(f"[WARN] {len(template_result['templates_without_output'])} 个模板缺少输出字段关联")
|
||||||
|
|
||||||
|
if len(template_result['templates_without_any']) > 0:
|
||||||
|
issues.append(f"[WARN] {len(template_result['templates_without_any'])} 个模板没有任何字段关联")
|
||||||
|
|
||||||
|
if len(input_result['fields_without_relations']) > 0:
|
||||||
|
issues.append(f"[WARN] {len(input_result['fields_without_relations'])} 个输入字段未关联任何模板")
|
||||||
|
|
||||||
|
if len(output_result['fields_without_relations']) > 0:
|
||||||
|
issues.append(f"[WARN] {len(output_result['fields_without_relations'])} 个输出字段未关联任何模板")
|
||||||
|
|
||||||
|
if len(invalid_result['invalid_file_relations']) > 0:
|
||||||
|
issues.append(f"[ERROR] {len(invalid_result['invalid_file_relations'])} 条无效的 file_id 关联")
|
||||||
|
|
||||||
|
if len(invalid_result['invalid_field_relations']) > 0:
|
||||||
|
issues.append(f"[ERROR] {len(invalid_result['invalid_field_relations'])} 条无效的 filed_id 关联")
|
||||||
|
|
||||||
|
if issues:
|
||||||
|
print("\n发现以下问题:\n")
|
||||||
|
for issue in issues:
|
||||||
|
print(f" {issue}")
|
||||||
|
else:
|
||||||
|
print("\n[OK] 未发现明显问题")
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n[ERROR] 检查过程中发生错误: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
print("\n数据库连接已关闭")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
||||||
234
link_all_templates_to_input_fields.py
Normal file
234
link_all_templates_to_input_fields.py
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
"""
|
||||||
|
将所有模板与两个输入字段关联
|
||||||
|
- 线索信息 (clue_info)
|
||||||
|
- 被核查人员工作基本情况线索 (target_basic_info_clue)
|
||||||
|
"""
|
||||||
|
import pymysql
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# 设置输出编码为UTF-8
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
import io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||||
|
|
||||||
|
# 数据库连接配置
|
||||||
|
DB_CONFIG = {
|
||||||
|
'host': os.getenv('DB_HOST', '152.136.177.240'),
|
||||||
|
'port': int(os.getenv('DB_PORT', 5012)),
|
||||||
|
'user': os.getenv('DB_USER', 'finyx'),
|
||||||
|
'password': os.getenv('DB_PASSWORD', '6QsGK6MpePZDE57Z'),
|
||||||
|
'database': os.getenv('DB_NAME', 'finyx'),
|
||||||
|
'charset': 'utf8mb4'
|
||||||
|
}
|
||||||
|
|
||||||
|
TENANT_ID = 615873064429507639
|
||||||
|
CREATED_BY = 655162080928945152
|
||||||
|
UPDATED_BY = 655162080928945152
|
||||||
|
|
||||||
|
|
||||||
|
def generate_id():
|
||||||
|
"""生成ID"""
|
||||||
|
timestamp = int(time.time() * 1000)
|
||||||
|
random_part = random.randint(100000, 999999)
|
||||||
|
return timestamp * 1000 + random_part
|
||||||
|
|
||||||
|
|
||||||
|
def get_input_field_ids(conn):
|
||||||
|
"""获取两个输入字段的ID"""
|
||||||
|
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||||
|
|
||||||
|
field_codes = ['clue_info', 'target_basic_info_clue']
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id, name, filed_code
|
||||||
|
FROM f_polic_field
|
||||||
|
WHERE tenant_id = %s
|
||||||
|
AND filed_code IN (%s, %s)
|
||||||
|
AND field_type = 1
|
||||||
|
AND state = 1
|
||||||
|
""", (TENANT_ID, field_codes[0], field_codes[1]))
|
||||||
|
|
||||||
|
fields = cursor.fetchall()
|
||||||
|
field_map = {field['filed_code']: field for field in fields}
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
for code in field_codes:
|
||||||
|
if code in field_map:
|
||||||
|
result[code] = field_map[code]
|
||||||
|
else:
|
||||||
|
print(f"[WARN] 未找到字段: {code}")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_templates(conn):
|
||||||
|
"""获取所有启用的模板"""
|
||||||
|
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id, name
|
||||||
|
FROM f_polic_file_config
|
||||||
|
WHERE tenant_id = %s AND state = 1
|
||||||
|
ORDER BY name
|
||||||
|
""", (TENANT_ID,))
|
||||||
|
|
||||||
|
return cursor.fetchall()
|
||||||
|
|
||||||
|
|
||||||
|
def get_existing_relations(conn, template_id, field_ids):
|
||||||
|
"""获取模板与字段的现有关联关系"""
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
if not field_ids:
|
||||||
|
return set()
|
||||||
|
|
||||||
|
placeholders = ','.join(['%s'] * len(field_ids))
|
||||||
|
cursor.execute(f"""
|
||||||
|
SELECT filed_id
|
||||||
|
FROM f_polic_file_field
|
||||||
|
WHERE tenant_id = %s
|
||||||
|
AND file_id = %s
|
||||||
|
AND filed_id IN ({placeholders})
|
||||||
|
AND state = 1
|
||||||
|
""", [TENANT_ID, template_id] + list(field_ids))
|
||||||
|
|
||||||
|
return {row[0] for row in cursor.fetchall()}
|
||||||
|
|
||||||
|
|
||||||
|
def create_relation(conn, template_id, field_id):
|
||||||
|
"""创建模板与字段的关联关系"""
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
current_time = datetime.now()
|
||||||
|
|
||||||
|
# 检查是否已存在
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id FROM f_polic_file_field
|
||||||
|
WHERE tenant_id = %s
|
||||||
|
AND file_id = %s
|
||||||
|
AND filed_id = %s
|
||||||
|
""", (TENANT_ID, template_id, field_id))
|
||||||
|
|
||||||
|
if cursor.fetchone():
|
||||||
|
return False # 已存在,不需要创建
|
||||||
|
|
||||||
|
# 创建新关联
|
||||||
|
relation_id = generate_id()
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO f_polic_file_field
|
||||||
|
(id, tenant_id, file_id, filed_id, created_time, created_by, updated_time, updated_by, state)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, 1)
|
||||||
|
""", (
|
||||||
|
relation_id,
|
||||||
|
TENANT_ID,
|
||||||
|
template_id,
|
||||||
|
field_id,
|
||||||
|
current_time,
|
||||||
|
CREATED_BY,
|
||||||
|
current_time,
|
||||||
|
UPDATED_BY
|
||||||
|
))
|
||||||
|
|
||||||
|
return True # 创建成功
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
print("="*80)
|
||||||
|
print("将所有模板与输入字段关联")
|
||||||
|
print("="*80)
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = pymysql.connect(**DB_CONFIG)
|
||||||
|
print("[OK] 数据库连接成功\n")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] 数据库连接失败: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. 获取输入字段ID
|
||||||
|
print("1. 获取输入字段ID...")
|
||||||
|
input_fields = get_input_field_ids(conn)
|
||||||
|
|
||||||
|
if len(input_fields) != 2:
|
||||||
|
print(f"[ERROR] 未找到所有输入字段,只找到: {list(input_fields.keys())}")
|
||||||
|
return
|
||||||
|
|
||||||
|
field_ids = [field['id'] for field in input_fields.values()]
|
||||||
|
print(f" 找到字段:")
|
||||||
|
for code, field in input_fields.items():
|
||||||
|
print(f" - {field['name']} ({code}): ID={field['id']}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# 2. 获取所有模板
|
||||||
|
print("2. 获取所有启用的模板...")
|
||||||
|
templates = get_all_templates(conn)
|
||||||
|
print(f" 找到 {len(templates)} 个模板\n")
|
||||||
|
|
||||||
|
# 3. 为每个模板创建关联关系
|
||||||
|
print("3. 创建关联关系...")
|
||||||
|
created_count = 0
|
||||||
|
existing_count = 0
|
||||||
|
error_count = 0
|
||||||
|
|
||||||
|
for template in templates:
|
||||||
|
template_id = template['id']
|
||||||
|
template_name = template['name']
|
||||||
|
|
||||||
|
# 获取现有关联
|
||||||
|
existing_relations = get_existing_relations(conn, template_id, field_ids)
|
||||||
|
|
||||||
|
# 为每个字段创建关联(如果不存在)
|
||||||
|
for field_code, field_info in input_fields.items():
|
||||||
|
field_id = field_info['id']
|
||||||
|
|
||||||
|
if field_id in existing_relations:
|
||||||
|
existing_count += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
if create_relation(conn, template_id, field_id):
|
||||||
|
created_count += 1
|
||||||
|
print(f" [OK] {template_name} <- {field_info['name']} ({field_code})")
|
||||||
|
else:
|
||||||
|
existing_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
error_count += 1
|
||||||
|
print(f" [ERROR] {template_name} <- {field_info['name']}: {e}")
|
||||||
|
|
||||||
|
# 提交事务
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
# 4. 统计结果
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("执行结果")
|
||||||
|
print("="*80)
|
||||||
|
print(f"模板总数: {len(templates)}")
|
||||||
|
print(f"字段总数: {len(input_fields)}")
|
||||||
|
print(f"预期关联数: {len(templates) * len(input_fields)}")
|
||||||
|
print(f"新创建关联: {created_count}")
|
||||||
|
print(f"已存在关联: {existing_count}")
|
||||||
|
print(f"错误数量: {error_count}")
|
||||||
|
print(f"实际关联数: {created_count + existing_count}")
|
||||||
|
|
||||||
|
if error_count == 0:
|
||||||
|
print("\n[OK] 所有关联关系已成功创建或已存在")
|
||||||
|
else:
|
||||||
|
print(f"\n[WARN] 有 {error_count} 个关联关系创建失败")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
conn.rollback()
|
||||||
|
print(f"\n[ERROR] 执行过程中发生错误: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
print("\n数据库连接已关闭")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
||||||
Binary file not shown.
@ -154,8 +154,23 @@ class DocumentService:
|
|||||||
|
|
||||||
# 扫描表格中的占位符
|
# 扫描表格中的占位符
|
||||||
for table in doc.tables:
|
for table in doc.tables:
|
||||||
|
try:
|
||||||
|
if not table.rows:
|
||||||
|
continue
|
||||||
for row in table.rows:
|
for row in table.rows:
|
||||||
for cell in row.cells:
|
try:
|
||||||
|
# 安全地访问 row.cells,避免 docx 库在处理异常表格结构时的 bug
|
||||||
|
if not hasattr(row, 'cells'):
|
||||||
|
continue
|
||||||
|
# 使用 try-except 包裹,防止 IndexError
|
||||||
|
try:
|
||||||
|
cells = row.cells
|
||||||
|
except (IndexError, AttributeError) as e:
|
||||||
|
print(f"[WARN] 无法访问表格行的单元格,跳过该行: {str(e)}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
for cell in cells:
|
||||||
|
try:
|
||||||
if hasattr(cell, 'paragraphs'):
|
if hasattr(cell, 'paragraphs'):
|
||||||
for paragraph in cell.paragraphs:
|
for paragraph in cell.paragraphs:
|
||||||
text = paragraph.text
|
text = paragraph.text
|
||||||
@ -164,6 +179,15 @@ class DocumentService:
|
|||||||
field_code = match.strip()
|
field_code = match.strip()
|
||||||
if field_code:
|
if field_code:
|
||||||
all_placeholders_in_template.add(field_code)
|
all_placeholders_in_template.add(field_code)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[WARN] 处理表格单元格时出错,跳过: {str(e)}")
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[WARN] 处理表格行时出错,跳过: {str(e)}")
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[WARN] 处理表格时出错,跳过该表格: {str(e)}")
|
||||||
|
continue
|
||||||
|
|
||||||
print(f"[DEBUG] 模板中发现 {len(all_placeholders_in_template)} 个不同的占位符: {sorted(all_placeholders_in_template)}")
|
print(f"[DEBUG] 模板中发现 {len(all_placeholders_in_template)} 个不同的占位符: {sorted(all_placeholders_in_template)}")
|
||||||
|
|
||||||
@ -379,8 +403,23 @@ class DocumentService:
|
|||||||
|
|
||||||
# 检查表格中的占位符
|
# 检查表格中的占位符
|
||||||
for table in doc.tables:
|
for table in doc.tables:
|
||||||
|
try:
|
||||||
|
if not table.rows:
|
||||||
|
continue
|
||||||
for row in table.rows:
|
for row in table.rows:
|
||||||
for cell in row.cells:
|
try:
|
||||||
|
# 安全地访问 row.cells,避免 docx 库在处理异常表格结构时的 bug
|
||||||
|
if not hasattr(row, 'cells'):
|
||||||
|
continue
|
||||||
|
# 使用 try-except 包裹,防止 IndexError
|
||||||
|
try:
|
||||||
|
cells = row.cells
|
||||||
|
except (IndexError, AttributeError) as e:
|
||||||
|
print(f"[WARN] 无法访问表格行的单元格,跳过该行: {str(e)}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
for cell in cells:
|
||||||
|
try:
|
||||||
if hasattr(cell, 'paragraphs'):
|
if hasattr(cell, 'paragraphs'):
|
||||||
for paragraph in cell.paragraphs:
|
for paragraph in cell.paragraphs:
|
||||||
text = paragraph.text
|
text = paragraph.text
|
||||||
@ -389,6 +428,15 @@ class DocumentService:
|
|||||||
field_code = match.strip()
|
field_code = match.strip()
|
||||||
if field_code:
|
if field_code:
|
||||||
remaining_placeholders.add(field_code)
|
remaining_placeholders.add(field_code)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[WARN] 处理表格单元格时出错,跳过: {str(e)}")
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[WARN] 处理表格行时出错,跳过: {str(e)}")
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[WARN] 处理表格时出错,跳过该表格: {str(e)}")
|
||||||
|
continue
|
||||||
|
|
||||||
# 输出统计信息
|
# 输出统计信息
|
||||||
print(f"[DEBUG] 占位符替换统计:")
|
print(f"[DEBUG] 占位符替换统计:")
|
||||||
|
|||||||
569
static/template_field_manager.html
Normal file
569
static/template_field_manager.html
Normal file
@ -0,0 +1,569 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>模板字段关联管理</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif;
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-selector {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-selector label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-selector select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fields-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 30px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-section {
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 20px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-section h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #333;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-section.output h2 {
|
||||||
|
border-bottom-color: #2196F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-count {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
font-weight: normal;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-list {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-item:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
border-color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-item.checked {
|
||||||
|
background: #e8f5e9;
|
||||||
|
border-color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-item input[type="checkbox"] {
|
||||||
|
margin-right: 10px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-code {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #45a049;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:disabled {
|
||||||
|
background: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.success {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.error {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background: #f0f7ff;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2196F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>模板字段关联管理</h1>
|
||||||
|
<p class="subtitle">维护模板与输入字段、输出字段的关联关系</p>
|
||||||
|
|
||||||
|
<div id="message" class="message"></div>
|
||||||
|
|
||||||
|
<div class="template-selector">
|
||||||
|
<label for="templateSelect">选择模板:</label>
|
||||||
|
<select id="templateSelect">
|
||||||
|
<option value="">请选择模板...</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="stats" class="stats" style="display: none;">
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-value" id="inputCount">0</div>
|
||||||
|
<div class="stat-label">已选输入字段</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-value" id="outputCount">0</div>
|
||||||
|
<div class="stat-label">已选输出字段</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-value" id="totalCount">0</div>
|
||||||
|
<div class="stat-label">总字段数</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fields-container" id="fieldsContainer" style="display: none;">
|
||||||
|
<div class="field-section input">
|
||||||
|
<h2>
|
||||||
|
输入字段
|
||||||
|
<span class="field-count" id="inputFieldCount">(0)</span>
|
||||||
|
</h2>
|
||||||
|
<div class="search-box">
|
||||||
|
<input type="text" id="inputSearch" placeholder="搜索输入字段...">
|
||||||
|
</div>
|
||||||
|
<div class="field-list" id="inputFieldsList"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-section output">
|
||||||
|
<h2>
|
||||||
|
输出字段
|
||||||
|
<span class="field-count" id="outputFieldCount">(0)</span>
|
||||||
|
</h2>
|
||||||
|
<div class="search-box">
|
||||||
|
<input type="text" id="outputSearch" placeholder="搜索输出字段...">
|
||||||
|
</div>
|
||||||
|
<div class="field-list" id="outputFieldsList"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions" id="actions" style="display: none;">
|
||||||
|
<button class="btn btn-secondary" onclick="resetSelection()">重置</button>
|
||||||
|
<button class="btn btn-primary" onclick="saveRelations()">保存关联关系</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="loading" class="loading">加载中...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let templates = [];
|
||||||
|
let inputFields = [];
|
||||||
|
let outputFields = [];
|
||||||
|
let relations = {};
|
||||||
|
let currentTemplateId = null;
|
||||||
|
let selectedInputFields = new Set();
|
||||||
|
let selectedOutputFields = new Set();
|
||||||
|
|
||||||
|
// 页面加载时初始化
|
||||||
|
window.onload = function() {
|
||||||
|
loadData();
|
||||||
|
setupSearch();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
async function loadData() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/template-field-relations');
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.isSuccess) {
|
||||||
|
templates = result.data.templates || [];
|
||||||
|
inputFields = result.data.input_fields || [];
|
||||||
|
outputFields = result.data.output_fields || [];
|
||||||
|
relations = result.data.relations || {};
|
||||||
|
|
||||||
|
populateTemplateSelect();
|
||||||
|
document.getElementById('loading').style.display = 'none';
|
||||||
|
} else {
|
||||||
|
showMessage('加载数据失败: ' + result.errorMsg, 'error');
|
||||||
|
document.getElementById('loading').style.display = 'none';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showMessage('加载数据失败: ' + error.message, 'error');
|
||||||
|
document.getElementById('loading').style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充模板选择框
|
||||||
|
function populateTemplateSelect() {
|
||||||
|
const select = document.getElementById('templateSelect');
|
||||||
|
select.innerHTML = '<option value="">请选择模板...</option>';
|
||||||
|
|
||||||
|
templates.forEach(template => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = template.id;
|
||||||
|
option.textContent = template.name;
|
||||||
|
select.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
select.onchange = function() {
|
||||||
|
const templateId = parseInt(this.value);
|
||||||
|
if (templateId) {
|
||||||
|
loadTemplateFields(templateId);
|
||||||
|
} else {
|
||||||
|
hideFields();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载模板字段
|
||||||
|
function loadTemplateFields(templateId) {
|
||||||
|
currentTemplateId = templateId;
|
||||||
|
const template = templates.find(t => t.id === templateId);
|
||||||
|
|
||||||
|
// 获取该模板的关联字段
|
||||||
|
const relatedFieldIds = new Set(relations[templateId] || []);
|
||||||
|
selectedInputFields = new Set();
|
||||||
|
selectedOutputFields = new Set();
|
||||||
|
|
||||||
|
inputFields.forEach(field => {
|
||||||
|
if (relatedFieldIds.has(field.id)) {
|
||||||
|
selectedInputFields.add(field.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
outputFields.forEach(field => {
|
||||||
|
if (relatedFieldIds.has(field.id)) {
|
||||||
|
selectedOutputFields.add(field.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
renderFields();
|
||||||
|
updateStats();
|
||||||
|
document.getElementById('fieldsContainer').style.display = 'grid';
|
||||||
|
document.getElementById('actions').style.display = 'flex';
|
||||||
|
document.getElementById('stats').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染字段列表
|
||||||
|
function renderFields() {
|
||||||
|
renderFieldList('inputFieldsList', inputFields, selectedInputFields, 'input');
|
||||||
|
renderFieldList('outputFieldsList', outputFields, selectedOutputFields, 'output');
|
||||||
|
|
||||||
|
document.getElementById('inputFieldCount').textContent = `(${inputFields.length})`;
|
||||||
|
document.getElementById('outputFieldCount').textContent = `(${outputFields.length})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染单个字段列表
|
||||||
|
function renderFieldList(containerId, fields, selectedSet, type) {
|
||||||
|
const container = document.getElementById(containerId);
|
||||||
|
const searchTerm = type === 'input'
|
||||||
|
? document.getElementById('inputSearch').value.toLowerCase()
|
||||||
|
: document.getElementById('outputSearch').value.toLowerCase();
|
||||||
|
|
||||||
|
const filteredFields = fields.filter(field => {
|
||||||
|
return field.name.toLowerCase().includes(searchTerm) ||
|
||||||
|
field.filed_code.toLowerCase().includes(searchTerm);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filteredFields.length === 0) {
|
||||||
|
container.innerHTML = '<div class="empty-state">没有找到匹配的字段</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = filteredFields.map(field => {
|
||||||
|
const isChecked = selectedSet.has(field.id);
|
||||||
|
return `
|
||||||
|
<div class="field-item ${isChecked ? 'checked' : ''}" onclick="toggleField(${field.id}, '${type}')">
|
||||||
|
<input type="checkbox" ${isChecked ? 'checked' : ''}
|
||||||
|
onclick="event.stopPropagation(); toggleField(${field.id}, '${type}')">
|
||||||
|
<div class="field-info">
|
||||||
|
<div class="field-name">${escapeHtml(field.name)}</div>
|
||||||
|
<div class="field-code">${escapeHtml(field.filed_code)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换字段选择
|
||||||
|
function toggleField(fieldId, type) {
|
||||||
|
if (type === 'input') {
|
||||||
|
if (selectedInputFields.has(fieldId)) {
|
||||||
|
selectedInputFields.delete(fieldId);
|
||||||
|
} else {
|
||||||
|
selectedInputFields.add(fieldId);
|
||||||
|
}
|
||||||
|
renderFieldList('inputFieldsList', inputFields, selectedInputFields, 'input');
|
||||||
|
} else {
|
||||||
|
if (selectedOutputFields.has(fieldId)) {
|
||||||
|
selectedOutputFields.delete(fieldId);
|
||||||
|
} else {
|
||||||
|
selectedOutputFields.add(fieldId);
|
||||||
|
}
|
||||||
|
renderFieldList('outputFieldsList', outputFields, selectedOutputFields, 'output');
|
||||||
|
}
|
||||||
|
updateStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新统计信息
|
||||||
|
function updateStats() {
|
||||||
|
document.getElementById('inputCount').textContent = selectedInputFields.size;
|
||||||
|
document.getElementById('outputCount').textContent = selectedOutputFields.size;
|
||||||
|
document.getElementById('totalCount').textContent = selectedInputFields.size + selectedOutputFields.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置搜索功能
|
||||||
|
function setupSearch() {
|
||||||
|
document.getElementById('inputSearch').oninput = function() {
|
||||||
|
renderFieldList('inputFieldsList', inputFields, selectedInputFields, 'input');
|
||||||
|
};
|
||||||
|
document.getElementById('outputSearch').oninput = function() {
|
||||||
|
renderFieldList('outputFieldsList', outputFields, selectedOutputFields, 'output');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置选择
|
||||||
|
function resetSelection() {
|
||||||
|
if (confirm('确定要重置当前选择吗?')) {
|
||||||
|
selectedInputFields.clear();
|
||||||
|
selectedOutputFields.clear();
|
||||||
|
renderFields();
|
||||||
|
updateStats();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存关联关系
|
||||||
|
async function saveRelations() {
|
||||||
|
if (!currentTemplateId) {
|
||||||
|
showMessage('请先选择模板', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const btn = event.target;
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = '保存中...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/template-field-relations', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
template_id: currentTemplateId,
|
||||||
|
input_field_ids: Array.from(selectedInputFields),
|
||||||
|
output_field_ids: Array.from(selectedOutputFields)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.isSuccess) {
|
||||||
|
showMessage('保存成功!', 'success');
|
||||||
|
// 更新本地关联关系
|
||||||
|
relations[currentTemplateId] = [
|
||||||
|
...selectedInputFields,
|
||||||
|
...selectedOutputFields
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
showMessage('保存失败: ' + result.errorMsg, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showMessage('保存失败: ' + error.message, 'error');
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = '保存关联关系';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示消息
|
||||||
|
function showMessage(message, type) {
|
||||||
|
const msgDiv = document.getElementById('message');
|
||||||
|
msgDiv.textContent = message;
|
||||||
|
msgDiv.className = 'message ' + type + ' show';
|
||||||
|
setTimeout(() => {
|
||||||
|
msgDiv.classList.remove('show');
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 隐藏字段区域
|
||||||
|
function hideFields() {
|
||||||
|
document.getElementById('fieldsContainer').style.display = 'none';
|
||||||
|
document.getElementById('actions').style.display = 'none';
|
||||||
|
document.getElementById('stats').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML转义
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
403
update_two_templates.py
Normal file
403
update_two_templates.py
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
"""
|
||||||
|
更新两个模板文件的信息并上传到MinIO
|
||||||
|
- 8.XXX初核情况报告.docx
|
||||||
|
- 8-1请示报告卡(初核报告结论) .docx
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import pymysql
|
||||||
|
from minio import Minio
|
||||||
|
from minio.error import S3Error
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from docx import Document
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
|
# 设置输出编码为UTF-8(Windows兼容)
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
import io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||||
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
||||||
|
|
||||||
|
# MinIO连接配置
|
||||||
|
MINIO_CONFIG = {
|
||||||
|
'endpoint': 'minio.datacubeworld.com:9000',
|
||||||
|
'access_key': 'JOLXFXny3avFSzB0uRA5',
|
||||||
|
'secret_key': 'G1BR8jStNfovkfH5ou39EmPl34E4l7dGrnd3Cz0I',
|
||||||
|
'secure': True
|
||||||
|
}
|
||||||
|
|
||||||
|
# 数据库连接配置
|
||||||
|
DB_CONFIG = {
|
||||||
|
'host': '152.136.177.240',
|
||||||
|
'port': 5012,
|
||||||
|
'user': 'finyx',
|
||||||
|
'password': '6QsGK6MpePZDE57Z',
|
||||||
|
'database': 'finyx',
|
||||||
|
'charset': 'utf8mb4'
|
||||||
|
}
|
||||||
|
|
||||||
|
# 固定值
|
||||||
|
TENANT_ID = 615873064429507639
|
||||||
|
CREATED_BY = 655162080928945152
|
||||||
|
UPDATED_BY = 655162080928945152
|
||||||
|
BUCKET_NAME = 'finyx'
|
||||||
|
|
||||||
|
# 要处理的模板文件
|
||||||
|
TEMPLATE_FILES = [
|
||||||
|
'template_finish/2-初核模版/3.初核结论/8.XXX初核情况报告.docx',
|
||||||
|
'template_finish/2-初核模版/3.初核结论/8-1请示报告卡(初核报告结论) .docx'
|
||||||
|
]
|
||||||
|
|
||||||
|
# 模板名称映射(用于查找数据库中的记录)
|
||||||
|
TEMPLATE_NAME_MAP = {
|
||||||
|
'8.XXX初核情况报告.docx': ['8.XXX初核情况报告', 'XXX初核情况报告'],
|
||||||
|
'8-1请示报告卡(初核报告结论) .docx': ['8-1请示报告卡(初核报告结论) ', '请示报告卡(初核报告结论)']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def generate_id():
|
||||||
|
"""生成ID"""
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
timestamp = int(time.time() * 1000)
|
||||||
|
random_part = random.randint(100000, 999999)
|
||||||
|
return timestamp * 1000 + random_part
|
||||||
|
|
||||||
|
|
||||||
|
def extract_placeholders_from_docx(file_path: str) -> List[str]:
|
||||||
|
"""
|
||||||
|
从docx文件中提取所有占位符
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: docx文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
占位符列表,格式: ['field_code1', 'field_code2', ...]
|
||||||
|
"""
|
||||||
|
placeholders = set()
|
||||||
|
pattern = r'\{\{([^}]+)\}\}' # 匹配 {{field_code}} 格式
|
||||||
|
|
||||||
|
try:
|
||||||
|
doc = Document(file_path)
|
||||||
|
|
||||||
|
# 从段落中提取占位符
|
||||||
|
for paragraph in doc.paragraphs:
|
||||||
|
text = paragraph.text
|
||||||
|
matches = re.findall(pattern, text)
|
||||||
|
for match in matches:
|
||||||
|
cleaned = match.strip()
|
||||||
|
# 过滤掉不完整的占位符(包含 { 或 } 的)
|
||||||
|
if cleaned and '{' not in cleaned and '}' not in cleaned:
|
||||||
|
placeholders.add(cleaned)
|
||||||
|
|
||||||
|
# 从表格中提取占位符
|
||||||
|
for table in doc.tables:
|
||||||
|
for row in table.rows:
|
||||||
|
for cell in row.cells:
|
||||||
|
for paragraph in cell.paragraphs:
|
||||||
|
text = paragraph.text
|
||||||
|
matches = re.findall(pattern, text)
|
||||||
|
for match in matches:
|
||||||
|
cleaned = match.strip()
|
||||||
|
# 过滤掉不完整的占位符(包含 { 或 } 的)
|
||||||
|
if cleaned and '{' not in cleaned and '}' not in cleaned:
|
||||||
|
placeholders.add(cleaned)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" 错误: 读取文件失败 - {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
return sorted(list(placeholders))
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_template_name(file_name: str) -> str:
|
||||||
|
"""
|
||||||
|
标准化模板名称(去掉扩展名、括号内容、数字前缀等)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_name: 文件名,如 "8.XXX初核情况报告.docx"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
标准化后的名称,如 "XXX初核情况报告"
|
||||||
|
"""
|
||||||
|
# 去掉扩展名
|
||||||
|
name = Path(file_name).stem
|
||||||
|
|
||||||
|
# 去掉括号内容
|
||||||
|
name = re.sub(r'[((].*?[))]', '', name)
|
||||||
|
name = name.strip()
|
||||||
|
|
||||||
|
# 去掉数字前缀和点号
|
||||||
|
name = re.sub(r'^\d+[\.\-]?\s*', '', name)
|
||||||
|
name = name.strip()
|
||||||
|
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def upload_to_minio(client: Minio, file_path: str, template_name: str) -> str:
|
||||||
|
"""上传文件到MinIO"""
|
||||||
|
try:
|
||||||
|
now = datetime.now()
|
||||||
|
object_name = f'{TENANT_ID}/TEMPLATE/{now.year}/{now.month:02d}/{template_name}'
|
||||||
|
|
||||||
|
client.fput_object(
|
||||||
|
BUCKET_NAME,
|
||||||
|
object_name,
|
||||||
|
file_path,
|
||||||
|
content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||||
|
)
|
||||||
|
|
||||||
|
return f"/{object_name}"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"上传到MinIO失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
def find_template_by_names(conn, possible_names: List[str]) -> Optional[Dict]:
|
||||||
|
"""根据可能的模板名称查找数据库中的模板"""
|
||||||
|
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 尝试每个可能的名称
|
||||||
|
for name in possible_names:
|
||||||
|
sql = """
|
||||||
|
SELECT id, name, file_path, parent_id, input_data
|
||||||
|
FROM f_polic_file_config
|
||||||
|
WHERE tenant_id = %s AND name = %s
|
||||||
|
"""
|
||||||
|
cursor.execute(sql, (TENANT_ID, name))
|
||||||
|
result = cursor.fetchone()
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
|
def get_template_code_from_input_data(input_data: Optional[str]) -> str:
|
||||||
|
"""从input_data中提取template_code,如果没有则生成一个"""
|
||||||
|
if input_data:
|
||||||
|
try:
|
||||||
|
data = json.loads(input_data)
|
||||||
|
return data.get('template_code', '')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def update_template(conn, template_file_path: str, template_info: Dict, minio_path: str):
|
||||||
|
"""
|
||||||
|
更新模板配置
|
||||||
|
|
||||||
|
Args:
|
||||||
|
conn: 数据库连接
|
||||||
|
template_file_path: 模板文件路径
|
||||||
|
template_info: 模板信息(包含占位符等)
|
||||||
|
minio_path: MinIO中的文件路径
|
||||||
|
"""
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
file_name = Path(template_file_path).name
|
||||||
|
possible_names = TEMPLATE_NAME_MAP.get(file_name, [normalize_template_name(file_name)])
|
||||||
|
|
||||||
|
# 查找现有记录
|
||||||
|
existing_template = find_template_by_names(conn, possible_names)
|
||||||
|
|
||||||
|
if not existing_template:
|
||||||
|
print(f" [WARN] 未找到数据库记录,将创建新记录")
|
||||||
|
template_id = generate_id()
|
||||||
|
template_name = possible_names[0] # 使用第一个名称
|
||||||
|
|
||||||
|
# 生成template_code
|
||||||
|
template_code = get_template_code_from_input_data(None)
|
||||||
|
if not template_code:
|
||||||
|
# 根据文件名生成template_code
|
||||||
|
if 'XXX初核情况报告' in file_name:
|
||||||
|
template_code = 'INVESTIGATION_REPORT'
|
||||||
|
elif '请示报告卡' in file_name and '初核报告结论' in file_name:
|
||||||
|
template_code = 'REPORT_CARD_CONCLUSION'
|
||||||
|
else:
|
||||||
|
template_code = f'TEMPLATE_{template_id % 1000000}'
|
||||||
|
|
||||||
|
# 准备input_data
|
||||||
|
input_data = json.dumps({
|
||||||
|
'template_code': template_code,
|
||||||
|
'business_type': 'INVESTIGATION',
|
||||||
|
'placeholders': template_info['placeholders']
|
||||||
|
}, ensure_ascii=False)
|
||||||
|
|
||||||
|
# 创建新记录
|
||||||
|
insert_sql = """
|
||||||
|
INSERT INTO f_polic_file_config
|
||||||
|
(id, tenant_id, parent_id, name, input_data, file_path, created_time, created_by, updated_time, updated_by, state)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, NOW(), %s, %s)
|
||||||
|
"""
|
||||||
|
cursor.execute(insert_sql, (
|
||||||
|
template_id,
|
||||||
|
TENANT_ID,
|
||||||
|
None, # parent_id 需要根据实际情况设置
|
||||||
|
template_name,
|
||||||
|
input_data,
|
||||||
|
minio_path,
|
||||||
|
CREATED_BY,
|
||||||
|
UPDATED_BY,
|
||||||
|
1 # state: 1表示启用
|
||||||
|
))
|
||||||
|
print(f" [OK] 创建模板配置: {template_name}, ID: {template_id}")
|
||||||
|
conn.commit()
|
||||||
|
return template_id
|
||||||
|
else:
|
||||||
|
# 更新现有记录
|
||||||
|
template_id = existing_template['id']
|
||||||
|
template_name = existing_template['name']
|
||||||
|
|
||||||
|
# 获取现有的template_code
|
||||||
|
existing_input_data = existing_template.get('input_data')
|
||||||
|
template_code = get_template_code_from_input_data(existing_input_data)
|
||||||
|
if not template_code:
|
||||||
|
# 根据文件名生成template_code
|
||||||
|
if 'XXX初核情况报告' in file_name:
|
||||||
|
template_code = 'INVESTIGATION_REPORT'
|
||||||
|
elif '请示报告卡' in file_name and '初核报告结论' in file_name:
|
||||||
|
template_code = 'REPORT_CARD_CONCLUSION'
|
||||||
|
else:
|
||||||
|
template_code = f'TEMPLATE_{template_id % 1000000}'
|
||||||
|
|
||||||
|
# 准备input_data
|
||||||
|
input_data = json.dumps({
|
||||||
|
'template_code': template_code,
|
||||||
|
'business_type': 'INVESTIGATION',
|
||||||
|
'placeholders': template_info['placeholders']
|
||||||
|
}, ensure_ascii=False)
|
||||||
|
|
||||||
|
update_sql = """
|
||||||
|
UPDATE f_polic_file_config
|
||||||
|
SET file_path = %s, input_data = %s, updated_time = NOW(), updated_by = %s, state = 1
|
||||||
|
WHERE id = %s AND tenant_id = %s
|
||||||
|
"""
|
||||||
|
cursor.execute(update_sql, (
|
||||||
|
minio_path,
|
||||||
|
input_data,
|
||||||
|
UPDATED_BY,
|
||||||
|
template_id,
|
||||||
|
TENANT_ID
|
||||||
|
))
|
||||||
|
print(f" [OK] 更新模板配置: {template_name}, ID: {template_id}")
|
||||||
|
print(f" 占位符数量: {len(template_info['placeholders'])}")
|
||||||
|
if template_info['placeholders']:
|
||||||
|
print(f" 占位符: {', '.join(template_info['placeholders'][:10])}{'...' if len(template_info['placeholders']) > 10 else ''}")
|
||||||
|
conn.commit()
|
||||||
|
return template_id
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
conn.rollback()
|
||||||
|
raise Exception(f"更新模板配置失败: {str(e)}")
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
print("=" * 80)
|
||||||
|
print("更新模板文件信息并上传到MinIO")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
# 连接数据库
|
||||||
|
try:
|
||||||
|
conn = pymysql.connect(**DB_CONFIG)
|
||||||
|
print("✓ 数据库连接成功")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ 数据库连接失败: {str(e)}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 创建MinIO客户端
|
||||||
|
try:
|
||||||
|
minio_client = Minio(
|
||||||
|
MINIO_CONFIG['endpoint'],
|
||||||
|
access_key=MINIO_CONFIG['access_key'],
|
||||||
|
secret_key=MINIO_CONFIG['secret_key'],
|
||||||
|
secure=MINIO_CONFIG['secure']
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查存储桶是否存在
|
||||||
|
found = minio_client.bucket_exists(BUCKET_NAME)
|
||||||
|
if not found:
|
||||||
|
print(f"✗ 存储桶 '{BUCKET_NAME}' 不存在")
|
||||||
|
conn.close()
|
||||||
|
return
|
||||||
|
print("✓ MinIO连接成功")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ MinIO连接失败: {str(e)}")
|
||||||
|
conn.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
# 处理每个模板文件
|
||||||
|
success_count = 0
|
||||||
|
failed_files = []
|
||||||
|
|
||||||
|
for template_file in TEMPLATE_FILES:
|
||||||
|
print(f"\n{'=' * 80}")
|
||||||
|
print(f"处理模板: {template_file}")
|
||||||
|
print(f"{'=' * 80}")
|
||||||
|
|
||||||
|
# 检查文件是否存在
|
||||||
|
if not os.path.exists(template_file):
|
||||||
|
print(f" [ERROR] 文件不存在: {template_file}")
|
||||||
|
failed_files.append(template_file)
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 提取占位符
|
||||||
|
print(f" 正在提取占位符...")
|
||||||
|
placeholders = extract_placeholders_from_docx(template_file)
|
||||||
|
print(f" ✓ 提取到 {len(placeholders)} 个占位符")
|
||||||
|
if placeholders:
|
||||||
|
print(f" 占位符: {', '.join(placeholders[:10])}{'...' if len(placeholders) > 10 else ''}")
|
||||||
|
|
||||||
|
# 准备模板信息
|
||||||
|
file_name = Path(template_file).name
|
||||||
|
template_info = {
|
||||||
|
'file_path': template_file,
|
||||||
|
'file_name': file_name,
|
||||||
|
'placeholders': placeholders
|
||||||
|
}
|
||||||
|
|
||||||
|
# 上传到MinIO
|
||||||
|
print(f" 正在上传到MinIO...")
|
||||||
|
minio_path = upload_to_minio(minio_client, template_file, file_name)
|
||||||
|
print(f" ✓ 上传成功: {minio_path}")
|
||||||
|
|
||||||
|
# 更新数据库
|
||||||
|
print(f" 正在更新数据库...")
|
||||||
|
template_id = update_template(conn, template_file, template_info, minio_path)
|
||||||
|
print(f" ✓ 更新成功,模板ID: {template_id}")
|
||||||
|
|
||||||
|
success_count += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" [ERROR] 处理失败: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
failed_files.append(template_file)
|
||||||
|
|
||||||
|
# 总结
|
||||||
|
print(f"\n{'=' * 80}")
|
||||||
|
print("处理完成")
|
||||||
|
print(f"{'=' * 80}")
|
||||||
|
print(f"成功: {success_count}/{len(TEMPLATE_FILES)}")
|
||||||
|
if failed_files:
|
||||||
|
print(f"失败的文件:")
|
||||||
|
for file in failed_files:
|
||||||
|
print(f" - {file}")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
||||||
147
检查结果报告.md
Normal file
147
检查结果报告.md
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
# f_polic_file_field 表关联关系检查报告
|
||||||
|
|
||||||
|
## 检查时间
|
||||||
|
2024年12月
|
||||||
|
|
||||||
|
## 检查结果摘要
|
||||||
|
|
||||||
|
### 总体统计
|
||||||
|
- **总关联数**: 114条
|
||||||
|
- 输入字段关联数: 4条
|
||||||
|
- 输出字段关联数: 110条
|
||||||
|
- **关联的模板数**: 19个(共27个启用模板)
|
||||||
|
- **关联的输入字段数**: 2个(共2个启用输入字段)
|
||||||
|
- **关联的输出字段数**: 34个(共72个启用输出字段)
|
||||||
|
|
||||||
|
### 发现的问题
|
||||||
|
|
||||||
|
#### 1. 输入字段关联缺失(严重问题)
|
||||||
|
- **23个模板缺少输入字段关联**
|
||||||
|
- 只有4个模板有输入字段关联:
|
||||||
|
- `8-1请示报告卡(初核报告结论)` - 关联了1个输入字段
|
||||||
|
- `谈话通知书` - 关联了1个输入字段
|
||||||
|
- `走读式谈话审批` - 关联了1个输入字段
|
||||||
|
- `走读式谈话流程` - 关联了1个输入字段
|
||||||
|
|
||||||
|
#### 2. 输出字段关联缺失
|
||||||
|
- **9个模板缺少输出字段关联**
|
||||||
|
- **38个输出字段未关联任何模板**
|
||||||
|
|
||||||
|
#### 3. 完全无关联的模板
|
||||||
|
- **9个模板没有任何字段关联**(既没有输入字段,也没有输出字段)
|
||||||
|
|
||||||
|
## 详细问题列表
|
||||||
|
|
||||||
|
### 缺少输入字段关联的模板(23个)
|
||||||
|
|
||||||
|
1. 1.初核请示
|
||||||
|
2. 1.请示报告卡(XXX)
|
||||||
|
3. 1.请示报告卡(初核谈话)
|
||||||
|
4. 1.谈话笔录
|
||||||
|
5. 2-初核模版
|
||||||
|
6. 2.初步核实审批表(XXX)
|
||||||
|
7. 2.谈话审批
|
||||||
|
8. 2.谈话询问对象情况摸底调查30问
|
||||||
|
9. 2谈话审批表
|
||||||
|
10. 3.初核结论
|
||||||
|
11. 3.被谈话人权利义务告知书
|
||||||
|
12. 3.谈话前安全风险评估表
|
||||||
|
13. 3.附件初核方案(XXX)
|
||||||
|
14. 4.点对点交接单
|
||||||
|
15. 4.谈话方案
|
||||||
|
16. 5.谈话后安全风险评估表
|
||||||
|
17. 5.陪送交接单(新)
|
||||||
|
18. 6.1保密承诺书(谈话对象使用-非中共党员用)
|
||||||
|
19. 7.办案人员-办案安全保密承诺书
|
||||||
|
20. 8.XXX初核情况报告
|
||||||
|
21. 谈话通知书第一联
|
||||||
|
22. 谈话通知书第三联
|
||||||
|
23. 谈话通知书第二联
|
||||||
|
|
||||||
|
### 缺少输出字段关联的模板(9个)
|
||||||
|
|
||||||
|
1. 1.初核请示
|
||||||
|
2. 1.请示报告卡(XXX)
|
||||||
|
3. 1.请示报告卡(初核谈话)
|
||||||
|
4. 2-初核模版
|
||||||
|
5. 2.谈话审批
|
||||||
|
6. 3.初核结论
|
||||||
|
7. 3.被谈话人权利义务告知书
|
||||||
|
8. 6.1保密承诺书(谈话对象使用-非中共党员用)
|
||||||
|
9. 8.XXX初核情况报告
|
||||||
|
|
||||||
|
### 完全无关联的模板(9个)
|
||||||
|
|
||||||
|
与"缺少输出字段关联的模板"相同
|
||||||
|
|
||||||
|
## 输入字段关联情况
|
||||||
|
|
||||||
|
### 已关联的输入字段(2个)
|
||||||
|
|
||||||
|
1. **线索信息 (clue_info)**
|
||||||
|
- 关联模板: `8-1请示报告卡(初核报告结论)`
|
||||||
|
|
||||||
|
2. **被核查人员工作基本情况线索 (target_basic_info_clue)**
|
||||||
|
- 关联模板:
|
||||||
|
- `谈话通知书`
|
||||||
|
- `走读式谈话审批`
|
||||||
|
- `走读式谈话流程`
|
||||||
|
|
||||||
|
## 输出字段关联情况
|
||||||
|
|
||||||
|
### 未关联任何模板的输出字段(38个)
|
||||||
|
|
||||||
|
1. 初步核实审批表填表人 (filler_name)
|
||||||
|
2. 拟谈话地点 (proposed_interview_location)
|
||||||
|
3. 拟谈话时间 (proposed_interview_time)
|
||||||
|
4. 纪委名称 (commission_name)
|
||||||
|
5. 补空人员 (backup_personnel)
|
||||||
|
6. 被核查人员交代问题程度 (target_confession_level)
|
||||||
|
7. 被核查人员住址 (target_address)
|
||||||
|
8. 被核查人员健康状况 (target_health_status)
|
||||||
|
9. 被核查人员其他情况 (target_other_situation)
|
||||||
|
10. 被核查人员减压后的表现 (target_behavior_after_relief)
|
||||||
|
11. 被核查人员出生年月日 (target_date_of_birth_full)
|
||||||
|
12. 被核查人员学历 (target_education)
|
||||||
|
13. 被核查人员工作履历 (target_work_history)
|
||||||
|
14. 被核查人员思想负担程度 (target_mental_burden_level)
|
||||||
|
15. 被核查人员性格特征 (target_personality)
|
||||||
|
16. 被核查人员承受能力 (target_tolerance)
|
||||||
|
17. 被核查人员本人认识和态度 (target_attitude)
|
||||||
|
18. 被核查人员此前被审查情况 (target_previous_investigation)
|
||||||
|
19. 被核查人员涉及其他问题的可能性 (target_other_issues_possibility)
|
||||||
|
20. 被核查人员涉及问题严重程度 (target_issue_severity)
|
||||||
|
21. 被核查人员社会负面事件 (target_negative_events)
|
||||||
|
22. 被核查人员职业 (target_occupation)
|
||||||
|
23. 被核查人员谈话中的表现 (target_behavior_during_interview)
|
||||||
|
24. 被核查人员问题严重程度 (target_issue_severity_level)
|
||||||
|
25. 被核查人员风险等级 (target_risk_level)
|
||||||
|
26. 被核查人基本情况 (target_basic_info)
|
||||||
|
27. 被核查人问题描述 (target_problem_description)
|
||||||
|
28. 记录人 (recorder)
|
||||||
|
29. 评估意见 (assessment_opinion)
|
||||||
|
30. 谈话事由 (interview_reason)
|
||||||
|
31. 谈话人 (interviewer)
|
||||||
|
32. 谈话人员-安全员 (interview_personnel_safety_officer)
|
||||||
|
33. 谈话人员-组长 (interview_personnel_leader)
|
||||||
|
34. 谈话人员-谈话人员 (interview_personnel)
|
||||||
|
35. 谈话前安全风险评估结果 (pre_interview_risk_assessment_result)
|
||||||
|
36. 谈话地点 (interview_location)
|
||||||
|
37. 谈话次数 (interview_count)
|
||||||
|
38. 风险等级 (risk_level)
|
||||||
|
|
||||||
|
## 建议
|
||||||
|
|
||||||
|
### 紧急修复
|
||||||
|
1. **恢复输入字段关联关系**:需要根据业务逻辑,为23个缺少输入字段关联的模板建立正确的关联关系
|
||||||
|
2. **检查历史数据**:查看是否有备份或历史记录可以帮助恢复关联关系
|
||||||
|
|
||||||
|
### 长期改进
|
||||||
|
1. **建立关联关系管理机制**:确保在更新数据时不会丢失关联关系
|
||||||
|
2. **定期检查**:定期运行检查脚本,及时发现关联关系问题
|
||||||
|
3. **文档化**:记录每个模板应该关联哪些输入和输出字段
|
||||||
|
|
||||||
|
## 检查脚本
|
||||||
|
|
||||||
|
使用 `check_file_field_relations_comprehensive.py` 脚本进行检查。
|
||||||
|
|
||||||
173
模板字段关联管理使用说明.md
Normal file
173
模板字段关联管理使用说明.md
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
# 模板字段关联管理使用说明
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
|
||||||
|
模板字段关联管理页面提供了一个可视化的界面,用于维护模板与输入字段、输出字段的关联关系。通过这个页面,您可以:
|
||||||
|
|
||||||
|
1. 查看所有启用的模板
|
||||||
|
2. 查看所有输入字段和输出字段
|
||||||
|
3. 为每个模板选择关联的输入字段和输出字段
|
||||||
|
4. 保存关联关系到数据库
|
||||||
|
|
||||||
|
## 访问方式
|
||||||
|
|
||||||
|
启动Flask服务后,访问以下URL:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://localhost:7500/template-field-manager
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用步骤
|
||||||
|
|
||||||
|
### 1. 选择模板
|
||||||
|
|
||||||
|
在页面顶部的下拉框中选择要管理的模板。选择后,页面会显示该模板当前关联的字段。
|
||||||
|
|
||||||
|
### 2. 查看字段
|
||||||
|
|
||||||
|
页面分为两个区域:
|
||||||
|
- **左侧**:输入字段列表(field_type=1)
|
||||||
|
- **右侧**:输出字段列表(field_type=2)
|
||||||
|
|
||||||
|
每个字段显示:
|
||||||
|
- 字段名称(中文)
|
||||||
|
- 字段编码(field_code)
|
||||||
|
|
||||||
|
### 3. 选择关联字段
|
||||||
|
|
||||||
|
- 点击字段项或复选框来勾选/取消勾选字段
|
||||||
|
- 已选中的字段会高亮显示(绿色背景)
|
||||||
|
- 可以使用搜索框快速查找字段
|
||||||
|
|
||||||
|
### 4. 查看统计信息
|
||||||
|
|
||||||
|
页面顶部显示当前选择的统计信息:
|
||||||
|
- 已选输入字段数量
|
||||||
|
- 已选输出字段数量
|
||||||
|
- 总字段数
|
||||||
|
|
||||||
|
### 5. 保存关联关系
|
||||||
|
|
||||||
|
点击"保存关联关系"按钮,系统会:
|
||||||
|
1. 删除该模板的所有现有关联关系
|
||||||
|
2. 创建新的关联关系(基于当前选择)
|
||||||
|
3. 显示保存结果
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
### 搜索功能
|
||||||
|
|
||||||
|
- 在输入字段或输出字段区域,可以使用搜索框过滤字段
|
||||||
|
- 支持按字段名称或字段编码搜索
|
||||||
|
- 实时过滤,无需点击按钮
|
||||||
|
|
||||||
|
### 重置功能
|
||||||
|
|
||||||
|
- 点击"重置"按钮可以清空当前模板的所有选择
|
||||||
|
- 需要确认操作
|
||||||
|
|
||||||
|
### 数据验证
|
||||||
|
|
||||||
|
- 系统会自动验证模板和字段是否存在
|
||||||
|
- 保存时会检查数据完整性
|
||||||
|
- 错误信息会显示在页面顶部
|
||||||
|
|
||||||
|
## API接口
|
||||||
|
|
||||||
|
### 获取关联关系
|
||||||
|
|
||||||
|
**GET** `/api/template-field-relations`
|
||||||
|
|
||||||
|
返回所有模板、字段和关联关系数据。
|
||||||
|
|
||||||
|
**响应格式:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"templates": [...],
|
||||||
|
"input_fields": [...],
|
||||||
|
"output_fields": [...],
|
||||||
|
"relations": {
|
||||||
|
"template_id": [field_id1, field_id2, ...]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isSuccess": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 保存关联关系
|
||||||
|
|
||||||
|
**POST** `/api/template-field-relations`
|
||||||
|
|
||||||
|
保存指定模板的字段关联关系。
|
||||||
|
|
||||||
|
**请求体:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"template_id": 123,
|
||||||
|
"input_field_ids": [1, 2, 3],
|
||||||
|
"output_field_ids": [4, 5, 6]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应格式:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"template_id": 123,
|
||||||
|
"input_field_count": 3,
|
||||||
|
"output_field_count": 3,
|
||||||
|
"total_field_count": 6
|
||||||
|
},
|
||||||
|
"msg": "保存成功",
|
||||||
|
"isSuccess": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **数据安全**:保存操作会删除该模板的所有现有关联关系,然后创建新的关联。请确保选择正确后再保存。
|
||||||
|
|
||||||
|
2. **字段状态**:只有状态为启用(state=1)的模板和字段才会显示在页面上。
|
||||||
|
|
||||||
|
3. **关联关系**:关联关系存储在 `f_polic_file_field` 表中,包含以下字段:
|
||||||
|
- `file_id`:模板ID
|
||||||
|
- `filed_id`:字段ID
|
||||||
|
- `tenant_id`:租户ID
|
||||||
|
- `state`:状态(1=启用)
|
||||||
|
|
||||||
|
4. **批量操作**:目前不支持批量操作多个模板,需要逐个模板进行管理。
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 页面无法加载
|
||||||
|
|
||||||
|
- 检查Flask服务是否正常运行
|
||||||
|
- 检查端口号是否正确(默认7500)
|
||||||
|
- 查看浏览器控制台是否有错误信息
|
||||||
|
|
||||||
|
### 保存失败
|
||||||
|
|
||||||
|
- 检查网络连接
|
||||||
|
- 查看页面显示的错误信息
|
||||||
|
- 检查数据库连接是否正常
|
||||||
|
- 确认模板和字段ID是否有效
|
||||||
|
|
||||||
|
### 字段不显示
|
||||||
|
|
||||||
|
- 确认字段状态为启用(state=1)
|
||||||
|
- 检查字段类型是否正确(1=输入字段,2=输出字段)
|
||||||
|
- 刷新页面重新加载数据
|
||||||
|
|
||||||
|
## 技术实现
|
||||||
|
|
||||||
|
- **前端**:纯HTML + CSS + JavaScript(无依赖)
|
||||||
|
- **后端**:Flask + PyMySQL
|
||||||
|
- **数据库**:MySQL
|
||||||
|
- **表结构**:
|
||||||
|
- `f_polic_file_config`:模板配置表
|
||||||
|
- `f_polic_field`:字段定义表
|
||||||
|
- `f_polic_file_field`:关联关系表
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user