feat: redesign db admin import flow

This commit is contained in:
Codex Agent 2025-11-13 19:21:59 +08:00
parent fd82b757fe
commit 66cc871e47
2 changed files with 1186 additions and 38 deletions

View File

@ -6,7 +6,7 @@ import time
from io import BytesIO from io import BytesIO
from flask import Blueprint, jsonify, request, send_file from flask import Blueprint, jsonify, request, send_file
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from typing import Any, Dict from typing import Any, Dict, Optional
from lawrisk.api.auth import login_required, get_current_user from lawrisk.api.auth import login_required, get_current_user
from lawrisk.services.lawrisk_v2_service import search_v2, list_regions from lawrisk.services.lawrisk_v2_service import search_v2, list_regions
@ -14,6 +14,7 @@ from lawrisk.services.licensing_repo import (
list_permits_for_region, list_permits_for_region,
load_permits_and_risks, load_permits_and_risks,
list_region_theme_options, list_region_theme_options,
list_region_permit_catalog,
load_theme_payload, load_theme_payload,
create_checkpoint, create_checkpoint,
list_checkpoints, list_checkpoints,
@ -26,6 +27,8 @@ from lawrisk.services.licensing_repo import (
start_permit_import_session, start_permit_import_session,
commit_permit_import_session, commit_permit_import_session,
fetch_permit_file, fetch_permit_file,
describe_permit_import_session,
resolve_region_permit_theme,
) )
from lawrisk.services.lawrisk_service import suggest_questions_embed from lawrisk.services.lawrisk_service import suggest_questions_embed
@ -220,27 +223,35 @@ def admin_themes():
@v2_bp.route('/admin/permits', methods=['GET']) @v2_bp.route('/admin/permits', methods=['GET'])
def admin_permits(): def admin_permits():
"""Get permits for a specific region-theme combination.""" """Get permits for a region. Optional theme filter keeps backward compatibility."""
region_value = request.args.get("region") or request.args.get("region_id") region_value = request.args.get("region") or request.args.get("region_id")
theme_value = request.args.get("theme") or request.args.get("theme_id") theme_value = request.args.get("theme") or request.args.get("theme_id")
if not region_value or not theme_value: if not region_value or (isinstance(region_value, str) and not region_value.strip()):
return jsonify({"success": False, "message": "region and theme are required", "data": {}}), 400 return jsonify({"success": False, "message": "region is required", "data": {}}), 400
region_token = region_value.strip() if isinstance(region_value, str) else str(region_value) region_token = region_value.strip() if isinstance(region_value, str) else str(region_value)
theme_token = theme_value.strip() if isinstance(theme_value, str) else str(theme_value) theme_token = (
theme_value.strip()
if theme_value and isinstance(theme_value, str)
else (str(theme_value) if theme_value is not None else None)
)
try: try:
permits = load_permits_and_risks(region_token, theme_token) if theme_token:
permits = load_permits_and_risks(region_token, theme_token)
return jsonify({ data = {
"success": True,
"data": {
"region": region_token, "region": region_token,
"theme": theme_token, "theme": theme_token,
"permits": permits "permits": permits,
} }
}) else:
catalog = list_region_permit_catalog(region_token)
data = {
"region": region_token,
"permits": catalog,
}
return jsonify({"success": True, "data": data})
except Exception as exc: except Exception as exc:
print(f"admin_permits error: {exc}") print(f"admin_permits error: {exc}")
return jsonify({"success": False, "message": str(exc)}), 500 return jsonify({"success": False, "message": str(exc)}), 500
@ -297,6 +308,35 @@ def admin_permit_import_template():
return jsonify({"success": False, "message": "模板文件暂时无法下载"}), 500 return jsonify({"success": False, "message": "模板文件暂时无法下载"}), 500
def _build_import_preview_response(session_token: str):
"""Internal helper to build preview response JSON."""
try:
data = describe_permit_import_session(session_token)
return jsonify({"success": True, "data": data})
except ValueError as exc:
return jsonify({"success": False, "message": str(exc)}), 400
except Exception as exc:
print(f"admin_permit_import_preview error: {exc}")
return jsonify({"success": False, "message": "无法加载预览数据"}), 500
@v2_bp.route('/admin/permit-import/session/<session_id>/preview', methods=['GET'])
def admin_permit_import_preview(session_id: str):
"""Return parsed workbook preview along with theme options (path param)."""
if not session_id:
return jsonify({"success": False, "message": "session_id 不能为空"}), 400
return _build_import_preview_response(session_id)
@v2_bp.route('/admin/permit-import/preview', methods=['GET'])
def admin_permit_import_preview_query():
"""Return preview via query string for compatibility."""
session_id = request.args.get("session_id") or request.args.get("sessionId")
if not session_id:
return jsonify({"success": False, "message": "session_id 不能为空"}), 400
return _build_import_preview_response(session_id)
@v2_bp.route('/admin/permit-import/commit', methods=['POST']) @v2_bp.route('/admin/permit-import/commit', methods=['POST'])
def admin_permit_import_commit(): def admin_permit_import_commit():
"""Commit an import session with selected sheets.""" """Commit an import session with selected sheets."""
@ -306,6 +346,7 @@ def admin_permit_import_commit():
overrides = payload.get('overrides') or {} overrides = payload.get('overrides') or {}
edited_by = payload.get('edited_by') or payload.get('editedBy') edited_by = payload.get('edited_by') or payload.get('editedBy')
change_summary = payload.get('change_summary') or payload.get('changeSummary') change_summary = payload.get('change_summary') or payload.get('changeSummary')
theme_bindings = payload.get('theme_bindings') or payload.get('themeBindings') or {}
if isinstance(sheet_names, str): if isinstance(sheet_names, str):
sheet_names = [sheet_names] sheet_names = [sheet_names]
@ -317,6 +358,7 @@ def admin_permit_import_commit():
overrides=overrides, overrides=overrides,
edited_by=edited_by, edited_by=edited_by,
change_summary=change_summary, change_summary=change_summary,
theme_bindings=theme_bindings,
) )
return jsonify({"success": True, "data": data}) return jsonify({"success": True, "data": data})
except ValueError as exc: except ValueError as exc:
@ -366,27 +408,58 @@ def admin_permit_details():
theme_value = request.args.get("theme") or request.args.get("theme_id") theme_value = request.args.get("theme") or request.args.get("theme_id")
permit_value = request.args.get("permit") or request.args.get("permit_id") permit_value = request.args.get("permit") or request.args.get("permit_id")
if not region_value or not theme_value or not permit_value: if not region_value or not permit_value:
return jsonify({"success": False, "message": "region, theme, and permit are required", "data": {}}), 400 return jsonify({"success": False, "message": "region and permit are required", "data": {}}), 400
region_token = region_value.strip() if isinstance(region_value, str) else str(region_value) region_token = region_value.strip() if isinstance(region_value, str) else str(region_value)
theme_token = theme_value.strip() if isinstance(theme_value, str) else str(theme_value)
permit_token = permit_value.strip() if isinstance(permit_value, str) else str(permit_value) permit_token = permit_value.strip() if isinstance(permit_value, str) else str(permit_value)
theme_token: Optional[str]
theme_display = None
if theme_value is None or (isinstance(theme_value, str) and not theme_value.strip()):
resolved = resolve_region_permit_theme(region_token, permit_token)
if not resolved or not resolved.get("id"):
return jsonify({"success": False, "message": "未找到许可所属主题", "data": {}}), 404
theme_token = resolved["id"]
theme_display = resolved
else:
theme_token = theme_value.strip() if isinstance(theme_value, str) else str(theme_value)
try: try:
permits = load_permits_and_risks(region_token, theme_token, permit_token) # 始终加载全部主题,详情页需要展示主题列表并高亮当前主题
permits = load_permits_and_risks(region_token, None, permit_token)
if not permits: if not permits:
return jsonify({"success": False, "message": "Permit not found", "data": {}}), 404 return jsonify({"success": False, "message": "Permit not found", "data": {}}), 404
return jsonify({ permit_payload = permits[0]
"success": True, payload = {
"data": { "region": region_token,
"region": region_token, "theme": theme_token,
"theme": theme_token, "permit": permit_payload,
"permit": permits[0] }
}
}) selected_theme_meta = None
theme_list = permit_payload.get("themes") or []
if theme_token:
for candidate in theme_list:
if candidate.get("id") == theme_token:
selected_theme_meta = candidate
break
if not selected_theme_meta and theme_display:
selected_theme_meta = theme_display
if not selected_theme_meta:
selected_theme_meta = permit_payload.get("theme")
if not selected_theme_meta and theme_list:
selected_theme_meta = theme_list[0]
if selected_theme_meta:
permit_payload["theme"] = selected_theme_meta
payload["theme_display"] = selected_theme_meta
payload["selected_theme_id"] = selected_theme_meta.get("id") or ""
elif theme_display:
payload["theme_display"] = theme_display
return jsonify({"success": True, "data": payload})
except Exception as exc: except Exception as exc:
print(f"admin_permit_details error: {exc}") print(f"admin_permit_details error: {exc}")
return jsonify({"success": False, "message": str(exc)}), 500 return jsonify({"success": False, "message": str(exc)}), 500
@ -426,12 +499,18 @@ def admin_delete_permit():
if not change_summary: if not change_summary:
change_summary = None change_summary = None
if not region_value or not theme_value or not permit_value: if not region_value or not permit_value:
return jsonify({"success": False, "message": "region_id, theme_id, permit_id 均为必填"}), 400 return jsonify({"success": False, "message": "region_id permit_id 均为必填"}), 400
region_id = region_value.strip() if isinstance(region_value, str) else str(region_value) region_id = region_value.strip() if isinstance(region_value, str) else str(region_value)
theme_id = theme_value.strip() if isinstance(theme_value, str) else str(theme_value)
permit_id = permit_value.strip() if isinstance(permit_value, str) else str(permit_value) permit_id = permit_value.strip() if isinstance(permit_value, str) else str(permit_value)
if theme_value:
theme_id = theme_value.strip() if isinstance(theme_value, str) else str(theme_value)
else:
resolved_theme = resolve_region_permit_theme(region_id, permit_id)
if not resolved_theme or not resolved_theme.get("id"):
return jsonify({"success": False, "message": "未找到许可所属主题,无法删除"}), 400
theme_id = resolved_theme["id"]
try: try:
result = delete_region_permit( result = delete_region_permit(

File diff suppressed because it is too large Load Diff