Commit 5a67a51e authored by David Yang's avatar David Yang

feat: 完善批量整改流程和严重程度计算

parent 1cf5afe5
This source diff could not be displayed because it is too large. You can view the blob instead.
allowBuilds:
core-js: false
core-js-pure: false
es5-ext: false
esbuild: false
{ {
"name": "problem_rectification_new", "name": "problem_rectification_new",
"version": "1.0.6", "version": "1.0.7",
"build": "abc", "build": "abc",
"description": "merge master", "description": "merge master",
"date": "2025-07-14 19:27:20", "date": "2025-07-14 19:27:20",
......
...@@ -40,7 +40,27 @@ function transformSaveValue(newValue, fieldDef) { ...@@ -40,7 +40,27 @@ function transformSaveValue(newValue, fieldDef) {
return newValue; return newValue;
} }
export function useSubjectRecord(recordName, refreshKey, readonly, subjectName) { function buildNestedPayload(fieldPath, value) {
return fieldPath.split('.').reduceRight((nestedValue, key) => ({ [key]: nestedValue }), value);
}
function mergePayload(target, source) {
return Object.entries(source || {}).reduce((result, [key, value]) => ({
...result,
[key]:
value && typeof value === 'object' && !Array.isArray(value)
? mergePayload(result[key] || {}, value)
: value,
}), target);
}
export function useSubjectRecord(
recordName,
refreshKey,
readonly,
subjectName,
deriveFieldValues,
) {
const [remoteRecord, setRemoteRecord] = useState(null); const [remoteRecord, setRemoteRecord] = useState(null);
const [editableRecord, setEditableRecord] = useState(null); const [editableRecord, setEditableRecord] = useState(null);
const [subject, setSubject] = useState(null); const [subject, setSubject] = useState(null);
...@@ -107,12 +127,24 @@ export function useSubjectRecord(recordName, refreshKey, readonly, subjectName) ...@@ -107,12 +127,24 @@ export function useSubjectRecord(recordName, refreshKey, readonly, subjectName)
} }
const previousRecord = editableRecord; const previousRecord = editableRecord;
const nextRecord = setFieldValue(editableRecord, fieldPath, newValue); let nextRecord = setFieldValue(editableRecord, fieldPath, newValue);
setEditableRecord(nextRecord);
try { try {
const saveValue = transformSaveValue(newValue, fieldDef); const saveValue = transformSaveValue(newValue, fieldDef);
const payload = fieldPath.split('.').reduceRight((value, key) => ({ [key]: value }), saveValue); let payload = buildNestedPayload(fieldPath, saveValue);
const derivedFieldValues = deriveFieldValues?.({
fieldPath,
fieldDef,
newValue: saveValue,
record: nextRecord,
}) || {};
Object.entries(derivedFieldValues).forEach(([derivedFieldPath, derivedValue]) => {
nextRecord = setFieldValue(nextRecord, derivedFieldPath, derivedValue);
payload = mergePayload(payload, buildNestedPayload(derivedFieldPath, derivedValue));
});
setEditableRecord(nextRecord);
await updateRecord(recordName, payload); await updateRecord(recordName, payload);
setRemoteRecord(nextRecord); setRemoteRecord(nextRecord);
} catch (updateError) { } catch (updateError) {
......
...@@ -51,6 +51,7 @@ const RecordDetailDrawer = (props, ref) => { ...@@ -51,6 +51,7 @@ const RecordDetailDrawer = (props, ref) => {
preferredViewName, preferredViewName,
disabledFields = EMPTY_ARRAY, disabledFields = EMPTY_ARRAY,
authorizationView = null, authorizationView = null,
deriveFieldValues,
} = props; } = props;
const [refreshKey, setRefreshKey] = useState(() => Date.now()); const [refreshKey, setRefreshKey] = useState(() => Date.now());
...@@ -66,7 +67,13 @@ const RecordDetailDrawer = (props, ref) => { ...@@ -66,7 +67,13 @@ const RecordDetailDrawer = (props, ref) => {
handleFieldUpdate, handleFieldUpdate,
isFieldDisabled, isFieldDisabled,
loading, loading,
} = useSubjectRecord(recordName, refreshKey, readonly, subjectName); } = useSubjectRecord(
recordName,
refreshKey,
readonly,
subjectName,
deriveFieldValues,
);
useEffect(() => { useEffect(() => {
if (open) { if (open) {
......
...@@ -464,6 +464,32 @@ function buildFocusIssueBarOption(data = []) { ...@@ -464,6 +464,32 @@ function buildFocusIssueBarOption(data = []) {
function buildBubbleMatrixOption(data = []) { function buildBubbleMatrixOption(data = []) {
const maxTotal = Math.max(...data.map((item) => item.totalCount), 1); const maxTotal = Math.max(...data.map((item) => item.totalCount), 1);
const maxOverdue = Math.max(...data.map((item) => item.overdueCount), 1); const maxOverdue = Math.max(...data.map((item) => item.overdueCount), 1);
const xAxisMax = maxTotal + Math.max(1, Math.ceil(maxTotal * 0.08));
const coordinateOccurrences = new Map();
const chartData = data.map((item) => {
const coordinateKey = `${item.totalCount}:${item.completionRate}`;
const labelStackIndex = coordinateOccurrences.get(coordinateKey) || 0;
coordinateOccurrences.set(coordinateKey, labelStackIndex + 1);
return {
...item,
value: [item.totalCount, item.completionRate],
symbolSize: 16 + (item.overdueCount / maxOverdue) * 34,
itemStyle: {
color:
item.completionRate >= 75
? '#22c55e'
: item.completionRate >= 45
? '#f59e0b'
: '#e11d48',
opacity: 0.85,
},
label: {
position: item.completionRate >= 75 ? 'bottom' : 'top',
distance: 8 + labelStackIndex * 18,
},
};
});
return { return {
tooltip: { tooltip: {
...@@ -481,15 +507,17 @@ function buildBubbleMatrixOption(data = []) { ...@@ -481,15 +507,17 @@ function buildBubbleMatrixOption(data = []) {
textStyle: { color: '#fff' }, textStyle: { color: '#fff' },
}, },
grid: { grid: {
left: 8, left: 12,
right: 16, right: 24,
top: 12, top: 28,
bottom: 10, bottom: 16,
containLabel: true, containLabel: true,
}, },
xAxis: { xAxis: {
type: 'value', type: 'value',
name: '问题总数', name: '问题总数',
min: 0,
max: xAxisMax,
nameTextStyle: { color: '#64748b' }, nameTextStyle: { color: '#64748b' },
splitLine: { lineStyle: { color: 'rgba(148,163,184,0.18)' } }, splitLine: { lineStyle: { color: 'rgba(148,163,184,0.18)' } },
axisLabel: { color: '#64748b' }, axisLabel: { color: '#64748b' },
...@@ -499,33 +527,41 @@ function buildBubbleMatrixOption(data = []) { ...@@ -499,33 +527,41 @@ function buildBubbleMatrixOption(data = []) {
name: '整改完成率(%)', name: '整改完成率(%)',
nameTextStyle: { color: '#64748b' }, nameTextStyle: { color: '#64748b' },
min: 0, min: 0,
max: 100, max: 105,
splitLine: { lineStyle: { color: 'rgba(148,163,184,0.18)' } }, splitLine: { lineStyle: { color: 'rgba(148,163,184,0.18)' } },
axisLabel: { color: '#64748b' }, axisLabel: { color: '#64748b' },
}, },
series: [ series: [
{ {
type: 'scatter', type: 'scatter',
data: data.map((item) => ({ data: chartData,
...item,
value: [item.totalCount, item.completionRate],
symbolSize: 16 + (item.overdueCount / maxOverdue) * 34,
itemStyle: {
color:
item.completionRate >= 75
? '#22c55e'
: item.completionRate >= 45
? '#f59e0b'
: '#e11d48',
opacity: 0.85,
},
})),
label: { label: {
show: true, show: true,
formatter: (params) => params.data.name, formatter: (params) => params.data.name,
position: 'top', position: 'top',
distance: 8,
color: '#334155', color: '#334155',
fontSize: 12, fontSize: 12,
backgroundColor: 'rgba(255,255,255,0.9)',
borderRadius: 3,
padding: [2, 4],
},
labelLayout: {
moveOverlap: 'shiftY',
hideOverlap: true,
},
emphasis: {
focus: 'self',
label: {
show: true,
color: '#0f172a',
fontWeight: 700,
backgroundColor: '#fff',
borderColor: '#cbd5e1',
borderWidth: 1,
borderRadius: 3,
padding: [3, 5],
},
}, },
}, },
], ],
......
...@@ -201,35 +201,49 @@ function buildDepartmentRows(records = []) { ...@@ -201,35 +201,49 @@ function buildDepartmentRows(records = []) {
function buildBatchRows(records = [], metadata = {}) { function buildBatchRows(records = [], metadata = {}) {
const editor = toId(toArray(metadata.editor)[0]); const editor = toId(toArray(metadata.editor)[0]);
const auditor = toId(toArray(metadata.auditor)[0]); const auditor = toId(toArray(metadata.auditor)[0]);
const scopedItemIds = new Set(toArray(metadata.department_item_ids).map(toId).filter(Boolean));
return records.map((record) => { return records.flatMap((record) => {
const recordMetadata = record.metadata || {}; const recordMetadata = record.metadata || {};
return { const recordId = record._id || record.name;
key: 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,
flowMode: FLOW_MODE_BATCH, flowMode: FLOW_MODE_BATCH,
record, record,
item: null, item,
itemId: record._id || record.name, itemId: item?.item_id || recordId,
recordId: record._id || record.name, recordId,
title: record.title || recordMetadata.project_name || '-', title: record.title || recordMetadata.project_name || '-',
projectName: recordMetadata.project_name || '-', projectName: recordMetadata.project_name || '-',
problemBrief: recordMetadata.problem_brief || '', problemBrief: recordMetadata.problem_brief || '',
relatedIssueDomain: toArray(recordMetadata.related_issue_domain).join('、'), relatedIssueDomain: toArray(recordMetadata.related_issue_domain).join('、'),
department: toArray(recordMetadata.responsible_department).join('、') || '-', department:
editor, item?.responsible_department ||
auditor, toArray(recordMetadata.responsible_department).join('、') ||
progress: recordMetadata.rectification_progress || '', '-',
situation: recordMetadata.rectification_situation || '', editor: item?.editor || editor,
dueDate: recordMetadata.the_time_of_rectification || '', auditor: item?.auditor || auditor,
proof: recordMetadata.rectification_proof_materials || null, 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, editorStatus: metadata.editor_status || EDITOR_STATUS_PENDING,
auditStatus: recordMetadata.audit_status || AUDIT_STATUS_PENDING, auditStatus: item?.audit_status || recordMetadata.audit_status || AUDIT_STATUS_PENDING,
finalAuditStatus: recordMetadata.final_audit_status || AUDIT_STATUS_PENDING, finalAuditStatus:
progressIdEditor: toArray(metadata.progress_id_editor)[0] || '', item?.final_audit_status || recordMetadata.final_audit_status || AUDIT_STATUS_PENDING,
progressIdAuditor: toArray(metadata.progress_id_auditor)[0] || '', progressIdEditor:
progressIdSender: metadata.progress_id_sender || '', item?.progress_id_editor || toArray(metadata.progress_id_editor)[0] || '',
progressIdAuditor:
item?.progress_id_auditor || toArray(metadata.progress_id_auditor)[0] || '',
progressIdSender: item?.progress_id_sender || metadata.progress_id_sender || '',
isLocked: recordMetadata.is_locked === true, isLocked: recordMetadata.is_locked === true,
}; }));
}); });
} }
...@@ -499,7 +513,11 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -499,7 +513,11 @@ export default function ProblemRectificationFlowTablePage() {
}; };
const updateDepartmentItem = async (row, patch) => { const updateDepartmentItem = async (row, patch) => {
const nextItems = normalizeDepartmentRectificationItems(row.record).map((item) => const latestRecord = await loadRecord(row.recordId);
if (latestRecord?.metadata?.is_locked === true) {
throw new Error('当前问题整改记录已锁定,不允许继续修改或审批。');
}
const nextItems = normalizeDepartmentRectificationItems(latestRecord).map((item) =>
item.item_id === row.itemId item.item_id === row.itemId
? { ? {
...item, ...item,
...@@ -624,6 +642,49 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -624,6 +642,49 @@ export default function ProblemRectificationFlowTablePage() {
}); });
}; };
const updateBatchDepartmentStates = async (targetRows, patch) => {
const rowsByRecordId = new Map();
for (const row of targetRows) {
const recordRows = rowsByRecordId.get(row.recordId) || [];
recordRows.push(row);
rowsByRecordId.set(row.recordId, recordRows);
}
for (const [recordId, recordRows] of rowsByRecordId) {
const latestRecord = await loadRecord(recordId);
const currentItems = normalizeDepartmentRectificationItems(latestRecord);
if (currentItems.length === 0) {
await batchUpdateRecords({
subject: PROBLEM_RECTIFICATION_SUBJECT,
filter: { _id: recordId },
data: {
'metadata.audit_status': patch.audit_status ?? recordRows[0].auditStatus,
'metadata.final_audit_status':
patch.final_audit_status ?? recordRows[0].finalAuditStatus,
},
});
continue;
}
const scopedItemIds = new Set(recordRows.map((row) => row.itemId));
const nextItems = currentItems.map((item) =>
scopedItemIds.has(item.item_id)
? {
...item,
...patch,
updated_by: currentUserId,
updated_at: dayjs().toISOString(),
}
: item,
);
await batchUpdateRecords({
subject: PROBLEM_RECTIFICATION_SUBJECT,
filter: { _id: recordId },
data: buildDepartmentParentBatchUpdateData(nextItems),
});
}
};
const withRowSubmit = async (row, handler) => { const withRowSubmit = async (row, handler) => {
if (row.isLocked) { if (row.isLocked) {
setError('当前问题整改记录已锁定,不允许继续修改或审批。'); setError('当前问题整改记录已锁定,不允许继续修改或审批。');
...@@ -649,7 +710,7 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -649,7 +710,7 @@ export default function ProblemRectificationFlowTablePage() {
} }
}; };
const saveRowDraft = async (row) => { const saveRowDraft = async (row, batchPatch = {}) => {
const draft = getDraft(row); const draft = getDraft(row);
const patch = { const patch = {
rectification_progress: draft.progress, rectification_progress: draft.progress,
...@@ -658,12 +719,12 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -658,12 +719,12 @@ export default function ProblemRectificationFlowTablePage() {
rectification_proof_materials: draft.proof || null, rectification_proof_materials: draft.proof || null,
}; };
if (row.flowMode === FLOW_MODE_DEPARTMENT) { if (row.item) {
await updateDepartmentItem(row, patch); await updateDepartmentItem(row, { ...patch, ...batchPatch });
return patch; return patch;
} }
await updateBatchRow(row, patch); await updateBatchRow(row, { ...patch, ...batchPatch });
return patch; return patch;
}; };
...@@ -683,7 +744,7 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -683,7 +744,7 @@ export default function ProblemRectificationFlowTablePage() {
const uploaded = await uploadFile(file); const uploaded = await uploadFile(file);
const fileValue = uploaded?.file || uploaded?.data || uploaded; const fileValue = uploaded?.file || uploaded?.data || uploaded;
setDraftValue(row.key, { proof: fileValue }); setDraftValue(row.key, { proof: fileValue });
if (row.flowMode === FLOW_MODE_DEPARTMENT) { if (row.item) {
await updateDepartmentItem(row, { rectification_proof_materials: fileValue }); await updateDepartmentItem(row, { rectification_proof_materials: fileValue });
return; return;
} }
...@@ -842,34 +903,203 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -842,34 +903,203 @@ export default function ProblemRectificationFlowTablePage() {
}); });
await completeProgressTask(row.progressIdEditor); await completeProgressTask(row.progressIdEditor);
if (row.flowMode === FLOW_MODE_DEPARTMENT) { await updateDepartmentItem(row, {
await updateDepartmentItem(row, { ...savedPatch,
...savedPatch, editor_status: EDITOR_STATUS_COMPLETED,
audit_status: AUDIT_STATUS_PENDING,
progress_id_auditor: progressIdAuditor,
});
await sendNoticeToUsers({
title: '问题整改表待复核',
content: `请点击链接复核问题整改表:${returnUrl}`,
from: currentUserId,
to: [row.auditor],
});
});
};
const handleBatchEditorSubmit = async () => {
const targetRows = rows.filter((row) => row.flowMode === FLOW_MODE_BATCH);
if (targetRows.length === 0) {
return;
}
if (targetRows.some((row) => row.isLocked)) {
setError('本批次存在已锁定的问题整改记录,不允许批量送审。');
return;
}
setSubmittingKey('batch-submit');
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('本批次存在已锁定的问题整改记录,请刷新后重试。');
}
const progressIdAuditor = await createProgressTask({
recipientId: targetRows[0].auditor,
title: `${dayjs().format('YYYY-MM-DD')} 问题整改表批量复核(${targetRows.length} 条)`,
currentUser: userInfo,
returnUrl,
});
for (const row of targetRows) {
await saveRowDraft(row, {
editor_status: EDITOR_STATUS_COMPLETED, editor_status: EDITOR_STATUS_COMPLETED,
audit_status: AUDIT_STATUS_PENDING, audit_status: AUDIT_STATUS_PENDING,
final_audit_status: AUDIT_STATUS_PENDING,
progress_id_auditor: progressIdAuditor, progress_id_auditor: progressIdAuditor,
}); progress_id_sender: '',
} else {
await updateBatchRow(row, {
...savedPatch,
audit_status: AUDIT_STATUS_PENDING,
final_audit_status: row.finalAuditStatus,
});
await updateRecord(messageRecord._id || messageRecord.name, {
metadata: {
progress_id_auditor: [progressIdAuditor],
editor_status: EDITOR_STATUS_COMPLETED,
auditor_status: EDITOR_STATUS_PENDING,
},
}); });
} }
await Promise.all(
Array.from(new Set(toArray(metadata.progress_id_editor).filter(Boolean))).map(
(progressId) => completeProgressTask(progressId),
),
);
await updateRecord(messageRecord?._id || messageRecord?.name || messageId, {
metadata: {
editor_status: EDITOR_STATUS_COMPLETED,
auditor_status: EDITOR_STATUS_PENDING,
sender_status: EDITOR_STATUS_PENDING,
progress_id_auditor: [progressIdAuditor],
progress_id_sender: '',
},
});
await sendNoticeToUsers({ await sendNoticeToUsers({
title: '问题整改表待复核', title: '问题整改表待复核',
content: `请点击链接复核问题整改表:${returnUrl}`, content: `请点击链接复核问题整改表:${returnUrl}`,
from: currentUserId, from: currentUserId,
to: [row.auditor], to: targetRows.map((row) => row.auditor),
});
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);
if (targetRows.length === 0) {
return;
}
if (targetRows.some((row) => row.isLocked)) {
setError('本批次存在已锁定的问题整改记录,不允许继续审批。');
return;
}
await withRowSubmit(targetRows[0], async () => {
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),
),
);
const progressIdSender = approved
? await createProgressTask({
recipientId: senderId,
title: `${dayjs().format('YYYY-MM-DD')} 问题整改表批量最终审批(${targetRows.length} 条)`,
currentUser: userInfo,
returnUrl,
})
: '';
const progressIdEditor = approved
? ''
: await createProgressTask({
recipientId: targetRows[0].editor,
title: `批量复核驳回,请重新填写问题整改表(${targetRows.length} 条)`,
currentUser: userInfo,
returnUrl,
});
await updateBatchDepartmentStates(targetRows, {
editor_status: approved ? EDITOR_STATUS_COMPLETED : EDITOR_STATUS_PENDING,
audit_status: approved ? AUDIT_STATUS_APPROVED : AUDIT_STATUS_REJECTED,
final_audit_status: AUDIT_STATUS_PENDING,
progress_id_sender: progressIdSender,
progress_id_editor: approved ? targetRows[0].progressIdEditor : progressIdEditor,
});
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,
sender_status: EDITOR_STATUS_PENDING,
progress_id_sender: progressIdSender,
progress_id_editor: approved ? toArray(metadata.progress_id_editor) : [progressIdEditor],
},
});
await sendNoticeToUsers({
title: approved ? '问题整改表待最终审批' : '问题整改表批量复核驳回',
content: approved
? `请点击链接最终审批问题整改表:${returnUrl}`
: `问题整改表批量复核未通过,请点击链接重新填写:${returnUrl}`,
from: currentUserId,
to: approved ? [senderId] : [targetRows[0].editor],
});
});
};
const handleBatchSenderDecision = async (approved) => {
const targetRows = rows.filter((row) => row.flowMode === FLOW_MODE_BATCH);
if (targetRows.length === 0) {
return;
}
if (targetRows.some((row) => row.isLocked)) {
setError('本批次存在已锁定的问题整改记录,不允许继续审批。');
return;
}
await withRowSubmit(targetRows[0], async () => {
const latestRecords = await Promise.all(
targetRows.map((row) => loadRecord(row.recordId)),
);
if (latestRecords.some((record) => record?.metadata?.is_locked === true)) {
throw new Error('本批次存在已锁定的问题整改记录,不允许继续审批。');
}
await completeProgressTask(targetRows[0].progressIdSender);
const progressIdEditor = approved
? ''
: await createProgressTask({
recipientId: targetRows[0].editor,
title: `批量最终审批驳回,请重新填写问题整改表(${targetRows.length} 条)`,
currentUser: userInfo,
returnUrl,
});
await updateBatchDepartmentStates(targetRows, {
editor_status: approved ? EDITOR_STATUS_COMPLETED : EDITOR_STATUS_PENDING,
final_audit_status: approved ? AUDIT_STATUS_APPROVED : AUDIT_STATUS_REJECTED,
progress_id_editor: approved ? targetRows[0].progressIdEditor : progressIdEditor,
});
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,
progress_id_editor: approved ? toArray(metadata.progress_id_editor) : [progressIdEditor],
},
}); });
if (!approved) {
await sendNoticeToUsers({
title: '问题整改表批量最终审批驳回',
content: `问题整改表批量最终审批未通过,请点击链接重新填写:${returnUrl}`,
from: currentUserId,
to: [targetRows[0].editor],
});
}
}); });
}; };
...@@ -894,32 +1124,13 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -894,32 +1124,13 @@ export default function ProblemRectificationFlowTablePage() {
returnUrl, returnUrl,
}); });
if (row.flowMode === FLOW_MODE_DEPARTMENT) { await updateDepartmentItem(row, {
await updateDepartmentItem(row, { audit_status: nextAuditStatus,
audit_status: nextAuditStatus, editor_status: approved ? EDITOR_STATUS_COMPLETED : EDITOR_STATUS_PENDING,
editor_status: approved ? EDITOR_STATUS_COMPLETED : EDITOR_STATUS_PENDING, final_audit_status: approved ? AUDIT_STATUS_PENDING : row.finalAuditStatus,
final_audit_status: approved ? AUDIT_STATUS_PENDING : row.finalAuditStatus, progress_id_sender: progressIdSender,
progress_id_sender: progressIdSender, progress_id_editor: approved ? row.progressIdEditor : progressIdEditor,
progress_id_editor: approved ? row.progressIdEditor : progressIdEditor, });
});
} else {
await updateBatchRow(row, {
rectification_progress: row.progress,
rectification_situation: row.situation,
the_time_of_rectification: row.dueDate,
rectification_proof_materials: row.proof,
audit_status: nextAuditStatus,
final_audit_status: approved ? AUDIT_STATUS_PENDING : row.finalAuditStatus,
});
await updateRecord(messageRecord._id || messageRecord.name, {
metadata: {
progress_id_sender: progressIdSender,
auditor_status: approved ? EDITOR_STATUS_COMPLETED : AUDIT_STATUS_REJECTED,
editor_status: approved ? EDITOR_STATUS_COMPLETED : EDITOR_STATUS_PENDING,
progress_id_editor: approved ? metadata.progress_id_editor : [progressIdEditor],
},
});
}
await sendNoticeToUsers({ await sendNoticeToUsers({
title: approved ? '问题整改表待最终审批' : '问题整改表复核驳回', title: approved ? '问题整改表待最终审批' : '问题整改表复核驳回',
...@@ -945,30 +1156,11 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -945,30 +1156,11 @@ export default function ProblemRectificationFlowTablePage() {
returnUrl, returnUrl,
}); });
if (row.flowMode === FLOW_MODE_DEPARTMENT) { await updateDepartmentItem(row, {
await updateDepartmentItem(row, { final_audit_status: nextFinalStatus,
final_audit_status: nextFinalStatus, editor_status: approved ? row.editorStatus : EDITOR_STATUS_PENDING,
editor_status: approved ? row.editorStatus : EDITOR_STATUS_PENDING, progress_id_editor: approved ? row.progressIdEditor : progressIdEditor,
progress_id_editor: approved ? row.progressIdEditor : progressIdEditor, });
});
} else {
await updateBatchRow(row, {
rectification_progress: row.progress,
rectification_situation: row.situation,
the_time_of_rectification: row.dueDate,
rectification_proof_materials: row.proof,
audit_status: row.auditStatus,
final_audit_status: nextFinalStatus,
});
if (!approved) {
await updateRecord(messageRecord._id || messageRecord.name, {
metadata: {
progress_id_editor: [progressIdEditor],
editor_status: EDITOR_STATUS_PENDING,
},
});
}
}
if (!approved) { if (!approved) {
await sendNoticeToUsers({ await sendNoticeToUsers({
...@@ -1017,6 +1209,28 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -1017,6 +1209,28 @@ export default function ProblemRectificationFlowTablePage() {
row.editorStatus === EDITOR_STATUS_COMPLETED && row.editorStatus === EDITOR_STATUS_COMPLETED &&
row.auditStatus === AUDIT_STATUS_APPROVED && row.auditStatus === AUDIT_STATUS_APPROVED &&
row.finalAuditStatus !== 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);
return ( return (
<div className="space-y-6 p-6"> <div className="space-y-6 p-6">
...@@ -1055,7 +1269,57 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -1055,7 +1269,57 @@ export default function ProblemRectificationFlowTablePage() {
{isDelayFlow ? '当前流程没有可处理的延期申请。' : '当前流程没有可处理的整改明细。'} {isDelayFlow ? '当前流程没有可处理的延期申请。' : '当前流程没有可处理的整改明细。'}
</div> </div>
) : ( ) : (
<Table bleed dense className="[--gutter:--spacing(4)]"> <>
{batchSubmitRowCount > 0 || batchCanAudit || batchCanFinalAudit ? (
<div className="flex flex-wrap justify-end gap-2 border-b border-zinc-200 px-4 py-3">
{batchSubmitRowCount > 0 ? (
<Button
color="blue"
disabled={Boolean(submittingKey)}
onClick={handleBatchEditorSubmit}
>
批量送审({batchSubmitRowCount})
</Button>
) : null}
{batchCanAudit ? (
<>
<Button
color="emerald"
disabled={Boolean(submittingKey)}
onClick={() => handleBatchAuditorDecision(true)}
>
批量审批通过
</Button>
<Button
color="red"
disabled={Boolean(submittingKey)}
onClick={() => handleBatchAuditorDecision(false)}
>
批量驳回
</Button>
</>
) : null}
{batchCanFinalAudit ? (
<>
<Button
color="blue"
disabled={Boolean(submittingKey)}
onClick={() => handleBatchSenderDecision(true)}
>
批量最终通过
</Button>
<Button
color="red"
disabled={Boolean(submittingKey)}
onClick={() => handleBatchSenderDecision(false)}
>
批量最终驳回
</Button>
</>
) : null}
</div>
) : null}
<Table bleed dense className="[--gutter:--spacing(4)]">
<TableHead> <TableHead>
<TableRow className="bg-zinc-50"> <TableRow className="bg-zinc-50">
<TableHeader>问题</TableHeader> <TableHeader>问题</TableHeader>
...@@ -1085,7 +1349,7 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -1085,7 +1349,7 @@ export default function ProblemRectificationFlowTablePage() {
{rows.map((row) => { {rows.map((row) => {
const draft = getDraft(row); const draft = getDraft(row);
const editable = isDelayFlow ? canEditDelayRow(row) : canEditRow(row); const editable = isDelayFlow ? canEditDelayRow(row) : canEditRow(row);
const submitting = submittingKey === row.key; const submitting = Boolean(submittingKey);
return ( return (
<TableRow key={row.key} className="align-top"> <TableRow key={row.key} className="align-top">
...@@ -1271,12 +1535,12 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -1271,12 +1535,12 @@ export default function ProblemRectificationFlowTablePage() {
保存 保存
</Button> </Button>
) : null} ) : null}
{canSubmitRow(row) ? ( {row.flowMode !== FLOW_MODE_BATCH && canSubmitRow(row) ? (
<Button color="blue" disabled={submitting} onClick={() => handleEditorSubmit(row)}> <Button color="blue" disabled={submitting} onClick={() => handleEditorSubmit(row)}>
送审 送审
</Button> </Button>
) : null} ) : null}
{canAuditRow(row) ? ( {row.flowMode !== FLOW_MODE_BATCH && canAuditRow(row) ? (
<> <>
<Button color="emerald" disabled={submitting} onClick={() => handleAuditorDecision(row, true)}> <Button color="emerald" disabled={submitting} onClick={() => handleAuditorDecision(row, true)}>
审批通过 审批通过
...@@ -1286,7 +1550,7 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -1286,7 +1550,7 @@ export default function ProblemRectificationFlowTablePage() {
</Button> </Button>
</> </>
) : null} ) : null}
{canFinalAuditRow(row) ? ( {row.flowMode !== FLOW_MODE_BATCH && canFinalAuditRow(row) ? (
<> <>
<Button color="blue" disabled={submitting} onClick={() => handleSenderDecision(row, true)}> <Button color="blue" disabled={submitting} onClick={() => handleSenderDecision(row, true)}>
最终通过 最终通过
...@@ -1296,7 +1560,7 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -1296,7 +1560,7 @@ export default function ProblemRectificationFlowTablePage() {
</Button> </Button>
</> </>
) : null} ) : null}
{!editable && !canAuditRow(row) && !canFinalAuditRow(row) ? ( {row.flowMode !== FLOW_MODE_BATCH && !editable && !canAuditRow(row) && !canFinalAuditRow(row) ? (
<span className="text-xs text-slate-400">当前无可执行操作</span> <span className="text-xs text-slate-400">当前无可执行操作</span>
) : null} ) : null}
</> </>
...@@ -1307,7 +1571,8 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -1307,7 +1571,8 @@ export default function ProblemRectificationFlowTablePage() {
); );
})} })}
</TableBody> </TableBody>
</Table> </Table>
</>
)} )}
</section> </section>
......
...@@ -78,6 +78,10 @@ import { ...@@ -78,6 +78,10 @@ import {
normalizeDelayRequestItems, normalizeDelayRequestItems,
normalizeDepartmentRectificationItems, normalizeDepartmentRectificationItems,
} from '../problemRectificationListService'; } from '../problemRectificationListService';
import {
calculateProblemSeverityLevel,
PROBLEM_SEVERITY_SOURCE_FIELDS,
} from '../problemSeverity';
const FIELD_HINTS = { const FIELD_HINTS = {
inspect_the_implementing_unit: '按对象配置的实施单位选项选择', inspect_the_implementing_unit: '按对象配置的实施单位选项选择',
...@@ -92,7 +96,7 @@ const FIELD_HINTS = { ...@@ -92,7 +96,7 @@ const FIELD_HINTS = {
has_external_impact: '建议统一填写“是”或“否”', has_external_impact: '建议统一填写“是”或“否”',
has_employee_accountability: '建议统一填写“是”或“否”', has_employee_accountability: '建议统一填写“是”或“否”',
accountability_person_count: '建议填写数字人数', accountability_person_count: '建议填写数字人数',
problem_severity_level: '建议统一填写“高 / 中 / 低”', problem_severity_level: '由经济损失、外部影响和员工问责情况自动计算',
rectification_timeliness_level: '建议统一填写“高 / 中 / 低”', rectification_timeliness_level: '建议统一填写“高 / 中 / 低”',
rectification_material_quality_level: '建议统一填写“高 / 中 / 低”', rectification_material_quality_level: '建议统一填写“高 / 中 / 低”',
involves_policy_revision: '建议统一填写“是”或“否”', involves_policy_revision: '建议统一填写“是”或“否”',
...@@ -112,7 +116,6 @@ const CREATE_FIELD_OPTION_OVERRIDES = { ...@@ -112,7 +116,6 @@ const CREATE_FIELD_OPTION_OVERRIDES = {
has_economic_loss: ['是', '否'], has_economic_loss: ['是', '否'],
has_external_impact: ['是', '否'], has_external_impact: ['是', '否'],
has_employee_accountability: ['是', '否'], has_employee_accountability: ['是', '否'],
problem_severity_level: ['高', '中', '低'],
problem_category_level_1: ['内控建设', '内控运行', '内控建设及运行', '其他'], problem_category_level_1: ['内控建设', '内控运行', '内控建设及运行', '其他'],
problem_category_level_2: ['公司治理', '投资管理', '销售管理', '员工行为管理', '基金运营', '财务管理', '信息技术'], problem_category_level_2: ['公司治理', '投资管理', '销售管理', '员工行为管理', '基金运营', '财务管理', '信息技术'],
problem_root_cause: ['内外规执行不到位', '系统化程度低', '规矩意识淡薄导致操作风险', '现有流程设计缺陷', '制度建设有待完善'], problem_root_cause: ['内外规执行不到位', '系统化程度低', '规矩意识淡薄导致操作风险', '现有流程设计缺陷', '制度建设有待完善'],
...@@ -405,6 +408,10 @@ function CreateField({ fieldDef, value, onChange }) { ...@@ -405,6 +408,10 @@ function CreateField({ fieldDef, value, onChange }) {
const placeholder = FIELD_HINTS[fieldDef?.name] || `请输入${fieldDef?.label || fieldDef?.name}`; const placeholder = FIELD_HINTS[fieldDef?.name] || `请输入${fieldDef?.label || fieldDef?.name}`;
const overrideOptions = CREATE_FIELD_OPTION_OVERRIDES[fieldDef?.name] || []; const overrideOptions = CREATE_FIELD_OPTION_OVERRIDES[fieldDef?.name] || [];
if (fieldDef?.name === 'problem_severity_level') {
return <Input value={value || '低'} disabled aria-label="问题综合严重程度(自动计算)" />;
}
if (fieldType === 'long_text') { if (fieldType === 'long_text') {
return ( return (
<Textarea <Textarea
...@@ -1289,6 +1296,39 @@ export default function ProblemRectificationListPage() { ...@@ -1289,6 +1296,39 @@ export default function ProblemRectificationListPage() {
const nextAuditorIds = isDepartmentFlow const nextAuditorIds = isDepartmentFlow
? Array.from(new Set(departmentAssignments.map((item) => item.auditor?._id).filter(Boolean))) ? Array.from(new Set(departmentAssignments.map((item) => item.auditor?._id).filter(Boolean)))
: auditorIds; : auditorIds;
const batchDepartmentItemsByRecordId = new Map();
if (!isDepartmentFlow) {
const now = dayjs().toISOString();
for (const selectedRecord of selectedRecords) {
const sourceRecord = await loadRecord(selectedRecord.id);
const existingItems = normalizeDepartmentRectificationItems(sourceRecord);
const nextItems = existingItems.length > 0
? existingItems.map((item) => ({
...item,
editor: nextFillerIds[0],
auditor: nextAuditorIds[0],
editor_status: EDITOR_STATUS_PENDING,
audit_status: AUDIT_STATUS_PENDING,
final_audit_status: AUDIT_STATUS_PENDING,
progress_id_editor: '',
progress_id_auditor: '',
progress_id_sender: '',
updated_by: currentUserInfo?._id || currentUserInfo?.id || '',
updated_at: now,
}))
: buildDepartmentRectificationItems(
sourceRecord,
buildDepartmentAssignmentsFromRecord(sourceRecord).map((assignment) => ({
...assignment,
editor: { _id: nextFillerIds[0] },
auditor: { _id: nextAuditorIds[0] },
})),
currentUserInfo,
);
batchDepartmentItemsByRecordId.set(selectedRecord.id, nextItems);
}
}
const messageRecord = await createRecord({ const messageRecord = await createRecord({
subject: 'message_reference_id', subject: 'message_reference_id',
...@@ -1303,7 +1343,11 @@ export default function ProblemRectificationListPage() { ...@@ -1303,7 +1343,11 @@ export default function ProblemRectificationListPage() {
view_name: activeGridView?.name || '', view_name: activeGridView?.name || '',
subject_name: PROBLEM_RECTIFICATION_SUBJECT, subject_name: PROBLEM_RECTIFICATION_SUBJECT,
flow_mode: isDepartmentFlow ? FLOW_MODE_DEPARTMENT : FLOW_MODE_BATCH, flow_mode: isDepartmentFlow ? FLOW_MODE_DEPARTMENT : FLOW_MODE_BATCH,
department_item_ids: isDepartmentFlow ? departmentAssignments.map((item) => item.item_id) : [], department_item_ids: isDepartmentFlow
? departmentAssignments.map((item) => item.item_id)
: Array.from(batchDepartmentItemsByRecordId.values())
.flat()
.map((item) => item.item_id),
editor_status: EDITOR_STATUS_PENDING, editor_status: EDITOR_STATUS_PENDING,
auditor_status: EDITOR_STATUS_PENDING, auditor_status: EDITOR_STATUS_PENDING,
sender_status: EDITOR_STATUS_PENDING, sender_status: EDITOR_STATUS_PENDING,
...@@ -1356,7 +1400,7 @@ export default function ProblemRectificationListPage() { ...@@ -1356,7 +1400,7 @@ export default function ProblemRectificationListPage() {
} else { } else {
const fillerProfile = await loadUser(nextFillerIds[0]); const fillerProfile = await loadUser(nextFillerIds[0]);
const oaProcessResult = await executeAction('oa_notice:createProgress', { const oaProcessResult = await executeAction('oa_notice:createProgress', {
title: `${dayjs().format('YYYY-MM-DD')} 填写问题整改表`, title: `${dayjs().format('YYYY-MM-DD')} 填写问题整改表(${selectedRecordIds.length} 条)`,
classify: '流程待办', classify: '流程待办',
contentType: '流程待办', contentType: '流程待办',
receiveUserId: fillerProfile._id, receiveUserId: fillerProfile._id,
...@@ -1368,20 +1412,32 @@ export default function ProblemRectificationListPage() { ...@@ -1368,20 +1412,32 @@ export default function ProblemRectificationListPage() {
returnUrl, returnUrl,
}); });
progressIdEditor.push(oaProcessResult?.data?.taskId); progressIdEditor.push(oaProcessResult?.data?.taskId);
const batchProgressIdEditor = oaProcessResult?.data?.taskId || '';
for (const selectedRecord of selectedRecords) {
const departmentItems = batchDepartmentItemsByRecordId.get(selectedRecord.id) || [];
const nextItems = departmentItems.map((item) => ({
...item,
progress_id_editor: batchProgressIdEditor,
}));
await batchUpdateRecords({
subject: PROBLEM_RECTIFICATION_SUBJECT,
filter: { _id: selectedRecord.id },
data: buildDepartmentParentBatchUpdateData(nextItems),
});
await batchUpdateRecords({ const savedRecord = await loadRecord(selectedRecord.id);
subject: PROBLEM_RECTIFICATION_SUBJECT, const savedItems = normalizeDepartmentRectificationItems(savedRecord);
filter: { const expectedItemIds = new Set(nextItems.map((item) => item.item_id));
_id: { $in: selectedRecordIds }, if (
}, savedItems.length !== nextItems.length ||
data: { savedItems.some((item) => !expectedItemIds.has(item.item_id))
'metadata.fill_in_the_people': nextFillerIds, ) {
'metadata.review_people': nextAuditorIds, throw new Error(
'metadata.audit_status': AUDIT_STATUS_PENDING, `批量推送明细写入失败:问题“${selectedRecord.title || selectedRecord.id}”预期 ${nextItems.length} 条,后台实际返回 ${savedItems.length} 条。`,
'metadata.final_audit_status': AUDIT_STATUS_PENDING, );
[`metadata.${DEPARTMENT_RECTIFICATION_FIELD}`]: [], }
}, }
});
} }
await updateRecord(messageRecord._id || messageRecord.name, { await updateRecord(messageRecord._id || messageRecord.name, {
...@@ -1410,7 +1466,7 @@ export default function ProblemRectificationListPage() { ...@@ -1410,7 +1466,7 @@ export default function ProblemRectificationListPage() {
setSuccessMessage( setSuccessMessage(
isDepartmentFlow isDepartmentFlow
? `推送成功,已生成 ${departmentAssignments.length} 条部门整改明细,可从记录标题右侧展开查看。` ? `推送成功,已生成 ${departmentAssignments.length} 条部门整改明细,可从记录标题右侧展开查看。`
: `推送成功,已将 ${selectedRecords.length} 条问题发送给填写人。`, : `推送成功,已将 ${selectedRecords.length} 条问题的 ${Array.from(batchDepartmentItemsByRecordId.values()).flat().length} 条部门整改明细发送给填写人。`,
); );
} catch (requestError) { } catch (requestError) {
setError(requestError?.message || '推送问题整改表失败,请稍后重试。'); setError(requestError?.message || '推送问题整改表失败,请稍后重试。');
...@@ -1636,7 +1692,23 @@ export default function ProblemRectificationListPage() { ...@@ -1636,7 +1692,23 @@ export default function ProblemRectificationListPage() {
recordName={recordName} recordName={recordName}
onClose={onClose} onClose={onClose}
onDataChange={onDataChange} onDataChange={onDataChange}
disabledFields={[DELAY_REQUEST_FIELD, DELAY_REQUEST_ITEMS_FIELD]} disabledFields={[
DELAY_REQUEST_FIELD,
DELAY_REQUEST_ITEMS_FIELD,
'problem_severity_level',
]}
deriveFieldValues={({ fieldPath, record: nextRecord }) => {
const fieldName = getFieldNameFromPath(fieldPath);
if (!PROBLEM_SEVERITY_SOURCE_FIELDS.includes(fieldName)) {
return {};
}
return {
'metadata.problem_severity_level': calculateProblemSeverityLevel(
nextRecord?.metadata,
),
};
}}
preferredViewName={activeGridView?.title || activeGridView?.name} preferredViewName={activeGridView?.title || activeGridView?.name}
authorizationView={activeGridView} authorizationView={activeGridView}
/> />
...@@ -1733,10 +1805,20 @@ export default function ProblemRectificationListPage() { ...@@ -1733,10 +1805,20 @@ export default function ProblemRectificationListPage() {
fieldDef={fieldDef} fieldDef={fieldDef}
value={formValues[fieldDef.name]} value={formValues[fieldDef.name]}
onChange={(nextValue) => onChange={(nextValue) =>
setFormValues((current) => ({ setFormValues((current) => {
...current, const nextValues = {
[fieldDef.name]: nextValue, ...current,
})) [fieldDef.name]: nextValue,
};
return PROBLEM_SEVERITY_SOURCE_FIELDS.includes(fieldDef.name)
? {
...nextValues,
problem_severity_level:
calculateProblemSeverityLevel(nextValues),
}
: nextValues;
})
} }
/> />
{FIELD_HINTS[fieldDef.name] ? ( {FIELD_HINTS[fieldDef.name] ? (
......
...@@ -286,7 +286,7 @@ ...@@ -286,7 +286,7 @@
- 若线上 Subject 配置存在 `problem_discovery_situation`,可兼容展示“问题发现情况”,但当前主对象字段定义不再将其作为固定字段 - 若线上 Subject 配置存在 `problem_discovery_situation`,可兼容展示“问题发现情况”,但当前主对象字段定义不再将其作为固定字段
3. 根据 2026 年整改驾驶舱要求,补录扩展治理字段: 3. 根据 2026 年整改驾驶舱要求,补录扩展治理字段:
- 问题后果:是否造成经济损失、对应经济损失、是否造成外部影响、处罚机构、是否涉及员工问责、问责人数 - 问题后果:是否造成经济损失、对应经济损失、是否造成外部影响、处罚机构、是否涉及员工问责、问责人数
- 问题画像:问题综合严重程度、问题一级至四级分类、问题原因 - 问题画像:问题综合严重程度(系统自动计算)、问题一级至四级分类、问题原因
- 整改质效:整改时间评估、整改证明材料质量、是否涉及制度修订、制度名称、是否涉及系统建设、系统名称、是否为往期发现问题、问题是否重复出现;综合整改质效结果由系统计算,不提供人工录入 - 整改质效:整改时间评估、整改证明材料质量、是否涉及制度修订、制度名称、是否涉及系统建设、系统名称、是否为往期发现问题、问题是否重复出现;综合整改质效结果由系统计算,不提供人工录入
- 责任识别:主责部门、实际整改完成时间、实际检查完成日期 - 责任识别:主责部门、实际整改完成时间、实际检查完成日期
4. 可预先填写预计整改完成时间 4. 可预先填写预计整改完成时间
...@@ -298,13 +298,15 @@ ...@@ -298,13 +298,15 @@
- 列表标题列只显示标题文本,不在标题下方追加严重程度、整改评级、主责部门、逾期或重点后果标签;相关信息由对应业务列展示 - 列表标题列只显示标题文本,不在标题下方追加严重程度、整改评级、主责部门、逾期或重点后果标签;相关信息由对应业务列展示
- 新增弹窗除基础字段外,还应补充严重程度、分类、原因和整改质效字段,避免驾驶舱后续无数据可用 - 新增弹窗除基础字段外,还应补充严重程度、分类、原因和整改质效字段,避免驾驶舱后续无数据可用
- 新增弹窗中 `select` 且 `multiple=true` 的字段使用 Headless UI 下拉多选,不使用原生多行选择框;选项仍从 Subject `settings.options` 读取,提交时保存选项值数组 - 新增弹窗中 `select` 且 `multiple=true` 的字段使用 Headless UI 下拉多选,不使用原生多行选择框;选项仍从 Subject `settings.options` 读取,提交时保存选项值数组
- 问题综合严重程度不允许手工修改:是否造成经济损失或是否造成外部影响任一项为“是”时为“高”;仅是否涉及员工问责为“是”时为“中”;三项均不为“是”或未填写时为“低”
- 新增弹窗实时展示计算结果;详情抽屉保存任一来源字段时,将结果一并写入 `metadata.problem_severity_level`;列表与驾驶舱对历史记录也按同一规则即时计算
### 7.2 发起整改填报 ### 7.2 发起整改填报
1. 在问题整改列表中勾选一条或多条问题 1. 在问题整改列表中勾选一条或多条问题
2. 点击“推送问题整改表” 2. 点击“推送问题整改表”
3. 如果只选择 1 条问题,系统按该问题的 `metadata.responsible_department` 生成部门配置行;用户可删除本次无需推送的部门配置行,但至少需保留 1 行;保留的每个责任部门必须选择 1 个填写人和 1 个审批人 3. 如果只选择 1 条问题,系统按该问题的 `metadata.responsible_department` 生成部门配置行;用户可删除本次无需推送的部门配置行,但至少需保留 1 行;保留的每个责任部门必须选择 1 个填写人和 1 个审批人
4. 如果选择多条问题,不拆分责任部门;本次推送只允许选择 1 个填写人和 1 个审批人,并批量应用到选中问题 4. 如果选择多条问题,本次推送只允许选择 1 个填写人和 1 个审批人;系统仍按每条问题的责任部门生成 `department_rectification_items`,并将同一组填写人、审批人应用到本批次全部明细
5. 创建 `message_reference_id`(信息关联id)流程主单,写入: 5. 创建 `message_reference_id`(信息关联id)流程主单,写入:
- `sender` - `sender`
- `editor` - `editor`
...@@ -319,15 +321,15 @@ ...@@ -319,15 +321,15 @@
- `editor_status = "未完成"` - `editor_status = "未完成"`
- `auditor_status = "未完成"` - `auditor_status = "未完成"`
- `sender_status = "未完成"` - `sender_status = "未完成"`
6. 单条部门推送时,回写该问题的 `metadata.department_rectification_items`;每个部门明细独立保存责任部门、填写人、审批人、整改字段、审批状态和待办 ID 6. 单条和多条推送均回写每个问题的 `metadata.department_rectification_items`;每个部门明细保存责任部门、填写人、审批人、整改字段、审批状态和待办 ID。已有明细时保留整改业务内容,只重置本次流程人员、状态和待办
- 写入后重新读取父问题并校验部门明细数量;后台未返回预期明细时,本次推送显示失败,不展示成功提示 - 写入后逐条重新读取父问题并校验明细数量和 `item_id`;后台未返回预期明细时,本次推送显示失败,不展示成功提示
7. 多选简单推送时,批量更新选中问题的 `metadata.fill_in_the_people`、`metadata.review_people` 7. 多选简单推送按批次只创建 1 条填写 OA 待办,同一批次全部部门明细共享流程主单状态和待办 ID;明细业务内容仍按各自 `item_id` 独立保存
8. 发送内部通知与 OA 待办,链接使用相对地址 `/problem_rectification_new/problem_rectification_flow_table?id=<message_record_name>`,不拼接浏览器当前域名;问题整改推送与延期申请推送统一遵守该规则 8. 发送内部通知和 OA 待办时,链接使用浏览器当前域名拼接完整地址 `<window.location.origin>/problem_rectification_new/problem_rectification_flow_table?id=<message_record_name>`;问题整改首次推送、流程内复核/驳回/最终审批和延期流程统一遵守该规则
推荐新交互: 推荐新交互:
- 顶部批量操作栏出现“推送整改表” - 顶部批量操作栏出现“推送整改表”
- 点击后打开推送弹窗;单条问题展示部门配置表格,多选问题展示单填写人 / 单复核人配置 - 点击后打开推送弹窗;单条问题展示部门配置表格,多选问题展示单填写人 / 单复核人配置,确认后仍为每条问题生成部门整改明细
- 弹窗中部门配置表展示自动生成的部门行数量,并提供行级删除操作;确认推送时只按保留的部门行生成 `department_rectification_items`、`department_item_ids`、OA 待办和内部通知 - 弹窗中部门配置表展示自动生成的部门行数量,并提供行级删除操作;确认推送时只按保留的部门行生成 `department_rectification_items`、`department_item_ids`、OA 待办和内部通知
- 弹窗底部明确展示本次选中记录数量和最终生成的部门明细数量 - 弹窗底部明确展示本次选中记录数量和最终生成的部门明细数量
- 成功后列表即时刷新,并给出已推送结果提示 - 成功后列表即时刷新,并给出已推送结果提示
...@@ -335,22 +337,23 @@ ...@@ -335,22 +337,23 @@
### 7.3 填报人补录整改内容 ### 7.3 填报人补录整改内容
1. 填报人通过消息链接进入新表格处理页 `/problem_rectification_new/problem_rectification_flow_table` 1. 填报人通过消息链接进入新表格处理页 `/problem_rectification_new/problem_rectification_flow_table`
2. 页面展示本次流程绑定的所有部门明细行;参与人可查看全部行,填写人只能编辑自己负责的行,流程推送人可以编辑全部部门明细行。表格同时只读展示父问题的 `problem_brief`(问题简述)和 `related_issue_domain`(涉及业务领域);填写人和审批人字段保存用户 `_id`,表格展示用户姓名,姓名查询不到时才回退显示 ID 2. 页面展示本次流程绑定的所有部门明细行;单条和多条推送使用相同的明细字段与表格结构。参与人可查看全部行,填写人只能编辑自己负责的行,流程推送人可以编辑全部部门明细行。表格同时只读展示父问题的 `problem_brief`(问题简述)和 `related_issue_domain`(涉及业务领域);填写人和审批人字段保存用户 `_id`,表格展示用户姓名,姓名查询不到时才回退显示 ID
3. 填报人可在表格行内编辑: 3. 填报人可在表格行内编辑:
- `rectification_progress` - `rectification_progress`
- `rectification_situation` - `rectification_situation`
- `the_time_of_rectification` - `the_time_of_rectification`
- `rectification_proof_materials` - `rectification_proof_materials`
4. 填写人送审后,该行填写业务字段锁定;只有审批驳回后,填写人恢复编辑 4. 部门流程按行送审并锁定当前行;多选简单推送在表格上方只展示一个“批量送审”按钮,点击后保存本批次全部问题并统一进入待复核,不在每条问题行重复展示“送审”按钮
5. 审批驳回后,原审批人也可以编辑该行整改业务字段,但不能代替填写人点击“送审”;待填写人重新送审后,审批人恢复审批操作 5. 审批驳回后,原审批人也可以编辑该行整改业务字段,但不能代替填写人点击“送审”;待填写人重新送审后,审批人恢复审批操作
6. 问题整改推送与延期申请推送的流程页均不提供父问题“编辑问题记录”入口或详情抽屉;流程参与人仅在当前表格行内执行其整改或延期填写、送审和审批操作 6. 问题整改推送与延期申请推送的流程页均不提供父问题“编辑问题记录”入口或详情抽屉;流程参与人仅在当前表格行内执行其整改或延期填写、送审和审批操作
7. 多选简单推送的填写、复核和最终审批都按批次一次处理;任一审批阶段驳回后,整批问题共同回到填写阶段
推荐新交互: 推荐新交互:
- 页面顶部显示本次流程步骤和当前身份 - 页面顶部显示本次流程步骤和当前身份
- 主体统一使用表格,不再使用旧填写页的详情式编辑 - 主体统一使用表格,不再使用旧填写页的详情式编辑
- 长文本、附件、预计完成时间和整改进度均在表格行内维护 - 长文本、附件、预计完成时间和整改进度均在表格行内维护
- “保存”“送审”“审批通过”“驳回”“最终通过”“最终驳回”作为行级操作展示 - 部门流程的“送审”和各阶段审批作为行级操作展示;多选简单推送的送审、复核和最终审批按钮统一放在表格上方,行内只保留“保存”
- 推送人具有部门明细“保存”权限,但不会因此获得填写人的“送审”权限;推送人同时是该行填写人时,才按填写人身份送审 - 推送人具有部门明细“保存”权限,但不会因此获得填写人的“送审”权限;推送人同时是该行填写人时,才按填写人身份送审
### 7.4 复核人审核 ### 7.4 复核人审核
...@@ -375,6 +378,8 @@ ...@@ -375,6 +378,8 @@
复核人审批通过后,该部门明细业务字段锁定;只有审批驳回状态下,复核人拥有该行“保存”权限。填写人重新送审后,复核人的编辑权限随行重新进入审批阶段而关闭。 复核人审批通过后,该部门明细业务字段锁定;只有审批驳回状态下,复核人拥有该行“保存”权限。填写人重新送审后,复核人的编辑权限随行重新进入审批阶段而关闭。
多选简单推送由同一复核人对整批问题一次通过或驳回。通过时批量更新全部关联问题为“已审批”并创建 1 条最终审批待办;驳回时批量更新为“已驳回”,流程主单填写状态重置为“未完成”,整批问题共同退回填写人。
推荐新交互: 推荐新交互:
- 表格行内提供“审批通过”“驳回”两个操作 - 表格行内提供“审批通过”“驳回”两个操作
...@@ -398,6 +403,8 @@ ...@@ -398,6 +403,8 @@
- 最终审批行级处理,不要求本次推送所有部门都先审批通过 - 最终审批行级处理,不要求本次推送所有部门都先审批通过
- 发起人编辑已经送审或审批完成的父问题、部门整改内容时,不自动重置审批状态,也不自动重新生成 OA 待办 - 发起人编辑已经送审或审批完成的父问题、部门整改内容时,不自动重置审批状态,也不自动重新生成 OA 待办
多选简单推送由发起人对整批问题一次最终通过或驳回。最终结果批量写入全部关联问题;驳回时整批问题共同退回填写阶段,并只创建 1 条填写 OA 待办。
### 7.6 问题延期申请 ### 7.6 问题延期申请
#### 7.6.1 发起与填写 #### 7.6.1 发起与填写
...@@ -462,7 +469,7 @@ ...@@ -462,7 +469,7 @@
- `editor` 记录填写人列表 - `editor` 记录填写人列表
- `auditor` 记录审核人列表 - `auditor` 记录审核人列表
- `flow_mode` 记录流程模式:`department` 表示单条问题按责任部门拆分整改明细,`batch` 表示多选问题的单填写人 / 单审批人简单推送,`delay` 表示历史问题级延期申请,`delay_department` 表示按责任部门拆分延期申请 - `flow_mode` 记录流程模式:`department` 表示单条问题按责任部门拆分整改明细,`batch` 表示多选问题的单填写人 / 单审批人简单推送,`delay` 表示历史问题级延期申请,`delay_department` 表示按责任部门拆分延期申请
- `department_item_ids` 记录本次部门流程关联的部门级 `item_id`;整改流程对应 `department_rectification_items[].item_id`,延期流程对应 `problem_rectification_delay_request_items[].item_id` - `department_item_ids` 记录本次流程关联的部门级 `item_id`;单条和多条整改流程对应 `department_rectification_items[].item_id`,延期流程对应 `problem_rectification_delay_request_items[].item_id`
- `restriction_key` 默认固定为 `_id` - `restriction_key` 默认固定为 `_id`
- `restriction_value` 保存本次推送绑定的整改记录 `_id[]` - `restriction_value` 保存本次推送绑定的整改记录 `_id[]`
- `reference_record_id` 作为关联记录 ID 冗余字段,和 `restriction_value` 一致 - `reference_record_id` 作为关联记录 ID 冗余字段,和 `restriction_value` 一致
...@@ -472,7 +479,7 @@ ...@@ -472,7 +479,7 @@
- `auditor_status`:`未完成`、`已完成` - `auditor_status`:`未完成`、`已完成`
- `sender_status`:`未完成`、`已完成` - `sender_status`:`未完成`、`已完成`
当前新页面不再依赖旧版 `未审批`、`已驳回` 之类流程主单状态,而是通过“状态字段 + 当前 progress_id”共同判断流程走向。新部门流程中,部门明细上的 `editor_status`、`audit_status`、`final_audit_status` 是事实来源;流程主单状态仅用于兼容和整体提示。驳回场景只重置对应部门明细,不影响其他部门明细。 单条和多条推送的整改业务内容均以部门明细为事实来源。部门流程的送审和审批按部门行独立处理;批量简单推送使用流程主单顶层状态和待办 ID 作为批次流程事实来源,同一批次全部部门明细共享填写、复核和最终审批状态,批次操作会同步更新全部关联明细及其父问题汇总状态。
### 8.3 部门明细汇总约定 ### 8.3 部门明细汇总约定
...@@ -601,6 +608,7 @@ ...@@ -601,6 +608,7 @@
- X 轴:责任部门问题总数 - X 轴:责任部门问题总数
- Y 轴:整改完成率 - Y 轴:整改完成率
- 气泡大小:逾期问题数 - 气泡大小:逾期问题数
- 指标坐标相同的部门名称按固定间距纵向分层,其余标签自动避让;极端密集且仍无法排开的标签自动隐藏,悬停对应气泡可查看完整部门名称、问题总数、完成率和逾期未完成数
- 问题整改分析不再展示单独的“整改时效分析”散点图 - 问题整改分析不再展示单独的“整改时效分析”散点图
当前逾期 / 延期判定说明: 当前逾期 / 延期判定说明:
......
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { calculateProblemSeverityLevel } from './problemSeverity';
import { queryRecords } from '@/services/DataService'; import { queryRecords } from '@/services/DataService';
export const PROBLEM_RECTIFICATION_SUBJECT = 'problem_rectification'; export const PROBLEM_RECTIFICATION_SUBJECT = 'problem_rectification';
...@@ -455,7 +456,7 @@ function normalizeProblemRecord(record) { ...@@ -455,7 +456,7 @@ function normalizeProblemRecord(record) {
problemType: normalizeText(metadata.problem_type), problemType: normalizeText(metadata.problem_type),
relatedIssueDomain: normalizeText(metadata.related_issue_domain) || '未分类', relatedIssueDomain: normalizeText(metadata.related_issue_domain) || '未分类',
brief: normalizeText(metadata.problem_brief) || '暂无问题简述', brief: normalizeText(metadata.problem_brief) || '暂无问题简述',
severityLevel: normalizeText(metadata.problem_severity_level), severityLevel: calculateProblemSeverityLevel(metadata),
categoryLevel1: normalizeText(metadata.problem_category_level_1), categoryLevel1: normalizeText(metadata.problem_category_level_1),
categoryLevel2: normalizeText(metadata.problem_category_level_2), categoryLevel2: normalizeText(metadata.problem_category_level_2),
categoryLevel3: normalizeText(metadata.problem_category_level_3), categoryLevel3: normalizeText(metadata.problem_category_level_3),
......
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
calculateRectificationQualityRating, calculateRectificationQualityRating,
DEPARTMENT_GROUPS, DEPARTMENT_GROUPS,
} from './problemRectificationDashboardService'; } from './problemRectificationDashboardService';
import { calculateProblemSeverityLevel } from './problemSeverity';
import { import {
buildViewColumns, buildViewColumns,
checkViewCondition, checkViewCondition,
...@@ -48,7 +49,6 @@ const LEVEL_ORDER = ['高', '中', '低']; ...@@ -48,7 +49,6 @@ const LEVEL_ORDER = ['高', '中', '低'];
const FILTER_FIELD_PATHS = { const FILTER_FIELD_PATHS = {
progress: 'metadata.rectification_progress', progress: 'metadata.rectification_progress',
auditStatus: 'metadata.audit_status', auditStatus: 'metadata.audit_status',
severityLevel: 'metadata.problem_severity_level',
primaryResponsibleDepartment: 'metadata.primary_responsible_department', primaryResponsibleDepartment: 'metadata.primary_responsible_department',
problemFrom: 'metadata.problem_from', problemFrom: 'metadata.problem_from',
rootCause: 'metadata.problem_root_cause', rootCause: 'metadata.problem_root_cause',
...@@ -189,7 +189,13 @@ export function getProblemRectificationFlowTableUrl(messageName) { ...@@ -189,7 +189,13 @@ export function getProblemRectificationFlowTableUrl(messageName) {
const query = messageName ? `?id=${encodeURIComponent(messageName)}` : ''; const query = messageName ? `?id=${encodeURIComponent(messageName)}` : '';
const basePath = String(PROBLEM_RECTIFICATION_APP_BASE_PATH || '/') const basePath = String(PROBLEM_RECTIFICATION_APP_BASE_PATH || '/')
.replace(/\/+$/, ''); .replace(/\/+$/, '');
return `${basePath}${PROBLEM_RECTIFICATION_FLOW_TABLE_PATH}${query}`; const appPath = `${basePath}${PROBLEM_RECTIFICATION_FLOW_TABLE_PATH}${query}`;
if (typeof window === 'undefined') {
return appPath;
}
return `${window.location.origin}${appPath}`;
} }
export function getProblemRectificationFormUrl(messageName) { export function getProblemRectificationFormUrl(messageName) {
...@@ -694,6 +700,40 @@ function buildInspectionReportMonthFilter(month) { ...@@ -694,6 +700,40 @@ function buildInspectionReportMonthFilter(month) {
}; };
} }
function buildProblemSeverityFilter(severityLevel) {
if (!LEVEL_ORDER.includes(severityLevel)) {
return {};
}
const economicLossFilter = {
'metadata.has_economic_loss': { $in: AFFIRMATIVE_FILTER_VALUES },
};
const externalImpactFilter = {
'metadata.has_external_impact': { $in: AFFIRMATIVE_FILTER_VALUES },
};
if (severityLevel === '高') {
return { $or: [economicLossFilter, externalImpactFilter] };
}
const noHighSeverityCause = {
'metadata.has_economic_loss': { $nin: AFFIRMATIVE_FILTER_VALUES },
'metadata.has_external_impact': { $nin: AFFIRMATIVE_FILTER_VALUES },
};
if (severityLevel === '中') {
return {
...noHighSeverityCause,
'metadata.has_employee_accountability': { $in: AFFIRMATIVE_FILTER_VALUES },
};
}
return {
...noHighSeverityCause,
'metadata.has_employee_accountability': { $nin: AFFIRMATIVE_FILTER_VALUES },
};
}
function buildFocusFlagFilter(focusFlag) { function buildFocusFlagFilter(focusFlag) {
if (focusFlag === 'consequence') { if (focusFlag === 'consequence') {
return { return {
...@@ -706,7 +746,7 @@ function buildFocusFlagFilter(focusFlag) { ...@@ -706,7 +746,7 @@ function buildFocusFlagFilter(focusFlag) {
} }
if (focusFlag === 'high_severity') { if (focusFlag === 'high_severity') {
return { 'metadata.problem_severity_level': '高' }; return buildProblemSeverityFilter('高');
} }
if (focusFlag === 'low_rating') { if (focusFlag === 'low_rating') {
...@@ -828,6 +868,7 @@ export function buildProblemRectificationQueryFilter(filters = {}, gridView, sub ...@@ -828,6 +868,7 @@ export function buildProblemRectificationQueryFilter(filters = {}, gridView, sub
buildViewConditionsFilter(gridView?.settings?.filters), buildViewConditionsFilter(gridView?.settings?.filters),
...uiFieldClauses, ...uiFieldClauses,
buildInspectionReportMonthFilter(filters?.inspectionReportMonth), buildInspectionReportMonthFilter(filters?.inspectionReportMonth),
buildProblemSeverityFilter(filters?.severityLevel),
buildCalculatedQualityRatingFilter(filters?.overallRating), buildCalculatedQualityRatingFilter(filters?.overallRating),
buildFocusFlagFilter(filters?.focusFlag), buildFocusFlagFilter(filters?.focusFlag),
buildKeywordFilter(filters?.keyword, columns), buildKeywordFilter(filters?.keyword, columns),
...@@ -893,7 +934,7 @@ export function normalizeProblemRectificationRecord(record) { ...@@ -893,7 +934,7 @@ export function normalizeProblemRectificationRecord(record) {
problemFrom: normalizeText(metadata.problem_from), problemFrom: normalizeText(metadata.problem_from),
relatedIssueDomain: normalizeText(metadata.related_issue_domain), relatedIssueDomain: normalizeText(metadata.related_issue_domain),
brief: normalizeText(metadata.problem_brief), brief: normalizeText(metadata.problem_brief),
severityLevel: normalizeText(metadata.problem_severity_level), severityLevel: calculateProblemSeverityLevel(metadata),
categoryLevel1: normalizeText(metadata.problem_category_level_1), categoryLevel1: normalizeText(metadata.problem_category_level_1),
categoryLevel2: normalizeText(metadata.problem_category_level_2), categoryLevel2: normalizeText(metadata.problem_category_level_2),
categoryLevel3: normalizeText(metadata.problem_category_level_3), categoryLevel3: normalizeText(metadata.problem_category_level_3),
...@@ -1375,10 +1416,15 @@ export function buildProblemRectificationCreateFormFields(gridView, subject) { ...@@ -1375,10 +1416,15 @@ export function buildProblemRectificationCreateFormFields(gridView, subject) {
} }
export function buildInitialCreateValues(createFields) { export function buildInitialCreateValues(createFields) {
return createFields.reduce((values, field) => ({ const values = createFields.reduce((nextValues, field) => ({
...values, ...nextValues,
[field.fieldDef.name]: emptyValueForField(field.fieldDef), [field.fieldDef.name]: emptyValueForField(field.fieldDef),
}), {}); }), {});
return {
...values,
problem_severity_level: calculateProblemSeverityLevel(values),
};
} }
export function buildProblemRectificationCreatePayload(formValues, createFields = []) { export function buildProblemRectificationCreatePayload(formValues, createFields = []) {
...@@ -1400,6 +1446,9 @@ export function buildProblemRectificationCreatePayload(formValues, createFields ...@@ -1400,6 +1446,9 @@ export function buildProblemRectificationCreatePayload(formValues, createFields
return { return {
subject: PROBLEM_RECTIFICATION_SUBJECT, subject: PROBLEM_RECTIFICATION_SUBJECT,
title: projectName || problemBrief || '问题整改记录', title: projectName || problemBrief || '问题整改记录',
metadata, metadata: {
...metadata,
problem_severity_level: calculateProblemSeverityLevel(metadata),
},
}; };
} }
import { normalizeText } from '@/utils/subjectView';
const AFFIRMATIVE_VALUES = new Set(['是', '有', '涉及', 'TRUE', '1', 'Y', 'YES']);
export const PROBLEM_SEVERITY_SOURCE_FIELDS = [
'has_economic_loss',
'has_external_impact',
'has_employee_accountability',
];
export function calculateProblemSeverityLevel(metadata = {}) {
const isAffirmative = (value) =>
AFFIRMATIVE_VALUES.has(normalizeText(value).toUpperCase());
if (
isAffirmative(metadata.has_economic_loss) ||
isAffirmative(metadata.has_external_impact)
) {
return '高';
}
if (isAffirmative(metadata.has_employee_accountability)) {
return '中';
}
return '低';
}
...@@ -155,6 +155,16 @@ type Record = { ...@@ -155,6 +155,16 @@ type Record = {
| 部门延期申请明细 | `problem_rectification_delay_request_items` | object | `[]` | 否 | 是 | 否 | 否 | 否 | | 部门延期申请明细 | `problem_rectification_delay_request_items` | object | `[]` | 否 | 是 | 否 | 否 | 否 |
| 状态锁定 | `is_locked` | boolean | `false` | 否 | 否 | 否 | 否 | 否 | | 状态锁定 | `is_locked` | boolean | `false` | 否 | 否 | 否 | 否 | 否 |
#### 问题综合严重程度计算规则
`problem_severity_level` 由系统根据三个问题后果字段自动计算,不由用户手工选择:
- `has_economic_loss` 或 `has_external_impact` 任一项为“是”时,结果为“高”
- 上述两项均不为“是”,仅 `has_employee_accountability` 为“是”时,结果为“中”
- 三项均不为“是”或未填写时,结果为“低”
创建记录和编辑上述任一来源字段时,计算结果写入 `metadata.problem_severity_level`。列表与驾驶舱读取历史记录时也按相同规则即时计算。
### 3.1.1 select 字段选项 ### 3.1.1 select 字段选项
- `inspect_the_implementing_unit`(检查实施单位):`法律合规部`、`源泰律师事务所(合规有效性评估)`、`普华永道会计师事务所`、`风险管理部`、`内部审计`、`内部合规检查`、`外部审计`、`外部检查`、`总行审计`、`工会经费审査`、`国家审计署`、`鑫沅资产`、`鑫元基金`、`其他` - `inspect_the_implementing_unit`(检查实施单位):`法律合规部`、`源泰律师事务所(合规有效性评估)`、`普华永道会计师事务所`、`风险管理部`、`内部审计`、`内部合规检查`、`外部审计`、`外部检查`、`总行审计`、`工会经费审査`、`国家审计署`、`鑫沅资产`、`鑫元基金`、`其他`
...@@ -177,7 +187,7 @@ type Record = { ...@@ -177,7 +187,7 @@ type Record = {
- 新增驾驶舱扩展字段当前在线上 Subject 中大多仍为 `text` 类型,前端工作台按标准值引导录入,用于支持后续严重程度、问题分类、问题原因和整改质效分析 - 新增驾驶舱扩展字段当前在线上 Subject 中大多仍为 `text` 类型,前端工作台按标准值引导录入,用于支持后续严重程度、问题分类、问题原因和整改质效分析
- 其余 `select` 类型的可选值必须以线上 Subject 配置中的 `settings.options` 为准,禁止在页面内硬编码 - 其余 `select` 类型的可选值必须以线上 Subject 配置中的 `settings.options` 为准,禁止在页面内硬编码
- `problem_discovery_situation` 曾在历史文档和归档对象中出现,但本次主对象字段定义未包含;新页面只应在 Subject View 或线上字段配置存在时展示,不能作为 `problem_rectification` 固定字段依赖 - `problem_discovery_situation` 曾在历史文档和归档对象中出现,但本次主对象字段定义未包含;新页面只应在 Subject View 或线上字段配置存在时展示,不能作为 `problem_rectification` 固定字段依赖
- `department_rectification_items` 用于新转发流程的部门级整改明细。单条问题即使只有 1 个责任部门,也生成 1 条明细;转发后 `rectification_progress`、`rectification_situation`、`the_time_of_rectification`、`rectification_proof_materials`、`audit_status`、`final_audit_status` 以明细为事实来源,父字段仅做兼容汇总展示;整改进度、审批状态和最终审批状态按部门明细中最落后的状态汇总,例如“整改中”优先于“已整改”,“未审批”优先于“已审批” - `department_rectification_items` 用于新转发流程的部门级整改明细。无论单条还是多条推送,每个问题都按责任部门生成明细;单条问题即使只有 1 个责任部门,也生成 1 条明细。转发后 `rectification_progress`、`rectification_situation`、`the_time_of_rectification`、`rectification_proof_materials`、`audit_status`、`final_audit_status` 以明细为事实来源,父字段仅做兼容汇总展示;整改进度、审批状态和最终审批状态按部门明细中最落后的状态汇总,例如“整改中”优先于“已整改”,“未审批”优先于“已审批”
- `problem_rectification_delay_request` 为历史问题级单次延期申请嵌套对象,与 `department_rectification_items` 平级;新发起延期默认按部门写入 `problem_rectification_delay_request_items` - `problem_rectification_delay_request` 为历史问题级单次延期申请嵌套对象,与 `department_rectification_items` 平级;新发起延期默认按部门写入 `problem_rectification_delay_request_items`
- `problem_rectification_delay_request_items` 为部门级延期申请明细数组;每个责任部门 1 条延期申请,独立保存填写人、一级审批人、最终审批人、申请完成时间、延期事由、状态和待办 ID - `problem_rectification_delay_request_items` 为部门级延期申请明细数组;每个责任部门 1 条延期申请,独立保存填写人、一级审批人、最终审批人、申请完成时间、延期事由、状态和待办 ID
- `is_locked` 为问题整改主记录的状态锁定标记;仅整改进度汇总值为 `已整改` 的记录允许写入 `true`,锁定后所有业务字段和流程状态均不可修改,只有解锁操作可以将其写回 `false` - `is_locked` 为问题整改主记录的状态锁定标记;仅整改进度汇总值为 `已整改` 的记录允许写入 `true`,锁定后所有业务字段和流程状态均不可修改,只有解锁操作可以将其写回 `false`
...@@ -370,13 +380,13 @@ type Record = { ...@@ -370,13 +380,13 @@ type Record = {
- `reference_record_id` 与 `restriction_value` 保持一致,作为显式的“关联记录 ID”冗余字段 - `reference_record_id` 与 `restriction_value` 保持一致,作为显式的“关联记录 ID”冗余字段
- `subject_name` 保存被推送记录所属对象名,当前整改流程固定为 `problem_rectification` - `subject_name` 保存被推送记录所属对象名,当前整改流程固定为 `problem_rectification`
- `flow_mode` 区分流程:`department` 表示单条问题按责任部门生成整改明细行,`batch` 表示多选问题的单填写人 / 单审批人简单推送,`delay` 表示历史问题级延期申请,`delay_department` 表示按责任部门生成延期申请明细 - `flow_mode` 区分流程:`department` 表示单条问题按责任部门生成整改明细行,`batch` 表示多选问题的单填写人 / 单审批人简单推送,`delay` 表示历史问题级延期申请,`delay_department` 表示按责任部门生成延期申请明细
- `department_item_ids` 保存本次部门流程涉及的部门级 `item_id`;整改流程对应 `department_rectification_items[].item_id`,延期流程对应 `problem_rectification_delay_request_items[].item_id` - `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` 匹配,不能将其当作普通整改流程 - 部分线上 `message_reference_id` 对象会过滤未配置的 `flow_mode` 或 `department_item_ids`;延期流程页须回退按关联问题 `problem_rectification_delay_request_items[].flow_record_id._id` 匹配当前流程主单 `_id`,或按共享的 `progress_id_editor` 匹配,不能将其当作普通整改流程
- `progress_id_editor`、`progress_id_auditor`、`progress_id_sender` 用于记录每一步用户确认动作生成的流程 ID,用来区分当前流程推进到了哪一步 - 批量流程的填写、复核、最终审批状态及待办 ID 直接使用流程主单顶层的 `editor_status`、`auditor_status`、`sender_status` 和 `progress_id_*`,同一批次的全部问题共享流程状态
- `editor_status`:当前项目统一使用 `未完成`、`已完成` - `editor_status`:当前项目统一使用 `未完成`、`已完成`
- `auditor_status`:当前项目统一使用 `未完成`、`已完成` - `auditor_status`:当前项目统一使用 `未完成`、`已完成`
- `sender_status`:当前项目统一使用 `未完成`、`已完成` - `sender_status`:当前项目统一使用 `未完成`、`已完成`
- 当复核或最终审核驳回时,流程主单会回写新的 `progress_id_editor`,并将 `editor_status` 重置为 `未完成`,从而重新进入填写阶段 - 当批量流程复核或最终审核驳回时,流程主单 `editor_status` 重置为 `未完成` 并回写新的 `progress_id_editor`,同一批次的全部问题共同重新进入填写阶段
## 3.5 项目管理 ## 3.5 项目管理
......
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