File size: 8,739 Bytes
b4f2238 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 | """案件导入 API。"""
from __future__ import annotations
import logging
import os
from typing import Any
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.utils import timezone
from ninja import Router, UploadedFile
from apps.oa_filing.models import CaseImportSession
from apps.oa_filing.schemas.case_import_schemas import (
CaseImportResponse,
CaseImportResult,
CaseImportSessionOut,
CasePreviewItem,
CasePreviewResponse,
)
logger = logging.getLogger("apps.oa_filing.api.case_import")
router = Router()
# Django-Q 默认 timeout=600 秒;OA 案件导入通常需要更长时间。
# 保持小于默认 retry(1200) 以降低重复执行风险。
CASE_IMPORT_TASK_TIMEOUT_SECONDS = int(os.environ.get("OA_CASE_IMPORT_TASK_TIMEOUT_SECONDS", "1100") or "1100")
@router.post("/case-import", response=CaseImportSessionOut)
def trigger_case_import(request: HttpRequest) -> Any:
"""触发从OA导入案件(预览模式)。
接收上传的Excel文件,解析案件编号,预览匹配结果。
"""
import json
from django.db.models import Q
from apps.organization.models import AccountCredential
if not request.user.is_authenticated:
return {"error": "未登录"}
lawyer_id = getattr(request.user, "id", None)
if lawyer_id is None:
return {"error": "无效用户"}
# 查找用户的 jtn.com 凭证
credential = AccountCredential.objects.filter(
Q(account__icontains="jtn.com") | Q(url__icontains="jtn.com"),
lawyer_id=lawyer_id,
).first()
if not credential:
return {"error": "未找到金诚同达OA账号凭证"}
# 获取上传的文件
file: UploadedFile | None = request.FILES.get("file")
if not file:
return {"error": "未上传文件"}
# 保存上传的文件
import uuid
from pathlib import Path
from django.conf import settings
upload_dir = Path(settings.BASE_DIR) / "media" / "oa_imports"
upload_dir.mkdir(parents=True, exist_ok=True)
filename = f"{uuid.uuid4().hex}_{file.name}"
file_path = upload_dir / filename
with open(file_path, "wb") as f:
for chunk in file.chunks():
f.write(chunk)
# 创建导入会话
from apps.organization.models import Lawyer
lawyer = Lawyer.objects.get(pk=lawyer_id)
session = CaseImportSession.objects.create(
lawyer=lawyer,
credential=credential,
status="pending",
uploaded_filename=file.name,
)
logger.info("创建案件导入会话: session_id=%d filename=%s", session.id, file.name)
# 启动后台任务进行预览
from django_q.tasks import async_task
async_task(
"apps.oa_filing.tasks.run_case_import_preview_task",
session.id,
str(file_path),
timeout=CASE_IMPORT_TASK_TIMEOUT_SECONDS,
task_name=f"oa_case_import_preview_{session.id}",
)
return session
@router.get("/case-import/{session_id}", response=CaseImportSessionOut)
def get_case_import_session(request: HttpRequest, session_id: int) -> Any:
"""查询案件导入会话状态。"""
return CaseImportSession.objects.get(pk=session_id)
@router.post("/case-import/{session_id}/execute")
def execute_case_import(request: HttpRequest, session_id: int) -> HttpResponse:
"""执行案件导入。
对预览阶段标记为 unmatched 的案件,从OA提取数据并创建/更新合同。
"""
import json
if not request.user.is_authenticated:
return JsonResponse({"error": "未登录"}, status=401)
lawyer_id = getattr(request.user, "id", None)
if lawyer_id is None:
return JsonResponse({"error": "无效用户"}, status=400)
# 获取会话
try:
session = CaseImportSession.objects.get(pk=session_id)
except CaseImportSession.DoesNotExist:
return JsonResponse({"error": "会话不存在"}, status=404)
# 解析请求体
try:
body = json.loads(request.body)
case_nos = body.get("case_nos", [])
matched_case_nos = body.get("matched_case_nos", [])
except Exception:
return JsonResponse({"error": "无效的请求数据"}, status=400)
if not case_nos:
return JsonResponse({"error": "案件编号列表为空"}, status=400)
# 启动后台任务执行导入
from django_q.tasks import async_task
async_task(
"apps.oa_filing.tasks.run_case_import_task",
session.id,
case_nos,
matched_case_nos=matched_case_nos,
timeout=CASE_IMPORT_TASK_TIMEOUT_SECONDS,
task_name=f"oa_case_import_{session.id}",
)
logger.info("启动案件导入任务: session_id=%d case_nos=%d", session_id, len(case_nos))
return JsonResponse(
{
"message": "导入任务已启动",
"session_id": session_id,
}
)
@router.get("/case-import/{session_id}/preview")
def get_case_import_preview(request: HttpRequest, session_id: int) -> JsonResponse:
"""获取案件导入预览结果。"""
try:
session = CaseImportSession.objects.get(pk=session_id)
except CaseImportSession.DoesNotExist:
return JsonResponse({"error": "会话不存在"}, status=404)
result_data = session.result_data or {}
preview_list = result_data.get("preview", [])
preview_items = [
CasePreviewItem(
case_no=item.get("case_no", ""),
status=item.get("status", "error"),
existing_contract_id=item.get("existing_contract_id"),
customer_names=item.get("customer_names", []),
error_message=item.get("error_message", ""),
)
for item in preview_list
]
total = len(preview_items)
matched = sum(1 for item in preview_items if item.status == "matched")
unmatched = sum(1 for item in preview_items if item.status == "unmatched")
response = CasePreviewResponse(
total_cases=total,
matched=matched,
unmatched=unmatched,
preview=preview_items,
)
return JsonResponse(response.model_dump())
@router.post("/case-import/{session_id}/batch-create")
def batch_create_cases(request: HttpRequest, session_id: int) -> Any:
"""批量创建案件(通过API调用避免异步上下文问题)。
接收案件数据列表,在新的HTTP请求中处理,不受Django-Q异步上下文影响。
"""
import json
from apps.oa_filing.models import CaseImportSession
# 获取会话
try:
session = CaseImportSession.objects.get(pk=session_id)
except CaseImportSession.DoesNotExist:
return {"error": "会话不存在"}
# 解析请求体
try:
body = json.loads(request.body)
cases = body.get("cases", [])
except Exception:
return {"error": "无效的请求数据"}
from apps.oa_filing.services.case_import_service import CaseImportService
service = CaseImportService(session)
results: list[CaseImportResult] = []
success_count = 0
skip_count = 0
error_count = 0
for case_data in cases:
case_no = case_data.get("case_no", "")
try:
from apps.oa_filing.services.oa_scripts.jtn_case_import import OACaseData
# 构造 OACaseData
oa_data = OACaseData(case_no=case_no, keyid="")
# 创建/更新
contract_id = service._create_or_update_case(oa_data)
if contract_id:
results.append(
CaseImportResult(
case_no=case_no,
status="created",
contract_id=contract_id,
)
)
success_count += 1
else:
results.append(
CaseImportResult(
case_no=case_no,
status="error",
message="创建合同失败",
)
)
error_count += 1
except Exception as exc:
logger.warning("批量创建案件异常 %s: %s", case_no, exc)
results.append(
CaseImportResult(
case_no=case_no,
status="error",
message=str(exc),
)
)
error_count += 1
# 更新会话
session.success_count = success_count
session.skip_count = skip_count
session.error_count = error_count
session.save()
return CaseImportResponse(
success=success_count,
failed=error_count,
skipped=skip_count,
total=len(cases),
details=results,
).model_dump()
|