fix(regulatory): 轮询时加载条件确认卡

This commit is contained in:
2026-06-07 11:27:12 +08:00
parent b8d711729d
commit 72f18167c5
4 changed files with 119 additions and 3 deletions

View File

@@ -23,8 +23,7 @@ def batch_status(request, batch_id: int):
workflow_type="regulatory_review",
workflow_batch_id=batch.pk,
).order_by("id")
return JsonResponse(
{
payload = {
"batch": {
"id": batch.pk,
"workflow_type": "regulatory_review",
@@ -46,7 +45,14 @@ def batch_status(request, batch_id: int):
for node in nodes
],
}
)
if batch.status == RegulatoryReviewBatch.Status.WAITING_USER and (batch.condition_json or {}).get("candidates"):
payload["condition_confirmation"] = {
"batch_id": batch.pk,
"batch_no": batch.batch_no,
"confirm_url": f"/api/review-agent/regulatory-review/{batch.pk}/conditions/",
"candidates": batch.condition_json["candidates"],
}
return JsonResponse(payload)
@require_http_methods(["POST"])

View File

@@ -644,6 +644,74 @@
selectWorkflowBatchIndex(activeIndex);
}
function ensureConditionConfirmationCard(confirmation) {
if (!chatScroll || !confirmation || !confirmation.candidates) {
return;
}
var cardId = "condition-confirmation-" + confirmation.batch_id;
if (document.getElementById(cardId)) {
return;
}
var article = document.createElement("article");
article.className = "message assistant";
article.id = cardId;
article.setAttribute("data-node-label", "AI 适用条件确认");
var avatar = document.createElement("div");
avatar.className = "message-avatar";
avatar.textContent = "AI";
var bubble = document.createElement("div");
bubble.className = "message-bubble";
var form = document.createElement("form");
form.className = "condition-confirm-form";
form.setAttribute("data-condition-confirm-form", "");
form.setAttribute("data-batch-id", confirmation.batch_id);
form.setAttribute("data-confirm-url", confirmation.confirm_url);
form.innerHTML =
'<input type="hidden" name="csrfmiddlewaretoken" value="' +
escapeHtml(new FormData(composer).get("csrfmiddlewaretoken") || "") +
'">' +
"<strong>适用条件确认</strong>" +
"<p>请确认 " +
escapeHtml(confirmation.batch_no || "") +
" 的产品类别、注册类型和临床评价路径,确认后我会继续法规核查。</p>" +
renderConditionFields(confirmation.candidates) +
'<button type="submit">确认并继续</button>' +
'<p class="condition-confirm-status" data-condition-confirm-status></p>';
bubble.appendChild(form);
article.appendChild(avatar);
article.appendChild(bubble);
chatScroll.appendChild(article);
bindConditionConfirmForms();
scrollChatToBottom();
}
function renderConditionFields(candidates) {
var html = "";
Object.keys(candidates || {}).forEach(function (field) {
var config = candidates[field] || {};
html += "<label><span>" + escapeHtml(config.label || field) + "</span>";
if (config.input_type === "select") {
html += '<select name="' + escapeHtml(field) + '">';
(config.options || []).forEach(function (option) {
var selected = option === config.suggested ? " selected" : "";
html += '<option value="' + escapeHtml(option) + '"' + selected + ">" + escapeHtml(option) + "</option>";
});
html += "</select>";
} else {
html +=
'<input type="text" name="' +
escapeHtml(field) +
'" value="' +
escapeHtml(config.suggested || "") +
'">';
}
html += "</label>";
});
return html;
}
async function refreshWorkflowCard(batchId, workflow_type) {
if (!summaryPanel || !batchId) {
return "";
@@ -662,6 +730,9 @@
return "";
}
var payload = await response.json();
if (payload.condition_confirmation) {
ensureConditionConfirmationCard(payload.condition_confirmation);
}
var card = ensureWorkflowCard({
batch_id: payload.batch.id,
batch_no: payload.batch.batch_no,

View File

@@ -159,5 +159,7 @@ def test_frontend_selects_status_url_by_workflow_type():
assert "statusUrlForWorkflow" in script
assert "bindConditionConfirmForms" in script
assert "data-condition-confirm-form" in script
assert "ensureConditionConfirmationCard" in script
assert "condition_confirmation" in script
assert "bindRectificationActionButtons" in script
assert "data-rectification-action" in script

View File

@@ -43,3 +43,40 @@ def test_regulatory_batch_status_requires_owner(client, django_user_model):
assert payload["batch"]["workflow_type"] == "regulatory_review"
assert payload["batch"]["batch_no"] == "RR-STATUS"
assert payload["nodes"][0]["node_code"] == "prepare"
def test_regulatory_batch_status_exposes_condition_confirmation(client, django_user_model):
owner = django_user_model.objects.create_user(username="owner", password="pass")
conversation = Conversation.objects.create(user=owner, title="会话")
summary = FileSummaryBatch.objects.create(
conversation=conversation,
user=owner,
batch_no="FS-OK",
status=FileSummaryBatch.Status.SUCCESS,
)
batch = RegulatoryReviewBatch.objects.create(
conversation=conversation,
user=owner,
source_summary_batch=summary,
batch_no="RR-WAIT",
status=RegulatoryReviewBatch.Status.WAITING_USER,
condition_json={
"confirmed": False,
"candidates": {
"product_category": {
"label": "产品类别",
"input_type": "select",
"options": ["体外诊断试剂", "医疗器械", "其他"],
"suggested": "体外诊断试剂",
}
},
},
)
client.force_login(owner)
response = client.get(reverse("regulatory_review_batch_status", args=[batch.pk]))
payload = response.json()
assert payload["batch"]["status"] == RegulatoryReviewBatch.Status.WAITING_USER
assert payload["condition_confirmation"]["batch_id"] == batch.pk
assert payload["condition_confirmation"]["candidates"]["product_category"]["suggested"] == "体外诊断试剂"