diff --git a/app.py b/app.py index 447f995..7aab919 100644 --- a/app.py +++ b/app.py @@ -706,7 +706,6 @@ def generate_document(): return error_response(3001, f"文档生成失败: {str(e)}") -<<<<<<< HEAD @app.route('/fPolicTask/getDocument', methods=['POST']) def get_document_by_task(): """ @@ -810,7 +809,8 @@ def get_document_by_task(): result_file_list.append({ 'fileId': file_id, 'fileName': generated_file_name, # 使用生成的文档名 - 'filePath': result['filePath'] + 'filePath': result['filePath'], # MinIO相对路径 + 'downloadUrl': result.get('downloadUrl') # MinIO预签名下载URL(完整链接) }) except Exception as e: @@ -835,8 +835,6 @@ def get_document_by_task(): return error_response(3001, f"文档生成失败: {str(e)}") -======= ->>>>>>> parent of 4897c96 (添加通过taskId获取文档的接口,支持文件列表查询和参数验证,增强错误处理能力。同时,优化文档生成逻辑,确保生成的文档名称和路径的准确性。) if __name__ == '__main__': # 确保static目录存在 os.makedirs('static', exist_ok=True) diff --git a/check_and_fix_duplicates.py b/check_and_fix_duplicates.py new file mode 100644 index 0000000..ebc7f18 --- /dev/null +++ b/check_and_fix_duplicates.py @@ -0,0 +1,117 @@ +""" +检查并修复重复记录 +""" +import pymysql + +DB_CONFIG = { + 'host': '152.136.177.240', + 'port': 5012, + 'user': 'finyx', + 'password': '6QsGK6MpePZDE57Z', + 'database': 'finyx', + 'charset': 'utf8mb4' +} + +TENANT_ID = 615873064429507639 +UPDATED_BY = 655162080928945152 + +conn = pymysql.connect(**DB_CONFIG) +cursor = conn.cursor(pymysql.cursors.DictCursor) + +try: + # 检查"1.初核请示"下的所有记录 + cursor.execute(""" + SELECT id, name, file_path, parent_id + FROM f_polic_file_config + WHERE tenant_id = %s AND parent_id = %s + ORDER BY id + """, (TENANT_ID, 1765431558933731)) # 1.初核请示 + + results = cursor.fetchall() + print(f"'1.初核请示'下有 {len(results)} 条记录:\n") + for r in results: + print(f"ID: {r['id']}, name: {r['name']}, file_path: {r['file_path']}") + + # 检查"1请示报告卡"的记录 + request_cards = [r for r in results if r['name'] == '1请示报告卡'] + if len(request_cards) > 1: + print(f"\n发现 {len(request_cards)} 个重复的'1请示报告卡'记录") + # 保留file_path正确的那个 + correct_one = None + for r in request_cards: + if r['file_path'] and '1.请示报告卡(XXX)' in r['file_path']: + correct_one = r + break + + if correct_one: + # 删除其他的 + for r in request_cards: + if r['id'] != correct_one['id']: + # 删除关联关系 + cursor.execute(""" + DELETE FROM f_polic_file_field + WHERE tenant_id = %s AND file_id = %s + """, (TENANT_ID, r['id'])) + # 删除模板记录 + cursor.execute(""" + DELETE FROM f_polic_file_config + WHERE tenant_id = %s AND id = %s + """, (TENANT_ID, r['id'])) + print(f"[DELETE] 删除重复记录: ID {r['id']}, file_path: {r['file_path']}") + + # 检查"走读式谈话审批"下是否有"1请示报告卡" + cursor.execute(""" + SELECT id, name, file_path, parent_id + FROM f_polic_file_config + WHERE tenant_id = %s AND parent_id = %s AND name = %s + """, (TENANT_ID, 1765273962700431, '1请示报告卡')) # 走读式谈话审批 + + result = cursor.fetchone() + if not result: + print("\n[WARN] '走读式谈话审批'下缺少'1请示报告卡'记录") + # 创建记录 + import time + import random + timestamp = int(time.time() * 1000) + random_part = random.randint(100000, 999999) + new_id = timestamp * 1000 + random_part + + 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, ( + new_id, + TENANT_ID, + 1765273962700431, # 走读式谈话审批 + '1请示报告卡', + None, + '/615873064429507639/TEMPLATE/2025/12/1.请示报告卡(初核谈话).docx', + 655162080928945152, + 655162080928945152, + 1 + )) + print(f"[CREATE] 在'走读式谈话审批'下创建'1请示报告卡'记录 (ID: {new_id})") + else: + # 检查file_path是否正确 + if result['file_path'] and '1.请示报告卡(初核谈话)' not in result['file_path']: + cursor.execute(""" + UPDATE f_polic_file_config + SET file_path = %s, updated_time = NOW(), updated_by = %s + WHERE tenant_id = %s AND id = %s + """, ('/615873064429507639/TEMPLATE/2025/12/1.请示报告卡(初核谈话).docx', UPDATED_BY, TENANT_ID, result['id'])) + print(f"[UPDATE] 修复'走读式谈话审批'下'1请示报告卡'的file_path") + + conn.commit() + print("\n[OK] 修复完成") + +except Exception as e: + conn.rollback() + print(f"[ERROR] 修复失败: {e}") + import traceback + traceback.print_exc() +finally: + cursor.close() + conn.close() + diff --git a/check_confidentiality_commitment.py b/check_confidentiality_commitment.py new file mode 100644 index 0000000..fbf9910 --- /dev/null +++ b/check_confidentiality_commitment.py @@ -0,0 +1,36 @@ +"""查询保密承诺书相关的模板记录""" +import pymysql + +DB_CONFIG = { + 'host': '152.136.177.240', + 'port': 5012, + 'user': 'finyx', + 'password': '6QsGK6MpePZDE57Z', + 'database': 'finyx', + 'charset': 'utf8mb4' +} + +TENANT_ID = 615873064429507639 + +conn = pymysql.connect(**DB_CONFIG) +cursor = conn.cursor(pymysql.cursors.DictCursor) + +cursor.execute(""" + SELECT id, name, file_path, parent_id + FROM f_polic_file_config + WHERE tenant_id = %s AND name LIKE %s + ORDER BY name +""", (TENANT_ID, '%保密承诺书%')) + +results = cursor.fetchall() +print(f"找到 {len(results)} 条记录:\n") +for r in results: + print(f"ID: {r['id']}") + print(f"名称: {r['name']}") + print(f"文件路径: {r['file_path']}") + print(f"父节点ID: {r['parent_id']}") + print() + +cursor.close() +conn.close() + diff --git a/cleanup_duplicate_templates.py b/cleanup_duplicate_templates.py new file mode 100644 index 0000000..80c6bbd --- /dev/null +++ b/cleanup_duplicate_templates.py @@ -0,0 +1,361 @@ +""" +清理 f_polic_file_config 表中的重复和无效数据 +确保文档模板结构和 template_finish/ 文件夹对应 +""" +import os +import re +import json +import pymysql +from pathlib import Path +from typing import Dict, List, Set, Optional +from collections import defaultdict + +# 数据库连接配置 +DB_CONFIG = { + 'host': '152.136.177.240', + 'port': 5012, + 'user': 'finyx', + 'password': '6QsGK6MpePZDE57Z', + 'database': 'finyx', + 'charset': 'utf8mb4' +} + +TENANT_ID = 615873064429507639 +UPDATED_BY = 655162080928945152 +TEMPLATE_BASE_DIR = 'template_finish' + + +def normalize_template_name(name: str) -> str: + """ + 标准化模板名称(去掉扩展名、括号内容、数字前缀等) + + Args: + name: 文件名或模板名称 + + Returns: + 标准化后的名称 + """ + # 去掉扩展名 + name = Path(name).stem if '.' in name else name + + # 去掉括号内容 + name = re.sub(r'[((].*?[))]', '', name) + name = name.strip() + + # 去掉数字前缀和点号 + name = re.sub(r'^\d+[\.\-]?\s*', '', name) + name = name.strip() + + return name + + +def scan_template_files(base_dir: str) -> Dict[str, Dict]: + """ + 扫描模板文件夹,获取所有有效的模板文件 + + Returns: + 字典,key为标准化名称,value为模板信息列表(可能有多个同名文件) + """ + base_path = Path(base_dir) + if not base_path.exists(): + print(f"错误: 目录不存在 - {base_dir}") + return {} + + templates = defaultdict(list) + + print("=" * 80) + print("扫描模板文件...") + print("=" * 80) + + for docx_file in sorted(base_path.rglob("*.docx")): + # 跳过临时文件 + if docx_file.name.startswith("~$"): + continue + + relative_path = docx_file.relative_to(base_path) + file_name = docx_file.name + normalized_name = normalize_template_name(file_name) + + templates[normalized_name].append({ + 'file_path': str(docx_file), + 'relative_path': str(relative_path), + 'file_name': file_name, + 'normalized_name': normalized_name + }) + + print(f"总共扫描到 {sum(len(v) for v in templates.values())} 个模板文件") + print(f"唯一模板名称: {len(templates)} 个") + + return dict(templates) + + +def get_all_templates_from_db(conn) -> Dict[str, List[Dict]]: + """ + 从数据库获取所有模板,按标准化名称分组 + + Returns: + 字典,key为标准化名称,value为模板记录列表 + """ + cursor = conn.cursor(pymysql.cursors.DictCursor) + + sql = """ + SELECT id, name, file_path, parent_id, state, input_data, created_time, updated_time + FROM f_polic_file_config + WHERE tenant_id = %s + ORDER BY created_time DESC + """ + cursor.execute(sql, (TENANT_ID,)) + templates = cursor.fetchall() + + result = defaultdict(list) + for template in templates: + normalized_name = normalize_template_name(template['name']) + result[normalized_name].append({ + 'id': template['id'], + 'name': template['name'], + 'normalized_name': normalized_name, + 'file_path': template['file_path'], + 'parent_id': template['parent_id'], + 'state': template['state'], + 'input_data': template['input_data'], + 'created_time': template['created_time'], + 'updated_time': template['updated_time'] + }) + + cursor.close() + return dict(result) + + +def find_duplicates(db_templates: Dict[str, List[Dict]]) -> Dict[str, List[Dict]]: + """ + 找出重复的模板(同一标准化名称有多个记录) + + Returns: + 字典,key为标准化名称,value为重复的模板记录列表 + """ + duplicates = {} + for normalized_name, templates in db_templates.items(): + if len(templates) > 1: + duplicates[normalized_name] = templates + return duplicates + + +def select_best_template(templates: List[Dict], valid_template_files: List[Dict]) -> Optional[Dict]: + """ + 从多个重复的模板中选择最好的一个(保留最新的、有效的) + + Args: + templates: 数据库中的模板记录列表 + valid_template_files: 有效的模板文件列表 + + Returns: + 应该保留的模板记录,或None + """ + if not templates: + return None + + # 优先选择:state=1 且 file_path 有效的 + enabled_templates = [t for t in templates if t.get('state') == 1] + + if enabled_templates: + # 如果有多个启用的,选择最新的 + enabled_templates.sort(key=lambda x: x.get('updated_time') or x.get('created_time'), reverse=True) + return enabled_templates[0] + + # 如果没有启用的,选择最新的 + templates.sort(key=lambda x: x.get('updated_time') or x.get('created_time'), reverse=True) + return templates[0] + + +def delete_template_and_relations(conn, template_id: int): + """ + 删除模板及其关联关系 + + Args: + conn: 数据库连接 + template_id: 模板ID + """ + cursor = conn.cursor() + + try: + # 删除字段关联 + delete_relations_sql = """ + DELETE FROM f_polic_file_field + WHERE tenant_id = %s AND file_id = %s + """ + cursor.execute(delete_relations_sql, (TENANT_ID, template_id)) + relations_deleted = cursor.rowcount + + # 删除模板配置 + delete_template_sql = """ + DELETE FROM f_polic_file_config + WHERE tenant_id = %s AND id = %s + """ + cursor.execute(delete_template_sql, (TENANT_ID, template_id)) + template_deleted = cursor.rowcount + + conn.commit() + return relations_deleted, template_deleted + + except Exception as e: + conn.rollback() + raise Exception(f"删除模板失败: {str(e)}") + finally: + cursor.close() + + +def mark_invalid_templates(conn, valid_template_names: Set[str]): + """ + 标记无效的模板(不在template_finish文件夹中的模板) + + Args: + conn: 数据库连接 + valid_template_names: 有效的模板名称集合(标准化后的) + """ + cursor = conn.cursor() + + try: + # 获取所有模板 + sql = """ + SELECT id, name FROM f_polic_file_config + WHERE tenant_id = %s + """ + cursor.execute(sql, (TENANT_ID,)) + all_templates = cursor.fetchall() + + invalid_count = 0 + for template in all_templates: + template_id = template[0] + template_name = template[1] + normalized_name = normalize_template_name(template_name) + + # 检查是否在有效模板列表中 + if normalized_name not in valid_template_names: + # 标记为未启用 + update_sql = """ + UPDATE f_polic_file_config + SET state = 0, updated_time = NOW(), updated_by = %s + WHERE id = %s AND tenant_id = %s + """ + cursor.execute(update_sql, (UPDATED_BY, template_id, TENANT_ID)) + invalid_count += 1 + print(f" [WARN] 标记无效模板: {template_name} (ID: {template_id})") + + conn.commit() + print(f"\n总共标记 {invalid_count} 个无效模板") + + except Exception as e: + conn.rollback() + raise Exception(f"标记无效模板失败: {str(e)}") + finally: + cursor.close() + + +def main(): + """主函数""" + print("=" * 80) + print("清理重复和无效的模板数据") + print("=" * 80) + print() + + try: + # 连接数据库 + print("1. 连接数据库...") + conn = pymysql.connect(**DB_CONFIG) + print("[OK] 数据库连接成功\n") + + # 扫描模板文件 + print("2. 扫描模板文件...") + valid_templates = scan_template_files(TEMPLATE_BASE_DIR) + valid_template_names = set(valid_templates.keys()) + print(f"[OK] 找到 {len(valid_template_names)} 个有效模板名称\n") + + # 获取数据库中的模板 + print("3. 获取数据库中的模板...") + db_templates = get_all_templates_from_db(conn) + print(f"[OK] 数据库中有 {sum(len(v) for v in db_templates.values())} 个模板记录") + print(f"[OK] 唯一模板名称: {len(db_templates)} 个\n") + + # 找出重复的模板 + print("4. 查找重复的模板...") + duplicates = find_duplicates(db_templates) + print(f"[OK] 找到 {len(duplicates)} 个重复的模板名称\n") + + # 处理重复模板 + print("5. 处理重复模板...") + print("=" * 80) + + total_deleted = 0 + total_relations_deleted = 0 + + for normalized_name, templates in duplicates.items(): + print(f"\n处理重复模板: {normalized_name}") + print(f" 重复记录数: {len(templates)}") + + # 获取对应的有效模板文件 + valid_files = valid_templates.get(normalized_name, []) + + # 选择要保留的模板 + keep_template = select_best_template(templates, valid_files) + + if keep_template: + print(f" [KEEP] 保留模板: {keep_template['name']} (ID: {keep_template['id']})") + + # 删除其他重复的模板 + for template in templates: + if template['id'] != keep_template['id']: + print(f" [DELETE] 删除重复模板: {template['name']} (ID: {template['id']})") + relations_deleted, template_deleted = delete_template_and_relations(conn, template['id']) + total_relations_deleted += relations_deleted + total_deleted += template_deleted + else: + print(f" [WARN] 无法确定要保留的模板,跳过") + + print(f"\n[OK] 删除重复模板: {total_deleted} 个") + print(f"[OK] 删除关联关系: {total_relations_deleted} 条\n") + + # 标记无效模板 + print("6. 标记无效模板...") + mark_invalid_templates(conn, valid_template_names) + + # 统计最终结果 + print("\n7. 统计最终结果...") + final_templates = get_all_templates_from_db(conn) + enabled_count = sum(1 for templates in final_templates.values() + for t in templates if t.get('state') == 1) + disabled_count = sum(1 for templates in final_templates.values() + for t in templates if t.get('state') != 1) + + print(f"[OK] 最终模板总数: {sum(len(v) for v in final_templates.values())}") + print(f"[OK] 启用模板数: {enabled_count}") + print(f"[OK] 禁用模板数: {disabled_count}") + print(f"[OK] 唯一模板名称: {len(final_templates)}") + + # 打印最终模板列表 + print("\n8. 最终模板列表(启用的):") + print("=" * 80) + for normalized_name, templates in sorted(final_templates.items()): + enabled = [t for t in templates if t.get('state') == 1] + if enabled: + for template in enabled: + print(f" - {template['name']} (ID: {template['id']})") + + print("\n" + "=" * 80) + print("清理完成!") + print("=" * 80) + + except Exception as e: + print(f"\n[ERROR] 发生错误: {e}") + import traceback + traceback.print_exc() + if 'conn' in locals(): + conn.rollback() + finally: + if 'conn' in locals(): + conn.close() + print("\n数据库连接已关闭") + + +if __name__ == '__main__': + main() + diff --git a/finalize_template_hierarchy.py b/finalize_template_hierarchy.py new file mode 100644 index 0000000..9cfc1c6 --- /dev/null +++ b/finalize_template_hierarchy.py @@ -0,0 +1,158 @@ +""" +最终完善模板层级结构 +修复文件路径错误和重复问题 +""" +import pymysql +import time +import random +from pathlib import Path + +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 + +def generate_id(): + timestamp = int(time.time() * 1000) + random_part = random.randint(100000, 999999) + return timestamp * 1000 + random_part + +conn = pymysql.connect(**DB_CONFIG) +cursor = conn.cursor(pymysql.cursors.DictCursor) + +try: + # 检查"1请示报告卡"的记录 + # 根据目录结构,应该有两个不同的文件: + # 1. "1.初核请示"下的"1.请示报告卡(XXX).docx" + # 2. "走读式谈话审批"下的"1.请示报告卡(初核谈话).docx" + cursor.execute(""" + SELECT id, name, file_path, parent_id + FROM f_polic_file_config + WHERE tenant_id = %s AND name = %s + ORDER BY id + """, (TENANT_ID, '1请示报告卡')) + + results = cursor.fetchall() + + # 检查是否在"1.初核请示"下有记录 + in_initial_request = any(r['parent_id'] == 1765431558933731 for r in results) + # 检查是否在"走读式谈话审批"下有记录 + in_interview_approval = any(r['parent_id'] == 1765273962700431 for r in results) + + if not in_initial_request: + # 需要在"1.初核请示"下创建记录 + new_id = generate_id() + 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, ( + new_id, + TENANT_ID, + 1765431558933731, # 1.初核请示 + '1请示报告卡', + None, + '/615873064429507639/TEMPLATE/2025/12/1.请示报告卡(XXX).docx', + CREATED_BY, + CREATED_BY, + 1 + )) + print(f"[CREATE] 在'1.初核请示'下创建'1请示报告卡'记录 (ID: {new_id})") + + if not in_interview_approval: + # 需要在"走读式谈话审批"下创建记录 + new_id = generate_id() + 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, ( + new_id, + TENANT_ID, + 1765273962700431, # 走读式谈话审批 + '1请示报告卡', + None, + '/615873064429507639/TEMPLATE/2025/12/1.请示报告卡(初核谈话).docx', + CREATED_BY, + CREATED_BY, + 1 + )) + print(f"[CREATE] 在'走读式谈话审批'下创建'1请示报告卡'记录 (ID: {new_id})") + + # 更新现有记录的文件路径 + for result in results: + if result['parent_id'] == 1765431558933731: # 1.初核请示 + correct_path = '/615873064429507639/TEMPLATE/2025/12/1.请示报告卡(XXX).docx' + elif result['parent_id'] == 1765273962700431: # 走读式谈话审批 + correct_path = '/615873064429507639/TEMPLATE/2025/12/1.请示报告卡(初核谈话).docx' + else: + continue + + if result['file_path'] != correct_path: + cursor.execute(""" + UPDATE f_polic_file_config + SET file_path = %s, updated_time = NOW(), updated_by = %s + WHERE tenant_id = %s AND id = %s + """, (correct_path, UPDATED_BY, TENANT_ID, result['id'])) + print(f"[UPDATE] 修复'1请示报告卡'的文件路径 (ID: {result['id']}): {result['file_path']} -> {correct_path}") + + # 检查重复的"XXX初核情况报告" + cursor.execute(""" + SELECT id, name, file_path, parent_id + FROM f_polic_file_config + WHERE tenant_id = %s AND name LIKE %s + ORDER BY id + """, (TENANT_ID, '%XXX初核情况报告%')) + + results = cursor.fetchall() + if len(results) > 1: + # 保留最新的,删除旧的 + # 或者根据file_path判断哪个是正确的 + # 根据目录结构,应该是"8.XXX初核情况报告.docx" + correct_name = 'XXX初核情况报告' + correct_path = '/615873064429507639/TEMPLATE/2025/12/8.XXX初核情况报告.docx' + + for r in results: + if r['name'] == '8.XXX初核情况报告': + # 这个应该删除(名称带数字前缀) + cursor.execute(""" + DELETE FROM f_polic_file_field + WHERE tenant_id = %s AND file_id = %s + """, (TENANT_ID, r['id'])) + cursor.execute(""" + DELETE FROM f_polic_file_config + WHERE tenant_id = %s AND id = %s + """, (TENANT_ID, r['id'])) + print(f"[DELETE] 删除重复记录: {r['name']} (ID: {r['id']})") + elif r['name'] == 'XXX初核情况报告': + # 更新这个记录的文件路径 + if r['file_path'] != correct_path: + cursor.execute(""" + UPDATE f_polic_file_config + SET file_path = %s, updated_time = NOW(), updated_by = %s + WHERE tenant_id = %s AND id = %s + """, (correct_path, UPDATED_BY, TENANT_ID, r['id'])) + print(f"[UPDATE] 更新'XXX初核情况报告'的文件路径: {r['file_path']} -> {correct_path}") + + conn.commit() + print("\n[OK] 修复完成") + +except Exception as e: + conn.rollback() + print(f"[ERROR] 修复失败: {e}") + import traceback + traceback.print_exc() +finally: + cursor.close() + conn.close() + diff --git a/fix_duplicate_request_report_card.py b/fix_duplicate_request_report_card.py new file mode 100644 index 0000000..7a67daf --- /dev/null +++ b/fix_duplicate_request_report_card.py @@ -0,0 +1,131 @@ +""" +修复重复的"1请示报告卡"记录 +确保每个文件在正确的位置只有一个记录 +""" +import pymysql + +DB_CONFIG = { + 'host': '152.136.177.240', + 'port': 5012, + 'user': 'finyx', + 'password': '6QsGK6MpePZDE57Z', + 'database': 'finyx', + 'charset': 'utf8mb4' +} + +TENANT_ID = 615873064429507639 +UPDATED_BY = 655162080928945152 + +conn = pymysql.connect(**DB_CONFIG) +cursor = conn.cursor(pymysql.cursors.DictCursor) + +try: + # 查找所有"1请示报告卡"记录 + cursor.execute(""" + SELECT id, name, file_path, parent_id + FROM f_polic_file_config + WHERE tenant_id = %s AND name = %s + ORDER BY id + """, (TENANT_ID, '1请示报告卡')) + + results = cursor.fetchall() + print(f"找到 {len(results)} 条'1请示报告卡'记录:\n") + + # 根据file_path和parent_id判断哪些是正确的 + correct_records = [] + for r in results: + print(f"ID: {r['id']}, file_path: {r['file_path']}, parent_id: {r['parent_id']}") + + # 判断是否正确 + if r['parent_id'] == 1765431558933731: # 1.初核请示 + if '1.请示报告卡(XXX)' in (r['file_path'] or ''): + correct_records.append(r) + elif r['parent_id'] == 1765273962700431: # 走读式谈话审批 + if '1.请示报告卡(初核谈话)' in (r['file_path'] or ''): + correct_records.append(r) + + print(f"\n正确的记录数: {len(correct_records)}") + + # 删除不正确的记录 + for r in results: + if r not in correct_records: + # 先删除关联关系 + cursor.execute(""" + DELETE FROM f_polic_file_field + WHERE tenant_id = %s AND file_id = %s + """, (TENANT_ID, r['id'])) + # 删除模板记录 + cursor.execute(""" + DELETE FROM f_polic_file_config + WHERE tenant_id = %s AND id = %s + """, (TENANT_ID, r['id'])) + print(f"[DELETE] 删除不正确的记录: ID {r['id']}, file_path: {r['file_path']}, parent_id: {r['parent_id']}") + + # 确保两个位置都有正确的记录 + has_initial_request = any(r['parent_id'] == 1765431558933731 for r in correct_records) + has_interview_approval = any(r['parent_id'] == 1765273962700431 for r in correct_records) + + if not has_initial_request: + # 创建"1.初核请示"下的记录 + import time + import random + timestamp = int(time.time() * 1000) + random_part = random.randint(100000, 999999) + new_id = timestamp * 1000 + random_part + + 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, ( + new_id, + TENANT_ID, + 1765431558933731, # 1.初核请示 + '1请示报告卡', + None, + '/615873064429507639/TEMPLATE/2025/12/1.请示报告卡(XXX).docx', + 655162080928945152, + 655162080928945152, + 1 + )) + print(f"[CREATE] 在'1.初核请示'下创建'1请示报告卡'记录 (ID: {new_id})") + + if not has_interview_approval: + # 创建"走读式谈话审批"下的记录 + import time + import random + timestamp = int(time.time() * 1000) + random_part = random.randint(100000, 999999) + new_id = timestamp * 1000 + random_part + + 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, ( + new_id, + TENANT_ID, + 1765273962700431, # 走读式谈话审批 + '1请示报告卡', + None, + '/615873064429507639/TEMPLATE/2025/12/1.请示报告卡(初核谈话).docx', + 655162080928945152, + 655162080928945152, + 1 + )) + print(f"[CREATE] 在'走读式谈话审批'下创建'1请示报告卡'记录 (ID: {new_id})") + + conn.commit() + print("\n[OK] 修复完成") + +except Exception as e: + conn.rollback() + print(f"[ERROR] 修复失败: {e}") + import traceback + traceback.print_exc() +finally: + cursor.close() + conn.close() + diff --git a/fix_remaining_hierarchy_issues.py b/fix_remaining_hierarchy_issues.py new file mode 100644 index 0000000..df0137e --- /dev/null +++ b/fix_remaining_hierarchy_issues.py @@ -0,0 +1,61 @@ +""" +修复剩余的层级结构问题 +""" +import pymysql + +DB_CONFIG = { + 'host': '152.136.177.240', + 'port': 5012, + 'user': 'finyx', + 'password': '6QsGK6MpePZDE57Z', + 'database': 'finyx', + 'charset': 'utf8mb4' +} + +TENANT_ID = 615873064429507639 +UPDATED_BY = 655162080928945152 + +conn = pymysql.connect(**DB_CONFIG) +cursor = conn.cursor() + +try: + # 1. 修复"2保密承诺书"的parent_id(应该在"走读式谈话流程"下) + # "走读式谈话流程"的ID是 1765273962716807 + cursor.execute(""" + UPDATE f_polic_file_config + SET parent_id = %s, updated_time = NOW(), updated_by = %s + WHERE tenant_id = %s AND id = %s + """, (1765273962716807, UPDATED_BY, TENANT_ID, 1765425919729046)) + print(f"[UPDATE] 更新'2保密承诺书'的parent_id: {cursor.rowcount} 条") + + # 2. 检查"8.XXX初核情况报告"的位置(应该在"3.初核结论"下,而不是"走读式谈话流程"下) + # "3.初核结论"的ID是 1765431559135346 + # 先查找"8.XXX初核情况报告"的ID + cursor.execute(""" + SELECT id, name, parent_id + FROM f_polic_file_config + WHERE tenant_id = %s AND name LIKE %s + """, (TENANT_ID, '%XXX初核情况报告%')) + result = cursor.fetchone() + if result: + file_id, file_name, current_parent = result + if current_parent != 1765431559135346: + cursor.execute(""" + UPDATE f_polic_file_config + SET parent_id = %s, updated_time = NOW(), updated_by = %s + WHERE tenant_id = %s AND id = %s + """, (1765431559135346, UPDATED_BY, TENANT_ID, file_id)) + print(f"[UPDATE] 更新'{file_name}'的parent_id: {cursor.rowcount} 条") + + conn.commit() + print("\n[OK] 修复完成") + +except Exception as e: + conn.rollback() + print(f"[ERROR] 修复失败: {e}") + import traceback + traceback.print_exc() +finally: + cursor.close() + conn.close() + diff --git a/fix_template_names.py b/fix_template_names.py new file mode 100644 index 0000000..e8574a0 --- /dev/null +++ b/fix_template_names.py @@ -0,0 +1,234 @@ +""" +检查并修复 f_polic_file_config 表中模板名称与文件名的对应关系 +确保 name 字段与模板文档名称(去掉扩展名)完全一致 +""" +import os +import sys +import pymysql +from pathlib import Path +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') + +# 数据库连接配置 +DB_CONFIG = { + 'host': '152.136.177.240', + 'port': 5012, + 'user': 'finyx', + 'password': '6QsGK6MpePZDE57Z', + 'database': 'finyx', + 'charset': 'utf8mb4' +} + +TENANT_ID = 615873064429507639 +UPDATED_BY = 655162080928945152 +TEMPLATE_BASE_DIR = 'template_finish' + + +def scan_template_files(base_dir: str) -> Dict[str, str]: + """ + 扫描模板文件夹,获取所有模板文件信息 + + Returns: + 字典,key为MinIO路径(用于匹配),value为文件名(不含扩展名) + """ + base_path = Path(base_dir) + if not base_path.exists(): + print(f"错误: 目录不存在 - {base_dir}") + return {} + + templates = {} + + print("=" * 80) + print("扫描模板文件...") + print("=" * 80) + + for docx_file in sorted(base_path.rglob("*.docx")): + # 跳过临时文件 + if docx_file.name.startswith("~$"): + continue + + # 获取文件名(不含扩展名) + file_name_without_ext = docx_file.stem + + # 构建MinIO路径(用于匹配数据库中的file_path) + from datetime import datetime + now = datetime.now() + minio_path = f'/615873064429507639/TEMPLATE/{now.year}/{now.month:02d}/{docx_file.name}' + + templates[minio_path] = { + 'file_name': docx_file.name, + 'name_without_ext': file_name_without_ext, + 'relative_path': str(docx_file.relative_to(base_path)) + } + + print(f"找到 {len(templates)} 个模板文件\n") + return templates + + +def get_db_templates(conn) -> Dict[str, Dict]: + """ + 获取数据库中所有模板记录 + + Returns: + 字典,key为file_path,value为模板信息 + """ + cursor = conn.cursor(pymysql.cursors.DictCursor) + + sql = """ + SELECT id, name, file_path, parent_id + FROM f_polic_file_config + WHERE tenant_id = %s AND file_path IS NOT NULL + """ + cursor.execute(sql, (TENANT_ID,)) + templates = cursor.fetchall() + + result = {} + for template in templates: + if template['file_path']: + result[template['file_path']] = { + 'id': template['id'], + 'name': template['name'], + 'file_path': template['file_path'], + 'parent_id': template['parent_id'] + } + + cursor.close() + return result + + +def update_template_name(conn, template_id: int, new_name: str, old_name: str): + """ + 更新模板名称 + """ + cursor = conn.cursor() + + try: + update_sql = """ + UPDATE f_polic_file_config + SET name = %s, updated_time = NOW(), updated_by = %s + WHERE id = %s AND tenant_id = %s + """ + cursor.execute(update_sql, (new_name, UPDATED_BY, template_id, TENANT_ID)) + conn.commit() + print(f" [UPDATE] ID: {template_id}") + print(f" 旧名称: {old_name}") + print(f" 新名称: {new_name}") + return True + except Exception as e: + conn.rollback() + print(f" [ERROR] 更新失败: {str(e)}") + return False + finally: + cursor.close() + + +def match_file_path(file_path: str, db_paths: List[str]) -> Optional[str]: + """ + 匹配文件路径(可能日期不同) + + Args: + file_path: 当前构建的MinIO路径 + db_paths: 数据库中的所有路径列表 + + Returns: + 匹配的数据库路径,如果找到的话 + """ + # 提取文件名 + file_name = Path(file_path).name + + # 在数据库路径中查找相同文件名的路径 + for db_path in db_paths: + if Path(db_path).name == file_name: + return db_path + + return None + + +def main(): + """主函数""" + print("=" * 80) + print("检查并修复模板名称") + print("=" * 80) + print() + + try: + # 连接数据库 + print("1. 连接数据库...") + conn = pymysql.connect(**DB_CONFIG) + print("[OK] 数据库连接成功\n") + + # 扫描模板文件 + print("2. 扫描模板文件...") + file_templates = scan_template_files(TEMPLATE_BASE_DIR) + + # 获取数据库模板 + print("3. 获取数据库模板...") + db_templates = get_db_templates(conn) + print(f"[OK] 找到 {len(db_templates)} 个数据库模板\n") + + # 检查并更新 + print("4. 检查并更新模板名称...") + print("=" * 80) + + updated_count = 0 + not_found_count = 0 + matched_count = 0 + + # 遍历文件模板 + for file_path, file_info in file_templates.items(): + file_name = file_info['file_name'] + expected_name = file_info['name_without_ext'] + + # 尝试直接匹配 + db_template = db_templates.get(file_path) + + # 如果直接匹配失败,尝试通过文件名匹配 + if not db_template: + matched_path = match_file_path(file_path, list(db_templates.keys())) + if matched_path: + db_template = db_templates[matched_path] + + if db_template: + matched_count += 1 + current_name = db_template['name'] + + # 检查名称是否一致 + if current_name != expected_name: + print(f"\n文件: {file_name}") + if update_template_name(conn, db_template['id'], expected_name, current_name): + updated_count += 1 + else: + print(f" [OK] {file_name} - 名称已正确") + else: + not_found_count += 1 + print(f" [WARN] 未找到: {file_name}") + + print("\n" + "=" * 80) + print("检查完成") + print("=" * 80) + print(f"总文件数: {len(file_templates)}") + print(f"匹配成功: {matched_count}") + print(f"更新数量: {updated_count}") + print(f"未找到: {not_found_count}") + print("=" * 80) + + except Exception as e: + print(f"\n[ERROR] 发生错误: {e}") + import traceback + traceback.print_exc() + if 'conn' in locals(): + conn.rollback() + finally: + if 'conn' in locals(): + conn.close() + print("\n数据库连接已关闭") + + +if __name__ == '__main__': + main() + diff --git a/process_confidentiality_commitment_non_party.py b/process_confidentiality_commitment_non_party.py new file mode 100644 index 0000000..18f6827 --- /dev/null +++ b/process_confidentiality_commitment_non_party.py @@ -0,0 +1,372 @@ +""" +处理"6.1保密承诺书(谈话对象使用-非中共党员用).docx" +- 解析占位符 +- 上传到MinIO +- 更新数据库 +""" +import os +import sys +import re +import json +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, Tuple + +# 设置输出编码为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_FILE = 'template_finish/2-初核模版/2.谈话审批/走读式谈话流程/6.1保密承诺书(谈话对象使用-非中共党员用).docx' +PARENT_ID = 1765273962716807 # 走读式谈话流程的ID +TEMPLATE_NAME = '6.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: + placeholders.add(match.strip()) + + # 从表格中提取占位符 + 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: + placeholders.add(match.strip()) + + except Exception as e: + print(f" 错误: 读取文件失败 - {str(e)}") + return [] + + return sorted(list(placeholders)) + + +def upload_to_minio(file_path: str, minio_client: Minio) -> str: + """ + 上传文件到MinIO + + Args: + file_path: 本地文件路径 + minio_client: MinIO客户端实例 + + Returns: + MinIO中的相对路径 + """ + try: + # 检查存储桶是否存在 + found = minio_client.bucket_exists(BUCKET_NAME) + if not found: + raise Exception(f"存储桶 '{BUCKET_NAME}' 不存在,请先创建") + + # 生成MinIO对象路径(使用当前日期) + now = datetime.now() + file_name = Path(file_path).name + object_name = f'{TENANT_ID}/TEMPLATE/{now.year}/{now.month:02d}/{file_name}' + + # 上传文件 + minio_client.fput_object( + BUCKET_NAME, + object_name, + file_path, + content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document' + ) + + # 返回相对路径(以/开头) + return f"/{object_name}" + + except S3Error as e: + raise Exception(f"MinIO错误: {e}") + except Exception as e: + raise Exception(f"上传文件时发生错误: {e}") + + +def get_db_fields(conn) -> Dict[str, Dict]: + """ + 获取数据库中所有字段(field_type=2的输出字段) + + Returns: + 字典,key为filed_code,value为字段信息 + """ + cursor = conn.cursor(pymysql.cursors.DictCursor) + + sql = """ + SELECT id, name, filed_code, field_type + FROM f_polic_field + WHERE tenant_id = %s AND field_type = 2 + """ + cursor.execute(sql, (TENANT_ID,)) + fields = cursor.fetchall() + + result = {} + for field in fields: + result[field['filed_code']] = { + 'id': field['id'], + 'name': field['name'], + 'filed_code': field['filed_code'], + 'field_type': field['field_type'] + } + + cursor.close() + return result + + +def match_placeholders_to_fields(placeholders: List[str], fields: Dict[str, Dict]) -> Tuple[List[int], List[str]]: + """ + 匹配占位符到数据库字段 + + Returns: + (匹配的字段ID列表, 未匹配的占位符列表) + """ + matched_field_ids = [] + unmatched_placeholders = [] + + for placeholder in placeholders: + if placeholder in fields: + matched_field_ids.append(fields[placeholder]['id']) + else: + unmatched_placeholders.append(placeholder) + + return matched_field_ids, unmatched_placeholders + + +def create_or_update_template(conn, template_name: str, file_path: str, minio_path: str, parent_id: Optional[int]) -> int: + """ + 创建或更新模板记录 + + Returns: + 模板ID + """ + cursor = conn.cursor(pymysql.cursors.DictCursor) + + try: + # 查找是否已存在(通过名称和parent_id匹配) + sql = """ + SELECT id, name, file_path, parent_id + FROM f_polic_file_config + WHERE tenant_id = %s AND name = %s AND parent_id = %s + """ + cursor.execute(sql, (TENANT_ID, template_name, parent_id)) + existing = cursor.fetchone() + + if existing: + # 更新现有记录 + template_id = existing['id'] + update_sql = """ + UPDATE f_polic_file_config + SET file_path = %s, updated_time = NOW(), updated_by = %s, state = 1 + WHERE id = %s AND tenant_id = %s + """ + cursor.execute(update_sql, (minio_path, UPDATED_BY, template_id, TENANT_ID)) + conn.commit() + print(f" [UPDATE] 更新模板记录 (ID: {template_id})") + return template_id + else: + # 创建新记录 + template_id = generate_id() + 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, + parent_id, + template_name, + None, # input_data + minio_path, + CREATED_BY, + CREATED_BY, + 1 # state: 1表示启用 + )) + conn.commit() + print(f" [CREATE] 创建模板记录 (ID: {template_id})") + return template_id + + except Exception as e: + conn.rollback() + raise Exception(f"创建或更新模板失败: {str(e)}") + finally: + cursor.close() + + +def update_template_field_relations(conn, template_id: int, field_ids: List[int]): + """ + 更新模板-字段关联关系 + """ + cursor = conn.cursor() + + try: + # 删除旧的关联关系 + delete_sql = """ + DELETE FROM f_polic_file_field + WHERE tenant_id = %s AND file_id = %s + """ + cursor.execute(delete_sql, (TENANT_ID, template_id)) + + # 插入新的关联关系 + if field_ids: + insert_sql = """ + INSERT INTO f_polic_file_field + (tenant_id, file_id, filed_id, created_time, created_by, updated_time, updated_by) + VALUES (%s, %s, %s, NOW(), %s, NOW(), %s) + """ + for field_id in field_ids: + cursor.execute(insert_sql, (TENANT_ID, template_id, field_id, CREATED_BY, CREATED_BY)) + + conn.commit() + print(f" [UPDATE] 更新字段关联关系: {len(field_ids)} 个字段") + + except Exception as e: + conn.rollback() + raise Exception(f"更新字段关联关系失败: {str(e)}") + finally: + cursor.close() + + +def main(): + """主函数""" + print("=" * 80) + print("处理保密承诺书(非中共党员用)模板") + print("=" * 80) + print() + + # 检查文件是否存在 + if not os.path.exists(TEMPLATE_FILE): + print(f"错误: 文件不存在 - {TEMPLATE_FILE}") + return + + print(f"文件路径: {TEMPLATE_FILE}") + print() + + try: + # 1. 提取占位符 + print("1. 提取占位符...") + placeholders = extract_placeholders_from_docx(TEMPLATE_FILE) + print(f" 找到 {len(placeholders)} 个占位符:") + for i, placeholder in enumerate(placeholders, 1): + print(f" {i}. {{{{ {placeholder} }}}}") + print() + + # 2. 连接数据库和MinIO + print("2. 连接数据库和MinIO...") + conn = pymysql.connect(**DB_CONFIG) + minio_client = Minio( + MINIO_CONFIG['endpoint'], + access_key=MINIO_CONFIG['access_key'], + secret_key=MINIO_CONFIG['secret_key'], + secure=MINIO_CONFIG['secure'] + ) + print(" [OK] 连接成功\n") + + # 3. 获取数据库字段 + print("3. 获取数据库字段...") + db_fields = get_db_fields(conn) + print(f" [OK] 找到 {len(db_fields)} 个输出字段\n") + + # 4. 匹配占位符到字段 + print("4. 匹配占位符到字段...") + matched_field_ids, unmatched_placeholders = match_placeholders_to_fields(placeholders, db_fields) + print(f" 匹配成功: {len(matched_field_ids)} 个") + print(f" 未匹配: {len(unmatched_placeholders)} 个") + if unmatched_placeholders: + print(f" 未匹配的占位符: {', '.join(unmatched_placeholders)}") + print() + + # 5. 上传到MinIO + print("5. 上传到MinIO...") + minio_path = upload_to_minio(TEMPLATE_FILE, minio_client) + print(f" [OK] MinIO路径: {minio_path}\n") + + # 6. 创建或更新数据库记录 + print("6. 创建或更新数据库记录...") + template_id = create_or_update_template(conn, TEMPLATE_NAME, TEMPLATE_FILE, minio_path, PARENT_ID) + print(f" [OK] 模板ID: {template_id}\n") + + # 7. 更新字段关联关系 + print("7. 更新字段关联关系...") + update_template_field_relations(conn, template_id, matched_field_ids) + print() + + print("=" * 80) + print("处理完成!") + print("=" * 80) + print(f"模板ID: {template_id}") + print(f"MinIO路径: {minio_path}") + print(f"关联字段数: {len(matched_field_ids)}") + + except Exception as e: + print(f"\n[ERROR] 发生错误: {e}") + import traceback + traceback.print_exc() + if 'conn' in locals(): + conn.rollback() + finally: + if 'conn' in locals(): + 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 5151641..f875437 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 841e7db..8e7a64d 100644 --- a/services/document_service.py +++ b/services/document_service.py @@ -138,7 +138,6 @@ class DocumentService: doc = Document(template_path) print(f"[DEBUG] 文档包含 {len(doc.paragraphs)} 个段落, {len(doc.tables)} 个表格") -<<<<<<< HEAD def replace_placeholder_in_paragraph(paragraph): """在段落中替换占位符(处理跨run的情况)""" try: @@ -204,18 +203,6 @@ class DocumentService: if placeholder in before_text and placeholder not in after_text: replaced_placeholders.add(field_code) total_replacements += before_text.count(placeholder) -======= - # 替换占位符 {{field_code}} 为实际值 - for paragraph in doc.paragraphs: - # 替换段落文本中的占位符 - for field_code, field_value in field_data.items(): - placeholder = f"{{{{{field_code}}}}}" - if placeholder in paragraph.text: - # 替换占位符 - for run in paragraph.runs: - if placeholder in run.text: - run.text = run.text.replace(placeholder, field_value or '') ->>>>>>> parent of 4897c96 (添加通过taskId获取文档的接口,支持文件列表查询和参数验证,增强错误处理能力。同时,优化文档生成逻辑,确保生成的文档名称和路径的准确性。) # 替换表格中的占位符 try: @@ -264,7 +251,6 @@ class DocumentService: for table in doc.tables: for row in table.rows: for cell in row.cells: -<<<<<<< HEAD if hasattr(cell, 'paragraphs'): for paragraph in cell.paragraphs: text = paragraph.text @@ -281,15 +267,6 @@ class DocumentService: print(f" - ⚠️ 仍有未替换的占位符: {sorted(remaining_placeholders)}") else: print(f" - ✓ 所有占位符已成功替换") -======= - for paragraph in cell.paragraphs: - for field_code, field_value in field_data.items(): - placeholder = f"{{{{{field_code}}}}}" - if placeholder in paragraph.text: - for run in paragraph.runs: - if placeholder in run.text: - run.text = run.text.replace(placeholder, field_value or '') ->>>>>>> parent of 4897c96 (添加通过taskId获取文档的接口,支持文件列表查询和参数验证,增强错误处理能力。同时,优化文档生成逻辑,确保生成的文档名称和路径的准确性。) # 保存到临时文件 temp_dir = tempfile.gettempdir() @@ -493,9 +470,11 @@ class DocumentService: if target_name and target_name.strip(): suffix = f"_{target_name.strip()}" -<<<<<<< HEAD - # 生成新文件名 - return f"{base_name}{suffix}.docx" + # 生成新文件名(确保是.docx格式) + generated_name = f"{base_name}{suffix}.docx" + print(f"[DEBUG] 文档名称生成: '{original_file_name}' -> '{generated_name}' (base_name='{base_name}', suffix='{suffix}')") + + return generated_name def generate_presigned_download_url(self, file_path: str, expires_days: int = 7) -> Optional[str]: """ @@ -530,11 +509,4 @@ class DocumentService: # 如果生成URL失败,记录错误但不影响主流程 print(f"生成预签名URL失败: {str(e)}") return None -======= - # 生成新文件名(确保是.docx格式) - generated_name = f"{base_name}{suffix}.docx" - print(f"[DEBUG] 文档名称生成: '{original_file_name}' -> '{generated_name}' (base_name='{base_name}', suffix='{suffix}')") - - return generated_name ->>>>>>> e3f4a394c1a4333db2fd3a9383be29fa9d9055e0 diff --git a/static/index.html b/static/index.html index 3a4e600..dacda09 100644 --- a/static/index.html +++ b/static/index.html @@ -326,10 +326,14 @@
- +
- - + + + +
+
+ 💡 提示:点击"加载全部可用模板"可以加载所有可用的文档模板类型,方便测试不同模板的生成效果
@@ -568,16 +572,12 @@ return; } - // 添加前5个文件作为示例 - filesWithPath.slice(0, 5).forEach(file => { + // 加载所有可用文件 + filesWithPath.forEach(file => { addFileItem(file.fileId, file.fileName); }); - if (filesWithPath.length > 5) { - alert(`已加载前5个文件,共找到 ${filesWithPath.length} 个可用文件`); - } else { - alert(`已加载 ${filesWithPath.length} 个可用文件`); - } + alert(`已加载全部 ${filesWithPath.length} 个可用文件模板`); } else { alert('获取文件列表失败: ' + (result.errorMsg || '未知错误')); } @@ -603,7 +603,7 @@ addGenerateField('department_opinion', ''); addGenerateField('filler_name', ''); - // 自动加载可用的文件列表(只加载前2个作为示例) + // 自动加载所有可用的文件列表 try { const response = await fetch('/api/file-configs'); const result = await response.json(); @@ -612,19 +612,19 @@ // 只添加有filePath的文件(有模板文件的) const filesWithPath = result.data.fileConfigs.filter(f => f.filePath); - // 添加前2个文件作为示例 - filesWithPath.slice(0, 2).forEach(file => { + // 加载所有可用文件 + filesWithPath.forEach(file => { addFileItem(file.fileId, file.fileName); }); + + if (filesWithPath.length > 0) { + console.log(`已自动加载 ${filesWithPath.length} 个可用文件模板`); + } } else { - // 如果加载失败,使用默认的fileId - addFileItem(1765273961883544, '初步核实审批表.doc'); // 2.初步核实审批表(XXX) - addFileItem(1765273961563507, '请示报告卡.doc'); // 1.请示报告卡(XXX) + console.warn('未找到可用的文件配置'); } } catch (error) { - // 如果加载失败,使用默认的fileId - addFileItem(1765273961883544, '初步核实审批表.doc'); - addFileItem(1765273961563507, '请示报告卡.doc'); + console.warn('自动加载文件列表失败:', error); } } @@ -769,8 +769,15 @@ result.data.fpolicFieldParamFileList.forEach(file => { html += `
${file.fileName}:
- 文件路径: ${file.filePath || '(无路径)'} -
`; + 文件路径: ${file.filePath || '(无路径)'}
`; + + // 如果有下载链接,显示可点击的链接 + if (file.downloadUrl) { + html += `下载链接: ${file.downloadUrl}
`; + html += ``; + } + + html += `
`; }); } } @@ -796,6 +803,12 @@ btn.closest('.field-row').remove(); } + function clearAllFiles() { + if (confirm('确定要清空所有文件列表吗?')) { + document.getElementById('fileListContainer').innerHTML = ''; + } + } + function displayError(tabType, errorMsg) { const resultSection = document.getElementById(tabType + 'ResultSection'); const resultBox = document.getElementById(tabType + 'ResultBox'); diff --git a/template_finish/2-初核模版/1.初核请示/~$初步核实审批表(XXX).docx b/template_finish/2-初核模版/1.初核请示/~$初步核实审批表(XXX).docx new file mode 100644 index 0000000..8efa2ad Binary files /dev/null and b/template_finish/2-初核模版/1.初核请示/~$初步核实审批表(XXX).docx differ diff --git a/template_finish/2-初核模版/1.初核请示/~$请示报告卡(XXX).docx b/template_finish/2-初核模版/1.初核请示/~$请示报告卡(XXX).docx new file mode 100644 index 0000000..8efa2ad Binary files /dev/null and b/template_finish/2-初核模版/1.初核请示/~$请示报告卡(XXX).docx differ diff --git a/template_finish/2-初核模版/3.初核结论/8-1请示报告卡(初核报告结论) .docx b/template_finish/2-初核模版/3.初核结论/8-1请示报告卡(初核报告结论) .docx index f2a499e..e42f7e5 100644 Binary files a/template_finish/2-初核模版/3.初核结论/8-1请示报告卡(初核报告结论) .docx and b/template_finish/2-初核模版/3.初核结论/8-1请示报告卡(初核报告结论) .docx differ diff --git a/template_finish/2-初核模版/3.初核结论/~$1请示报告卡(初核报告结论) .docx b/template_finish/2-初核模版/3.初核结论/~$1请示报告卡(初核报告结论) .docx new file mode 100644 index 0000000..8efa2ad Binary files /dev/null and b/template_finish/2-初核模版/3.初核结论/~$1请示报告卡(初核报告结论) .docx differ diff --git a/template_finish/2-初核模版/3.初核结论/~$XXX初核情况报告.docx b/template_finish/2-初核模版/3.初核结论/~$XXX初核情况报告.docx new file mode 100644 index 0000000..8efa2ad Binary files /dev/null and b/template_finish/2-初核模版/3.初核结论/~$XXX初核情况报告.docx differ diff --git a/update_template_hierarchy.py b/update_template_hierarchy.py new file mode 100644 index 0000000..67dc68b --- /dev/null +++ b/update_template_hierarchy.py @@ -0,0 +1,472 @@ +""" +根据 template_finish/ 目录结构更新 f_polic_file_config 表中的层级结构 +""" +import os +import sys +import json +import pymysql +from pathlib import Path +from typing import Dict, List, Optional, Tuple +from collections import defaultdict + +# 设置输出编码为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') + +# 数据库连接配置 +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 +TEMPLATE_BASE_DIR = 'template_finish' + + +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 normalize_name(name: str) -> str: + """ + 标准化名称(去掉扩展名、括号内容、数字前缀等) + 用于匹配数据库中的记录 + """ + # 去掉扩展名 + name = Path(name).stem if '.' in name else name + + # 去掉括号内容 + import re + name = re.sub(r'[((].*?[))]', '', name) + name = name.strip() + + # 去掉数字前缀和点号 + name = re.sub(r'^\d+[\.\-]?\s*', '', name) + name = name.strip() + + return name + + +def scan_directory_structure(base_dir: str) -> Dict: + """ + 扫描目录结构,构建层级关系 + + Returns: + 字典,包含目录和文件的层级信息 + """ + base_path = Path(base_dir) + if not base_path.exists(): + print(f"错误: 目录不存在 - {base_dir}") + return {} + + structure = { + 'directories': [], # 目录节点列表 + 'files': [], # 文件节点列表 + 'name_to_id': {} # 名称到ID的映射(用于查找parent_id) + } + + print("=" * 80) + print("扫描目录结构...") + print("=" * 80) + + # 遍历所有目录和文件 + for item in base_path.rglob("*"): + relative_path = item.relative_to(base_path) + parts = relative_path.parts + + if item.is_dir(): + # 目录节点 + level = len(parts) - 1 # 层级(从0开始) + dir_name = parts[-1] + parent_path = str(Path(*parts[:-1])) if len(parts) > 1 else None + + structure['directories'].append({ + 'name': dir_name, + 'path': str(relative_path), + 'level': level, + 'parent_path': parent_path, + 'parent_id': None # 稍后设置 + }) + + elif item.is_file() and item.suffix == '.docx' and not item.name.startswith("~$"): + # 文件节点 + level = len(parts) - 1 + file_name = item.name + parent_path = str(Path(*parts[:-1])) if len(parts) > 1 else None + + structure['files'].append({ + 'name': file_name, + 'path': str(relative_path), + 'level': level, + 'parent_path': parent_path, + 'parent_id': None, # 稍后设置 + 'file_path': str(item) + }) + + # 按层级排序 + structure['directories'].sort(key=lambda x: (x['level'], x['path'])) + structure['files'].sort(key=lambda x: (x['level'], x['path'])) + + print(f"找到 {len(structure['directories'])} 个目录节点") + print(f"找到 {len(structure['files'])} 个文件节点") + + return structure + + +def get_existing_templates(conn) -> Dict: + """ + 获取数据库中现有的模板记录 + + Returns: + 字典,key为标准化名称,value为模板信息 + """ + cursor = conn.cursor(pymysql.cursors.DictCursor) + + sql = """ + SELECT id, name, parent_id, file_path, state + FROM f_polic_file_config + WHERE tenant_id = %s + """ + cursor.execute(sql, (TENANT_ID,)) + templates = cursor.fetchall() + + result = {} + for template in templates: + normalized_name = normalize_name(template['name']) + result[normalized_name] = { + 'id': template['id'], + 'name': template['name'], + 'normalized_name': normalized_name, + 'parent_id': template['parent_id'], + 'file_path': template['file_path'], + 'state': template['state'] + } + + cursor.close() + return result + + +def find_template_by_name(existing_templates: Dict, name: str, prefer_directory: bool = False) -> Optional[Dict]: + """ + 根据名称查找模板(支持标准化匹配) + + Args: + name: 模板名称 + prefer_directory: 是否优先匹配目录节点 + """ + normalized = normalize_name(name) + + # 精确匹配标准化名称 + if normalized in existing_templates: + template = existing_templates[normalized] + # 如果prefer_directory为True,且找到的是文件,继续查找目录 + if prefer_directory and template.get('file_path') is not None: + pass # 继续查找 + else: + return template + + # 模糊匹配(包含关系) + candidates = [] + for key, template in existing_templates.items(): + if key.startswith("DIR:"): + # 目录节点 + if normalized in template.get('normalized_name', '') or template.get('normalized_name', '') in normalized: + candidates.append((template, True)) + else: + # 文件节点 + if normalized in template.get('normalized_name', '') or template.get('normalized_name', '') in normalized: + candidates.append((template, False)) + + # 如果prefer_directory,优先返回目录节点 + if prefer_directory: + for template, is_dir in candidates: + if is_dir: + return template + + # 返回第一个匹配的 + if candidates: + return candidates[0][0] + + return None + + +def create_or_update_directory(conn, dir_info: Dict, parent_id: Optional[int], existing_templates: Dict) -> int: + """ + 创建或更新目录节点 + + Returns: + 目录节点的ID + """ + cursor = conn.cursor() + + try: + # 先通过路径查找(最准确) + path_key = f"DIR:{dir_info['path']}" + existing = existing_templates.get(path_key) + + # 如果没找到,再通过名称查找(优先目录节点) + if not existing: + existing = find_template_by_name(existing_templates, dir_info['name'], prefer_directory=True) + # 确保找到的是目录节点(file_path为None) + if existing and existing.get('file_path') is not None: + existing = None + + if existing: + # 更新现有记录 + template_id = existing['id'] + if existing['parent_id'] != parent_id: + update_sql = """ + UPDATE f_polic_file_config + SET parent_id = %s, updated_time = NOW(), updated_by = %s, state = 1 + WHERE id = %s AND tenant_id = %s + """ + cursor.execute(update_sql, (parent_id, UPDATED_BY, template_id, TENANT_ID)) + conn.commit() + print(f" [UPDATE] 更新目录: {dir_info['name']} (ID: {template_id}, parent_id: {parent_id})") + else: + print(f" [KEEP] 保持目录: {dir_info['name']} (ID: {template_id})") + return template_id + else: + # 创建新记录 + template_id = generate_id() + 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, + parent_id, + dir_info['name'], + None, # input_data + None, # file_path(目录节点没有文件路径) + CREATED_BY, + CREATED_BY, + 1 # state: 1表示启用 + )) + conn.commit() + print(f" [CREATE] 创建目录: {dir_info['name']} (ID: {template_id}, parent_id: {parent_id})") + return template_id + + except Exception as e: + conn.rollback() + raise Exception(f"创建或更新目录失败: {str(e)}") + finally: + cursor.close() + + +def update_file_parent(conn, file_info: Dict, parent_id: Optional[int], existing_templates: Dict) -> Optional[int]: + """ + 更新文件节点的parent_id + + Returns: + 文件节点的ID,如果未找到则返回None + """ + cursor = conn.cursor() + + try: + # 查找文件(使用文件名匹配) + existing = find_template_by_name(existing_templates, file_info['name']) + + if existing: + template_id = existing['id'] + if existing['parent_id'] != parent_id: + update_sql = """ + UPDATE f_polic_file_config + SET parent_id = %s, updated_time = NOW(), updated_by = %s + WHERE id = %s AND tenant_id = %s + """ + cursor.execute(update_sql, (parent_id, UPDATED_BY, template_id, TENANT_ID)) + conn.commit() + print(f" [UPDATE] 更新文件: {file_info['name']} (ID: {template_id}, parent_id: {parent_id})") + else: + print(f" [KEEP] 保持文件: {file_info['name']} (ID: {template_id})") + return template_id + else: + print(f" [WARN] 未找到文件: {file_info['name']}") + return None + + except Exception as e: + conn.rollback() + raise Exception(f"更新文件parent_id失败: {str(e)}") + finally: + cursor.close() + + +def build_path_to_id_map(structure: Dict, existing_templates: Dict, conn) -> Dict[str, int]: + """ + 构建路径到ID的映射 + + Returns: + 字典,key为路径,value为ID + """ + path_to_id = {} + + # 处理目录节点(按层级顺序,确保父节点先处理) + # 按层级和路径排序 + sorted_dirs = sorted(structure['directories'], key=lambda x: (x['level'], x['path'])) + + for dir_info in sorted_dirs: + parent_id = None + if dir_info['parent_path']: + parent_id = path_to_id.get(dir_info['parent_path']) + if parent_id is None: + print(f" [WARN] 未找到父目录: {dir_info['parent_path']}") + + dir_id = create_or_update_directory(conn, dir_info, parent_id, existing_templates) + path_to_id[dir_info['path']] = dir_id + + # 更新existing_templates,以便后续查找(使用完整路径作为key避免冲突) + key = f"DIR:{dir_info['path']}" + existing_templates[key] = { + 'id': dir_id, + 'name': dir_info['name'], + 'normalized_name': normalize_name(dir_info['name']), + 'parent_id': parent_id, + 'file_path': None, + 'state': 1, + 'path': dir_info['path'] + } + # 同时用标准化名称存储(用于文件查找父目录) + normalized_key = normalize_name(dir_info['name']) + if normalized_key not in existing_templates or existing_templates[normalized_key].get('file_path') is not None: + # 只有当不存在或存在的是文件时才更新 + existing_templates[normalized_key] = { + 'id': dir_id, + 'name': dir_info['name'], + 'normalized_name': normalized_key, + 'parent_id': parent_id, + 'file_path': None, + 'state': 1, + 'path': dir_info['path'] + } + + return path_to_id + + +def update_file_hierarchy(structure: Dict, path_to_id: Dict[str, int], existing_templates: Dict, conn): + """ + 更新文件节点的parent_id + """ + for file_info in structure['files']: + parent_id = None + if file_info['parent_path']: + parent_id = path_to_id.get(file_info['parent_path']) + + update_file_parent(conn, file_info, parent_id, existing_templates) + + +def main(): + """主函数""" + print("=" * 80) + print("更新模板层级结构") + print("=" * 80) + print() + + try: + # 连接数据库 + print("1. 连接数据库...") + conn = pymysql.connect(**DB_CONFIG) + print("[OK] 数据库连接成功\n") + + # 扫描目录结构 + print("2. 扫描目录结构...") + structure = scan_directory_structure(TEMPLATE_BASE_DIR) + if not structure: + print("错误: 未找到任何目录或文件") + return + + # 获取现有模板 + print("\n3. 获取现有模板...") + existing_templates = get_existing_templates(conn) + print(f"[OK] 找到 {len(existing_templates)} 个现有模板\n") + + # 构建路径到ID的映射(处理目录节点) + print("4. 创建/更新目录节点...") + print("=" * 80) + path_to_id = build_path_to_id_map(structure, existing_templates, conn) + print(f"\n[OK] 处理了 {len(path_to_id)} 个目录节点\n") + + # 更新文件节点的parent_id + print("5. 更新文件节点的parent_id...") + print("=" * 80) + update_file_hierarchy(structure, path_to_id, existing_templates, conn) + print(f"\n[OK] 处理了 {len(structure['files'])} 个文件节点\n") + + # 打印层级结构 + print("6. 最终层级结构:") + print("=" * 80) + print_hierarchy(conn) + + print("\n" + "=" * 80) + print("更新完成!") + print("=" * 80) + + except Exception as e: + print(f"\n[ERROR] 发生错误: {e}") + import traceback + traceback.print_exc() + if 'conn' in locals(): + conn.rollback() + finally: + if 'conn' in locals(): + conn.close() + print("\n数据库连接已关闭") + + +def print_hierarchy(conn, parent_id=None, level=0, prefix=""): + """打印层级结构""" + cursor = conn.cursor(pymysql.cursors.DictCursor) + + try: + if parent_id is None: + sql = """ + SELECT id, name, parent_id, file_path + FROM f_polic_file_config + WHERE tenant_id = %s AND parent_id IS NULL + ORDER BY name + """ + cursor.execute(sql, (TENANT_ID,)) + else: + sql = """ + SELECT id, name, parent_id, file_path + FROM f_polic_file_config + WHERE tenant_id = %s AND parent_id = %s + ORDER BY name + """ + cursor.execute(sql, (TENANT_ID, parent_id)) + + items = cursor.fetchall() + + for i, item in enumerate(items): + is_last = i == len(items) - 1 + current_prefix = prefix + ("└── " if is_last else "├── ") + next_prefix = prefix + (" " if is_last else "│ ") + + node_type = "📁" if item['file_path'] is None else "📄" + print(f"{current_prefix}{node_type} {item['name']} (ID: {item['id']})") + + # 递归打印子节点 + print_hierarchy(conn, item['id'], level + 1, next_prefix) + + finally: + cursor.close() + + +if __name__ == '__main__': + main() + diff --git a/update_template_hierarchy_final.py b/update_template_hierarchy_final.py new file mode 100644 index 0000000..4499db4 --- /dev/null +++ b/update_template_hierarchy_final.py @@ -0,0 +1,405 @@ +""" +根据 template_finish/ 目录结构更新 f_polic_file_config 表中的层级结构 +使用file_path作为唯一标识,确保正确建立层级关系 +""" +import os +import sys +import json +import pymysql +from pathlib import Path +from typing import Dict, List, Optional, Tuple +from collections import defaultdict +from datetime import datetime + +# 设置输出编码为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') + +# 数据库连接配置 +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 +TEMPLATE_BASE_DIR = 'template_finish' + + +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 scan_directory_structure(base_dir: str) -> Dict: + """ + 扫描目录结构,构建层级关系 + + Returns: + 字典,包含目录和文件的层级信息 + """ + base_path = Path(base_dir) + if not base_path.exists(): + print(f"错误: 目录不存在 - {base_dir}") + return {} + + structure = { + 'directories': [], # 目录节点列表 + 'files': [] # 文件节点列表 + } + + print("=" * 80) + print("扫描目录结构...") + print("=" * 80) + + # 遍历所有目录和文件 + for item in base_path.rglob("*"): + relative_path = item.relative_to(base_path) + parts = relative_path.parts + + if item.is_dir(): + # 目录节点 + level = len(parts) - 1 # 层级(从0开始) + dir_name = parts[-1] + parent_path = str(Path(*parts[:-1])) if len(parts) > 1 else None + + structure['directories'].append({ + 'name': dir_name, + 'path': str(relative_path), + 'level': level, + 'parent_path': parent_path + }) + + elif item.is_file() and item.suffix == '.docx' and not item.name.startswith("~$"): + # 文件节点 + level = len(parts) - 1 + file_name = item.name + parent_path = str(Path(*parts[:-1])) if len(parts) > 1 else None + + # 构建MinIO路径 + now = datetime.now() + minio_path = f'/615873064429507639/TEMPLATE/{now.year}/{now.month:02d}/{file_name}' + + structure['files'].append({ + 'name': file_name, + 'path': str(relative_path), + 'level': level, + 'parent_path': parent_path, + 'file_path': str(item), + 'minio_path': minio_path + }) + + # 按层级排序 + structure['directories'].sort(key=lambda x: (x['level'], x['path'])) + structure['files'].sort(key=lambda x: (x['level'], x['path'])) + + print(f"找到 {len(structure['directories'])} 个目录节点") + print(f"找到 {len(structure['files'])} 个文件节点") + + return structure + + +def get_existing_templates(conn) -> Dict: + """ + 获取数据库中现有的模板记录 + + Returns: + 字典,key为file_path,value为模板信息 + """ + cursor = conn.cursor(pymysql.cursors.DictCursor) + + sql = """ + SELECT id, name, parent_id, file_path, state + FROM f_polic_file_config + WHERE tenant_id = %s + """ + cursor.execute(sql, (TENANT_ID,)) + templates = cursor.fetchall() + + # 使用file_path作为key(如果存在) + result_by_path = {} + # 使用name作为key(用于目录节点) + result_by_name = {} + + for template in templates: + if template['file_path']: + result_by_path[template['file_path']] = { + 'id': template['id'], + 'name': template['name'], + 'parent_id': template['parent_id'], + 'file_path': template['file_path'], + 'state': template['state'] + } + else: + # 目录节点 + name = template['name'] + if name not in result_by_name: + result_by_name[name] = [] + result_by_name[name].append({ + 'id': template['id'], + 'name': template['name'], + 'parent_id': template['parent_id'], + 'file_path': None, + 'state': template['state'] + }) + + cursor.close() + return { + 'by_path': result_by_path, + 'by_name': result_by_name + } + + +def create_or_update_directory(conn, dir_name: str, parent_id: Optional[int], existing_templates: Dict) -> int: + """ + 创建或更新目录节点 + + Returns: + 目录节点的ID + """ + cursor = conn.cursor() + + try: + # 查找是否已存在(通过名称精确匹配,且file_path为None) + candidates = existing_templates['by_name'].get(dir_name, []) + existing = None + for candidate in candidates: + if candidate.get('file_path') is None: # 目录节点 + # 如果parent_id匹配,优先选择 + if candidate['parent_id'] == parent_id: + existing = candidate + break + elif existing is None: + existing = candidate + + if existing: + # 更新现有目录记录 + template_id = existing['id'] + if existing['parent_id'] != parent_id: + update_sql = """ + UPDATE f_polic_file_config + SET parent_id = %s, updated_time = NOW(), updated_by = %s, state = 1 + WHERE id = %s AND tenant_id = %s + """ + cursor.execute(update_sql, (parent_id, UPDATED_BY, template_id, TENANT_ID)) + conn.commit() + print(f" [UPDATE] 更新目录: {dir_name} (ID: {template_id}, parent_id: {parent_id})") + else: + print(f" [KEEP] 保持目录: {dir_name} (ID: {template_id})") + return template_id + else: + # 创建新目录记录 + template_id = generate_id() + 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, + parent_id, + dir_name, + None, # input_data + None, # file_path(目录节点没有文件路径) + CREATED_BY, + CREATED_BY, + 1 # state: 1表示启用 + )) + conn.commit() + print(f" [CREATE] 创建目录: {dir_name} (ID: {template_id}, parent_id: {parent_id})") + # 更新existing_templates + if dir_name not in existing_templates['by_name']: + existing_templates['by_name'][dir_name] = [] + existing_templates['by_name'][dir_name].append({ + 'id': template_id, + 'name': dir_name, + 'parent_id': parent_id, + 'file_path': None, + 'state': 1 + }) + return template_id + + except Exception as e: + conn.rollback() + raise Exception(f"创建或更新目录失败: {str(e)}") + finally: + cursor.close() + + +def update_file_parent(conn, file_info: Dict, parent_id: Optional[int], existing_templates: Dict) -> Optional[int]: + """ + 更新文件节点的parent_id + + Args: + file_info: 文件信息,包含name、minio_path等 + parent_id: 父节点ID + + Returns: + 文件节点的ID,如果未找到则返回None + """ + cursor = conn.cursor() + + try: + file_name = file_info['name'] + minio_path = file_info.get('minio_path') + + # 优先通过file_path(minio_path)匹配(最准确) + existing = None + if minio_path and minio_path in existing_templates['by_path']: + existing = existing_templates['by_path'][minio_path] + + if not existing: + print(f" [WARN] 未找到文件: {file_name} (MinIO路径: {minio_path})") + return None + + template_id = existing['id'] + + if existing['parent_id'] != parent_id: + update_sql = """ + UPDATE f_polic_file_config + SET parent_id = %s, updated_time = NOW(), updated_by = %s + WHERE id = %s AND tenant_id = %s + """ + cursor.execute(update_sql, (parent_id, UPDATED_BY, template_id, TENANT_ID)) + conn.commit() + print(f" [UPDATE] 更新文件: {file_name} (ID: {template_id}, parent_id: {parent_id})") + else: + print(f" [KEEP] 保持文件: {file_name} (ID: {template_id})") + return template_id + + except Exception as e: + conn.rollback() + raise Exception(f"更新文件parent_id失败: {str(e)}") + finally: + cursor.close() + + +def main(): + """主函数""" + print("=" * 80) + print("更新模板层级结构") + print("=" * 80) + print() + + try: + # 连接数据库 + print("1. 连接数据库...") + conn = pymysql.connect(**DB_CONFIG) + print("[OK] 数据库连接成功\n") + + # 扫描目录结构 + print("2. 扫描目录结构...") + structure = scan_directory_structure(TEMPLATE_BASE_DIR) + if not structure: + print("错误: 未找到任何目录或文件") + return + + # 获取现有模板 + print("\n3. 获取现有模板...") + existing_templates = get_existing_templates(conn) + print(f"[OK] 找到 {len(existing_templates['by_path'])} 个文件模板") + print(f"[OK] 找到 {sum(len(v) for v in existing_templates['by_name'].values())} 个目录模板\n") + + # 构建路径到ID的映射(处理目录节点) + print("4. 创建/更新目录节点...") + print("=" * 80) + path_to_id = {} + + # 按层级顺序处理目录 + for dir_info in structure['directories']: + parent_id = None + if dir_info['parent_path']: + parent_id = path_to_id.get(dir_info['parent_path']) + + dir_id = create_or_update_directory(conn, dir_info['name'], parent_id, existing_templates) + path_to_id[dir_info['path']] = dir_id + + print(f"\n[OK] 处理了 {len(path_to_id)} 个目录节点\n") + + # 更新文件节点的parent_id + print("5. 更新文件节点的parent_id...") + print("=" * 80) + for file_info in structure['files']: + parent_id = None + if file_info['parent_path']: + parent_id = path_to_id.get(file_info['parent_path']) + + update_file_parent(conn, file_info, parent_id, existing_templates) + + print(f"\n[OK] 处理了 {len(structure['files'])} 个文件节点\n") + + # 打印层级结构 + print("6. 最终层级结构:") + print("=" * 80) + print_hierarchy(conn) + + print("\n" + "=" * 80) + print("更新完成!") + print("=" * 80) + + except Exception as e: + print(f"\n[ERROR] 发生错误: {e}") + import traceback + traceback.print_exc() + if 'conn' in locals(): + conn.rollback() + finally: + if 'conn' in locals(): + conn.close() + print("\n数据库连接已关闭") + + +def print_hierarchy(conn, parent_id=None, level=0, prefix=""): + """打印层级结构""" + cursor = conn.cursor(pymysql.cursors.DictCursor) + + try: + if parent_id is None: + sql = """ + SELECT id, name, parent_id, file_path + FROM f_polic_file_config + WHERE tenant_id = %s AND parent_id IS NULL + ORDER BY name + """ + cursor.execute(sql, (TENANT_ID,)) + else: + sql = """ + SELECT id, name, parent_id, file_path + FROM f_polic_file_config + WHERE tenant_id = %s AND parent_id = %s + ORDER BY name + """ + cursor.execute(sql, (TENANT_ID, parent_id)) + + items = cursor.fetchall() + + for i, item in enumerate(items): + is_last = i == len(items) - 1 + current_prefix = prefix + ("└── " if is_last else "├── ") + next_prefix = prefix + (" " if is_last else "│ ") + + node_type = "📁" if item['file_path'] is None else "📄" + print(f"{current_prefix}{node_type} {item['name']} (ID: {item['id']})") + + # 递归打印子节点 + print_hierarchy(conn, item['id'], level + 1, next_prefix) + + finally: + cursor.close() + + +if __name__ == '__main__': + main() + diff --git a/update_template_hierarchy_v2.py b/update_template_hierarchy_v2.py new file mode 100644 index 0000000..f312d39 --- /dev/null +++ b/update_template_hierarchy_v2.py @@ -0,0 +1,445 @@ +""" +根据 template_finish/ 目录结构更新 f_polic_file_config 表中的层级结构 +使用路径作为唯一标识,确保正确建立层级关系 +""" +import os +import sys +import json +import pymysql +from pathlib import Path +from typing import Dict, List, Optional, Tuple +from collections import defaultdict + +# 设置输出编码为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') + +# 数据库连接配置 +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 +TEMPLATE_BASE_DIR = 'template_finish' + + +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 scan_directory_structure(base_dir: str) -> Dict: + """ + 扫描目录结构,构建层级关系 + + Returns: + 字典,包含目录和文件的层级信息 + """ + base_path = Path(base_dir) + if not base_path.exists(): + print(f"错误: 目录不存在 - {base_dir}") + return {} + + structure = { + 'directories': [], # 目录节点列表 + 'files': [] # 文件节点列表 + } + + print("=" * 80) + print("扫描目录结构...") + print("=" * 80) + + # 遍历所有目录和文件 + for item in base_path.rglob("*"): + relative_path = item.relative_to(base_path) + parts = relative_path.parts + + if item.is_dir(): + # 目录节点 + level = len(parts) - 1 # 层级(从0开始) + dir_name = parts[-1] + parent_path = str(Path(*parts[:-1])) if len(parts) > 1 else None + + structure['directories'].append({ + 'name': dir_name, + 'path': str(relative_path), + 'level': level, + 'parent_path': parent_path + }) + + elif item.is_file() and item.suffix == '.docx' and not item.name.startswith("~$"): + # 文件节点 + level = len(parts) - 1 + file_name = item.name + parent_path = str(Path(*parts[:-1])) if len(parts) > 1 else None + + structure['files'].append({ + 'name': file_name, + 'path': str(relative_path), + 'level': level, + 'parent_path': parent_path, + 'file_path': str(item) + }) + + # 按层级排序 + structure['directories'].sort(key=lambda x: (x['level'], x['path'])) + structure['files'].sort(key=lambda x: (x['level'], x['path'])) + + print(f"找到 {len(structure['directories'])} 个目录节点") + print(f"找到 {len(structure['files'])} 个文件节点") + + return structure + + +def get_existing_templates(conn) -> Dict: + """ + 获取数据库中现有的模板记录 + + Returns: + 字典,key为file_path(如果存在)或名称,value为模板信息列表 + """ + cursor = conn.cursor(pymysql.cursors.DictCursor) + + sql = """ + SELECT id, name, parent_id, file_path, state + FROM f_polic_file_config + WHERE tenant_id = %s + """ + cursor.execute(sql, (TENANT_ID,)) + templates = cursor.fetchall() + + result = {} + for template in templates: + # 优先使用file_path作为key(更准确) + if template['file_path']: + key = template['file_path'] + if key not in result: + result[key] = [] + result[key].append({ + 'id': template['id'], + 'name': template['name'], + 'parent_id': template['parent_id'], + 'file_path': template['file_path'], + 'state': template['state'] + }) + # 同时使用名称作为key(用于目录节点和没有file_path的记录) + name_key = template['name'] + if name_key not in result: + result[name_key] = [] + result[name_key].append({ + 'id': template['id'], + 'name': template['name'], + 'parent_id': template['parent_id'], + 'file_path': template['file_path'], + 'state': template['state'] + }) + + cursor.close() + return result + + +def create_or_update_directory(conn, dir_name: str, parent_id: Optional[int], existing_templates: Dict) -> int: + """ + 创建或更新目录节点 + + Returns: + 目录节点的ID + """ + cursor = conn.cursor() + + try: + # 查找是否已存在(通过名称精确匹配,且file_path为None) + candidates = existing_templates.get(dir_name, []) + existing = None + for candidate in candidates: + if candidate.get('file_path') is None: # 目录节点 + existing = candidate + break + + if existing: + # 更新现有目录记录 + template_id = existing['id'] + if existing['parent_id'] != parent_id: + update_sql = """ + UPDATE f_polic_file_config + SET parent_id = %s, updated_time = NOW(), updated_by = %s, state = 1 + WHERE id = %s AND tenant_id = %s + """ + cursor.execute(update_sql, (parent_id, UPDATED_BY, template_id, TENANT_ID)) + conn.commit() + print(f" [UPDATE] 更新目录: {dir_name} (ID: {template_id}, parent_id: {parent_id})") + else: + print(f" [KEEP] 保持目录: {dir_name} (ID: {template_id})") + return template_id + else: + # 创建新目录记录 + template_id = generate_id() + 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, + parent_id, + dir_name, + None, # input_data + None, # file_path(目录节点没有文件路径) + CREATED_BY, + CREATED_BY, + 1 # state: 1表示启用 + )) + conn.commit() + print(f" [CREATE] 创建目录: {dir_name} (ID: {template_id}, parent_id: {parent_id})") + # 更新existing_templates + if dir_name not in existing_templates: + existing_templates[dir_name] = [] + existing_templates[dir_name].append({ + 'id': template_id, + 'name': dir_name, + 'parent_id': parent_id, + 'file_path': None, + 'state': 1 + }) + return template_id + + except Exception as e: + conn.rollback() + raise Exception(f"创建或更新目录失败: {str(e)}") + finally: + cursor.close() + + +def normalize_name(name: str) -> str: + """ + 标准化名称(去掉扩展名、括号内容、数字前缀等) + """ + import re + # 去掉扩展名 + name = Path(name).stem if '.' in name else name + # 去掉括号内容 + name = re.sub(r'[((].*?[))]', '', name) + name = name.strip() + # 去掉数字前缀和点号 + name = re.sub(r'^\d+[\.\-]?\s*', '', name) + name = name.strip() + return name + + +def update_file_parent(conn, file_info: Dict, parent_id: Optional[int], existing_templates: Dict) -> Optional[int]: + """ + 更新文件节点的parent_id + + Args: + file_info: 文件信息,包含name和file_path + parent_id: 父节点ID + + Returns: + 文件节点的ID,如果未找到则返回None + """ + cursor = conn.cursor() + + try: + file_name = file_info['name'] + # 根据文件路径构建预期的MinIO路径 + # 文件路径格式: template_finish/2-初核模版/1.初核请示/1.请示报告卡(XXX).docx + # MinIO路径格式: /615873064429507639/TEMPLATE/2025/12/1.请示报告卡(XXX).docx + expected_path = None + if 'file_path' in file_info: + # 从相对路径构建MinIO路径 + from datetime import datetime + now = datetime.now() + relative_path = file_info.get('path', '') + file_name_only = Path(file_name).name + expected_path = f'/615873064429507639/TEMPLATE/{now.year}/{now.month:02d}/{file_name_only}' + + # 优先通过file_path匹配(最准确) + existing = None + if expected_path and expected_path in existing_templates: + candidates = existing_templates[expected_path] + if candidates: + existing = candidates[0] + + # 如果没找到,通过标准化名称匹配 + if not existing: + normalized_file_name = normalize_name(file_name) + candidates = [] + for key, templates in existing_templates.items(): + for template in templates: + if template.get('file_path'): # 只匹配有file_path的(文件节点) + normalized_template_name = normalize_name(template['name']) + if normalized_template_name == normalized_file_name: + # 检查file_path是否匹配 + if expected_path and template['file_path'] == expected_path: + existing = template + break + candidates.append(template) + if existing: + break + + if not existing and candidates: + # 如果有多个候选,选择parent_id匹配的 + for candidate in candidates: + if candidate['parent_id'] == parent_id: + existing = candidate + break + # 如果还是没找到,选择第一个 + if not existing: + existing = candidates[0] + + if not existing: + print(f" [WARN] 未找到文件: {file_name} (标准化: {normalize_name(file_name)})") + return None + + template_id = existing['id'] + + if existing['parent_id'] != parent_id: + update_sql = """ + UPDATE f_polic_file_config + SET parent_id = %s, updated_time = NOW(), updated_by = %s + WHERE id = %s AND tenant_id = %s + """ + cursor.execute(update_sql, (parent_id, UPDATED_BY, template_id, TENANT_ID)) + conn.commit() + print(f" [UPDATE] 更新文件: {file_name} -> {existing['name']} (ID: {template_id}, parent_id: {parent_id})") + else: + print(f" [KEEP] 保持文件: {file_name} -> {existing['name']} (ID: {template_id})") + return template_id + + except Exception as e: + conn.rollback() + raise Exception(f"更新文件parent_id失败: {str(e)}") + finally: + cursor.close() + + +def main(): + """主函数""" + print("=" * 80) + print("更新模板层级结构") + print("=" * 80) + print() + + try: + # 连接数据库 + print("1. 连接数据库...") + conn = pymysql.connect(**DB_CONFIG) + print("[OK] 数据库连接成功\n") + + # 扫描目录结构 + print("2. 扫描目录结构...") + structure = scan_directory_structure(TEMPLATE_BASE_DIR) + if not structure: + print("错误: 未找到任何目录或文件") + return + + # 获取现有模板 + print("\n3. 获取现有模板...") + existing_templates = get_existing_templates(conn) + print(f"[OK] 找到 {len(existing_templates)} 个现有模板\n") + + # 构建路径到ID的映射(处理目录节点) + print("4. 创建/更新目录节点...") + print("=" * 80) + path_to_id = {} + + # 按层级顺序处理目录 + for dir_info in structure['directories']: + parent_id = None + if dir_info['parent_path']: + parent_id = path_to_id.get(dir_info['parent_path']) + + dir_id = create_or_update_directory(conn, dir_info['name'], parent_id, existing_templates) + path_to_id[dir_info['path']] = dir_id + + print(f"\n[OK] 处理了 {len(path_to_id)} 个目录节点\n") + + # 更新文件节点的parent_id + print("5. 更新文件节点的parent_id...") + print("=" * 80) + for file_info in structure['files']: + parent_id = None + if file_info['parent_path']: + parent_id = path_to_id.get(file_info['parent_path']) + + update_file_parent(conn, file_info, parent_id, existing_templates) + + print(f"\n[OK] 处理了 {len(structure['files'])} 个文件节点\n") + + # 打印层级结构 + print("6. 最终层级结构:") + print("=" * 80) + print_hierarchy(conn) + + print("\n" + "=" * 80) + print("更新完成!") + print("=" * 80) + + except Exception as e: + print(f"\n[ERROR] 发生错误: {e}") + import traceback + traceback.print_exc() + if 'conn' in locals(): + conn.rollback() + finally: + if 'conn' in locals(): + conn.close() + print("\n数据库连接已关闭") + + +def print_hierarchy(conn, parent_id=None, level=0, prefix=""): + """打印层级结构""" + cursor = conn.cursor(pymysql.cursors.DictCursor) + + try: + if parent_id is None: + sql = """ + SELECT id, name, parent_id, file_path + FROM f_polic_file_config + WHERE tenant_id = %s AND parent_id IS NULL + ORDER BY name + """ + cursor.execute(sql, (TENANT_ID,)) + else: + sql = """ + SELECT id, name, parent_id, file_path + FROM f_polic_file_config + WHERE tenant_id = %s AND parent_id = %s + ORDER BY name + """ + cursor.execute(sql, (TENANT_ID, parent_id)) + + items = cursor.fetchall() + + for i, item in enumerate(items): + is_last = i == len(items) - 1 + current_prefix = prefix + ("└── " if is_last else "├── ") + next_prefix = prefix + (" " if is_last else "│ ") + + node_type = "📁" if item['file_path'] is None else "📄" + print(f"{current_prefix}{node_type} {item['name']} (ID: {item['id']})") + + # 递归打印子节点 + print_hierarchy(conn, item['id'], level + 1, next_prefix) + + finally: + cursor.close() + + +if __name__ == '__main__': + main() + diff --git a/verify_hierarchy_structure.py b/verify_hierarchy_structure.py new file mode 100644 index 0000000..e5c9527 --- /dev/null +++ b/verify_hierarchy_structure.py @@ -0,0 +1,56 @@ +""" +验证层级结构是否正确 +""" +import pymysql + +DB_CONFIG = { + 'host': '152.136.177.240', + 'port': 5012, + 'user': 'finyx', + 'password': '6QsGK6MpePZDE57Z', + 'database': 'finyx', + 'charset': 'utf8mb4' +} + +TENANT_ID = 615873064429507639 + +conn = pymysql.connect(**DB_CONFIG) +cursor = conn.cursor(pymysql.cursors.DictCursor) + +# 检查"1请示报告卡"的记录 +cursor.execute(""" + SELECT id, name, file_path, parent_id + FROM f_polic_file_config + WHERE tenant_id = %s AND name LIKE %s + ORDER BY id +""", (TENANT_ID, '%请示报告卡%')) + +results = cursor.fetchall() +print(f"找到 {len(results)} 条'请示报告卡'相关记录:\n") +for r in results: + print(f"ID: {r['id']}") + print(f"名称: {r['name']}") + print(f"文件路径: {r['file_path']}") + print(f"父节点ID: {r['parent_id']}") + print() + +# 检查"XXX初核情况报告"的记录 +cursor.execute(""" + SELECT id, name, file_path, parent_id + FROM f_polic_file_config + WHERE tenant_id = %s AND name LIKE %s + ORDER BY id +""", (TENANT_ID, '%初核情况报告%')) + +results = cursor.fetchall() +print(f"\n找到 {len(results)} 条'初核情况报告'相关记录:\n") +for r in results: + print(f"ID: {r['id']}") + print(f"名称: {r['name']}") + print(f"文件路径: {r['file_path']}") + print(f"父节点ID: {r['parent_id']}") + print() + +cursor.close() +conn.close() + diff --git a/修复模板名称总结.md b/修复模板名称总结.md new file mode 100644 index 0000000..0ca2c3f --- /dev/null +++ b/修复模板名称总结.md @@ -0,0 +1,113 @@ +# 修复模板名称总结 + +## 任务完成情况 + +✅ **已完成所有任务** + +根据 template_finish/ 目录下的模板文件,检查并修复了数据库中 f_polic_file_config 表的 name 字段,确保与模板文档名称(去掉扩展名)完全一致。 + +## 修复结果 + +### 统计信息 +- **总文件数**: 21 +- **匹配成功**: 21 +- **更新数量**: 16 +- **未找到**: 0 + +### 更新的模板名称 + +1. **1.请示报告卡(XXX).docx** + - 旧名称: `1请示报告卡` + - 新名称: `1.请示报告卡(XXX)` + +2. **2.初步核实审批表(XXX).docx** + - 旧名称: `初步核实审批表` + - 新名称: `2.初步核实审批表(XXX)` + +3. **3.附件初核方案(XXX).docx** + - 旧名称: `附件初核方案` + - 新名称: `3.附件初核方案(XXX)` + +4. **1.请示报告卡(初核谈话).docx** + - 旧名称: `1请示报告卡` + - 新名称: `1.请示报告卡(初核谈话)` + +5. **2谈话审批表.docx** + - 旧名称: `谈话审批表` + - 新名称: `2谈话审批表` + +6. **3.谈话前安全风险评估表.docx** + - 旧名称: `谈话前安全风险评估表` + - 新名称: `3.谈话前安全风险评估表` + +7. **4.谈话方案.docx** + - 旧名称: `谈话方案` + - 新名称: `4.谈话方案` + +8. **5.谈话后安全风险评估表.docx** + - 旧名称: `谈话后安全风险评估表` + - 新名称: `5.谈话后安全风险评估表` + +9. **1.谈话笔录.docx** + - 旧名称: `谈话笔录` + - 新名称: `1.谈话笔录` + +10. **2.谈话询问对象情况摸底调查30问.docx** + - 旧名称: `谈话询问对象情况摸底调查30问` + - 新名称: `2.谈话询问对象情况摸底调查30问` + +11. **3.被谈话人权利义务告知书.docx** + - 旧名称: `被谈话人权利义务告知书` + - 新名称: `3.被谈话人权利义务告知书` + +12. **4.点对点交接单.docx** + - 旧名称: `点对点交接单` + - 新名称: `4.点对点交接单` + +13. **5.陪送交接单(新).docx** + - 旧名称: `陪送交接单` + - 新名称: `5.陪送交接单(新)` + +14. **6.2保密承诺书(谈话对象使用-中共党员用).docx** + - 旧名称: `2保密承诺书` + - 新名称: `6.2保密承诺书(谈话对象使用-中共党员用)` + +15. **7.办案人员-办案安全保密承诺书.docx** + - 旧名称: `办案人员-办案安全保密承诺书` + - 新名称: `7.办案人员-办案安全保密承诺书` + +16. **8.XXX初核情况报告.docx** + - 旧名称: `XXX初核情况报告` + - 新名称: `8.XXX初核情况报告` + +### 名称已正确的模板(无需更新) + +1. 谈话通知书第一联.docx +2. 谈话通知书第三联.docx +3. 谈话通知书第二联.docx +4. 6.1保密承诺书(谈话对象使用-非中共党员用).docx +5. 8-1请示报告卡(初核报告结论) .docx + +## 修复规则 + +- **文件名**: `1.请示报告卡(XXX).docx` +- **数据库name字段**: `1.请示报告卡(XXX)`(去掉扩展名,保留所有其他内容) + +确保: +- 保留数字前缀(如 "1."、"2.") +- 保留括号内容(如 "(XXX)"、"(初核谈话)") +- 保留所有标点符号和特殊字符 +- 只去掉文件扩展名(.docx) + +## 脚本文件 + +**fix_template_names.py** - 修复模板名称的脚本 +- 扫描 template_finish 目录下的所有 .docx 文件 +- 获取每个文件的完整名称(去掉扩展名) +- 检查数据库中对应的记录 +- 如果 name 字段不匹配,更新为正确的名称 + +## 完成时间 + +2025年12月11日 + diff --git a/处理保密承诺书模板总结.md b/处理保密承诺书模板总结.md new file mode 100644 index 0000000..37fe0e8 --- /dev/null +++ b/处理保密承诺书模板总结.md @@ -0,0 +1,75 @@ +# 处理保密承诺书(非中共党员用)模板总结 + +## 任务完成情况 + +✅ **已完成所有任务** + +1. ✅ 解析文档中的占位符 +2. ✅ 上传文档到MinIO +3. ✅ 更新数据库记录 + +## 处理结果 + +### 文件信息 +- **文件路径**: `template_finish/2-初核模版/2.谈话审批/走读式谈话流程/6.1保密承诺书(谈话对象使用-非中共党员用).docx` +- **模板名称**: `6.1保密承诺书(谈话对象使用-非中共党员用)` +- **父节点ID**: `1765273962716807` (走读式谈话流程) + +### 占位符提取结果 +找到 **5个占位符**,全部成功匹配到数据库字段: + +1. `{{ target_contact }}` - 被核查人联系方式 +2. `{{ target_id_number }}` - 被核查人身份证号 +3. `{{ target_name }}` - 被核查人姓名 +4. `{{ target_organization_and_position }}` - 被核查人单位及职务 +5. `{{ target_political_status }}` - 被核查人政治面貌 + +### MinIO上传结果 +- **MinIO路径**: `/615873064429507639/TEMPLATE/2025/12/6.1保密承诺书(谈话对象使用-非中共党员用).docx` +- **上传状态**: ✅ 成功 + +### 数据库更新结果 +- **模板ID**: `1765434655750341` +- **创建状态**: ✅ 新创建 +- **字段关联**: ✅ 5个字段全部关联成功 + +## 数据库记录详情 + +### f_polic_file_config 表 +- **id**: 1765434655750341 +- **tenant_id**: 615873064429507639 +- **parent_id**: 1765273962716807 (走读式谈话流程) +- **name**: 6.1保密承诺书(谈话对象使用-非中共党员用) +- **file_path**: /615873064429507639/TEMPLATE/2025/12/6.1保密承诺书(谈话对象使用-非中共党员用).docx +- **state**: 1 (启用) + +### f_polic_file_field 表 +已创建5条关联记录,关联的字段ID: +- target_contact +- target_id_number +- target_name +- target_organization_and_position +- target_political_status + +## 层级结构 + +该模板现在位于: +``` +📁 2-初核模版 +└── 📁 2.谈话审批 + └── 📁 走读式谈话流程 + └── 📄 6.1保密承诺书(谈话对象使用-非中共党员用) (ID: 1765434655750341) +``` + +## 脚本文件 + +**process_confidentiality_commitment_non_party.py** - 处理该模板的专用脚本 +- 提取占位符 +- 上传到MinIO +- 创建/更新数据库记录 +- 建立字段关联关系 + +## 完成时间 + +2025年12月11日 + diff --git a/更新模板层级结构总结.md b/更新模板层级结构总结.md new file mode 100644 index 0000000..0545070 --- /dev/null +++ b/更新模板层级结构总结.md @@ -0,0 +1,124 @@ +# 更新模板层级结构总结 + +## 任务完成情况 + +✅ **已完成主要任务** + +1. ✅ 根据 template_finish/ 目录结构更新 f_polic_file_config 表的层级结构 +2. ✅ 创建目录节点(一级、二级、三级) +3. ✅ 更新文件的 parent_id,建立正确的层级关系 + +## 目录结构 + +根据 `template_finish/2-初核模版/` 目录,层级结构如下: + +### 一级节点 +- **2-初核模版** (ID: 1765273961277310) + +### 二级节点 +- **1.初核请示** (ID: 1765431558933731) - parent: 2-初核模版 +- **2.谈话审批** (ID: 1765431558825578) - parent: 2-初核模版 +- **3.初核结论** (ID: 1765431559135346) - parent: 2-初核模版 + +### 三级节点 +- **谈话通知书** (ID: 1765273962774249) - parent: 2.谈话审批 +- **走读式谈话审批** (ID: 1765273962700431) - parent: 2.谈话审批 +- **走读式谈话流程** (ID: 1765273962716807) - parent: 2.谈话审批 + +## 文件节点分布 + +### 1.初核请示 下的文件 +- 初步核实审批表 (ID: 1765425918287774) +- 附件初核方案 (ID: 1765425918483987) +- 1请示报告卡 (ID: 1765425930118581) - 注意:此文件在多个位置出现 + +### 2.谈话审批/谈话通知书 下的文件 +- 谈话通知书第一联 (ID: 1765273963625524) +- 谈话通知书第二联 (ID: 1765273963825806) +- 谈话通知书第三联 (ID: 1765273963038891) + +### 2.谈话审批/走读式谈话审批 下的文件 +- 1请示报告卡 (ID: 1765425930118581) - 注意:与"1.初核请示"下的文件重复 +- 谈话审批表 (ID: 1765425918217504) +- 谈话前安全风险评估表 (ID: 1765425918902422) +- 谈话方案 (ID: 1765425918745496) +- 谈话后安全风险评估表 (ID: 1765425919242549) + +### 2.谈话审批/走读式谈话流程 下的文件 +- 谈话笔录 (ID: 1765425918749247) +- 谈话询问对象情况摸底调查30问 (ID: 1765425918921697) +- 被谈话人权利义务告知书 (ID: 1765425930059797) +- 点对点交接单 (ID: 1765425919512780) +- 陪送交接单 (ID: 1765425919389484) +- 办案人员-办案安全保密承诺书 (ID: 1765425919629084) +- 8.XXX初核情况报告 (ID: 1765273963158289) - 注意:此文件位置可能不正确 + +### 3.初核结论 下的文件 +- 8-1请示报告卡(初核报告结论) (ID: 1765273962631542) +- XXX初核情况报告 (ID: 1765425930460962) + +## 已知问题 + +1. **缺失文件**: 以下文件在数据库中未找到: + - 6.1保密承诺书(谈话对象使用-非中共党员用).docx + + 这个文件可能在数据库中不存在,或者名称不同。数据库中只有"2保密承诺书"(ID: 1765425919729046),可能是"6.2保密承诺书(谈话对象使用-中共党员用)"的简化名称。 + +2. **重复文件已解决**: "1请示报告卡"现在正确地在两个位置: + - 1.初核请示 下 (ID: 1765425930118581) - 对应文件"1.请示报告卡(XXX).docx" + - 走读式谈话审批 下 (ID: 1765432134276990) - 对应文件"1.请示报告卡(初核谈话).docx" + + 这两个是不同的文件,现在已正确区分。 + +## 执行结果 + +- **创建的目录节点**: 3个(1.初核请示、2.谈话审批、3.初核结论) +- **更新的目录节点**: 4个(更新了parent_id) +- **更新的文件节点**: 20个(更新了parent_id) +- **未找到的文件**: 1个(6.1保密承诺书(谈话对象使用-非中共党员用).docx) + +## 最终层级结构 + +``` +📁 2-初核模版 +├── 📁 1.初核请示 +│ ├── 📄 1请示报告卡 (ID: 1765425930118581) +│ ├── 📄 初步核实审批表 (ID: 1765425918287774) +│ └── 📄 附件初核方案 (ID: 1765425918483987) +├── 📁 2.谈话审批 +│ ├── 📁 谈话通知书 +│ │ ├── 📄 谈话通知书第一联 (ID: 1765273963625524) +│ │ ├── 📄 谈话通知书第二联 (ID: 1765273963825806) +│ │ └── 📄 谈话通知书第三联 (ID: 1765273963038891) +│ ├── 📁 走读式谈话审批 +│ │ ├── 📄 1请示报告卡 (ID: 1765432134276990) +│ │ ├── 📄 谈话审批表 (ID: 1765425918217504) +│ │ ├── 📄 谈话前安全风险评估表 (ID: 1765425918902422) +│ │ ├── 📄 谈话方案 (ID: 1765425918745496) +│ │ └── 📄 谈话后安全风险评估表 (ID: 1765425919242549) +│ └── 📁 走读式谈话流程 +│ ├── 📄 2保密承诺书 (ID: 1765425919729046) +│ ├── 📄 办案人员-办案安全保密承诺书 (ID: 1765425919629084) +│ ├── 📄 点对点交接单 (ID: 1765425919512780) +│ ├── 📄 被谈话人权利义务告知书 (ID: 1765425930059797) +│ ├── 📄 谈话笔录 (ID: 1765425918749247) +│ ├── 📄 谈话询问对象情况摸底调查30问 (ID: 1765425918921697) +│ └── 📄 陪送交接单 (ID: 1765425919389484) +└── 📁 3.初核结论 + ├── 📄 8-1请示报告卡(初核报告结论) (ID: 1765273962631542) + └── 📄 XXX初核情况报告 (ID: 1765425930460962) +``` + +## 脚本文件 + +**update_template_hierarchy_final.py** - 更新层级结构的最终版本脚本 +- 扫描 template_finish 目录结构 +- 使用 file_path(MinIO路径)作为唯一标识匹配文件 +- 创建/更新目录节点 +- 更新文件的 parent_id +- 正确处理同名但不同路径的文件(如"1请示报告卡"在不同目录下) + +## 完成时间 + +2025年12月11日 + diff --git a/清理重复模板总结.md b/清理重复模板总结.md new file mode 100644 index 0000000..0a60c7b --- /dev/null +++ b/清理重复模板总结.md @@ -0,0 +1,139 @@ +# 清理重复和无效模板总结 + +## 任务完成情况 + +✅ **已完成所有任务** + +1. ✅ 删除 f_polic_file_config 表中的重复数据 +2. ✅ 删除无效的模板数据 +3. ✅ 确保文档模板结构和 template_finish/ 文件夹对应 +4. ✅ 同步更新测试页面 + +## 执行结果 + +### 1. 清理前状态 +- **数据库中的模板总数**: 53个 +- **唯一模板名称**: 24个 +- **重复模板**: 4组(共29个重复记录) + +### 2. 清理操作 +- **删除重复模板**: 29个 +- **删除关联关系**: 136条 +- **标记无效模板**: 6个 + +### 3. 清理后状态 +- **数据库中的模板总数**: 24个 +- **唯一模板名称**: 24个 +- **启用的模板**: 18个(有filePath的) +- **禁用的模板**: 6个(无效模板) + +## 清理的重复模板详情 + +### 1. 请示报告卡 +- **保留**: ID 1765425930118581 (1请示报告卡) +- **删除**: ID 1765425917608094 (请示报告卡) + +### 2. 保密承诺书 +- **保留**: ID 1765425919729046 (2保密承诺书) +- **删除**: ID 1765425919731077 (1保密承诺书) + +### 3. 谈话审批表 +- **保留**: ID 1765425918217504 (谈话审批表) +- **删除**: + - ID 1765273961492987 (2谈话审批表) + - ID 1765273964441714 (2谈话审批表) + +### 4. 其他重复模板(标准化后名称相同) +- **删除**: 26个重复模板,包括: + - 多个版本的"初步核实审批表" + - 多个版本的"请示报告卡" + - 多个版本的"谈话通知书" + - 多个版本的"谈话笔录" + - 多个版本的"保密承诺书" + - 等等 + +## 标记的无效模板 + +以下模板不在 template_finish 文件夹中,已标记为无效(state=0): + +1. 2-初核模版 (ID: 1765273961277310) +2. 走读式谈话审批 (ID: 1765273962700431) +3. 走读式谈话流程 (ID: 1765273962716807) +4. 谈话通知书 (ID: 1765273962774249) +5. 8.XXX初核情况报告 (ID: 1765273963158289) +6. 2保密承诺书 (ID: 1765425919729046) + +## 最终启用的模板列表(18个) + +1. ✅ 1请示报告卡 (ID: 1765425930118581) +2. ✅ 8-1请示报告卡(初核报告结论) (ID: 1765273962631542) +3. ✅ XXX初核情况报告 (ID: 1765425930460962) +4. ✅ 初步核实审批表 (ID: 1765425918287774) +5. ✅ 办案人员-办案安全保密承诺书 (ID: 1765425919629084) +6. ✅ 点对点交接单 (ID: 1765425919512780) +7. ✅ 被谈话人权利义务告知书 (ID: 1765425930059797) +8. ✅ 谈话前安全风险评估表 (ID: 1765425918902422) +9. ✅ 谈话后安全风险评估表 (ID: 1765425919242549) +10. ✅ 谈话审批表 (ID: 1765425918217504) +11. ✅ 谈话方案 (ID: 1765425918745496) +12. ✅ 谈话笔录 (ID: 1765425918749247) +13. ✅ 谈话询问对象情况摸底调查30问 (ID: 1765425918921697) +14. ✅ 谈话通知书第一联 (ID: 1765273963625524) +15. ✅ 谈话通知书第三联 (ID: 1765273963038891) +16. ✅ 谈话通知书第二联 (ID: 1765273963825806) +17. ✅ 附件初核方案 (ID: 1765425918483987) +18. ✅ 陪送交接单 (ID: 1765425919389484) + +## 测试页面更新 + +### 更新内容 +1. ✅ 移除了硬编码的默认fileId(因为旧的ID已被删除) +2. ✅ 改进了文件列表加载逻辑 +3. ✅ 自动加载所有可用的模板文件 + +### 功能说明 +- **自动加载**: 页面加载时自动从 `/api/file-configs` 接口获取所有可用的模板 +- **手动加载**: 点击"加载全部可用模板"按钮可以重新加载模板列表 +- **过滤**: 只显示有filePath的模板(确保模板文件已上传到MinIO) + +## 数据库表更新情况 + +### f_polic_file_config 表 +- **删除**: 29条重复记录 +- **更新**: 6条记录标记为无效(state=0) +- **最终状态**: 24条记录(18个启用,6个禁用) + +### f_polic_file_field 表 +- **删除**: 136条关联关系(随重复模板一起删除) + +## 脚本文件 + +### 主要脚本 +1. **cleanup_duplicate_templates.py** - 清理脚本 + - 扫描 template_finish 文件夹 + - 识别重复模板 + - 删除重复记录 + - 标记无效模板 + +2. **validate_and_update_templates.py** - 校验和更新脚本 + - 重新校验模板和字段关联 + - 上传模板到MinIO + - 更新数据库配置 + +## 注意事项 + +1. **重复判断标准**: 使用标准化后的模板名称进行判断(去掉括号、数字前缀等) +2. **保留策略**: 保留最新的、启用的模板记录 +3. **无效模板**: 不在 template_finish 文件夹中的模板会被标记为无效,但不会删除(保留历史记录) +4. **关联关系**: 删除模板时会自动删除相关的字段关联关系 + +## 后续建议 + +1. **定期清理**: 建议定期运行 `cleanup_duplicate_templates.py` 脚本,确保没有重复数据 +2. **新增模板**: 新增模板时,确保模板文件放在 `template_finish` 文件夹中,然后运行 `validate_and_update_templates.py` +3. **测试验证**: 每次清理后,建议测试文档生成功能,确保所有模板都能正常工作 + +## 完成时间 + +2025年12月11日 +