feat: add report review publish flow
This commit is contained in:
@@ -515,6 +515,9 @@ export interface AdminReportListItem {
|
|||||||
report_entry_admin_name: string;
|
report_entry_admin_name: string;
|
||||||
report_entered_at: string;
|
report_entered_at: string;
|
||||||
trace_info_visible: boolean;
|
trace_info_visible: boolean;
|
||||||
|
reject_reason: string;
|
||||||
|
rejected_by_name: string;
|
||||||
|
rejected_at: string;
|
||||||
product_name: string;
|
product_name: string;
|
||||||
category_name: string;
|
category_name: string;
|
||||||
brand_name: string;
|
brand_name: string;
|
||||||
@@ -542,6 +545,9 @@ export interface AdminReportDetail {
|
|||||||
report_entry_admin_name: string;
|
report_entry_admin_name: string;
|
||||||
report_entered_at: string;
|
report_entered_at: string;
|
||||||
trace_info_visible: boolean;
|
trace_info_visible: boolean;
|
||||||
|
reject_reason: string;
|
||||||
|
rejected_by_name: string;
|
||||||
|
rejected_at: string;
|
||||||
};
|
};
|
||||||
product_info: Record<string, any>;
|
product_info: Record<string, any>;
|
||||||
result_info: Record<string, any>;
|
result_info: Record<string, any>;
|
||||||
@@ -565,6 +571,8 @@ export interface AdminReportDetail {
|
|||||||
report_page_url: string;
|
report_page_url: string;
|
||||||
verify_count: number;
|
verify_count: number;
|
||||||
};
|
};
|
||||||
|
audit_logs: AdminReportLog[];
|
||||||
|
change_logs: AdminReportLog[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminPublishReportResponse {
|
export interface AdminPublishReportResponse {
|
||||||
@@ -575,6 +583,18 @@ export interface AdminPublishReportResponse {
|
|||||||
report_page_url: string;
|
report_page_url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AdminReportLog {
|
||||||
|
id: number;
|
||||||
|
action: string;
|
||||||
|
action_text: string;
|
||||||
|
operator_id: number;
|
||||||
|
operator_name: string;
|
||||||
|
before_data: Record<string, any>;
|
||||||
|
after_data: Record<string, any>;
|
||||||
|
remark: string;
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AdminManualInspectionPayload {
|
export interface AdminManualInspectionPayload {
|
||||||
id?: number;
|
id?: number;
|
||||||
report_header: {
|
report_header: {
|
||||||
@@ -1864,6 +1884,16 @@ export const adminApi = {
|
|||||||
data: AdminPublishReportResponse & { material_tag?: AdminMaterialTagCode | null };
|
data: AdminPublishReportResponse & { material_tag?: AdminMaterialTagCode | null };
|
||||||
}>;
|
}>;
|
||||||
},
|
},
|
||||||
|
rejectReport(id: number, reason: string) {
|
||||||
|
return request.post("/api/admin/report/reject", {
|
||||||
|
id,
|
||||||
|
reason,
|
||||||
|
}) as Promise<{
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: { id: number; report_status: string; reject_reason: string };
|
||||||
|
}>;
|
||||||
|
},
|
||||||
updateReportTraceVisibility(id: number, visible: boolean) {
|
updateReportTraceVisibility(id: number, visible: boolean) {
|
||||||
return request.post("/api/admin/report/trace-visibility", {
|
return request.post("/api/admin/report/trace-visibility", {
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -256,6 +256,15 @@ const isTaskReadonly = computed(() => {
|
|||||||
if (!detail.value) {
|
if (!detail.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reportStatus = detail.value.report_summary?.report_status || "";
|
||||||
|
if (["draft", "pending_publish", "updated", "rejected"].includes(reportStatus)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (reportStatus === "published") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
["submitted", "completed"].includes(detail.value.task_info.status) ||
|
["submitted", "completed"].includes(detail.value.task_info.status) ||
|
||||||
(Boolean(detail.value.task_info.submitted_at) && Boolean(detail.value.report_summary))
|
(Boolean(detail.value.task_info.submitted_at) && Boolean(detail.value.report_summary))
|
||||||
@@ -799,7 +808,7 @@ async function submitResult(action: "save" | "submit") {
|
|||||||
key_points: normalizedKeyPoints(),
|
key_points: normalizedKeyPoints(),
|
||||||
...(qrInput ? { qr_input: qrInput } : {}),
|
...(qrInput ? { qr_input: qrInput } : {}),
|
||||||
});
|
});
|
||||||
ElMessage.success(response.message || (action === "submit" ? "验真吊牌已绑定,报告已发布" : "结论已保存"));
|
ElMessage.success(response.message || (action === "submit" ? "验真吊牌已绑定,报告待管理员发布" : "结论已保存"));
|
||||||
await loadDetail(detail.value.task_info.id);
|
await loadDetail(detail.value.task_info.id);
|
||||||
await fetchTasks();
|
await fetchTasks();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -817,7 +826,7 @@ async function publishCurrentTaskWithMaterialTag(qrInput: string) {
|
|||||||
id: detail.value.task_info.id,
|
id: detail.value.task_info.id,
|
||||||
qr_input: qrInput,
|
qr_input: qrInput,
|
||||||
});
|
});
|
||||||
ElMessage.success("验真吊牌已绑定,报告已发布");
|
ElMessage.success("验真吊牌已绑定,报告待管理员发布");
|
||||||
await loadDetail(detail.value.task_info.id);
|
await loadDetail(detail.value.task_info.id);
|
||||||
await fetchTasks();
|
await fetchTasks();
|
||||||
}
|
}
|
||||||
@@ -835,7 +844,7 @@ async function bindMaterialTag() {
|
|||||||
materialTagInput.value = "";
|
materialTagInput.value = "";
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
ElMessage.error(error?.message || "验真吊牌绑定或报告发布失败");
|
ElMessage.error(error?.message || "验真吊牌绑定失败");
|
||||||
} finally {
|
} finally {
|
||||||
materialTagBinding.value = false;
|
materialTagBinding.value = false;
|
||||||
}
|
}
|
||||||
@@ -843,12 +852,12 @@ async function bindMaterialTag() {
|
|||||||
|
|
||||||
async function promptPublishMaterialTagInput() {
|
async function promptPublishMaterialTagInput() {
|
||||||
try {
|
try {
|
||||||
const result = await ElMessageBox.prompt("是否已鉴定完成并确定发布报告?", "绑定验真吊牌并发布报告", {
|
const result = await ElMessageBox.prompt("是否已鉴定完成并提交报告待发布?", "绑定验真吊牌并提交报告", {
|
||||||
type: "warning",
|
type: "warning",
|
||||||
inputPlaceholder: "请扫描验真吊牌二维码",
|
inputPlaceholder: "请扫描验真吊牌二维码",
|
||||||
inputPattern: /\S+/,
|
inputPattern: /\S+/,
|
||||||
inputErrorMessage: "请扫描验真吊牌二维码",
|
inputErrorMessage: "请扫描验真吊牌二维码",
|
||||||
confirmButtonText: "是的,去绑定验真吊牌",
|
confirmButtonText: "是的,绑定并提交",
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
closeOnClickModal: false,
|
closeOnClickModal: false,
|
||||||
});
|
});
|
||||||
@@ -904,7 +913,7 @@ async function submitZhongjianReport() {
|
|||||||
report_files: zhongjianReportFiles.value,
|
report_files: zhongjianReportFiles.value,
|
||||||
qr_input: qrInput,
|
qr_input: qrInput,
|
||||||
});
|
});
|
||||||
ElMessage.success(response.message || "验真吊牌已绑定,报告已发布");
|
ElMessage.success(response.message || "验真吊牌已绑定,报告待管理员发布");
|
||||||
await loadDetail(detail.value.task_info.id);
|
await loadDetail(detail.value.task_info.id);
|
||||||
await fetchTasks();
|
await fetchTasks();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -1124,7 +1133,7 @@ onMounted(async () => {
|
|||||||
<el-button plain @click="openAssigneeDialog">分配处理人</el-button>
|
<el-button plain @click="openAssigneeDialog">分配处理人</el-button>
|
||||||
<el-button v-if="canClaimTask" plain type="primary" :loading="assigneeSubmitting" @click="claimTask">认领给我</el-button>
|
<el-button v-if="canClaimTask" plain type="primary" :loading="assigneeSubmitting" @click="claimTask">认领给我</el-button>
|
||||||
<el-button @click="resetZhongjianReportForm">重置内容</el-button>
|
<el-button @click="resetZhongjianReportForm">重置内容</el-button>
|
||||||
<el-button type="primary" :loading="zhongjianReportSubmitting" @click="submitZhongjianReport">提交并发布报告</el-button>
|
<el-button type="primary" :loading="zhongjianReportSubmitting" @click="submitZhongjianReport">提交待发布报告</el-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<el-button plain @click="returnToResultWorkbench">返回结论操作</el-button>
|
<el-button plain @click="returnToResultWorkbench">返回结论操作</el-button>
|
||||||
@@ -1487,7 +1496,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<div class="task-form-block">
|
<div class="task-form-block">
|
||||||
<div class="task-form-block__title">吊牌绑定</div>
|
<div class="task-form-block__title">吊牌绑定</div>
|
||||||
<div class="task-panel__desc">提交结论或中检报告时扫描平台验真吊牌,绑定成功后发布报告。</div>
|
<div class="task-panel__desc">提交结论或中检报告时扫描平台验真吊牌,绑定成功后进入待发布。</div>
|
||||||
<div v-if="detail.material_tag" class="task-material-tag-bound">
|
<div v-if="detail.material_tag" class="task-material-tag-bound">
|
||||||
<div class="task-info-grid">
|
<div class="task-info-grid">
|
||||||
<div class="task-info-item task-info-item--full">
|
<div class="task-info-item task-info-item--full">
|
||||||
@@ -1672,7 +1681,7 @@ onMounted(async () => {
|
|||||||
<el-tab-pane v-if="isZhongjianTask" label="中检报告录入" name="zhongjian">
|
<el-tab-pane v-if="isZhongjianTask" label="中检报告录入" name="zhongjian">
|
||||||
<div :key="`zhongjian-${formRenderKey}`" class="task-form-stack">
|
<div :key="`zhongjian-${formRenderKey}`" class="task-form-stack">
|
||||||
<el-alert
|
<el-alert
|
||||||
title="请先在“填写结论”中补全物品信息、鉴定结论和模板项,再提交中检报告编号和文件;绑定吊牌成功后才会发布报告。"
|
title="请先在“填写结论”中补全物品信息、鉴定结论和模板项,再提交中检报告编号和文件;绑定吊牌成功后报告进入待发布。"
|
||||||
type="info"
|
type="info"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
show-icon
|
show-icon
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ const drawerVisible = ref(false);
|
|||||||
const inspectionDrawerVisible = ref(false);
|
const inspectionDrawerVisible = ref(false);
|
||||||
const inspectionSubmitting = ref(false);
|
const inspectionSubmitting = ref(false);
|
||||||
const publishingId = ref<number | null>(null);
|
const publishingId = ref<number | null>(null);
|
||||||
|
const rejectingId = ref<number | null>(null);
|
||||||
const traceVisibilitySavingId = ref<number | null>(null);
|
const traceVisibilitySavingId = ref<number | null>(null);
|
||||||
const detailQrDataUrl = ref("");
|
const detailQrDataUrl = ref("");
|
||||||
|
|
||||||
@@ -69,6 +70,9 @@ const inspectionForm = ref<AdminManualInspectionPayload>(createInspectionPayload
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const canPublishCurrentReport = computed(() => detail.value?.report_header.report_status === "pending_publish");
|
const canPublishCurrentReport = computed(() => detail.value?.report_header.report_status === "pending_publish");
|
||||||
|
const canRejectCurrentReport = computed(
|
||||||
|
() => detail.value?.report_header.report_type !== "inspection" && detail.value?.report_header.report_status === "pending_publish",
|
||||||
|
);
|
||||||
const canEditCurrentInspection = computed(
|
const canEditCurrentInspection = computed(
|
||||||
() => detail.value?.report_header.report_type === "inspection" && detail.value?.report_header.report_status !== "published",
|
() => detail.value?.report_header.report_type === "inspection" && detail.value?.report_header.report_status !== "published",
|
||||||
);
|
);
|
||||||
@@ -84,6 +88,7 @@ const statusOptions = [
|
|||||||
{ label: "全部状态", value: "" },
|
{ label: "全部状态", value: "" },
|
||||||
{ label: "已发布", value: "published" },
|
{ label: "已发布", value: "published" },
|
||||||
{ label: "待发布", value: "pending_publish" },
|
{ label: "待发布", value: "pending_publish" },
|
||||||
|
{ label: "已驳回", value: "rejected" },
|
||||||
{ label: "草稿中", value: "draft" },
|
{ label: "草稿中", value: "draft" },
|
||||||
{ label: "已更新", value: "updated" },
|
{ label: "已更新", value: "updated" },
|
||||||
{ label: "已作废", value: "invalid" },
|
{ label: "已作废", value: "invalid" },
|
||||||
@@ -280,6 +285,11 @@ type PublishReportTarget = Pick<AdminReportListItem, "id" | "report_status" | "r
|
|||||||
report_type: string;
|
report_type: string;
|
||||||
material_tag_bound: boolean;
|
material_tag_bound: boolean;
|
||||||
};
|
};
|
||||||
|
type RejectReportTarget = Pick<AdminReportListItem, "id" | "report_status" | "report_type"> | {
|
||||||
|
id: number;
|
||||||
|
report_status: string;
|
||||||
|
report_type: string;
|
||||||
|
};
|
||||||
type ReportTraceVisibilityTarget = Pick<AdminReportListItem, "id" | "trace_info_visible">;
|
type ReportTraceVisibilityTarget = Pick<AdminReportListItem, "id" | "trace_info_visible">;
|
||||||
|
|
||||||
async function promptReportMaterialTagInput() {
|
async function promptReportMaterialTagInput() {
|
||||||
@@ -346,6 +356,62 @@ async function publishReport(row: PublishReportTarget) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function rejectReport(row: RejectReportTarget) {
|
||||||
|
if (row.report_status !== "pending_publish") {
|
||||||
|
ElMessage.warning("仅待发布报告可以驳回");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (row.report_type === "inspection") {
|
||||||
|
ElMessage.warning("补录检查单不支持驳回复鉴");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let reason = "";
|
||||||
|
try {
|
||||||
|
const result = await ElMessageBox.prompt("请填写驳回原因,鉴定师将据此重新鉴定。", "驳回报告", {
|
||||||
|
type: "warning",
|
||||||
|
inputType: "textarea",
|
||||||
|
inputPlaceholder: "例如:结论说明不完整、图片附件不清晰",
|
||||||
|
inputPattern: /\S+/,
|
||||||
|
inputErrorMessage: "请填写驳回原因",
|
||||||
|
confirmButtonText: "确认驳回",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
closeOnClickModal: false,
|
||||||
|
});
|
||||||
|
reason = String(result.value || "").trim();
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rejectingId.value = row.id;
|
||||||
|
try {
|
||||||
|
const response = await adminApi.rejectReport(row.id, reason);
|
||||||
|
if (response.code !== 0) {
|
||||||
|
ElMessage.error(response.message || "报告驳回失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.success(response.message || "报告已驳回");
|
||||||
|
await fetchReports();
|
||||||
|
if (drawerVisible.value && detail.value?.report_header.id === row.id) {
|
||||||
|
await loadDetail(row.id);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
ElMessage.error(error?.message || "报告驳回失败");
|
||||||
|
} finally {
|
||||||
|
rejectingId.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function logSummary(data: Record<string, any>) {
|
||||||
|
const status = data.report_status || "-";
|
||||||
|
const version = data.report_version ? `v${data.report_version}` : "";
|
||||||
|
const publishTime = data.publish_time ? ` · ${data.publish_time}` : "";
|
||||||
|
const rejectReason = data.reject_reason || data.invalid_reason ? ` · ${data.reject_reason || data.invalid_reason}` : "";
|
||||||
|
return [status, version].filter(Boolean).join(" / ") + publishTime + rejectReason;
|
||||||
|
}
|
||||||
|
|
||||||
function switchValueToBoolean(value: unknown) {
|
function switchValueToBoolean(value: unknown) {
|
||||||
if (typeof value === "boolean") return value;
|
if (typeof value === "boolean") return value;
|
||||||
if (typeof value === "number") return value === 1;
|
if (typeof value === "number") return value === 1;
|
||||||
@@ -522,7 +588,7 @@ watch(
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="institution_name" label="出具机构" min-width="160" />
|
<el-table-column prop="institution_name" label="出具机构" min-width="160" />
|
||||||
<el-table-column prop="publish_time" label="发布时间" min-width="170" />
|
<el-table-column prop="publish_time" label="发布时间" min-width="170" />
|
||||||
<el-table-column label="操作" fixed="right" width="220">
|
<el-table-column label="操作" fixed="right" width="280">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button link type="primary" @click="openDetail(row)">查看详情</el-button>
|
<el-button link type="primary" @click="openDetail(row)">查看详情</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
@@ -534,7 +600,7 @@ watch(
|
|||||||
编辑检查单
|
编辑检查单
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="row.report_status === 'pending_publish'"
|
v-if="row.report_type !== 'inspection' && row.report_status === 'pending_publish'"
|
||||||
link
|
link
|
||||||
type="warning"
|
type="warning"
|
||||||
:loading="publishingId === row.id"
|
:loading="publishingId === row.id"
|
||||||
@@ -542,6 +608,15 @@ watch(
|
|||||||
>
|
>
|
||||||
发布报告
|
发布报告
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="row.report_type !== 'inspection' && row.report_status === 'pending_publish'"
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
:loading="rejectingId === row.id"
|
||||||
|
@click="rejectReport(row)"
|
||||||
|
>
|
||||||
|
驳回报告
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -566,6 +641,19 @@ watch(
|
|||||||
>
|
>
|
||||||
发布报告
|
发布报告
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="canRejectCurrentReport"
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
:loading="rejectingId === detail.report_header.id"
|
||||||
|
@click="rejectReport({
|
||||||
|
id: detail.report_header.id,
|
||||||
|
report_status: detail.report_header.report_status,
|
||||||
|
report_type: detail.report_header.report_type,
|
||||||
|
})"
|
||||||
|
>
|
||||||
|
驳回报告
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="detail-card">
|
<div class="detail-card">
|
||||||
@@ -588,6 +676,14 @@ watch(
|
|||||||
<OrderStatusTag :status="detail.report_header.report_status_text" />
|
<OrderStatusTag :status="detail.report_header.report_status_text" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="detail.report_header.reject_reason" class="detail-card__desc">
|
||||||
|
<div class="detail-label">驳回原因</div>
|
||||||
|
<div class="detail-value">{{ detail.report_header.reject_reason }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="detail.report_header.rejected_at" class="detail-card__desc">
|
||||||
|
<div class="detail-label">驳回信息</div>
|
||||||
|
<div class="detail-value">{{ detail.report_header.rejected_by_name || "-" }} / {{ detail.report_header.rejected_at }}</div>
|
||||||
|
</div>
|
||||||
<div class="detail-card__desc">
|
<div class="detail-card__desc">
|
||||||
<div class="detail-label">出具机构</div>
|
<div class="detail-label">出具机构</div>
|
||||||
<div class="detail-value">{{ detail.report_header.institution_name }}</div>
|
<div class="detail-value">{{ detail.report_header.institution_name }}</div>
|
||||||
@@ -634,6 +730,44 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-card detail-card--wide">
|
||||||
|
<div class="detail-card__title">审核记录</div>
|
||||||
|
<div v-if="detail.audit_logs.length" class="report-log-list">
|
||||||
|
<div v-for="log in detail.audit_logs" :key="`audit-${log.id}`" class="report-log-item">
|
||||||
|
<div class="report-log-main">
|
||||||
|
<span>{{ log.action_text }}</span>
|
||||||
|
<span>{{ log.operator_name || "-" }}</span>
|
||||||
|
<span>{{ log.created_at }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="log.remark" class="report-log-remark">{{ log.remark }}</div>
|
||||||
|
<div class="report-log-snapshot">{{ logSummary(log.after_data) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="detail-card__desc">
|
||||||
|
<div class="detail-value">暂无审核记录</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-card detail-card--wide">
|
||||||
|
<div class="detail-card__title">修改记录</div>
|
||||||
|
<div v-if="detail.change_logs.length" class="report-log-list">
|
||||||
|
<div v-for="log in detail.change_logs" :key="`change-${log.id}`" class="report-log-item">
|
||||||
|
<div class="report-log-main">
|
||||||
|
<span>{{ log.action_text }}</span>
|
||||||
|
<span>{{ log.operator_name || "-" }}</span>
|
||||||
|
<span>{{ log.created_at }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="log.remark" class="report-log-remark">{{ log.remark }}</div>
|
||||||
|
<div class="report-log-snapshot">
|
||||||
|
{{ logSummary(log.before_data) }} -> {{ logSummary(log.after_data) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="detail-card__desc">
|
||||||
|
<div class="detail-value">暂无修改记录</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="detail-card">
|
<div class="detail-card">
|
||||||
<div class="detail-card__title">商品信息</div>
|
<div class="detail-card__title">商品信息</div>
|
||||||
<div class="detail-card__desc">
|
<div class="detail-card__desc">
|
||||||
@@ -1068,4 +1202,39 @@ watch(
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-card--wide {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-log-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-log-item {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--admin-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fffdfa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-log-main {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
color: var(--admin-text-main);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-log-remark,
|
||||||
|
.report-log-snapshot {
|
||||||
|
color: var(--admin-text-subtle);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -434,8 +434,6 @@ class AppraisalTasksController
|
|||||||
Db::rollback();
|
Db::rollback();
|
||||||
return api_error('请先提交鉴定结论生成报告草稿', 422);
|
return api_error('请先提交鉴定结论生成报告草稿', 422);
|
||||||
}
|
}
|
||||||
$publish = $this->publishReportRecord($report, $request, false);
|
|
||||||
(new FulfillmentFlowService())->markReportPublished((int)$task['order_id'], $request);
|
|
||||||
Db::commit();
|
Db::commit();
|
||||||
} catch (\InvalidArgumentException $e) {
|
} catch (\InvalidArgumentException $e) {
|
||||||
Db::rollback();
|
Db::rollback();
|
||||||
@@ -445,14 +443,20 @@ class AppraisalTasksController
|
|||||||
return api_error($e->getMessage(), $e->getCode() ?: 404);
|
return api_error($e->getMessage(), $e->getCode() ?: 404);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
Db::rollback();
|
Db::rollback();
|
||||||
return api_error('验真吊牌绑定或报告发布失败', 500, ['detail' => $e->getMessage()]);
|
return api_error('验真吊牌绑定失败', 500, ['detail' => $e->getMessage()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return api_success([
|
return api_success([
|
||||||
'id' => $id,
|
'id' => $id,
|
||||||
'material_tag' => $tag,
|
'material_tag' => $tag,
|
||||||
'report' => $publish,
|
'report' => [
|
||||||
], '验真吊牌已绑定,报告已发布');
|
'id' => (int)$report['id'],
|
||||||
|
'report_status' => (string)$report['report_status'],
|
||||||
|
'publish_time' => (string)($report['publish_time'] ?? ''),
|
||||||
|
'verify_url' => '',
|
||||||
|
'report_page_url' => '',
|
||||||
|
],
|
||||||
|
], '验真吊牌已绑定,报告待管理员发布');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveZhongjianReport(Request $request)
|
public function saveZhongjianReport(Request $request)
|
||||||
@@ -571,8 +575,8 @@ class AppraisalTasksController
|
|||||||
}
|
}
|
||||||
$this->saveTaskKeyPoints($savedResultId, $keyPoints, $now);
|
$this->saveTaskKeyPoints($savedResultId, $keyPoints, $now);
|
||||||
|
|
||||||
$this->createOrUpdateReportDraft((int)$task['order_id'], $task, $resultPayload, $now);
|
$draftChange = $this->createOrUpdateReportDraft((int)$task['order_id'], $task, $resultPayload, $now);
|
||||||
$report = $this->findLatestAppraisalReport((int)$task['order_id']);
|
$report = $draftChange['report'];
|
||||||
if (!$report) {
|
if (!$report) {
|
||||||
Db::rollback();
|
Db::rollback();
|
||||||
return api_error('中检报告草稿生成失败', 500);
|
return api_error('中检报告草稿生成失败', 500);
|
||||||
@@ -612,16 +616,22 @@ class AppraisalTasksController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$tag = (new MaterialTagService())->bindTagToReportByTask($id, $qrInput, $request);
|
$tag = (new MaterialTagService())->bindTagToReportByTask($id, $qrInput, $request);
|
||||||
$publish = $this->publishReportRecord($freshReport, $request, false);
|
$this->insertReportLog((int)$freshReport['id'], $draftChange['action'], $draftChange['before'], $freshReport, $request, '报告已提交,待管理员发布');
|
||||||
(new FulfillmentFlowService())->markReportPublished((int)$task['order_id'], $request);
|
$this->insertReportLog((int)$freshReport['id'], 'submit', $draftChange['before'], $freshReport, $request, '鉴定师提交报告');
|
||||||
|
|
||||||
Db::commit();
|
Db::commit();
|
||||||
|
|
||||||
return api_success([
|
return api_success([
|
||||||
'id' => $id,
|
'id' => $id,
|
||||||
'material_tag' => $tag,
|
'material_tag' => $tag,
|
||||||
'report' => $publish,
|
'report' => [
|
||||||
], '验真吊牌已绑定,报告已发布');
|
'id' => (int)$freshReport['id'],
|
||||||
|
'report_status' => (string)$freshReport['report_status'],
|
||||||
|
'publish_time' => (string)($freshReport['publish_time'] ?? ''),
|
||||||
|
'verify_url' => '',
|
||||||
|
'report_page_url' => '',
|
||||||
|
],
|
||||||
|
], '报告已提交,待管理员发布');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
Db::rollback();
|
Db::rollback();
|
||||||
return api_error('中检报告录入失败', 500, ['detail' => $e->getMessage()]);
|
return api_error('中检报告录入失败', 500, ['detail' => $e->getMessage()]);
|
||||||
@@ -820,15 +830,16 @@ class AppraisalTasksController
|
|||||||
'created_at' => $now,
|
'created_at' => $now,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->createOrUpdateReportDraft((int)$task['order_id'], $task, $payload, $now);
|
$draftChange = $this->createOrUpdateReportDraft((int)$task['order_id'], $task, $payload, $now);
|
||||||
$report = $this->findLatestAppraisalReport((int)$task['order_id']);
|
$report = $draftChange['report'];
|
||||||
if (!$report) {
|
if (!$report) {
|
||||||
Db::rollback();
|
Db::rollback();
|
||||||
return api_error('报告草稿生成失败', 500);
|
return api_error('报告草稿生成失败', 500);
|
||||||
}
|
}
|
||||||
$tag = (new MaterialTagService())->bindTagToReportByTask($id, $qrInput, $request);
|
$tag = (new MaterialTagService())->bindTagToReportByTask($id, $qrInput, $request);
|
||||||
$publish = $this->publishReportRecord($report, $request, false);
|
$freshReport = $this->findLatestAppraisalReport((int)$task['order_id']) ?: $report;
|
||||||
(new FulfillmentFlowService())->markReportPublished((int)$task['order_id'], $request);
|
$this->insertReportLog((int)$freshReport['id'], $draftChange['action'], $draftChange['before'], $freshReport, $request, '报告已提交,待管理员发布');
|
||||||
|
$this->insertReportLog((int)$freshReport['id'], 'submit', $draftChange['before'], $freshReport, $request, '鉴定师提交报告');
|
||||||
|
|
||||||
Db::commit();
|
Db::commit();
|
||||||
(new EnterpriseWebhookService())->recordOrderEvent((int)$task['order_id'], 'appraisal_finished', [
|
(new EnterpriseWebhookService())->recordOrderEvent((int)$task['order_id'], 'appraisal_finished', [
|
||||||
@@ -839,8 +850,14 @@ class AppraisalTasksController
|
|||||||
return api_success([
|
return api_success([
|
||||||
'id' => $id,
|
'id' => $id,
|
||||||
'material_tag' => $tag,
|
'material_tag' => $tag,
|
||||||
'report' => $publish,
|
'report' => [
|
||||||
], '验真吊牌已绑定,报告已发布');
|
'id' => (int)$freshReport['id'],
|
||||||
|
'report_status' => (string)$freshReport['report_status'],
|
||||||
|
'publish_time' => (string)($freshReport['publish_time'] ?? ''),
|
||||||
|
'verify_url' => '',
|
||||||
|
'report_page_url' => '',
|
||||||
|
],
|
||||||
|
], '报告已提交,待管理员发布');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
Db::rollback();
|
Db::rollback();
|
||||||
return api_error('结论保存失败', 500, [
|
return api_error('结论保存失败', 500, [
|
||||||
@@ -1467,6 +1484,15 @@ class AppraisalTasksController
|
|||||||
$stage = (string)($task['task_stage'] ?? '');
|
$stage = (string)($task['task_stage'] ?? '');
|
||||||
$submittedAt = (string)($task['submitted_at'] ?? '');
|
$submittedAt = (string)($task['submitted_at'] ?? '');
|
||||||
$orderStatus = (string)($task['order_status'] ?? '');
|
$orderStatus = (string)($task['order_status'] ?? '');
|
||||||
|
$reportStatus = $report ? (string)($report['report_status'] ?? '') : '';
|
||||||
|
|
||||||
|
if ($reportStatus === 'published') {
|
||||||
|
return 'completed';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($reportStatus, ['draft', 'pending_publish', 'updated', 'rejected'], true)) {
|
||||||
|
return 'processing';
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
$submittedAt !== ''
|
$submittedAt !== ''
|
||||||
@@ -1504,6 +1530,7 @@ class AppraisalTasksController
|
|||||||
'draft' => '草稿中',
|
'draft' => '草稿中',
|
||||||
'pending_publish' => '待发布',
|
'pending_publish' => '待发布',
|
||||||
'published' => '已发布',
|
'published' => '已发布',
|
||||||
|
'rejected' => '已驳回',
|
||||||
'updated' => '已更新',
|
'updated' => '已更新',
|
||||||
'invalid' => '已作废',
|
'invalid' => '已作废',
|
||||||
default => $status,
|
default => $status,
|
||||||
@@ -1804,7 +1831,7 @@ class AppraisalTasksController
|
|||||||
return (string)Db::name($table)->where('id', $id)->value($field);
|
return (string)Db::name($table)->where('id', $id)->value($field);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createOrUpdateReportDraft(int $orderId, array $task, array $resultPayload, string $now): void
|
private function createOrUpdateReportDraft(int $orderId, array $task, array $resultPayload, string $now): array
|
||||||
{
|
{
|
||||||
$report = Db::name('reports')->where('order_id', $orderId)->order('id', 'desc')->find();
|
$report = Db::name('reports')->where('order_id', $orderId)->order('id', 'desc')->find();
|
||||||
$order = Db::name('orders')->where('id', $orderId)->find();
|
$order = Db::name('orders')->where('id', $orderId)->find();
|
||||||
@@ -1842,17 +1869,27 @@ class AppraisalTasksController
|
|||||||
'report_title' => $task['service_provider'] === 'zhongjian' ? '中检鉴定报告' : '安心验鉴定报告',
|
'report_title' => $task['service_provider'] === 'zhongjian' ? '中检鉴定报告' : '安心验鉴定报告',
|
||||||
'report_status' => 'pending_publish',
|
'report_status' => 'pending_publish',
|
||||||
'publish_time' => null,
|
'publish_time' => null,
|
||||||
|
'invalid_reason' => '',
|
||||||
|
'reject_reason' => '',
|
||||||
|
'rejected_by' => null,
|
||||||
|
'rejected_by_name' => '',
|
||||||
|
'rejected_at' => null,
|
||||||
'updated_at' => $now,
|
'updated_at' => $now,
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($report) {
|
if ($report) {
|
||||||
|
$beforeReport = $report;
|
||||||
|
$reportData['report_version'] = (int)($report['report_version'] ?? 1) + 1;
|
||||||
Db::name('reports')->where('id', $report['id'])->update($reportData);
|
Db::name('reports')->where('id', $report['id'])->update($reportData);
|
||||||
$reportId = (int)$report['id'];
|
$reportId = (int)$report['id'];
|
||||||
|
$logAction = 'update_draft';
|
||||||
} else {
|
} else {
|
||||||
$reportData['report_no'] = 'AXY-R-' . date('Ymd') . '-' . mt_rand(1000, 9999);
|
$reportData['report_no'] = 'AXY-R-' . date('Ymd') . '-' . mt_rand(1000, 9999);
|
||||||
$reportData['report_version'] = 1;
|
$reportData['report_version'] = 1;
|
||||||
$reportData['created_at'] = $now;
|
$reportData['created_at'] = $now;
|
||||||
$reportId = (int)Db::name('reports')->insertGetId($reportData);
|
$reportId = (int)Db::name('reports')->insertGetId($reportData);
|
||||||
|
$beforeReport = [];
|
||||||
|
$logAction = 'create_draft';
|
||||||
}
|
}
|
||||||
|
|
||||||
$contentPayload = [
|
$contentPayload = [
|
||||||
@@ -1892,6 +1929,12 @@ class AppraisalTasksController
|
|||||||
$contentPayload['created_at'] = $now;
|
$contentPayload['created_at'] = $now;
|
||||||
Db::name('report_contents')->insert($contentPayload);
|
Db::name('report_contents')->insert($contentPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'report' => Db::name('reports')->where('id', $reportId)->find() ?: [],
|
||||||
|
'before' => $beforeReport,
|
||||||
|
'action' => $logAction,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildAppraisalSnapshot(string $serviceProvider, string $fallbackTime, ?array $firstReviewTask, ?array $finalReviewTask): array
|
private function buildAppraisalSnapshot(string $serviceProvider, string $fallbackTime, ?array $firstReviewTask, ?array $finalReviewTask): array
|
||||||
@@ -1915,6 +1958,41 @@ class AppraisalTasksController
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function insertReportLog(int $reportId, string $action, array $before, array $after, Request $request, string $remark = ''): void
|
||||||
|
{
|
||||||
|
if ($reportId <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Db::name('report_logs')->insert([
|
||||||
|
'report_id' => $reportId,
|
||||||
|
'action' => $action,
|
||||||
|
'operator_id' => (int)$request->header('x-admin-id', 0) ?: null,
|
||||||
|
'operator_name' => trim((string)$request->header('x-admin-name', '')),
|
||||||
|
'before_data' => $before ? json_encode($this->reportLogSnapshot($before), JSON_UNESCAPED_UNICODE) : null,
|
||||||
|
'after_data' => $after ? json_encode($this->reportLogSnapshot($after), JSON_UNESCAPED_UNICODE) : null,
|
||||||
|
'remark' => mb_substr($remark, 0, 255),
|
||||||
|
'created_at' => date('Y-m-d H:i:s'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function reportLogSnapshot(array $report): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => (int)($report['id'] ?? 0),
|
||||||
|
'report_no' => (string)($report['report_no'] ?? ''),
|
||||||
|
'order_id' => (int)($report['order_id'] ?? 0),
|
||||||
|
'report_status' => (string)($report['report_status'] ?? ''),
|
||||||
|
'report_version' => (int)($report['report_version'] ?? 0),
|
||||||
|
'publish_time' => (string)($report['publish_time'] ?? ''),
|
||||||
|
'zhongjian_report_no' => (string)($report['zhongjian_report_no'] ?? ''),
|
||||||
|
'invalid_reason' => (string)($report['invalid_reason'] ?? ''),
|
||||||
|
'reject_reason' => (string)($report['reject_reason'] ?? ''),
|
||||||
|
'rejected_by_name' => (string)($report['rejected_by_name'] ?? ''),
|
||||||
|
'rejected_at' => (string)($report['rejected_at'] ?? ''),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
private function normalizeAssigneeName(?string $value): string
|
private function normalizeAssigneeName(?string $value): string
|
||||||
{
|
{
|
||||||
$name = trim((string)$value);
|
$name = trim((string)$value);
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ class ReportsController
|
|||||||
'r.report_entry_admin_name',
|
'r.report_entry_admin_name',
|
||||||
'r.report_entered_at',
|
'r.report_entered_at',
|
||||||
'r.trace_info_visible',
|
'r.trace_info_visible',
|
||||||
|
'r.invalid_reason',
|
||||||
|
'r.reject_reason',
|
||||||
|
'r.rejected_by_name',
|
||||||
|
'r.rejected_at',
|
||||||
'o.order_no',
|
'o.order_no',
|
||||||
'p.product_name',
|
'p.product_name',
|
||||||
'p.category_name',
|
'p.category_name',
|
||||||
@@ -85,6 +89,9 @@ class ReportsController
|
|||||||
'report_entry_admin_name' => (string)($item['report_entry_admin_name'] ?? ''),
|
'report_entry_admin_name' => (string)($item['report_entry_admin_name'] ?? ''),
|
||||||
'report_entered_at' => (string)($item['report_entered_at'] ?? ''),
|
'report_entered_at' => (string)($item['report_entered_at'] ?? ''),
|
||||||
'trace_info_visible' => (int)($item['trace_info_visible'] ?? 0) === 1,
|
'trace_info_visible' => (int)($item['trace_info_visible'] ?? 0) === 1,
|
||||||
|
'reject_reason' => (string)($item['reject_reason'] ?? $item['invalid_reason'] ?? ''),
|
||||||
|
'rejected_by_name' => (string)($item['rejected_by_name'] ?? ''),
|
||||||
|
'rejected_at' => (string)($item['rejected_at'] ?? ''),
|
||||||
'product_name' => $item['product_name'] ?: (string)($productSnapshot['product_name'] ?? ''),
|
'product_name' => $item['product_name'] ?: (string)($productSnapshot['product_name'] ?? ''),
|
||||||
'category_name' => $item['category_name'] ?: (string)($productSnapshot['category_name'] ?? ''),
|
'category_name' => $item['category_name'] ?: (string)($productSnapshot['category_name'] ?? ''),
|
||||||
'brand_name' => $item['brand_name'] ?: (string)($productSnapshot['brand_name'] ?? ''),
|
'brand_name' => $item['brand_name'] ?: (string)($productSnapshot['brand_name'] ?? ''),
|
||||||
@@ -137,6 +144,7 @@ class ReportsController
|
|||||||
$appraisalSnapshot = $this->enrichAppraisalSnapshot($report, $appraisalSnapshot);
|
$appraisalSnapshot = $this->enrichAppraisalSnapshot($report, $appraisalSnapshot);
|
||||||
$evidenceAttachments = $this->evidenceService()->normalize($content['evidence_attachments_json'] ?? null, $request);
|
$evidenceAttachments = $this->evidenceService()->normalize($content['evidence_attachments_json'] ?? null, $request);
|
||||||
$materialTag = (new MaterialTagService())->findBoundTagForReport($id);
|
$materialTag = (new MaterialTagService())->findBoundTagForReport($id);
|
||||||
|
$logs = $this->reportLogs($id);
|
||||||
|
|
||||||
$verify = Db::name('report_verifies')->where('report_id', $id)->find() ?: [];
|
$verify = Db::name('report_verifies')->where('report_id', $id)->find() ?: [];
|
||||||
if (($report['report_status'] ?? '') === 'published') {
|
if (($report['report_status'] ?? '') === 'published') {
|
||||||
@@ -172,6 +180,9 @@ class ReportsController
|
|||||||
'report_entry_admin_name' => (string)($report['report_entry_admin_name'] ?? ''),
|
'report_entry_admin_name' => (string)($report['report_entry_admin_name'] ?? ''),
|
||||||
'report_entered_at' => (string)($report['report_entered_at'] ?? ''),
|
'report_entered_at' => (string)($report['report_entered_at'] ?? ''),
|
||||||
'trace_info_visible' => (int)($report['trace_info_visible'] ?? 0) === 1,
|
'trace_info_visible' => (int)($report['trace_info_visible'] ?? 0) === 1,
|
||||||
|
'reject_reason' => (string)($report['reject_reason'] ?? $report['invalid_reason'] ?? ''),
|
||||||
|
'rejected_by_name' => (string)($report['rejected_by_name'] ?? ''),
|
||||||
|
'rejected_at' => (string)($report['rejected_at'] ?? ''),
|
||||||
],
|
],
|
||||||
'product_info' => $productSnapshot,
|
'product_info' => $productSnapshot,
|
||||||
'result_info' => $resultSnapshot,
|
'result_info' => $resultSnapshot,
|
||||||
@@ -188,6 +199,8 @@ class ReportsController
|
|||||||
'report_page_url' => $verify['report_page_url'] ?? $reportPageUrl,
|
'report_page_url' => $verify['report_page_url'] ?? $reportPageUrl,
|
||||||
'verify_count' => (int)($verify['verify_count'] ?? 0),
|
'verify_count' => (int)($verify['verify_count'] ?? 0),
|
||||||
],
|
],
|
||||||
|
'audit_logs' => array_values(array_filter($logs, fn(array $log) => in_array($log['action'], ['publish', 'reject'], true))),
|
||||||
|
'change_logs' => array_values(array_filter($logs, fn(array $log) => !in_array($log['action'], ['publish', 'reject'], true))),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,6 +425,7 @@ class ReportsController
|
|||||||
Db::rollback();
|
Db::rollback();
|
||||||
return api_error('报告不存在', 404);
|
return api_error('报告不存在', 404);
|
||||||
}
|
}
|
||||||
|
$beforeReport = $report;
|
||||||
|
|
||||||
if (!in_array($report['report_status'], ['draft', 'pending_publish', 'updated', 'published'], true)) {
|
if (!in_array($report['report_status'], ['draft', 'pending_publish', 'updated', 'published'], true)) {
|
||||||
Db::rollback();
|
Db::rollback();
|
||||||
@@ -446,10 +460,18 @@ class ReportsController
|
|||||||
Db::name('reports')->where('id', $id)->update([
|
Db::name('reports')->where('id', $id)->update([
|
||||||
'report_status' => 'published',
|
'report_status' => 'published',
|
||||||
'publish_time' => $effectivePublishTime,
|
'publish_time' => $effectivePublishTime,
|
||||||
|
'invalid_reason' => '',
|
||||||
|
'reject_reason' => '',
|
||||||
|
'rejected_by' => null,
|
||||||
|
'rejected_by_name' => '',
|
||||||
|
'rejected_at' => null,
|
||||||
'updated_at' => $now,
|
'updated_at' => $now,
|
||||||
]);
|
]);
|
||||||
$report['report_status'] = 'published';
|
$report = Db::name('reports')->where('id', $id)->find() ?: array_merge($report, [
|
||||||
$report['publish_time'] = $effectivePublishTime;
|
'report_status' => 'published',
|
||||||
|
'publish_time' => $effectivePublishTime,
|
||||||
|
]);
|
||||||
|
$this->insertReportLog($id, 'publish', $beforeReport, $report, $request, '管理员审核通过并发布报告');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($isOrderAppraisalReport) {
|
if ($isOrderAppraisalReport) {
|
||||||
@@ -532,18 +554,177 @@ class ReportsController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function reject(Request $request)
|
||||||
|
{
|
||||||
|
$id = (int)$request->input('id', 0);
|
||||||
|
$reason = trim((string)$request->input('reason', ''));
|
||||||
|
if (!$id) {
|
||||||
|
return api_error('报告 ID 不能为空', 422);
|
||||||
|
}
|
||||||
|
if ($reason === '') {
|
||||||
|
return api_error('驳回原因不能为空', 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$now = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
Db::startTrans();
|
||||||
|
try {
|
||||||
|
$report = Db::name('reports')->where('id', $id)->find();
|
||||||
|
if (!$report) {
|
||||||
|
Db::rollback();
|
||||||
|
return api_error('报告不存在', 404);
|
||||||
|
}
|
||||||
|
if (($report['report_status'] ?? '') !== 'pending_publish') {
|
||||||
|
Db::rollback();
|
||||||
|
return api_error('仅待发布报告可以驳回', 422);
|
||||||
|
}
|
||||||
|
if (($report['report_type'] ?? 'appraisal') !== 'appraisal' || (int)($report['order_id'] ?? 0) <= 0) {
|
||||||
|
Db::rollback();
|
||||||
|
return api_error('仅订单鉴定报告可以驳回复鉴', 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$beforeReport = $report;
|
||||||
|
Db::name('reports')->where('id', $id)->update([
|
||||||
|
'report_status' => 'rejected',
|
||||||
|
'publish_time' => null,
|
||||||
|
'invalid_reason' => mb_substr($reason, 0, 255),
|
||||||
|
'reject_reason' => mb_substr($reason, 0, 500),
|
||||||
|
'rejected_by' => (int)$request->header('x-admin-id', 0) ?: null,
|
||||||
|
'rejected_by_name' => trim((string)$request->header('x-admin-name', '')),
|
||||||
|
'rejected_at' => $now,
|
||||||
|
'updated_at' => $now,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$task = Db::name('appraisal_tasks')
|
||||||
|
->where('order_id', (int)$report['order_id'])
|
||||||
|
->order('id', 'desc')
|
||||||
|
->find();
|
||||||
|
if ($task) {
|
||||||
|
Db::name('appraisal_tasks')->where('id', (int)$task['id'])->update([
|
||||||
|
'status' => 'processing',
|
||||||
|
'submitted_at' => null,
|
||||||
|
'updated_at' => $now,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Db::name('orders')->where('id', (int)$report['order_id'])->update([
|
||||||
|
'order_status' => (($task['task_stage'] ?? '') === 'final_review') ? 'in_final_review' : 'in_first_review',
|
||||||
|
'display_status' => '报告已驳回,待重新鉴定',
|
||||||
|
'updated_at' => $now,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Db::name('order_timelines')->insert([
|
||||||
|
'order_id' => (int)$report['order_id'],
|
||||||
|
'node_code' => 'report_rejected',
|
||||||
|
'node_text' => '报告已驳回',
|
||||||
|
'node_desc' => mb_substr($reason, 0, 255),
|
||||||
|
'operator_type' => 'admin',
|
||||||
|
'operator_id' => (int)$request->header('x-admin-id', 0) ?: null,
|
||||||
|
'occurred_at' => $now,
|
||||||
|
'created_at' => $now,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$freshReport = Db::name('reports')->where('id', $id)->find() ?: [];
|
||||||
|
$this->insertReportLog($id, 'reject', $beforeReport, $freshReport, $request, $reason);
|
||||||
|
|
||||||
|
Db::commit();
|
||||||
|
|
||||||
|
return api_success([
|
||||||
|
'id' => $id,
|
||||||
|
'report_status' => 'rejected',
|
||||||
|
'reject_reason' => mb_substr($reason, 0, 255),
|
||||||
|
], '报告已驳回');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Db::rollback();
|
||||||
|
return api_error('报告驳回失败', 500, [
|
||||||
|
'detail' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function reportStatusText(string $status): string
|
private function reportStatusText(string $status): string
|
||||||
{
|
{
|
||||||
return match ($status) {
|
return match ($status) {
|
||||||
'draft' => '草稿中',
|
'draft' => '草稿中',
|
||||||
'pending_publish' => '待发布',
|
'pending_publish' => '待发布',
|
||||||
'published' => '已发布',
|
'published' => '已发布',
|
||||||
|
'rejected' => '已驳回',
|
||||||
'updated' => '已更新',
|
'updated' => '已更新',
|
||||||
'invalid' => '已作废',
|
'invalid' => '已作废',
|
||||||
default => $status,
|
default => $status,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function reportLogs(int $reportId): array
|
||||||
|
{
|
||||||
|
$rows = Db::name('report_logs')
|
||||||
|
->where('report_id', $reportId)
|
||||||
|
->order('id', 'desc')
|
||||||
|
->select()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
return array_map(function (array $row) {
|
||||||
|
return [
|
||||||
|
'id' => (int)$row['id'],
|
||||||
|
'action' => (string)$row['action'],
|
||||||
|
'action_text' => $this->reportLogActionText((string)$row['action']),
|
||||||
|
'operator_id' => (int)($row['operator_id'] ?? 0),
|
||||||
|
'operator_name' => (string)($row['operator_name'] ?? ''),
|
||||||
|
'before_data' => $this->decodeJsonField($row['before_data'] ?? null),
|
||||||
|
'after_data' => $this->decodeJsonField($row['after_data'] ?? null),
|
||||||
|
'remark' => (string)($row['remark'] ?? ''),
|
||||||
|
'created_at' => (string)($row['created_at'] ?? ''),
|
||||||
|
];
|
||||||
|
}, $rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function reportLogActionText(string $action): string
|
||||||
|
{
|
||||||
|
return match ($action) {
|
||||||
|
'submit' => '提交报告',
|
||||||
|
'create_draft' => '生成待发布报告',
|
||||||
|
'update_draft' => '更新待发布报告',
|
||||||
|
'publish' => '发布报告',
|
||||||
|
'reject' => '驳回报告',
|
||||||
|
default => $action,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function insertReportLog(int $reportId, string $action, array $before, array $after, Request $request, string $remark = ''): void
|
||||||
|
{
|
||||||
|
if ($reportId <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Db::name('report_logs')->insert([
|
||||||
|
'report_id' => $reportId,
|
||||||
|
'action' => $action,
|
||||||
|
'operator_id' => (int)$request->header('x-admin-id', 0) ?: null,
|
||||||
|
'operator_name' => trim((string)$request->header('x-admin-name', '')),
|
||||||
|
'before_data' => $before ? json_encode($this->reportLogSnapshot($before), JSON_UNESCAPED_UNICODE) : null,
|
||||||
|
'after_data' => $after ? json_encode($this->reportLogSnapshot($after), JSON_UNESCAPED_UNICODE) : null,
|
||||||
|
'remark' => mb_substr($remark, 0, 255),
|
||||||
|
'created_at' => date('Y-m-d H:i:s'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function reportLogSnapshot(array $report): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => (int)($report['id'] ?? 0),
|
||||||
|
'report_no' => (string)($report['report_no'] ?? ''),
|
||||||
|
'order_id' => (int)($report['order_id'] ?? 0),
|
||||||
|
'report_status' => (string)($report['report_status'] ?? ''),
|
||||||
|
'report_version' => (int)($report['report_version'] ?? 0),
|
||||||
|
'publish_time' => (string)($report['publish_time'] ?? ''),
|
||||||
|
'zhongjian_report_no' => (string)($report['zhongjian_report_no'] ?? ''),
|
||||||
|
'invalid_reason' => (string)($report['invalid_reason'] ?? ''),
|
||||||
|
'reject_reason' => (string)($report['reject_reason'] ?? ''),
|
||||||
|
'rejected_by_name' => (string)($report['rejected_by_name'] ?? ''),
|
||||||
|
'rejected_at' => (string)($report['rejected_at'] ?? ''),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
private function reportTypeText(string $reportType): string
|
private function reportTypeText(string $reportType): string
|
||||||
{
|
{
|
||||||
return match ($reportType) {
|
return match ($reportType) {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class ReportsController
|
|||||||
])
|
])
|
||||||
->where('o.user_id', $userId)
|
->where('o.user_id', $userId)
|
||||||
->whereIn('o.order_status', ['in_first_review', 'in_final_review', 'generating_report', 'report_published', 'completed'])
|
->whereIn('o.order_status', ['in_first_review', 'in_final_review', 'generating_report', 'report_published', 'completed'])
|
||||||
|
->whereRaw('r.id IS NOT NULL')
|
||||||
->order('o.id', 'desc')
|
->order('o.id', 'desc')
|
||||||
->select()
|
->select()
|
||||||
->toArray();
|
->toArray();
|
||||||
|
|||||||
@@ -194,7 +194,14 @@ class TicketsController
|
|||||||
$bizType = 'order';
|
$bizType = 'order';
|
||||||
$bizId = $orderId;
|
$bizId = $orderId;
|
||||||
} elseif ($reportId > 0) {
|
} elseif ($reportId > 0) {
|
||||||
$report = Db::name('reports')->where('id', $reportId)->find();
|
$report = Db::name('reports')
|
||||||
|
->alias('r')
|
||||||
|
->join('orders o', 'o.id = r.order_id')
|
||||||
|
->where('r.id', $reportId)
|
||||||
|
->where('r.report_status', 'published')
|
||||||
|
->where('o.user_id', $userId)
|
||||||
|
->field('r.id')
|
||||||
|
->find();
|
||||||
if (!$report) {
|
if (!$report) {
|
||||||
return api_error('关联报告不存在', 404);
|
return api_error('关联报告不存在', 404);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,7 +179,11 @@ class EnterpriseOrderService
|
|||||||
$timeline = Db::name('order_timelines')->where('order_id', (int)$order['id'])->order('occurred_at', 'asc')->select()->toArray();
|
$timeline = Db::name('order_timelines')->where('order_id', (int)$order['id'])->order('occurred_at', 'asc')->select()->toArray();
|
||||||
$sendLogistics = Db::name('order_logistics')->where('order_id', (int)$order['id'])->where('logistics_type', 'send_to_center')->order('id', 'desc')->find();
|
$sendLogistics = Db::name('order_logistics')->where('order_id', (int)$order['id'])->where('logistics_type', 'send_to_center')->order('id', 'desc')->find();
|
||||||
$returnLogistics = Db::name('order_logistics')->where('order_id', (int)$order['id'])->where('logistics_type', 'return_to_user')->order('id', 'desc')->find();
|
$returnLogistics = Db::name('order_logistics')->where('order_id', (int)$order['id'])->where('logistics_type', 'return_to_user')->order('id', 'desc')->find();
|
||||||
$report = Db::name('reports')->where('order_id', (int)$order['id'])->order('id', 'desc')->find();
|
$report = Db::name('reports')
|
||||||
|
->where('order_id', (int)$order['id'])
|
||||||
|
->where('report_status', 'published')
|
||||||
|
->order('id', 'desc')
|
||||||
|
->find();
|
||||||
$verify = $report ? (Db::name('report_verifies')->where('report_id', (int)$report['id'])->find() ?: null) : null;
|
$verify = $report ? (Db::name('report_verifies')->where('report_id', (int)$report['id'])->find() ?: null) : null;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ class FulfillmentFlowService
|
|||||||
$context = $this->formatOrderContext((int)$flow['order_id'], $request);
|
$context = $this->formatOrderContext((int)$flow['order_id'], $request);
|
||||||
$report = $context['report_info'] ?? null;
|
$report = $context['report_info'] ?? null;
|
||||||
if (!$report || ($report['report_status'] ?? '') !== 'published') {
|
if (!$report || ($report['report_status'] ?? '') !== 'published') {
|
||||||
throw new \InvalidArgumentException('订单报告未发布,不能进入寄回流程');
|
throw new \InvalidArgumentException('该报告未发布,不符合寄回条件');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $context + [
|
return $context + [
|
||||||
@@ -289,7 +289,7 @@ class FulfillmentFlowService
|
|||||||
}
|
}
|
||||||
$report = $this->latestReport((int)$flow['order_id']);
|
$report = $this->latestReport((int)$flow['order_id']);
|
||||||
if (!$report || ($report['report_status'] ?? '') !== 'published') {
|
if (!$report || ($report['report_status'] ?? '') !== 'published') {
|
||||||
throw new \InvalidArgumentException('订单报告未发布,不能确认寄回');
|
throw new \InvalidArgumentException('该报告未发布,不符合寄回条件');
|
||||||
}
|
}
|
||||||
|
|
||||||
$tag = (new MaterialTagService())->findTagByInput($qrInput);
|
$tag = (new MaterialTagService())->findTagByInput($qrInput);
|
||||||
@@ -320,7 +320,10 @@ class FulfillmentFlowService
|
|||||||
$report = $this->latestReport((int)$flow['order_id']);
|
$report = $this->latestReport((int)$flow['order_id']);
|
||||||
$content = $report ? Db::name('report_contents')->where('report_id', (int)$report['id'])->find() : null;
|
$content = $report ? Db::name('report_contents')->where('report_id', (int)$report['id'])->find() : null;
|
||||||
$files = $this->decodeJsonArray($content['zhongjian_report_files_json'] ?? null);
|
$files = $this->decodeJsonArray($content['zhongjian_report_files_json'] ?? null);
|
||||||
if (!$report || ($report['report_status'] ?? '') !== 'published' || trim((string)($report['zhongjian_report_no'] ?? '')) === '' || !$files) {
|
if (!$report || ($report['report_status'] ?? '') !== 'published') {
|
||||||
|
throw new \InvalidArgumentException('该报告未发布,不符合寄回条件');
|
||||||
|
}
|
||||||
|
if (trim((string)($report['zhongjian_report_no'] ?? '')) === '' || !$files) {
|
||||||
throw new \InvalidArgumentException('中检报告未完整录入,不能确认寄回');
|
throw new \InvalidArgumentException('中检报告未完整录入,不能确认寄回');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,7 +340,7 @@ class FulfillmentFlowService
|
|||||||
|
|
||||||
$report = $this->latestReport((int)$flow['order_id']);
|
$report = $this->latestReport((int)$flow['order_id']);
|
||||||
if (!$report || ($report['report_status'] ?? '') !== 'published') {
|
if (!$report || ($report['report_status'] ?? '') !== 'published') {
|
||||||
throw new \InvalidArgumentException('订单报告未发布,不能确认寄回');
|
throw new \InvalidArgumentException('该报告未发布,不符合寄回条件');
|
||||||
}
|
}
|
||||||
if ((int)$report['id'] !== $reportId) {
|
if ((int)$report['id'] !== $reportId) {
|
||||||
throw new \InvalidArgumentException('确认的报告与当前订单报告不匹配');
|
throw new \InvalidArgumentException('确认的报告与当前订单报告不匹配');
|
||||||
@@ -371,6 +374,10 @@ class FulfillmentFlowService
|
|||||||
if (!$flow) {
|
if (!$flow) {
|
||||||
throw new \RuntimeException('未找到可用的内部流转挂牌', 404);
|
throw new \RuntimeException('未找到可用的内部流转挂牌', 404);
|
||||||
}
|
}
|
||||||
|
$report = $this->latestReport((int)$flow['order_id']);
|
||||||
|
if (!$report || ($report['report_status'] ?? '') !== 'published') {
|
||||||
|
throw new \InvalidArgumentException('该报告未发布,不符合寄回条件');
|
||||||
|
}
|
||||||
if ((string)($flow['current_stage'] ?? '') !== 'return_confirmed') {
|
if ((string)($flow['current_stage'] ?? '') !== 'return_confirmed') {
|
||||||
throw new \InvalidArgumentException('请先完成报告确认,再登记回寄运单');
|
throw new \InvalidArgumentException('请先完成报告确认,再登记回寄运单');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -344,10 +344,6 @@ class MaterialTagService
|
|||||||
if ($batch && ($batch['status'] ?? 'active') === 'invalid') {
|
if ($batch && ($batch['status'] ?? 'active') === 'invalid') {
|
||||||
throw new \InvalidArgumentException('该吊牌所属批次已失效,不能绑定报告');
|
throw new \InvalidArgumentException('该吊牌所属批次已失效,不能绑定报告');
|
||||||
}
|
}
|
||||||
if (($tag['bind_status'] ?? '') === 'bound' || (int)($tag['report_id'] ?? 0) > 0) {
|
|
||||||
throw new \InvalidArgumentException('该吊牌已绑定报告,不能重复绑定');
|
|
||||||
}
|
|
||||||
|
|
||||||
$task = Db::name('appraisal_tasks')->where('id', $taskId)->find();
|
$task = Db::name('appraisal_tasks')->where('id', $taskId)->find();
|
||||||
if (!$task) {
|
if (!$task) {
|
||||||
throw new \RuntimeException('任务不存在', 404);
|
throw new \RuntimeException('任务不存在', 404);
|
||||||
@@ -364,6 +360,21 @@ class MaterialTagService
|
|||||||
throw new \InvalidArgumentException('报告已发布,不能再绑定或更换吊牌');
|
throw new \InvalidArgumentException('报告已发布,不能再绑定或更换吊牌');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (($tag['bind_status'] ?? '') === 'bound' || (int)($tag['report_id'] ?? 0) > 0) {
|
||||||
|
if (
|
||||||
|
(int)($tag['report_id'] ?? 0) === (int)$report['id']
|
||||||
|
&& in_array((string)($report['report_status'] ?? ''), ['draft', 'pending_publish', 'updated', 'rejected'], true)
|
||||||
|
) {
|
||||||
|
return $this->formatTagCode($tag, [
|
||||||
|
'id' => (int)$report['id'],
|
||||||
|
'report_no' => (string)$report['report_no'],
|
||||||
|
'report_status' => (string)$report['report_status'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \InvalidArgumentException('该吊牌已绑定报告,不能重复绑定');
|
||||||
|
}
|
||||||
|
|
||||||
$existing = Db::name('material_tag_codes')->where('report_id', (int)$report['id'])->find();
|
$existing = Db::name('material_tag_codes')->where('report_id', (int)$report['id'])->find();
|
||||||
if ($existing) {
|
if ($existing) {
|
||||||
throw new \InvalidArgumentException('当前报告已绑定吊牌,不能重复绑定');
|
throw new \InvalidArgumentException('当前报告已绑定吊牌,不能重复绑定');
|
||||||
@@ -530,18 +541,13 @@ class MaterialTagService
|
|||||||
if (($report['report_status'] ?? '') !== 'published') {
|
if (($report['report_status'] ?? '') !== 'published') {
|
||||||
return [
|
return [
|
||||||
'tag_status' => 'pending_report',
|
'tag_status' => 'pending_report',
|
||||||
'status_text' => '报告生成中',
|
'status_text' => '报告未发布',
|
||||||
'message' => '该吊牌已关联报告,正式报告发布后可查看完整内容。',
|
'message' => '该吊牌已关联报告,正式报告发布后可查看完整内容。',
|
||||||
'qr_token' => (string)$tag['qr_token'],
|
'qr_token' => (string)$tag['qr_token'],
|
||||||
'qr_url' => (string)$tag['qr_url'],
|
'qr_url' => (string)$tag['qr_url'],
|
||||||
'scan_count' => (int)$tag['scan_count'],
|
'scan_count' => (int)$tag['scan_count'],
|
||||||
'verify_count' => (int)$tag['verify_count'],
|
'verify_count' => (int)$tag['verify_count'],
|
||||||
'report_summary' => [
|
'report_summary' => null,
|
||||||
'report_no' => (string)$report['report_no'],
|
|
||||||
'report_title' => (string)$report['report_title'],
|
|
||||||
'institution_name' => (string)$report['institution_name'],
|
|
||||||
'publish_time' => (string)($report['publish_time'] ?? ''),
|
|
||||||
],
|
|
||||||
'product_summary' => [],
|
'product_summary' => [],
|
||||||
'result_summary' => [],
|
'result_summary' => [],
|
||||||
'verify_passed' => false,
|
'verify_passed' => false,
|
||||||
|
|||||||
@@ -249,6 +249,7 @@ Route::get('/api/admin/report/detail', [AdminReportsController::class, 'detail']
|
|||||||
Route::post('/api/admin/report/trace-visibility', [AdminReportsController::class, 'updateTraceVisibility']);
|
Route::post('/api/admin/report/trace-visibility', [AdminReportsController::class, 'updateTraceVisibility']);
|
||||||
Route::post('/api/admin/report/inspection/save', [AdminReportsController::class, 'saveInspection']);
|
Route::post('/api/admin/report/inspection/save', [AdminReportsController::class, 'saveInspection']);
|
||||||
Route::post('/api/admin/report/publish', [AdminReportsController::class, 'publish']);
|
Route::post('/api/admin/report/publish', [AdminReportsController::class, 'publish']);
|
||||||
|
Route::post('/api/admin/report/reject', [AdminReportsController::class, 'reject']);
|
||||||
Route::get('/api/admin/appraisal-tasks', [AdminAppraisalTasksController::class, 'index']);
|
Route::get('/api/admin/appraisal-tasks', [AdminAppraisalTasksController::class, 'index']);
|
||||||
Route::get('/api/admin/appraisal-task/detail', [AdminAppraisalTasksController::class, 'detail']);
|
Route::get('/api/admin/appraisal-task/detail', [AdminAppraisalTasksController::class, 'detail']);
|
||||||
Route::get('/api/admin/appraisal-task/assignable-admins', [AdminAppraisalTasksController::class, 'assignableAdmins']);
|
Route::get('/api/admin/appraisal-task/assignable-admins', [AdminAppraisalTasksController::class, 'assignableAdmins']);
|
||||||
|
|||||||
@@ -1067,6 +1067,10 @@ CREATE TABLE reports (
|
|||||||
report_entered_at DATETIME NULL DEFAULT NULL,
|
report_entered_at DATETIME NULL DEFAULT NULL,
|
||||||
trace_info_visible TINYINT(1) NOT NULL DEFAULT 0,
|
trace_info_visible TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
invalid_reason VARCHAR(255) NOT NULL DEFAULT '',
|
invalid_reason VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
reject_reason VARCHAR(500) NOT NULL DEFAULT '',
|
||||||
|
rejected_by BIGINT UNSIGNED NULL DEFAULT NULL,
|
||||||
|
rejected_by_name VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
rejected_at DATETIME NULL DEFAULT NULL,
|
||||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
|
|||||||
67
server-api/tools/schema_upgrade_report_review_flow.php
Normal file
67
server-api/tools/schema_upgrade_report_review_flow.php
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||||
|
$dotenv->safeLoad();
|
||||||
|
|
||||||
|
$dsn = sprintf(
|
||||||
|
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||||
|
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||||
|
$_ENV['DB_PORT'] ?? '3306',
|
||||||
|
$_ENV['DB_DATABASE'] ?? '',
|
||||||
|
$_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||||
|
);
|
||||||
|
|
||||||
|
$pdo = new PDO(
|
||||||
|
$dsn,
|
||||||
|
$_ENV['DB_USERNAME'] ?? '',
|
||||||
|
$_ENV['DB_PASSWORD'] ?? '',
|
||||||
|
[
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
function reportReviewHasColumn(PDO $pdo, string $table, string $column): bool
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?');
|
||||||
|
$stmt->execute([$table, $column]);
|
||||||
|
return (int)$stmt->fetchColumn() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$reportColumns = [
|
||||||
|
'reject_reason' => "ALTER TABLE reports ADD COLUMN reject_reason VARCHAR(500) NOT NULL DEFAULT '' AFTER invalid_reason",
|
||||||
|
'rejected_by' => 'ALTER TABLE reports ADD COLUMN rejected_by BIGINT UNSIGNED NULL DEFAULT NULL AFTER reject_reason',
|
||||||
|
'rejected_by_name' => "ALTER TABLE reports ADD COLUMN rejected_by_name VARCHAR(64) NOT NULL DEFAULT '' AFTER rejected_by",
|
||||||
|
'rejected_at' => 'ALTER TABLE reports ADD COLUMN rejected_at DATETIME NULL DEFAULT NULL AFTER rejected_by_name',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($reportColumns as $column => $sql) {
|
||||||
|
if (!reportReviewHasColumn($pdo, 'reports', $column)) {
|
||||||
|
$pdo->exec($sql);
|
||||||
|
echo "ADD_COLUMN reports.{$column}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->exec("UPDATE reports SET reject_reason = invalid_reason WHERE report_status = 'rejected' AND reject_reason = '' AND invalid_reason <> ''");
|
||||||
|
|
||||||
|
$pdo->exec(<<<'SQL'
|
||||||
|
CREATE TABLE IF NOT EXISTS report_logs (
|
||||||
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
report_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
action VARCHAR(64) NOT NULL,
|
||||||
|
operator_id BIGINT UNSIGNED NULL DEFAULT NULL,
|
||||||
|
operator_name VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
before_data JSON NULL,
|
||||||
|
after_data JSON NULL,
|
||||||
|
remark VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
KEY idx_report_logs_report_id (report_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='报告操作日志'
|
||||||
|
SQL);
|
||||||
|
|
||||||
|
echo "SCHEMA_UPGRADE_REPORT_REVIEW_FLOW_OK\n";
|
||||||
@@ -41,6 +41,14 @@ const supplementForm = reactive({
|
|||||||
|
|
||||||
const isZhongjian = computed(() => detail.value?.task_info.service_provider === "zhongjian");
|
const isZhongjian = computed(() => detail.value?.task_info.service_provider === "zhongjian");
|
||||||
const isTaskReadonly = computed(() => {
|
const isTaskReadonly = computed(() => {
|
||||||
|
const reportStatus = detail.value?.report_summary?.report_status || "";
|
||||||
|
if (["draft", "pending_publish", "updated", "rejected"].includes(reportStatus)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (reportStatus === "published") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const status = detail.value?.task_info.status || "";
|
const status = detail.value?.task_info.status || "";
|
||||||
return status === "submitted" || status === "completed";
|
return status === "submitted" || status === "completed";
|
||||||
});
|
});
|
||||||
@@ -293,7 +301,7 @@ function confirmPublishReport() {
|
|||||||
return new Promise<boolean>((resolve) => {
|
return new Promise<boolean>((resolve) => {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: "提交确认",
|
title: "提交确认",
|
||||||
content: "是否已鉴定完成并确定发布报告?",
|
content: "是否已鉴定完成并提交报告待发布?",
|
||||||
cancelText: "取消",
|
cancelText: "取消",
|
||||||
confirmText: "去绑定",
|
confirmText: "去绑定",
|
||||||
success: (result) => resolve(Boolean(result.confirm)),
|
success: (result) => resolve(Boolean(result.confirm)),
|
||||||
@@ -558,7 +566,7 @@ async function submitResult(action: "save" | "submit") {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
if (action === "submit") {
|
if (action === "submit") {
|
||||||
returnToWorkOrders("验真吊牌已绑定,报告已发布");
|
returnToWorkOrders("报告已提交,待管理员发布");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showInfoToast("鉴定已保存");
|
showInfoToast("鉴定已保存");
|
||||||
@@ -659,7 +667,7 @@ async function submitZhongjianReport() {
|
|||||||
report_files: zhongjianFiles.value,
|
report_files: zhongjianFiles.value,
|
||||||
qr_input: qrInput,
|
qr_input: qrInput,
|
||||||
});
|
});
|
||||||
returnToWorkOrders("验真吊牌已绑定,报告已发布");
|
returnToWorkOrders("报告已提交,待管理员发布");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast(error, "中检报告录入失败");
|
showErrorToast(error, "中检报告录入失败");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -953,7 +961,7 @@ onShow(() => {
|
|||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="!isTaskReadonly || detail.report_summary?.id" class="form-actions" :class="detail.report_summary?.id && !isTaskReadonly ? '' : 'form-actions--single'">
|
<view v-if="!isTaskReadonly || detail.report_summary?.id" class="form-actions" :class="detail.report_summary?.id && !isTaskReadonly ? '' : 'form-actions--single'">
|
||||||
<button v-if="!isTaskReadonly" class="form-action form-action--primary" :disabled="submitting" @click="submitZhongjianReport">提交并发布</button>
|
<button v-if="!isTaskReadonly" class="form-action form-action--primary" :disabled="submitting" @click="submitZhongjianReport">提交待发布</button>
|
||||||
<button v-if="detail.report_summary?.id" class="form-action form-action--secondary" @click="openReportDetail">查看报告</button>
|
<button v-if="detail.report_summary?.id" class="form-action form-action--secondary" @click="openReportDetail">查看报告</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
Reference in New Issue
Block a user