Commit 508a8e17 authored by David Yang's avatar David Yang

fix: map department proof materials in issue list

parent 4b89489e
......@@ -32,6 +32,11 @@ export default defineConfig({
layout: false,
component: './Login',
},
{
path: ROUTE_PATHS.problemRectificationFlowTable,
wrappers: ['@/wrapper/Site', '@/wrapper/Auth'],
component: './problem_rectification/flow-table/index',
},
{
path: '/',
wrappers: ['@/wrapper/Site', '@/wrapper/Auth'],
......@@ -65,10 +70,6 @@ export default defineConfig({
path: ROUTE_PATHS.problemRectificationForm,
component: './problem_rectification/form/index',
},
{
path: ROUTE_PATHS.problemRectificationFlowTable,
component: './problem_rectification/flow-table/index',
},
],
},
],
......
{
"name": "problem_rectification_new",
"version": "1.0.7",
"version": "1.0.9",
"build": "abc",
"description": "merge master",
"date": "2025-07-14 19:27:20",
......
......@@ -21,9 +21,8 @@ import {
import { normalizeText, toArray } from '@/utils/subjectView';
import clsx from 'clsx';
import dayjs from 'dayjs';
import { Alert, Button, Card, Chip, Spinner, TextArea, Tooltip } from '@heroui/react';
import { Alert, Button, Card, Chip, Spinner, TextArea, Toast, Tooltip, toast } from '@heroui/react';
import { useContext, useEffect, useMemo, useState } from 'react';
import { toast } from '@heroui/react';
import { useSearchParams } from 'umi';
import { UserContext } from '@/wrapper/Auth';
import {
......@@ -191,12 +190,13 @@ async function sendNoticeToUsers({ title, content, from, to }) {
function buildDepartmentRows(records = []) {
return records.flatMap((record) => {
const items = normalizeDepartmentRectificationItems(record);
return items.map((item) => ({
key: `${record._id || record.name}-${item.item_id}`,
return items.map((item, itemIndex) => ({
key: `${record._id || record.name}-${item.item_id}-${itemIndex}`,
flowMode: FLOW_MODE_DEPARTMENT,
record,
item,
itemId: item.item_id,
itemIndex,
recordId: record._id || record.name,
title: record.title || record.metadata?.project_name || '-',
projectName: record.metadata?.project_name || '-',
......@@ -229,17 +229,20 @@ function buildBatchRows(records = [], metadata = {}) {
const recordMetadata = record.metadata || {};
const recordId = record._id || record.name;
const allDepartmentItems = normalizeDepartmentRectificationItems(record);
const departmentItems = scopedItemIds.size > 0
? allDepartmentItems.filter((item) => scopedItemIds.has(item.item_id))
: allDepartmentItems;
const sourceItems = departmentItems.length > 0 ? departmentItems : [null];
return sourceItems.map((item) => ({
key: item ? `${recordId}-${item.item_id}` : recordId,
const departmentItems = allDepartmentItems
.map((item, itemIndex) => ({ item, itemIndex }))
.filter(({ item }) => scopedItemIds.size === 0 || scopedItemIds.has(item.item_id));
const sourceItems = departmentItems.length > 0
? departmentItems
: [{ item: null, itemIndex: -1 }];
return sourceItems.map(({ item, itemIndex }) => ({
key: item ? `${recordId}-${item.item_id}-${itemIndex}` : recordId,
flowMode: FLOW_MODE_BATCH,
record,
item,
itemId: item?.item_id || recordId,
itemIndex,
recordId,
title: record.title || recordMetadata.project_name || '-',
projectName: recordMetadata.project_name || '-',
......@@ -251,11 +254,21 @@ function buildBatchRows(records = [], metadata = {}) {
'-',
editor: item?.editor || editor,
auditor: item?.auditor || auditor,
progress: item?.rectification_progress || recordMetadata.rectification_progress || '',
situation: item?.rectification_situation || recordMetadata.rectification_situation || '',
dueDate: item?.the_time_of_rectification || recordMetadata.the_time_of_rectification || '',
proof: item?.rectification_proof_materials || recordMetadata.rectification_proof_materials || null,
editorStatus: metadata.editor_status || EDITOR_STATUS_PENDING,
progress: item
? item.rectification_progress || ''
: recordMetadata.rectification_progress || '',
situation: item
? item.rectification_situation || ''
: recordMetadata.rectification_situation || '',
dueDate: item
? item.the_time_of_rectification || ''
: recordMetadata.the_time_of_rectification || '',
proof: item
? item.rectification_proof_materials || null
: recordMetadata.rectification_proof_materials || null,
editorStatus: item
? item.editor_status || EDITOR_STATUS_PENDING
: metadata.editor_status || EDITOR_STATUS_PENDING,
auditStatus: item?.audit_status || recordMetadata.audit_status || AUDIT_STATUS_PENDING,
finalAuditStatus:
item?.final_audit_status || recordMetadata.final_audit_status || AUDIT_STATUS_PENDING,
......@@ -400,6 +413,7 @@ export default function ProblemRectificationFlowTablePage() {
const userInfo = useContext(UserContext);
const [searchParams] = useSearchParams();
const messageId = searchParams.get('id') || '';
const scopedRecordId = searchParams.get('record') || '';
const [loading, setLoading] = useState(true);
const [submittingKey, setSubmittingKey] = useState('');
const [error, setError] = useState('');
......@@ -418,7 +432,7 @@ export default function ProblemRectificationFlowTablePage() {
? FLOW_MODE_DELAY_DEPARTMENT
: metadata.flow_mode || FLOW_MODE_BATCH;
const isDelayFlow = flowMode === FLOW_MODE_DELAY || flowMode === FLOW_MODE_DELAY_DEPARTMENT;
const rows = useMemo(
const allRows = useMemo(
() => {
if (flowMode === FLOW_MODE_DELAY || flowMode === FLOW_MODE_DELAY_DEPARTMENT) {
return buildDelayRows(
......@@ -434,6 +448,12 @@ export default function ProblemRectificationFlowTablePage() {
},
[flowMode, messageRecord, metadata, records],
);
const rows = useMemo(
() => scopedRecordId
? allRows.filter((row) => [row.recordId, row.record?.name].includes(scopedRecordId))
: allRows,
[allRows, scopedRecordId],
);
const currentUserId = toId(userInfo?._id);
const senderId = toId(metadata.sender);
const isSender = Boolean(currentUserId && senderId === currentUserId);
......@@ -539,13 +559,39 @@ export default function ProblemRectificationFlowTablePage() {
if (latestRecord?.metadata?.is_locked === true) {
throw new Error('当前问题整改记录已锁定,不允许继续修改或审批。');
}
const nextItems = normalizeDepartmentRectificationItems(latestRecord).map((item) =>
item.item_id === row.itemId
const currentItems = normalizeDepartmentRectificationItems(latestRecord);
const itemAtOriginalIndex = currentItems[row.itemIndex];
const originalIndexStillMatches =
Number.isInteger(row.itemIndex) &&
itemAtOriginalIndex?.item_id === row.itemId &&
itemAtOriginalIndex?.responsible_department === row.department;
const matchingItemIndexes = currentItems.reduce((indexes, item, itemIndex) => {
if (
item.item_id === row.itemId &&
item.responsible_department === row.department
) {
indexes.push(itemIndex);
}
return indexes;
}, []);
const targetItemIndex = originalIndexStillMatches
? row.itemIndex
: matchingItemIndexes.length === 1
? matchingItemIndexes[0]
: -1;
if (targetItemIndex < 0 || targetItemIndex >= currentItems.length) {
throw new Error('当前部门整改明细已发生变化,请刷新后重试。');
}
const updatedAt = dayjs().toISOString();
const nextItems = currentItems.map((item, itemIndex) =>
itemIndex === targetItemIndex
? {
...item,
...patch,
updated_by: currentUserId,
updated_at: dayjs().toISOString(),
updated_at: updatedAt,
}
: item,
);
......@@ -734,11 +780,14 @@ export default function ProblemRectificationFlowTablePage() {
const saveRowDraft = async (row, batchPatch = {}) => {
const draft = getDraft(row);
const rowDraft = drafts[row.key] || {};
const patch = {
rectification_progress: draft.progress,
rectification_situation: draft.situation,
the_time_of_rectification: draft.dueDate ? dayjs(draft.dueDate).toISOString() : null,
rectification_proof_materials: draft.proof || null,
...(Object.prototype.hasOwnProperty.call(rowDraft, 'proof')
? { rectification_proof_materials: draft.proof || null }
: {}),
};
if (row.item) {
......@@ -762,23 +811,20 @@ export default function ProblemRectificationFlowTablePage() {
return;
}
await withRowSubmit(row, async () => {
setSubmittingKey(`upload-${row.key}`);
setError('');
setSuccess('');
try {
const uploaded = await uploadFile(file);
const fileValue = uploaded?.file || uploaded?.data || uploaded;
setDraftValue(row.key, { proof: fileValue });
if (row.item) {
await updateDepartmentItem(row, { rectification_proof_materials: fileValue });
return;
setSuccess('附件已上传,请点击当前行“保存”完成记录。');
} catch (requestError) {
setError(requestError?.message || '附件上传失败,请稍后重试。');
} finally {
setSubmittingKey('');
}
await updateBatchRow(row, {
rectification_progress: row.progress,
rectification_situation: row.situation,
the_time_of_rectification: row.dueDate || null,
rectification_proof_materials: fileValue,
audit_status: row.auditStatus,
final_audit_status: row.finalAuditStatus,
});
});
};
const saveDelayDraft = async (row, { requireComplete = false } = {}) => {
......@@ -923,7 +969,16 @@ export default function ProblemRectificationFlowTablePage() {
currentUser: userInfo,
returnUrl,
});
const isLastPendingBatchRow =
row.flowMode === FLOW_MODE_BATCH &&
allRows.every(
(candidate) =>
candidate.key === row.key ||
candidate.editorStatus === EDITOR_STATUS_COMPLETED,
);
if (row.flowMode !== FLOW_MODE_BATCH || isLastPendingBatchRow) {
await completeProgressTask(row.progressIdEditor);
}
await updateDepartmentItem(row, {
...savedPatch,
......@@ -932,6 +987,24 @@ export default function ProblemRectificationFlowTablePage() {
progress_id_auditor: progressIdAuditor,
});
if (row.flowMode === FLOW_MODE_BATCH) {
await updateRecord(messageRecord?._id || messageRecord?.name || messageId, {
metadata: {
editor_status: isLastPendingBatchRow
? EDITOR_STATUS_COMPLETED
: EDITOR_STATUS_PENDING,
auditor_status: EDITOR_STATUS_PENDING,
sender_status: EDITOR_STATUS_PENDING,
progress_id_auditor: Array.from(
new Set([
...toArray(metadata.progress_id_auditor).filter(Boolean),
progressIdAuditor,
]),
),
},
});
}
await sendNoticeToUsers({
title: '问题整改表待复核',
content: `请点击链接复核问题整改表:${returnUrl}`,
......@@ -941,8 +1014,35 @@ export default function ProblemRectificationFlowTablePage() {
});
};
const handleEditorRecall = async (row) => {
await withRowSubmit(row, async () => {
await completeProgressTask(row.progressIdAuditor);
await updateDepartmentItem(row, {
editor_status: EDITOR_STATUS_PENDING,
audit_status: AUDIT_STATUS_PENDING,
final_audit_status: AUDIT_STATUS_PENDING,
progress_id_auditor: '',
progress_id_sender: '',
});
if (row.flowMode === FLOW_MODE_BATCH) {
await updateRecord(messageRecord?._id || messageRecord?.name || messageId, {
metadata: {
editor_status: EDITOR_STATUS_PENDING,
auditor_status: EDITOR_STATUS_PENDING,
sender_status: EDITOR_STATUS_PENDING,
progress_id_auditor: toArray(metadata.progress_id_auditor).filter(
(progressId) => progressId && progressId !== row.progressIdAuditor,
),
},
});
}
});
};
const handleBatchEditorSubmit = async () => {
const targetRows = rows.filter((row) => row.flowMode === FLOW_MODE_BATCH);
const targetRows = rows.filter(
(row) => row.flowMode === FLOW_MODE_BATCH && canSubmitRow(row),
);
if (targetRows.length === 0) {
return;
}
......@@ -990,7 +1090,12 @@ export default function ProblemRectificationFlowTablePage() {
editor_status: EDITOR_STATUS_COMPLETED,
auditor_status: EDITOR_STATUS_PENDING,
sender_status: EDITOR_STATUS_PENDING,
progress_id_auditor: [progressIdAuditor],
progress_id_auditor: Array.from(
new Set([
...toArray(metadata.progress_id_auditor).filter(Boolean),
progressIdAuditor,
]),
),
progress_id_sender: '',
},
});
......@@ -1009,8 +1114,64 @@ export default function ProblemRectificationFlowTablePage() {
}
};
const handleBatchEditorRecall = async () => {
const targetRows = rows.filter(
(row) => row.flowMode === FLOW_MODE_BATCH && canRecallRow(row),
);
if (targetRows.length === 0) {
return;
}
if (targetRows.some((row) => row.isLocked)) {
setError('本批次存在已锁定的问题整改记录,不允许撤回送审。');
return;
}
setSubmittingKey('batch-recall');
setError('');
setSuccess('');
try {
const latestRecords = await Promise.all(
targetRows.map((row) => loadRecord(row.recordId)),
);
if (latestRecords.some((record) => record?.metadata?.is_locked === true)) {
throw new Error('本批次存在已锁定的问题整改记录,请刷新后重试。');
}
await Promise.all(
Array.from(new Set(toArray(metadata.progress_id_auditor).filter(Boolean))).map(
(progressId) => completeProgressTask(progressId),
),
);
await updateBatchDepartmentStates(targetRows, {
editor_status: EDITOR_STATUS_PENDING,
audit_status: AUDIT_STATUS_PENDING,
final_audit_status: AUDIT_STATUS_PENDING,
progress_id_auditor: '',
progress_id_sender: '',
});
await updateRecord(messageRecord?._id || messageRecord?.name || messageId, {
metadata: {
editor_status: EDITOR_STATUS_PENDING,
auditor_status: EDITOR_STATUS_PENDING,
sender_status: EDITOR_STATUS_PENDING,
progress_id_auditor: [],
progress_id_sender: '',
},
});
await loadFlow();
setSuccess(`已撤回 ${targetRows.length} 条问题,可继续填写。`);
} catch (requestError) {
setError(requestError?.message || '撤回送审失败,请稍后重试。');
} finally {
setSubmittingKey('');
}
};
const handleBatchAuditorDecision = async (approved) => {
const targetRows = rows.filter((row) => row.flowMode === FLOW_MODE_BATCH);
const targetRows = rows.filter(
(row) => row.flowMode === FLOW_MODE_BATCH && canAuditRow(row),
);
if (targetRows.length === 0) {
return;
}
......@@ -1055,10 +1216,23 @@ export default function ProblemRectificationFlowTablePage() {
progress_id_sender: progressIdSender,
progress_id_editor: approved ? targetRows[0].progressIdEditor : progressIdEditor,
});
const targetRowKeys = new Set(targetRows.map((row) => row.key));
const allRowsApproved =
approved &&
allRows.every(
(row) =>
targetRowKeys.has(row.key) ||
row.auditStatus === AUDIT_STATUS_APPROVED,
);
await updateRecord(messageRecord?._id || messageRecord?.name || messageId, {
metadata: {
editor_status: approved ? EDITOR_STATUS_COMPLETED : EDITOR_STATUS_PENDING,
auditor_status: approved ? EDITOR_STATUS_COMPLETED : EDITOR_STATUS_PENDING,
editor_status:
approved && allRows.every((row) => row.editorStatus === EDITOR_STATUS_COMPLETED)
? EDITOR_STATUS_COMPLETED
: EDITOR_STATUS_PENDING,
auditor_status: allRowsApproved
? EDITOR_STATUS_COMPLETED
: EDITOR_STATUS_PENDING,
sender_status: EDITOR_STATUS_PENDING,
progress_id_sender: progressIdSender,
progress_id_editor: approved ? toArray(metadata.progress_id_editor) : [progressIdEditor],
......@@ -1076,7 +1250,9 @@ export default function ProblemRectificationFlowTablePage() {
};
const handleBatchSenderDecision = async (approved) => {
const targetRows = rows.filter((row) => row.flowMode === FLOW_MODE_BATCH);
const targetRows = rows.filter(
(row) => row.flowMode === FLOW_MODE_BATCH && canFinalAuditRow(row),
);
if (targetRows.length === 0) {
return;
}
......@@ -1092,7 +1268,11 @@ export default function ProblemRectificationFlowTablePage() {
if (latestRecords.some((record) => record?.metadata?.is_locked === true)) {
throw new Error('本批次存在已锁定的问题整改记录,不允许继续审批。');
}
await completeProgressTask(targetRows[0].progressIdSender);
await Promise.all(
Array.from(new Set(targetRows.map((row) => row.progressIdSender).filter(Boolean))).map(
(progressId) => completeProgressTask(progressId),
),
);
const progressIdEditor = approved
? ''
: await createProgressTask({
......@@ -1107,10 +1287,23 @@ export default function ProblemRectificationFlowTablePage() {
final_audit_status: approved ? AUDIT_STATUS_APPROVED : AUDIT_STATUS_REJECTED,
progress_id_editor: approved ? targetRows[0].progressIdEditor : progressIdEditor,
});
const targetRowKeys = new Set(targetRows.map((row) => row.key));
const allRowsApproved =
approved &&
allRows.every(
(row) =>
targetRowKeys.has(row.key) ||
row.finalAuditStatus === AUDIT_STATUS_APPROVED,
);
await updateRecord(messageRecord?._id || messageRecord?.name || messageId, {
metadata: {
editor_status: approved ? EDITOR_STATUS_COMPLETED : EDITOR_STATUS_PENDING,
sender_status: approved ? EDITOR_STATUS_COMPLETED : EDITOR_STATUS_PENDING,
editor_status:
approved && allRows.every((row) => row.editorStatus === EDITOR_STATUS_COMPLETED)
? EDITOR_STATUS_COMPLETED
: EDITOR_STATUS_PENDING,
sender_status: allRowsApproved
? EDITOR_STATUS_COMPLETED
: EDITOR_STATUS_PENDING,
progress_id_editor: approved ? toArray(metadata.progress_id_editor) : [progressIdEditor],
},
});
......@@ -1154,6 +1347,34 @@ export default function ProblemRectificationFlowTablePage() {
progress_id_editor: approved ? row.progressIdEditor : progressIdEditor,
});
if (row.flowMode === FLOW_MODE_BATCH) {
const allRowsFilled =
approved &&
allRows.every(
(candidate) =>
candidate.key === row.key ||
candidate.editorStatus === EDITOR_STATUS_COMPLETED,
);
const allRowsApproved =
approved &&
allRows.every(
(candidate) =>
candidate.key === row.key ||
candidate.auditStatus === AUDIT_STATUS_APPROVED,
);
await updateRecord(messageRecord?._id || messageRecord?.name || messageId, {
metadata: {
editor_status: allRowsFilled
? EDITOR_STATUS_COMPLETED
: EDITOR_STATUS_PENDING,
auditor_status: allRowsApproved
? EDITOR_STATUS_COMPLETED
: EDITOR_STATUS_PENDING,
sender_status: EDITOR_STATUS_PENDING,
},
});
}
await sendNoticeToUsers({
title: approved ? '问题整改表待最终审批' : '问题整改表复核驳回',
content: approved
......@@ -1184,6 +1405,33 @@ export default function ProblemRectificationFlowTablePage() {
progress_id_editor: approved ? row.progressIdEditor : progressIdEditor,
});
if (row.flowMode === FLOW_MODE_BATCH) {
const allRowsFilled =
approved &&
allRows.every(
(candidate) =>
candidate.key === row.key ||
candidate.editorStatus === EDITOR_STATUS_COMPLETED,
);
const allRowsApproved =
approved &&
allRows.every(
(candidate) =>
candidate.key === row.key ||
candidate.finalAuditStatus === AUDIT_STATUS_APPROVED,
);
await updateRecord(messageRecord?._id || messageRecord?.name || messageId, {
metadata: {
editor_status: allRowsFilled
? EDITOR_STATUS_COMPLETED
: EDITOR_STATUS_PENDING,
sender_status: allRowsApproved
? EDITOR_STATUS_COMPLETED
: EDITOR_STATUS_PENDING,
},
});
}
if (!approved) {
await sendNoticeToUsers({
title: '问题整改表最终审批驳回',
......@@ -1203,8 +1451,14 @@ export default function ProblemRectificationFlowTablePage() {
);
const canSubmitRow = (row) =>
!row.isLocked &&
row.editor === currentUserId &&
(row.editor === currentUserId || isSender) &&
row.editorStatus !== EDITOR_STATUS_COMPLETED;
const canRecallRow = (row) =>
!row.isLocked &&
(row.editor === currentUserId || isSender) &&
row.editorStatus === EDITOR_STATUS_COMPLETED &&
row.auditStatus === AUDIT_STATUS_PENDING &&
row.finalAuditStatus === AUDIT_STATUS_PENDING;
const canAuditRow = (row) =>
!row.isLocked &&
row.auditor === currentUserId &&
......@@ -1231,28 +1485,6 @@ export default function ProblemRectificationFlowTablePage() {
row.editorStatus === EDITOR_STATUS_COMPLETED &&
row.auditStatus === AUDIT_STATUS_APPROVED &&
row.finalAuditStatus !== AUDIT_STATUS_APPROVED;
const batchCanSubmit =
flowMode === FLOW_MODE_BATCH &&
rows.length > 0 &&
rows.every(
(row) =>
!row.isLocked &&
row.editor === currentUserId &&
row.editorStatus !== EDITOR_STATUS_COMPLETED,
);
const batchSubmitRowCount = batchCanSubmit ? rows.length : 0;
const batchCanAudit =
flowMode === FLOW_MODE_BATCH &&
rows.length > 0 &&
rows.every((row) => !row.isLocked && row.editorStatus === EDITOR_STATUS_COMPLETED) &&
rows[0].auditor === currentUserId &&
rows.some((row) => row.auditStatus !== AUDIT_STATUS_APPROVED);
const batchCanFinalAudit =
flowMode === FLOW_MODE_BATCH &&
rows.length > 0 &&
rows.every((row) => !row.isLocked && row.auditStatus === AUDIT_STATUS_APPROVED) &&
senderId === currentUserId &&
rows.some((row) => row.finalAuditStatus !== AUDIT_STATUS_APPROVED);
const flowModeLabel = isDelayFlow
? flowMode === FLOW_MODE_DELAY_DEPARTMENT
? '按责任部门'
......@@ -1328,12 +1560,17 @@ export default function ProblemRectificationFlowTablePage() {
保存
</Button>
) : null}
{row.flowMode !== FLOW_MODE_BATCH && canSubmitRow(row) ? (
{canSubmitRow(row) ? (
<Button isDisabled={submitting} size="sm" variant="primary" onPress={() => handleEditorSubmit(row)}>
送审
提交审批
</Button>
) : null}
{row.flowMode !== FLOW_MODE_BATCH && canAuditRow(row) ? (
{canRecallRow(row) ? (
<Button isDisabled={submitting} size="sm" variant="secondary" onPress={() => handleEditorRecall(row)}>
撤回送审
</Button>
) : null}
{canAuditRow(row) ? (
<>
<Button isDisabled={submitting} size="sm" variant="primary" onPress={() => handleAuditorDecision(row, true)}>
审批通过
......@@ -1343,7 +1580,7 @@ export default function ProblemRectificationFlowTablePage() {
</Button>
</>
) : null}
{row.flowMode !== FLOW_MODE_BATCH && canFinalAuditRow(row) ? (
{canFinalAuditRow(row) ? (
<>
<Button isDisabled={submitting} size="sm" variant="primary" onPress={() => handleSenderDecision(row, true)}>
最终通过
......@@ -1353,7 +1590,7 @@ export default function ProblemRectificationFlowTablePage() {
</Button>
</>
) : null}
{row.flowMode !== FLOW_MODE_BATCH && !editable && !canAuditRow(row) && !canFinalAuditRow(row) ? (
{!editable && !canRecallRow(row) && !canAuditRow(row) && !canFinalAuditRow(row) ? (
<span className="text-xs text-muted">当前无可执行操作</span>
) : null}
</>
......@@ -1486,12 +1723,21 @@ export default function ProblemRectificationFlowTablePage() {
minWidth: 224,
render: (row) => {
const submitting = Boolean(submittingKey);
const draft = getDraft(row);
const proof = draft.proof;
const hasPendingProof = Object.prototype.hasOwnProperty.call(
drafts[row.key] || {},
'proof',
);
return (
<div>
{row.proof?.url ? (
<a href={row.proof.url} target="_blank" rel="noreferrer" className="text-sm font-medium text-accent">
{row.proof.name || '查看附件'}
{proof?.url ? (
<div className="flex flex-wrap items-center gap-2">
<a href={proof.url} target="_blank" rel="noreferrer" className="text-sm font-medium text-accent">
{proof.name || '查看附件'}
</a>
{hasPendingProof ? <Chip color="warning" size="sm">待保存</Chip> : null}
</div>
) : (
<div className="text-sm text-muted">未上传</div>
)}
......@@ -1499,6 +1745,7 @@ export default function ProblemRectificationFlowTablePage() {
<FileUploadField
className="mt-2"
description="上传整改证明材料。"
fileName={proof?.name || ''}
isDisabled={submitting}
triggerLabel="上传证明"
onFilesChange={(files) => handleUpload(row, files)}
......@@ -1537,7 +1784,12 @@ export default function ProblemRectificationFlowTablePage() {
setSuccess('');
}, [success]);
useEffect(() => {
document.title = '整改流程 - 问题整改';
}, []);
return (
<main className="min-h-dvh bg-background px-4 py-4 text-foreground sm:px-6 sm:py-6 lg:px-12">
<div className="rc-page-stack">
<Card>
<Card.Content className="flex flex-wrap items-start justify-between gap-4">
......@@ -1613,60 +1865,6 @@ export default function ProblemRectificationFlowTablePage() {
<DataTableEmptyState title={isDelayFlow ? '当前流程没有可处理的延期申请' : '当前流程没有可处理的整改明细'} />
) : (
<>
{batchSubmitRowCount > 0 || batchCanAudit || batchCanFinalAudit ? (
<div className="flex flex-wrap justify-end gap-2">
{batchSubmitRowCount > 0 ? (
<Button
isDisabled={Boolean(submittingKey)}
size="sm"
variant="primary"
onPress={handleBatchEditorSubmit}
>
批量送审({batchSubmitRowCount})
</Button>
) : null}
{batchCanAudit ? (
<>
<Button
isDisabled={Boolean(submittingKey)}
size="sm"
variant="primary"
onPress={() => handleBatchAuditorDecision(true)}
>
批量审批通过
</Button>
<Button
isDisabled={Boolean(submittingKey)}
size="sm"
variant="danger-soft"
onPress={() => handleBatchAuditorDecision(false)}
>
批量驳回
</Button>
</>
) : null}
{batchCanFinalAudit ? (
<>
<Button
isDisabled={Boolean(submittingKey)}
size="sm"
variant="primary"
onPress={() => handleBatchSenderDecision(true)}
>
批量最终通过
</Button>
<Button
isDisabled={Boolean(submittingKey)}
size="sm"
variant="danger-soft"
onPress={() => handleBatchSenderDecision(false)}
>
批量最终驳回
</Button>
</>
) : null}
</div>
) : null}
<DataTable
ariaLabel={flowPresentation.detailTitle}
className="problem-rectification-flow-grid"
......@@ -1678,7 +1876,8 @@ export default function ProblemRectificationFlowTablePage() {
</>
)}
</section>
</div>
<Toast.Provider maxVisibleToasts={3} placement="top" />
</main>
);
}
......@@ -5,6 +5,7 @@ import {
CalendarDaysIcon,
ChevronRightIcon,
Close,
Edit,
LockClosedIcon,
LockOpenIcon,
} from '@/components/AppIcons';
......@@ -25,6 +26,7 @@ import {
executeAction,
loadRecord,
loadUser,
queryRecords,
queryUsers,
sendNotice,
updateRecord,
......@@ -52,6 +54,7 @@ import {
getSelectOptions,
normalizeText,
shouldHideViewTitleColumn,
toArray,
} from '@/utils/subjectView';
import {
DEFAULT_PROBLEM_RECTIFICATION_PAGE_SIZE,
......@@ -126,6 +129,11 @@ const CREATE_FIELD_TYPE_OVERRIDES = {
actual_rectification_completed_at: 'date',
inspection_completed_at: 'date',
};
function getPushedProjectName(record) {
const projectName = normalizeText(record?.projectName);
return projectName && projectName !== '-' ? projectName : '未填写检查项目名称';
}
const CREATE_FIELD_OPTION_OVERRIDES = {
has_economic_loss: ['是', '否'],
has_external_impact: ['是', '否'],
......@@ -583,7 +591,10 @@ function UserIdentityCell({ fieldName, record, value }) {
function FieldValuePreview({ column, record }) {
const fieldName = getFieldNameFromPath(column.field);
const rawRecord = record.raw || record;
const value = getFieldValue(rawRecord, column.field);
const value =
fieldName === 'rectification_proof_materials' && !record.isDepartmentDetailRow
? record.rectificationProofMaterials
: getFieldValue(rawRecord, column.field);
if (fieldName === 'title') {
if (record.isDepartmentDetailRow) {
......@@ -802,7 +813,16 @@ export default function ProblemRectificationListPage() {
});
const [gridViewCounts, setGridViewCounts] = useState({});
const [workflowUserLabels, setWorkflowUserLabels] = useState({});
const [maintenanceFlowByRecordId, setMaintenanceFlowByRecordId] = useState({});
const createFieldVariant = 'secondary';
const currentUserId = useMemo(() => {
try {
const currentUser = JSON.parse(window.localStorage.getItem('current_user') || '{}');
return currentUser?._id || currentUser?.id || '';
} catch {
return '';
}
}, []);
const reloadResources = useCallback(async () => {
setLoading(true);
......@@ -880,6 +900,61 @@ export default function ProblemRectificationListPage() {
};
}, [records]);
useEffect(() => {
const recordIds = records.map((record) => record.id).filter(Boolean);
if (!currentUserId || recordIds.length === 0) {
setMaintenanceFlowByRecordId({});
return undefined;
}
let cancelled = false;
queryRecords({
subject: 'message_reference_id',
page: 0,
page_size: 100,
filter: {
'metadata.subject_name': PROBLEM_RECTIFICATION_SUBJECT,
'metadata.sender': currentUserId,
'metadata.restriction_value': { $in: recordIds },
},
sort: '-created_at',
mode: 'full',
})
.then((result) => {
if (cancelled) return;
const recordIdSet = new Set(recordIds);
const nextFlowByRecordId = {};
(result?.records || []).forEach((flowRecord) => {
const flowMetadata = flowRecord?.metadata || {};
if (
[FLOW_MODE_DELAY_DEPARTMENT, 'delay'].includes(flowMetadata.flow_mode) ||
String(flowRecord?.title || '').includes('延期申请')
) {
return;
}
const flowName = flowRecord?.name || flowRecord?._id;
toArray(flowMetadata.restriction_value).forEach((recordId) => {
const normalizedRecordId = String(recordId?._id || recordId || '');
if (recordIdSet.has(normalizedRecordId) && !nextFlowByRecordId[normalizedRecordId]) {
nextFlowByRecordId[normalizedRecordId] = flowName;
}
});
});
setMaintenanceFlowByRecordId(nextFlowByRecordId);
})
.catch(() => {
if (!cancelled) {
setMaintenanceFlowByRecordId({});
}
});
return () => {
cancelled = true;
};
}, [currentUserId, records]);
const gridViews = useMemo(() => buildGridViews(views), [views]);
useEffect(() => {
......@@ -1882,6 +1957,19 @@ export default function ProblemRectificationListPage() {
hidden: ({ record }) => !resolveDelayRequestDisplay(record),
onClick: ({ record }) => setDelayDetailRecord(record),
},
{
key: 'maintain-rectification',
label: '维护整改内容',
icon: Edit,
variant: 'secondary',
hidden: ({ record }) => !maintenanceFlowByRecordId[record.id],
onClick: ({ record }) => {
window.location.href = getProblemRectificationFlowTableUrl(
maintenanceFlowByRecordId[record.id],
{ recordId: record.id },
);
},
},
{
key: 'lock',
label: '锁定',
......@@ -2297,12 +2385,12 @@ export default function ProblemRectificationListPage() {
<Card className="mt-6" variant="secondary">
<Card.Header>
<Card.Title>本次推送记录</Card.Title>
<Card.Title>本次推送检查项目名称</Card.Title>
</Card.Header>
<Card.Content className="flex flex-wrap gap-2">
{selectedRecords.map((record) => (
<Chip key={record.id} size="sm">
{record.title || record.projectName || record.name}
{getPushedProjectName(record)}
</Chip>
))}
</Card.Content>
......
......@@ -349,17 +349,19 @@
### 7.3 填报人补录整改内容
1. 填报人通过消息链接进入新表格处理页 `<appBasePath>/problem_rectification_flow_table`
1. 填报人通过消息链接进入新表格处理页 `<appBasePath>/problem_rectification_flow_table`;该地址使用独立流程页面布局,不展示问题整改应用左侧菜单,但继续保留登录校验、流程标题、刷新入口和处理结果提示
2. 页面展示本次流程绑定的所有部门明细行;单条和多条推送使用相同的明细字段与表格结构。参与人可查看全部行,填写人只能编辑自己负责的行,流程推送人可以编辑全部部门明细行。表格同时只读展示父问题的 `problem_brief`(问题简述)和 `related_issue_domain`(涉及业务领域);填写人和审批人字段保存用户 `_id`,表格展示用户姓名,姓名查询不到时才回退显示 ID
3. 填报人可在表格行内编辑:
- `rectification_progress`
- `rectification_situation`
- `the_time_of_rectification`
- `rectification_proof_materials`
4. 部门流程按行送审并锁定当前行;多选简单推送在表格上方只展示一个“批量送审”按钮,点击后保存本批次全部问题并统一进入待复核,不在每条问题行重复展示“送审”按钮
5. 审批驳回后,原审批人也可以编辑该行整改业务字段,但不能代替填写人点击“送审”;待填写人重新送审后,审批人恢复审批操作
6. 问题整改推送与延期申请推送的流程页均不提供父问题“编辑问题记录”入口或详情抽屉;流程参与人仅在当前表格行内执行其整改或延期填写、送审和审批操作
7. 多选简单推送的填写、复核和最终审批都按批次一次处理;任一审批阶段驳回后,整批问题共同回到填写阶段
4. 每行草稿、展示值和保存目标按父记录中的部门明细位置独立隔离;存在部门明细时不得使用父记录汇总字段为明细空值兜底。选择证明材料后只调用 `/upload` 上传文件并更新当前行本地草稿,页面不重新加载,也不立即写入 `rectification_proof_materials`;新附件显示为“待保存”,用户点击该行“保存”或“提交审批”后才写入当前部门明细。即使历史数据存在重复 `item_id`,保存时也不得联动修改同一父问题下的其它部门明细
5. 单条和多条推送都支持按行提交审批并锁定当前行;行填写人和流程发起人均可执行“提交审批”。当前页面不展示批量提交入口,单行提交不得改变同批次其它行的填写和审批状态
6. 提交后、一级审批人尚未处理前,行填写人或流程发起人可以撤回送审。撤回会结束已经创建的审批 OA 待办,把对应部门明细或批量流程退回 `editor_status = "未完成"`、`audit_status = "未审批"`、`final_audit_status = "未审批"`,清空审批和最终审批待办 ID,页面重新进入可填写状态,审批人无需继续审批
7. 审批驳回后,原审批人也可以编辑该行整改业务字段,但不能代替填写人点击“送审”;待填写人重新送审后,审批人恢复审批操作
8. 问题整改推送与延期申请推送的流程页均不提供父问题“编辑问题记录”入口或详情抽屉;流程参与人仅在当前表格行内执行其整改或延期填写、送审和审批操作
9. 多选简单推送支持逐行提交、撤回、复核和最终审批;当前页面仅开放行内操作,单行驳回只退回该行
推荐新交互:
......@@ -367,8 +369,8 @@
- 明细表顶部再次显示“问题整改明细”或“延期申请明细”及当前行数,用户从 OA 待办直接进入页面或横向浏览表格时仍能识别当前处理类型;流程类型不能只靠颜色表达
- 主体统一使用表格,不再使用旧填写页的详情式编辑
- 长文本、附件、预计完成时间和整改进度均在表格行内维护
- 部门流程的“送审”和各阶段审批作为行级操作展示;多选简单推送的送审、复核和最终审批按钮统一放在表格上方,行内只保留“保存”
- 推送人具有部门明细“保存”权限,但不会因此获得填写人的“送审”权限;推送人同时是该行填写人时,才按填写人身份送审
- 部门流程和多选简单推送都只在行内展示“保存”“提交审批”“撤回送审”和当前阶段审批操作;批量提交、批量撤回和批量审批入口暂不展示
- 推送人具有部门明细“保存”和“提交审批”权限,可以代表行填写人将当前行或整批流程提交给一级审批人
### 7.4 复核人审核
......@@ -392,7 +394,7 @@
复核人审批通过后,该部门明细业务字段锁定;只有审批驳回状态下,复核人拥有该行“保存”权限。填写人重新送审后,复核人的编辑权限随行重新进入审批阶段而关闭。
多选简单推送由同一复核人对整批问题一次通过或驳回。通过时批量更新全部关联问题为“已审批”并创建 1 条最终审批待办;驳回时批量更新为“已驳回”,流程主单填写状态重置为“未完成”,整批问题共同退回填写人。
多选简单推送由同一复核人处理,但支持逐行通过或驳回;通过时只推进当前行并创建该行最终审批待办,驳回时只退回当前行。当前页面不展示批量通过、批量驳回和批量撤回入口,流程主单顶层状态按全部部门明细汇总。
推荐新交互:
......@@ -417,7 +419,7 @@
- 最终审批行级处理,不要求本次推送所有部门都先审批通过
- 发起人编辑已经送审或审批完成的父问题、部门整改内容时,不自动重置审批状态,也不自动重新生成 OA 待办
多选简单推送由发起人对整批问题一次最终通过或驳回。最终结果批量写入全部关联问题;驳回时整批问题共同退回填写阶段,并只创建 1 条填写 OA 待办。
多选简单推送由发起人执行最终审批,支持逐行通过或驳回;当前页面不展示批量最终审批入口。单行驳回只退回当前行并为该行重新创建填写 OA 待办。
### 7.6 问题延期申请
......@@ -465,7 +467,8 @@
- 发起校验:至少选择 1 条问题;已锁定记录不能推送;单条问题按责任部门配置填写人和复核人,多条问题只允许 1 个填写人和 1 个复核人
- 发起写入:先创建 `message_reference_id`,写入 `restriction_value`、`reference_record_id`、`subject_name = "problem_rectification"`、`flow_mode`、`department_item_ids` 和三段流程状态;再通过 `/record/batch/update` 回写每条问题的部门整改明细。已推送过的问题再次推送时,不新建同部门的替代明细,不清空原整改内容,而是在原 `item_id` 对应行上更新本次流程人员、状态和待办 ID
- 待办通知:单条部门流程按部门明细创建填写 OA 待办;多条批量流程只创建 1 条填写 OA 待办并由整批问题共享;内部通知发送给本次填写人
- 填写阶段:填写人只编辑本人负责明细的整改进度、整改情况、预计完成时间和整改证明材料;推送人可保存本次流程关联的全部部门明细业务字段,但不能代替填写人送审
- 填写阶段:填写人只编辑本人负责明细的整改进度、整改情况、预计完成时间和整改证明材料;推送人可保存本次流程关联的全部部门明细业务字段,并可代表填写人提交当前行审批
- 撤回送审:填写人或推送人提交后、一级审批尚未处理前,可以逐行撤回对应部门明细。撤回完成当前审批 OA 待办,清空审批与最终审批待办 ID,并将状态退回填写阶段,审批人无需审批
- 复核阶段:复核通过写入 `audit_status = "已审批"` 并创建最终审批待办;复核驳回写入 `audit_status = "已驳回"`、重置 `editor_status = "未完成"`,并重新创建填写待办
- 最终审批阶段:最终审批通过写入 `final_audit_status = "已审批"`;最终审批驳回写入 `final_audit_status = "已驳回"`、重置 `editor_status = "未完成"`,并重新通知填写人
- 父记录汇总:父记录的整改进度、审批状态、最终审批状态按部门明细中最落后的状态汇总,父字段只做兼容展示
......@@ -543,8 +546,10 @@
- 父行的整改进度、审批状态、最终审批状态按部门明细中最落后的状态汇总;例如 1 条“整改中”与 1 条“已整改”汇总为“整改中”,1 条“未审批”与 1 条“已审批”汇总为“未审批”
- 父行可展开显示部门明细,明细中分开展示责任部门、填写人、审批人、整改进度、预计完成时间、审批状态、最终审批状态和附件状态
- 存在部门明细的父行在标题右侧显示“N 条部门明细”入口;单部门推送同样生成 1 条明细。展开后部门明细作为父行下方子行平铺展示,复用主表列顺序、列宽、固定列和列分组,与问题整改列头一一对齐;子行不参与主表勾选和批量操作,并将填写人、审批人 ID 转为用户显示名
- 转发后的整改填写、审批和最终审批均优先更新部门明细,父字段仅做兼容汇总展示
- 新流程页中,填写人只能维护本人负责行的整改业务字段,送审后锁定,复核驳回后恢复;复核驳回状态下对应审批人可以维护该行整改业务字段,审批通过后锁定,且审批人不能点击填写人的“送审”;流程推送人可以维护全部关联部门明细整改业务字段,但流程页不提供父问题记录编辑或详情抽屉,且不能直接编辑流程状态与待办 ID
- 转发后的整改填写、审批和最终审批均优先更新部门明细。填写人或推送人保存后,父对象只同步 `rectification_progress`(最落后的部门进度)和 `the_time_of_rectification`(最晚的部门预计完成时间)。问题清单父行直接收集全部部门明细的 `rectification_proof_materials` 用于展示,不回写父对象,不修改主对象文件字段的单值配置;`rectification_situation` 继续按部门明细展示
- 问题清单根据当前用户作为 `message_reference_id.metadata.sender` 发起的最近一次整改流程,为对应父问题显示行内“维护整改内容”。推送人点击后复用原流程主单进入独立流程表格,并通过 `record=<problem_record_id>` 只展示当前问题,无需重新推送,不创建新待办或通知;保存业务字段不重置已有审批状态
- 新流程页中,填写人只能维护本人负责行的整改业务字段,提交审批后锁定,复核驳回后恢复;复核驳回状态下对应审批人可以维护该行整改业务字段,审批通过后锁定,但审批人不能代替填写人提交。流程推送人可以维护全部关联部门明细整改业务字段,并可提交当前行审批,但流程页不提供父问题记录编辑或详情抽屉,且不能直接编辑流程状态与待办 ID
- 填写人或流程发起人撤回送审仅允许在一级审批未处理前执行;撤回后当前审批 OA 待办置完成,明细和父级汇总重新表现为未审批、可填写状态
- 推送人的内容修改保持当前审批状态不变,不触发重新送审
### 8.4 延期申请状态约定
......
......@@ -187,15 +187,54 @@ function summarizeDepartmentAuditStatus(values = []) {
}
function summarizeDate(values = []) {
const normalizedValues = uniqueTruthy(values);
return normalizedValues.length === 1 ? normalizedValues[0] : null;
return values.reduce((latestValue, value) => {
const normalizedValue = normalizeText(value);
if (!normalizedValue) {
return latestValue;
}
const parsedValue = dayjs(normalizedValue);
if (!parsedValue.isValid()) {
return latestValue;
}
return !latestValue || parsedValue.isAfter(dayjs(latestValue))
? normalizedValue
: latestValue;
}, null);
}
export function getProblemRectificationFlowTableUrl(messageName) {
const query = messageName ? `?id=${encodeURIComponent(messageName)}` : '';
function collectDepartmentProofMaterials(items = []) {
const seenKeys = new Set();
return items
.flatMap((item) => toArray(item?.rectification_proof_materials))
.filter((file) => {
if (!file) {
return false;
}
const fileKey = normalizeText(
file?.url || `${file?.name || file?.title || ''}-${file?.size || ''}`,
);
if (!fileKey || seenKeys.has(fileKey)) {
return false;
}
seenKeys.add(fileKey);
return true;
});
}
export function getProblemRectificationFlowTableUrl(messageName, { recordId = '' } = {}) {
const query = new URLSearchParams();
if (messageName) {
query.set('id', messageName);
}
if (recordId) {
query.set('record', recordId);
}
const basePath = String(PROBLEM_RECTIFICATION_APP_BASE_PATH || '/')
.replace(/\/+$/, '');
const appPath = `${basePath}${PROBLEM_RECTIFICATION_FLOW_TABLE_PATH}${query}`;
const queryString = query.toString();
const appPath = `${basePath}${PROBLEM_RECTIFICATION_FLOW_TABLE_PATH}${queryString ? `?${queryString}` : ''}`;
if (typeof window === 'undefined') {
return appPath;
......@@ -966,6 +1005,9 @@ export function normalizeProblemRectificationRecord(record) {
const summarizedDueDate = hasDepartmentItems
? summarizeDate(departmentItems.map((item) => item.the_time_of_rectification))
: metadata.the_time_of_rectification;
const rectificationProofMaterials = hasDepartmentItems
? collectDepartmentProofMaterials(departmentItems)
: metadata.rectification_proof_materials;
const dueDate = summarizedDueDate
? dayjs(summarizedDueDate)
: null;
......@@ -1043,6 +1085,7 @@ export function normalizeProblemRectificationRecord(record) {
.map(normalizeUserLabel)
.filter(Boolean),
dueDateLabel: formatDateText(summarizedDueDate) || (hasDepartmentItems ? '见部门明细' : '-'),
rectificationProofMaterials,
departmentItems,
hasDepartmentItems,
delayRequest: normalizeDelayRequest(metadata[DELAY_REQUEST_FIELD]),
......
......@@ -188,11 +188,11 @@ type Record = {
- 其余 `select` 类型的可选值必须以线上 Subject 配置中的 `settings.options` 为准,禁止在页面内硬编码
- `problem_discovery_situation` 曾在历史文档和归档对象中出现,但本次主对象字段定义未包含;新页面只应在 Subject View 或线上字段配置存在时展示,不能作为 `problem_rectification` 固定字段依赖
- 问题清单详情弹框中的 `rectification_proof_materials` 支持从 `am_material.metadata.file_asset` 复制文件快照,或上传本地文件后创建 `am_material`。控件运行时读取线上字段的 `multiple`:当前定义为单值时保存 `{name,type,size,url}`;若后续配置为多值则保存文件对象数组,并支持追加、逐项删除及为每个本地文件分别建档。问题字段不保存材料主档 `_id`,材料后续修改或软删除不反向联动问题记录
- `department_rectification_items` 用于新转发流程的部门级整改明细。无论单条还是多条推送,每个问题都按责任部门生成明细;单条问题即使只有 1 个责任部门,也生成 1 条明细。已推送过的问题再次推送时,必须保留原部门明细记录和 `item_id`,只在本次参与推送的原明细行上更新填写人、审批人、流程状态和待办 ID;未参与本次推送的原明细行保持不变,不因弹窗删除部门配置而从父记录移除。转发后 `rectification_progress`、`rectification_situation`、`the_time_of_rectification`、`rectification_proof_materials`、`audit_status`、`final_audit_status` 以明细为事实来源,父字段仅做兼容汇总展示;整改进度、审批状态和最终审批状态按部门明细中最落后的状态汇总,例如“整改中”优先于“已整改”,“未审批”优先于“已审批”
- `department_rectification_items` 用于新转发流程的部门级整改明细。无论单条还是多条推送,每个问题都按责任部门生成明细;单条问题即使只有 1 个责任部门,也生成 1 条明细。已推送过的问题再次推送时,必须保留原部门明细记录和 `item_id`,只在本次参与推送的原明细行上更新填写人、审批人、流程状态和待办 ID;未参与本次推送的原明细行保持不变,不因弹窗删除部门配置而从父记录移除。转发后 4 个业务字段均以明细为事实来源:父对象 `rectification_progress` 汇总最落后的部门进度,`the_time_of_rectification` 汇总最晚的部门预计完成时间;问题清单父行直接收集全部部门明细的 `rectification_proof_materials` 用于展示,不写回主对象,也不要求主对象文件字段改为多值;`rectification_situation` 不汇总到父对象,继续按部门明细展示。审批状态和最终审批状态也按部门明细中最落后的状态汇总,例如“未审批”优先于“已审批”
- `problem_rectification_delay_request` 为历史问题级单次延期申请嵌套对象,与 `department_rectification_items` 平级;新发起延期默认按部门写入 `problem_rectification_delay_request_items`
- `problem_rectification_delay_request_items` 为部门级延期申请明细数组;每个责任部门 1 条延期申请,独立保存填写人、一级审批人、最终审批人、申请完成时间、延期事由、状态和待办 ID
- `is_locked` 为问题整改主记录的状态锁定标记;仅整改进度汇总值为 `已整改` 的记录允许写入 `true`,锁定后所有业务字段和流程状态均不可修改,只有解锁操作可以将其写回 `false`
- 部门明细的填写人只能编辑自己负责明细的整改业务字段,送审后(`editor_status = "已完成"`)不再允许编辑;审批人审批通过后(`audit_status = "已审批"`)不再允许编辑,审批驳回后(`audit_status = "已驳回"`)审批人可以编辑该明细的整改业务字段,填写人也恢复编辑。审批人恢复的只是业务字段编辑权限,不获得填写人的送审权限。流程推送人可以编辑本次推送关联的全部部门明细整改业务字段,但流程页不提供父问题记录编辑或详情抽屉。审批状态、待办 ID、明细 ID 等流程控制字段不作为普通表单字段开放
- 部门明细的填写人只能编辑自己负责明细的整改业务字段,流程推送人可以编辑本次推送关联的全部部门明细整改业务字段;填写人或流程推送人均可提交对应明细或整批流程审批。提交后(`editor_status = "已完成"`)不再允许填写人编辑;在一级审批人尚未审批且 `audit_status = "未审批"`、`final_audit_status = "未审批"` 时,填写人或流程推送人可以撤回送审。撤回后该明细回到 `editor_status = "未完成"`、`audit_status = "未审批"`、`final_audit_status = "未审批"`,清空审批与最终审批待办 ID,并将已创建的审批 OA 待办置为完成,审批人无需继续审批。审批人审批通过后(`audit_status = "已审批"`)不再允许编辑,审批驳回后(`audit_status = "已驳回"`)审批人可以编辑该明细的整改业务字段,填写人也恢复编辑。审批人恢复的只是业务字段编辑权限,不获得提交审批权限。流程页不提供父问题记录编辑或详情抽屉,审批状态、待办 ID、明细 ID 等流程控制字段不作为普通表单字段开放
- 推送人修改父问题记录或部门明细时,不自动重置该明细已经形成的审批状态,也不自动重新生成流程待办
### 3.1.3 `department_rectification_items` 子结构
......@@ -207,7 +207,7 @@ type Record = {
| 整改情况 | `rectification_situation` | long_text | 否 | 填写人维护 |
| 延期事由(兼容) | `delay_reason` | long_text | 否 | 已存在的明细字段;新延期流程改为写入问题主记录平级嵌套对象的同名字段,本字段仅保留兼容数据 |
| 预计整改完成时间 | `the_time_of_rectification` | date | 否 | 填写人维护,提交时保存 ISO 8601 |
| 整改证明材料 | `rectification_proof_materials` | file | 否 | 填写人上传,按部门明细独立保存 |
| 整改证明材料 | `rectification_proof_materials` | file | 否 | 填写人或推送人上传,按部门明细独立保存;问题清单父行直接汇总展示全部部门材料,不写回主对象字段 |
| 填写状态 | `editor_status` | text | 否 | `未完成`、`已完成` |
| 审批状态 | `audit_status` | text | 否 | `未审批`、`已审批`、`已驳回` |
| 最终审批状态 | `final_audit_status` | text | 否 | `未审批`、`已审批`、`已驳回` |
......@@ -402,11 +402,11 @@ type Record = {
- `flow_mode` 区分流程:`department` 表示单条问题按责任部门生成整改明细行,`batch` 表示多选问题的单填写人 / 单审批人简单推送,`delay` 表示历史问题级延期申请,`delay_department` 表示按责任部门生成延期申请明细
- `department_item_ids` 保存本次流程涉及的部门级 `item_id`;单条和多条整改流程对应 `department_rectification_items[].item_id`,延期流程对应 `problem_rectification_delay_request_items[].item_id`
- 部分线上 `message_reference_id` 对象会过滤未配置的 `flow_mode` 或 `department_item_ids`;延期流程页须回退按关联问题 `problem_rectification_delay_request_items[].flow_record_id._id` 匹配当前流程主单 `_id`,或按共享的 `progress_id_editor` 匹配,不能将其当作普通整改流程
- 批量流程的填写、复核、最终审批状态及待办 ID 直接使用流程主单顶层的 `editor_status`、`auditor_status`、`sender_status` 和 `progress_id_*`,同一批次的全部问题共享流程状态
- 批量流程以各 `department_rectification_items[]` 的填写、复核、最终审批状态及待办 ID 为行级事实来源,支持逐行提交和审批;流程主单顶层的 `editor_status`、`auditor_status`、`sender_status` 仅汇总全部关联明细是否完成,`progress_id_*` 用于保存该批次仍有效的待办 ID
- `editor_status`:当前项目统一使用 `未完成`、`已完成`
- `auditor_status`:当前项目统一使用 `未完成`、`已完成`
- `sender_status`:当前项目统一使用 `未完成`、`已完成`
- 当批量流程复核或最终审核驳回时,流程主单 `editor_status` 重置为 `未完成` 并回写新的 `progress_id_editor`,同一批次的全部问题共同重新进入填写阶段
- 当批量流程单行复核或最终审核驳回时,只将该部门明细 `editor_status` 重置为 `未完成` 并回写新的 `progress_id_editor`;流程主单顶层 `editor_status` 汇总为 `未完成`,其它已提交或已审批明细保持原状态
## 3.6 项目管理
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment