fix(regulatory): 轮询时加载条件确认卡
This commit is contained in:
@@ -23,8 +23,7 @@ def batch_status(request, batch_id: int):
|
|||||||
workflow_type="regulatory_review",
|
workflow_type="regulatory_review",
|
||||||
workflow_batch_id=batch.pk,
|
workflow_batch_id=batch.pk,
|
||||||
).order_by("id")
|
).order_by("id")
|
||||||
return JsonResponse(
|
payload = {
|
||||||
{
|
|
||||||
"batch": {
|
"batch": {
|
||||||
"id": batch.pk,
|
"id": batch.pk,
|
||||||
"workflow_type": "regulatory_review",
|
"workflow_type": "regulatory_review",
|
||||||
@@ -46,7 +45,14 @@ def batch_status(request, batch_id: int):
|
|||||||
for node in nodes
|
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"])
|
@require_http_methods(["POST"])
|
||||||
|
|||||||
@@ -644,6 +644,74 @@
|
|||||||
selectWorkflowBatchIndex(activeIndex);
|
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) {
|
async function refreshWorkflowCard(batchId, workflow_type) {
|
||||||
if (!summaryPanel || !batchId) {
|
if (!summaryPanel || !batchId) {
|
||||||
return "";
|
return "";
|
||||||
@@ -662,6 +730,9 @@
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
var payload = await response.json();
|
var payload = await response.json();
|
||||||
|
if (payload.condition_confirmation) {
|
||||||
|
ensureConditionConfirmationCard(payload.condition_confirmation);
|
||||||
|
}
|
||||||
var card = ensureWorkflowCard({
|
var card = ensureWorkflowCard({
|
||||||
batch_id: payload.batch.id,
|
batch_id: payload.batch.id,
|
||||||
batch_no: payload.batch.batch_no,
|
batch_no: payload.batch.batch_no,
|
||||||
|
|||||||
@@ -159,5 +159,7 @@ def test_frontend_selects_status_url_by_workflow_type():
|
|||||||
assert "statusUrlForWorkflow" in script
|
assert "statusUrlForWorkflow" in script
|
||||||
assert "bindConditionConfirmForms" in script
|
assert "bindConditionConfirmForms" in script
|
||||||
assert "data-condition-confirm-form" in script
|
assert "data-condition-confirm-form" in script
|
||||||
|
assert "ensureConditionConfirmationCard" in script
|
||||||
|
assert "condition_confirmation" in script
|
||||||
assert "bindRectificationActionButtons" in script
|
assert "bindRectificationActionButtons" in script
|
||||||
assert "data-rectification-action" in script
|
assert "data-rectification-action" in script
|
||||||
|
|||||||
@@ -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"]["workflow_type"] == "regulatory_review"
|
||||||
assert payload["batch"]["batch_no"] == "RR-STATUS"
|
assert payload["batch"]["batch_no"] == "RR-STATUS"
|
||||||
assert payload["nodes"][0]["node_code"] == "prepare"
|
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"] == "体外诊断试剂"
|
||||||
|
|||||||
Reference in New Issue
Block a user