diff --git a/app.py b/app.py index 7aab919..ad10173 100644 --- a/app.py +++ b/app.py @@ -835,6 +835,180 @@ def get_document_by_task(): 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__': # 确保static目录存在 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}/template-field-manager") print(f"Swagger API文档: http://localhost:{port}/api-docs") app.run(host='0.0.0.0', port=port, debug=debug) diff --git a/check_file_field_relations_comprehensive.py b/check_file_field_relations_comprehensive.py new file mode 100644 index 0000000..78d4c75 --- /dev/null +++ b/check_file_field_relations_comprehensive.py @@ -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() + diff --git a/link_all_templates_to_input_fields.py b/link_all_templates_to_input_fields.py new file mode 100644 index 0000000..8795d1b --- /dev/null +++ b/link_all_templates_to_input_fields.py @@ -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() + diff --git a/services/__pycache__/document_service.cpython-312.pyc b/services/__pycache__/document_service.cpython-312.pyc index f875437..b77a15a 100644 Binary files a/services/__pycache__/document_service.cpython-312.pyc and b/services/__pycache__/document_service.cpython-312.pyc differ diff --git a/services/document_service.py b/services/document_service.py index 0f4111c..2234573 100644 --- a/services/document_service.py +++ b/services/document_service.py @@ -154,16 +154,40 @@ class DocumentService: # 扫描表格中的占位符 for table in doc.tables: - for row in table.rows: - for cell in row.cells: - if hasattr(cell, 'paragraphs'): - for paragraph in cell.paragraphs: - text = paragraph.text - matches = placeholder_pattern.findall(text) - for match in matches: - field_code = match.strip() - if field_code: - all_placeholders_in_template.add(field_code) + try: + if not table.rows: + continue + for row in table.rows: + 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'): + for paragraph in cell.paragraphs: + text = paragraph.text + matches = placeholder_pattern.findall(text) + for match in matches: + field_code = match.strip() + if 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)}") @@ -379,16 +403,40 @@ class DocumentService: # 检查表格中的占位符 for table in doc.tables: - for row in table.rows: - for cell in row.cells: - if hasattr(cell, 'paragraphs'): - for paragraph in cell.paragraphs: - text = paragraph.text - matches = placeholder_pattern.findall(text) - for match in matches: - field_code = match.strip() - if field_code: - remaining_placeholders.add(field_code) + try: + if not table.rows: + continue + for row in table.rows: + 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'): + for paragraph in cell.paragraphs: + text = paragraph.text + matches = placeholder_pattern.findall(text) + for match in matches: + field_code = match.strip() + if 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] 占位符替换统计:") diff --git a/static/template_field_manager.html b/static/template_field_manager.html new file mode 100644 index 0000000..7184248 --- /dev/null +++ b/static/template_field_manager.html @@ -0,0 +1,569 @@ + + + + + + 模板字段关联管理 + + + +
+

模板字段关联管理

+

维护模板与输入字段、输出字段的关联关系

+ +
+ +
+ + +
+ + + + + + + +
加载中...
+
+ + + + + diff --git a/template_finish/2-初核模版/3.初核结论/~$1请示报告卡(初核报告结论) .docx b/template_finish/2-初核模版/2.谈话审批/走读式谈话审批/~$2谈话审批表.docx similarity index 100% rename from template_finish/2-初核模版/3.初核结论/~$1请示报告卡(初核报告结论) .docx rename to template_finish/2-初核模版/2.谈话审批/走读式谈话审批/~$2谈话审批表.docx diff --git a/template_finish/2-初核模版/3.初核结论/8.XXX初核情况报告.docx b/template_finish/2-初核模版/3.初核结论/8.XXX初核情况报告.docx index 9462a1f..f88dafd 100644 Binary files a/template_finish/2-初核模版/3.初核结论/8.XXX初核情况报告.docx and b/template_finish/2-初核模版/3.初核结论/8.XXX初核情况报告.docx differ diff --git a/template_finish/2-初核模版/3.初核结论/~$XXX初核情况报告.docx b/template_finish/2-初核模版/3.初核结论/~$XXX初核情况报告.docx deleted file mode 100644 index 8efa2ad..0000000 Binary files a/template_finish/2-初核模版/3.初核结论/~$XXX初核情况报告.docx and /dev/null differ diff --git a/update_two_templates.py b/update_two_templates.py new file mode 100644 index 0000000..a3cec1e --- /dev/null +++ b/update_two_templates.py @@ -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() + diff --git a/检查结果报告.md b/检查结果报告.md new file mode 100644 index 0000000..0cafef8 --- /dev/null +++ b/检查结果报告.md @@ -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` 脚本进行检查。 + diff --git a/模板字段关联管理使用说明.md b/模板字段关联管理使用说明.md new file mode 100644 index 0000000..62b0c9d --- /dev/null +++ b/模板字段关联管理使用说明.md @@ -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`:关联关系表 +