|
|
@@ -1335,7 +1335,7 @@ def get_lineage(member_id):
|
|
|
FROM family_relation_info r
|
|
|
JOIN family_member_info c ON r.child_mid = c.id
|
|
|
WHERE r.parent_mid = %s AND r.relation_type IN (1, 2) AND c.id != %s
|
|
|
- ORDER BY c.id
|
|
|
+ ORDER BY COALESCE(r.child_order, 99999), c.id
|
|
|
LIMIT 30
|
|
|
""", (grandparent['id'], parent['id']))
|
|
|
parent_siblings = cursor.fetchall()
|
|
|
@@ -1383,7 +1383,7 @@ def get_lineage(member_id):
|
|
|
FROM family_relation_info r
|
|
|
JOIN family_member_info c ON r.child_mid = c.id
|
|
|
WHERE r.parent_mid = %s AND r.relation_type IN (1, 2)
|
|
|
- ORDER BY c.id
|
|
|
+ ORDER BY COALESCE(r.child_order, 99999), c.id
|
|
|
LIMIT 30
|
|
|
""", (member_id,))
|
|
|
children = cursor.fetchall()
|
|
|
@@ -1422,7 +1422,7 @@ def get_lineage(member_id):
|
|
|
FROM family_relation_info r
|
|
|
JOIN family_member_info c ON r.child_mid = c.id
|
|
|
WHERE r.parent_mid = %s AND r.relation_type IN (1, 2) AND c.id != %s
|
|
|
- ORDER BY c.id
|
|
|
+ ORDER BY COALESCE(r.child_order, 99999), c.id
|
|
|
LIMIT 30
|
|
|
""", (parent_id, member_id))
|
|
|
siblings = cursor.fetchall()
|
|
|
@@ -1472,7 +1472,7 @@ def get_descendants(parent_id):
|
|
|
FROM family_relation_info r
|
|
|
JOIN family_member_info c ON r.child_mid = c.id
|
|
|
WHERE r.parent_mid = %s AND r.relation_type IN (1, 2) AND c.id NOT IN ({placeholders})
|
|
|
- ORDER BY c.id
|
|
|
+ ORDER BY COALESCE(r.child_order, 99999), c.id
|
|
|
LIMIT 20
|
|
|
""", (parent_id,) + tuple(excluded_list))
|
|
|
else:
|
|
|
@@ -1482,7 +1482,7 @@ def get_descendants(parent_id):
|
|
|
FROM family_relation_info r
|
|
|
JOIN family_member_info c ON r.child_mid = c.id
|
|
|
WHERE r.parent_mid = %s AND r.relation_type IN (1, 2)
|
|
|
- ORDER BY c.id
|
|
|
+ ORDER BY COALESCE(r.child_order, 99999), c.id
|
|
|
LIMIT 20
|
|
|
""", (parent_id,))
|
|
|
|
|
|
@@ -2167,17 +2167,18 @@ def add_member():
|
|
|
# 录入关系(支持多条)
|
|
|
sql_relation = """
|
|
|
INSERT INTO family_relation_info
|
|
|
- (parent_mid, child_mid, relation_type, sub_relation_type, source_mid, generation_diff)
|
|
|
- VALUES (%s, %s, %s, %s, %s, %s)
|
|
|
+ (parent_mid, child_mid, relation_type, sub_relation_type, source_mid, generation_diff, child_order)
|
|
|
+ VALUES (%s, %s, %s, %s, %s, %s, %s)
|
|
|
"""
|
|
|
-
|
|
|
+
|
|
|
for rel in relations:
|
|
|
rel_type = rel['relation_type']
|
|
|
parent_mid = rel['parent_mid']
|
|
|
sub_relation_type = rel['sub_relation_type']
|
|
|
+ child_order = rel.get('child_order') if rel_type in [1, 2] else None
|
|
|
gen_diff = 1 if rel_type in [1, 2] else 0
|
|
|
- print(f"[Add Member] Inserting relation: parent_mid={parent_mid}, child_mid={member_id}, relation_type={rel_type}, sub_relation_type={sub_relation_type}")
|
|
|
- cursor.execute(sql_relation, (parent_mid, member_id, rel_type, sub_relation_type, member_id, gen_diff))
|
|
|
+ print(f"[Add Member] Inserting relation: parent_mid={parent_mid}, child_mid={member_id}, relation_type={rel_type}, sub_relation_type={sub_relation_type}, child_order={child_order}")
|
|
|
+ cursor.execute(sql_relation, (parent_mid, member_id, rel_type, sub_relation_type, member_id, gen_diff, child_order))
|
|
|
|
|
|
# Update AI Record Status if applicable
|
|
|
source_record_id = data.get('source_record_id')
|
|
|
@@ -2265,26 +2266,31 @@ def edit_member(member_id):
|
|
|
parent_mid = request.form.get(f'relations[{i}][parent_mid]')
|
|
|
rel_type = request.form.get(f'relations[{i}][relation_type]')
|
|
|
sub_rel_type = request.form.get(f'relations[{i}][sub_relation_type]', '0')
|
|
|
-
|
|
|
+ child_order_raw = request.form.get(f'relations[{i}][child_order]', '')
|
|
|
+
|
|
|
if not parent_mid or not rel_type:
|
|
|
break
|
|
|
-
|
|
|
+
|
|
|
+ child_order = int(child_order_raw) if child_order_raw.strip().isdigit() else None
|
|
|
relations.append({
|
|
|
'parent_mid': int(parent_mid),
|
|
|
'relation_type': int(rel_type),
|
|
|
- 'sub_relation_type': int(sub_rel_type)
|
|
|
+ 'sub_relation_type': int(sub_rel_type),
|
|
|
+ 'child_order': child_order,
|
|
|
})
|
|
|
i += 1
|
|
|
-
|
|
|
+
|
|
|
# For backward compatibility
|
|
|
if not relations:
|
|
|
related_mid = request.form.get('related_mid')
|
|
|
relation_type = request.form.get('relation_type')
|
|
|
if related_mid and relation_type:
|
|
|
+ child_order_raw = request.form.get('child_order', '')
|
|
|
relations.append({
|
|
|
'parent_mid': int(related_mid),
|
|
|
'relation_type': int(relation_type),
|
|
|
- 'sub_relation_type': int(request.form.get('sub_relation_type', '0'))
|
|
|
+ 'sub_relation_type': int(request.form.get('sub_relation_type', '0')),
|
|
|
+ 'child_order': int(child_order_raw) if child_order_raw.strip().isdigit() else None,
|
|
|
})
|
|
|
|
|
|
# 年龄校验逻辑
|
|
|
@@ -2364,20 +2370,21 @@ def edit_member(member_id):
|
|
|
# 更新关系(支持多条)
|
|
|
print(f"[Edit Member] Deleting existing relations for member ID: {member_id}")
|
|
|
cursor.execute("DELETE FROM family_relation_info WHERE source_mid = %s", (member_id,))
|
|
|
-
|
|
|
+
|
|
|
sql_relation = """
|
|
|
INSERT INTO family_relation_info
|
|
|
- (parent_mid, child_mid, relation_type, sub_relation_type, source_mid, generation_diff)
|
|
|
- VALUES (%s, %s, %s, %s, %s, %s)
|
|
|
+ (parent_mid, child_mid, relation_type, sub_relation_type, source_mid, generation_diff, child_order)
|
|
|
+ VALUES (%s, %s, %s, %s, %s, %s, %s)
|
|
|
"""
|
|
|
-
|
|
|
+
|
|
|
for rel in relations:
|
|
|
rel_type = rel['relation_type']
|
|
|
parent_mid = rel['parent_mid']
|
|
|
sub_relation_type = rel['sub_relation_type']
|
|
|
+ child_order = rel.get('child_order') if rel_type in [1, 2] else None
|
|
|
gen_diff = 1 if rel_type in [1, 2] else 0
|
|
|
- print(f"[Edit Member] Inserting relation: parent_mid={parent_mid}, child_mid={member_id}, relation_type={rel_type}, sub_relation_type={sub_relation_type}")
|
|
|
- cursor.execute(sql_relation, (parent_mid, member_id, rel_type, sub_relation_type, member_id, gen_diff))
|
|
|
+ print(f"[Edit Member] Inserting relation: parent_mid={parent_mid}, child_mid={member_id}, relation_type={rel_type}, sub_relation_type={sub_relation_type}, child_order={child_order}")
|
|
|
+ cursor.execute(sql_relation, (parent_mid, member_id, rel_type, sub_relation_type, member_id, gen_diff, child_order))
|
|
|
|
|
|
# Update AI Record Status if applicable
|
|
|
source_record_id = data.get('source_record_id')
|
|
|
@@ -3308,8 +3315,8 @@ def add_settlement():
|
|
|
with conn.cursor() as cursor:
|
|
|
cursor.execute("""
|
|
|
INSERT INTO family_settlements
|
|
|
- (name, region, latitude, longitude, population, representative_id, description, surname_type, new_surname)
|
|
|
- VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
|
+ (name, region, latitude, longitude, population, representative_id, description, surname_type, new_surname, enthusiastic_members)
|
|
|
+ VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
|
""", (
|
|
|
data.get('name'),
|
|
|
data.get('region'),
|
|
|
@@ -3319,7 +3326,8 @@ def add_settlement():
|
|
|
data.get('representative_id') or None,
|
|
|
data.get('description'),
|
|
|
data.get('surname_type') or 0,
|
|
|
- data.get('new_surname') or None
|
|
|
+ data.get('new_surname') or None,
|
|
|
+ data.get('enthusiastic_members') or None
|
|
|
))
|
|
|
conn.commit()
|
|
|
return jsonify({"success": True, "message": "添加成功"})
|
|
|
@@ -3343,7 +3351,7 @@ def update_settlement(id):
|
|
|
UPDATE family_settlements
|
|
|
SET name=%s, region=%s, latitude=%s, longitude=%s,
|
|
|
population=%s, representative_id=%s, description=%s,
|
|
|
- surname_type=%s, new_surname=%s
|
|
|
+ surname_type=%s, new_surname=%s, enthusiastic_members=%s
|
|
|
WHERE id=%s
|
|
|
""", (
|
|
|
data.get('name'),
|
|
|
@@ -3355,6 +3363,7 @@ def update_settlement(id):
|
|
|
data.get('description'),
|
|
|
data.get('surname_type') or 0,
|
|
|
data.get('new_surname') or None,
|
|
|
+ data.get('enthusiastic_members') or None,
|
|
|
id
|
|
|
))
|
|
|
conn.commit()
|
|
|
@@ -3416,6 +3425,48 @@ def init_batch_task_table():
|
|
|
# 初始化表
|
|
|
init_batch_task_table()
|
|
|
|
|
|
+def migrate_child_order_column():
|
|
|
+ """为 family_relation_info 表添加 child_order 字段(如不存在)"""
|
|
|
+ conn = get_db_connection()
|
|
|
+ try:
|
|
|
+ with conn.cursor() as cursor:
|
|
|
+ cursor.execute("SHOW COLUMNS FROM family_relation_info LIKE 'child_order'")
|
|
|
+ if not cursor.fetchone():
|
|
|
+ cursor.execute(
|
|
|
+ "ALTER TABLE family_relation_info ADD COLUMN child_order INT DEFAULT NULL COMMENT '第几子,用于兄弟排序'"
|
|
|
+ )
|
|
|
+ conn.commit()
|
|
|
+ print("[DB Migrate] Added child_order column to family_relation_info")
|
|
|
+ else:
|
|
|
+ print("[DB Migrate] child_order column already exists")
|
|
|
+ except Exception as e:
|
|
|
+ print(f"[DB Migrate] Error adding child_order: {e}")
|
|
|
+ finally:
|
|
|
+ conn.close()
|
|
|
+
|
|
|
+migrate_child_order_column()
|
|
|
+
|
|
|
+def migrate_enthusiastic_members_column():
|
|
|
+ """为 family_settlements 表添加 enthusiastic_members 字段(如不存在)"""
|
|
|
+ conn = get_db_connection()
|
|
|
+ try:
|
|
|
+ with conn.cursor() as cursor:
|
|
|
+ cursor.execute("SHOW COLUMNS FROM family_settlements LIKE 'enthusiastic_members'")
|
|
|
+ if not cursor.fetchone():
|
|
|
+ cursor.execute(
|
|
|
+ "ALTER TABLE family_settlements ADD COLUMN enthusiastic_members TEXT DEFAULT NULL COMMENT '热心宗亲,多人以逗号分隔'"
|
|
|
+ )
|
|
|
+ conn.commit()
|
|
|
+ print("[DB Migrate] Added enthusiastic_members column to family_settlements")
|
|
|
+ else:
|
|
|
+ print("[DB Migrate] enthusiastic_members column already exists")
|
|
|
+ except Exception as e:
|
|
|
+ print(f"[DB Migrate] Error adding enthusiastic_members: {e}")
|
|
|
+ finally:
|
|
|
+ conn.close()
|
|
|
+
|
|
|
+migrate_enthusiastic_members_column()
|
|
|
+
|
|
|
def async_process_genealogy_task(task_id, member_ids, user_id):
|
|
|
"""异步处理族谱原文任务"""
|
|
|
results = []
|
|
|
@@ -4400,12 +4451,75 @@ def extract_single_genealogy(member_id):
|
|
|
finally:
|
|
|
conn.close()
|
|
|
|
|
|
+@app.route('/manager/api/members/batch_resume_task', methods=['GET'])
|
|
|
+def batch_resume_task():
|
|
|
+ """
|
|
|
+ 恢复因服务重启而中断的批量任务(GET,方便浏览器直接访问)。
|
|
|
+ 可选参数:?task_id=xxx 不传则自动找最近一条中断任务。
|
|
|
+ """
|
|
|
+ if 'user_id' not in session:
|
|
|
+ return jsonify({"success": False, "message": "Unauthorized"}), 401
|
|
|
+
|
|
|
+ task_id = request.args.get('task_id')
|
|
|
+
|
|
|
+ conn = get_db_connection()
|
|
|
+ try:
|
|
|
+ with conn.cursor() as cursor:
|
|
|
+ if task_id:
|
|
|
+ cursor.execute("""
|
|
|
+ SELECT task_id, status, last_processed_id, total_count, completed_count, failed_count
|
|
|
+ FROM batch_genealogy_task
|
|
|
+ WHERE task_id = %s AND user_id = %s
|
|
|
+ """, (task_id, session['user_id']))
|
|
|
+ else:
|
|
|
+ # 找最近一条中断的任务
|
|
|
+ cursor.execute("""
|
|
|
+ SELECT task_id, status, last_processed_id, total_count, completed_count, failed_count
|
|
|
+ FROM batch_genealogy_task
|
|
|
+ WHERE user_id = %s AND status IN ('pending', 'processing', 'interrupted')
|
|
|
+ ORDER BY created_at DESC
|
|
|
+ LIMIT 1
|
|
|
+ """, (session['user_id'],))
|
|
|
+ task = cursor.fetchone()
|
|
|
+
|
|
|
+ if not task:
|
|
|
+ return jsonify({"success": False, "message": "未找到可恢复的任务"}), 404
|
|
|
+
|
|
|
+ task_id = task['task_id']
|
|
|
+
|
|
|
+ # 重新标记为 processing,准备恢复线程
|
|
|
+ with conn.cursor() as cursor:
|
|
|
+ cursor.execute("""
|
|
|
+ UPDATE batch_genealogy_task
|
|
|
+ SET status = 'processing'
|
|
|
+ WHERE task_id = %s
|
|
|
+ """, (task_id,))
|
|
|
+ conn.commit()
|
|
|
+
|
|
|
+ threading.Thread(
|
|
|
+ target=async_process_all_empty_genealogy,
|
|
|
+ args=(task_id, session['user_id']),
|
|
|
+ daemon=True
|
|
|
+ ).start()
|
|
|
+
|
|
|
+ return jsonify({
|
|
|
+ "success": True,
|
|
|
+ "task_id": task_id,
|
|
|
+ "message": f"任务已从断点恢复(已完成 {task['completed_count']},从 last_processed_id={task['last_processed_id']} 继续)",
|
|
|
+ "last_processed_id": task['last_processed_id'],
|
|
|
+ "completed_count": task['completed_count'],
|
|
|
+ "total_count": task['total_count'],
|
|
|
+ })
|
|
|
+ finally:
|
|
|
+ conn.close()
|
|
|
+
|
|
|
+
|
|
|
@app.route('/manager/api/members/batch_process_all_empty', methods=['GET'])
|
|
|
def batch_process_all_empty():
|
|
|
"""简便批量处理接口:自动处理所有族谱原文为空的成员,支持断点续跑"""
|
|
|
if 'user_id' not in session:
|
|
|
return jsonify({"success": False, "message": "Unauthorized"}), 401
|
|
|
-
|
|
|
+
|
|
|
conn = get_db_connection()
|
|
|
try:
|
|
|
with conn.cursor() as cursor:
|
|
|
@@ -4417,7 +4531,7 @@ def batch_process_all_empty():
|
|
|
""")
|
|
|
result = cursor.fetchone()
|
|
|
total_empty = result['count'] if result else 0
|
|
|
-
|
|
|
+
|
|
|
cursor.execute("""
|
|
|
SELECT task_id, status, last_processed_id, total_count, completed_count, failed_count
|
|
|
FROM batch_genealogy_task
|
|
|
@@ -4426,33 +4540,34 @@ def batch_process_all_empty():
|
|
|
LIMIT 1
|
|
|
""", (session['user_id'],))
|
|
|
running_task = cursor.fetchone()
|
|
|
-
|
|
|
+
|
|
|
if running_task:
|
|
|
return jsonify({
|
|
|
"success": False,
|
|
|
- "message": "存在正在进行的任务",
|
|
|
+ "message": "存在正在进行的任务,若服务已重启可调用 POST /manager/api/members/batch_resume_task 恢复",
|
|
|
"task_id": running_task['task_id'],
|
|
|
"status": running_task['status'],
|
|
|
"last_processed_id": running_task['last_processed_id'],
|
|
|
"completed_count": running_task['completed_count'],
|
|
|
- "total_count": running_task['total_count']
|
|
|
+ "total_count": running_task['total_count'],
|
|
|
+ "resume_tip": "POST /manager/api/members/batch_resume_task body: {\"task_id\": \"" + running_task['task_id'] + "\"}"
|
|
|
})
|
|
|
-
|
|
|
+
|
|
|
task_id = str(uuid.uuid4())
|
|
|
-
|
|
|
+
|
|
|
with conn.cursor() as cursor:
|
|
|
cursor.execute("""
|
|
|
INSERT INTO batch_genealogy_task (task_id, user_id, status, total_count, last_processed_id)
|
|
|
VALUES (%s, %s, 'processing', %s, 0)
|
|
|
""", (task_id, session['user_id'], total_empty))
|
|
|
conn.commit()
|
|
|
-
|
|
|
+
|
|
|
threading.Thread(
|
|
|
target=async_process_all_empty_genealogy,
|
|
|
args=(task_id, session['user_id']),
|
|
|
daemon=True
|
|
|
).start()
|
|
|
-
|
|
|
+
|
|
|
return jsonify({
|
|
|
"success": True,
|
|
|
"task_id": task_id,
|