Commit f91ad267 authored by David Yang's avatar David Yang

fix: 修复延期申请流程表

parent 004e66ec
......@@ -10,13 +10,11 @@ import {
TableRow,
} from '@/catalyst-ui-kit/javascript/table';
import { Textarea } from '@/catalyst-ui-kit/javascript/textarea';
import RecordDetailDrawer from '@/pages/RecordDetailDrawer';
import {
batchUpdateRecords,
createNotice,
executeAction,
loadRecord,
queryRecords,
queryUsers,
sendNotice,
updateRecord,
......@@ -25,7 +23,7 @@ import {
import { normalizeText, toArray } from '@/utils/subjectView';
import clsx from 'clsx';
import dayjs from 'dayjs';
import { LockClosedIcon, PencilSquareIcon } from '@heroicons/react/24/outline';
import { LockClosedIcon } from '@heroicons/react/24/outline';
import { useContext, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'umi';
import { UserContext } from '@/wrapper/Auth';
......@@ -50,23 +48,6 @@ import {
normalizeDepartmentRectificationItems,
} from '../problemRectificationListService';
const FLOW_MANAGED_PARENT_FIELDS = [
'fill_in_the_people',
'review_people',
'audit_status',
'final_audit_status',
'department_rectification_items',
DELAY_REQUEST_FIELD,
DELAY_REQUEST_ITEMS_FIELD,
];
const DEPARTMENT_MANAGED_PARENT_FIELDS = [
...FLOW_MANAGED_PARENT_FIELDS,
'rectification_progress',
'rectification_situation',
'the_time_of_rectification',
'rectification_proof_materials',
];
function toId(value) {
if (!value) {
return '';
......@@ -252,7 +233,27 @@ function buildBatchRows(records = [], metadata = {}) {
});
}
function buildDelayRows(records = [], metadata = {}, flowMode = FLOW_MODE_DELAY) {
function getFlowEditorProgressIds(flowRecord) {
return new Set(
toArray(flowRecord?.metadata?.progress_id_editor).map((item) => normalizeText(item)).filter(Boolean),
);
}
function isDelayRequestFlowTitle(flowRecord) {
return normalizeText(flowRecord?.title).includes('延期申请');
}
function delayRequestBelongsToFlow(request, flowRecord) {
const flowRecordId = toId(flowRecord);
const editorProgressIds = getFlowEditorProgressIds(flowRecord);
return Boolean(
(flowRecordId && toId(request?.flow_record_id) === flowRecordId) ||
(editorProgressIds.size > 0 && editorProgressIds.has(normalizeText(request?.progress_id_editor))),
);
}
function buildDelayRows(records = [], metadata = {}, flowMode = FLOW_MODE_DELAY, flowRecord = null) {
const editor = toId(toArray(metadata.editor)[0]);
const auditor = toId(toArray(metadata.auditor)[0]);
const scopedItemIds = new Set(toArray(metadata.department_item_ids).map(toId).filter(Boolean));
......@@ -260,12 +261,18 @@ function buildDelayRows(records = [], metadata = {}, flowMode = FLOW_MODE_DELAY)
return records.flatMap((record) => {
const recordMetadata = record.metadata || {};
if (flowMode === FLOW_MODE_DELAY_DEPARTMENT) {
return normalizeDelayRequestItems(recordMetadata[DELAY_REQUEST_ITEMS_FIELD])
const delayRequests = normalizeDelayRequestItems(recordMetadata[DELAY_REQUEST_ITEMS_FIELD]);
const hasMatchedDelayRequest = delayRequests.some((request) =>
delayRequestBelongsToFlow(request, flowRecord),
);
return delayRequests
.filter(
(request) =>
scopedItemIds.size === 0 ||
(!flowRecord || !hasMatchedDelayRequest || delayRequestBelongsToFlow(request, flowRecord)) &&
(scopedItemIds.size === 0 ||
scopedItemIds.has(request.item_id) ||
scopedItemIds.has(request.source_department_item_id),
scopedItemIds.has(request.source_department_item_id)),
)
.map((request) => ({
key: `${record._id || record.name}-delay-${request.item_id}`,
......@@ -330,6 +337,29 @@ function buildDelayRows(records = [], metadata = {}, flowMode = FLOW_MODE_DELAY)
});
}
function hasDelayDepartmentRequestForFlow(records = [], flowRecord) {
if (!toId(flowRecord) && getFlowEditorProgressIds(flowRecord).size === 0) {
return false;
}
const hasMatchedDelayRequest = records.some((record) =>
normalizeDelayRequestItems(record?.metadata?.[DELAY_REQUEST_ITEMS_FIELD]).some((request) =>
delayRequestBelongsToFlow(request, flowRecord),
),
);
if (hasMatchedDelayRequest) {
return true;
}
return (
isDelayRequestFlowTitle(flowRecord) &&
records.some(
(record) => normalizeDelayRequestItems(record?.metadata?.[DELAY_REQUEST_ITEMS_FIELD]).length > 0,
)
);
}
export default function ProblemRectificationFlowTablePage() {
const userInfo = useContext(UserContext);
const [searchParams] = useSearchParams();
......@@ -342,21 +372,31 @@ export default function ProblemRectificationFlowTablePage() {
const [records, setRecords] = useState([]);
const [userLabels, setUserLabels] = useState({});
const [drafts, setDrafts] = useState({});
const [editingRecordName, setEditingRecordName] = useState('');
const metadata = useMemo(() => messageRecord?.metadata || {}, [messageRecord]);
const flowMode = metadata.flow_mode || FLOW_MODE_BATCH;
const isDelayDepartmentFlow = useMemo(
() => hasDelayDepartmentRequestForFlow(records, messageRecord),
[messageRecord, records],
);
const flowMode = isDelayDepartmentFlow
? FLOW_MODE_DELAY_DEPARTMENT
: metadata.flow_mode || FLOW_MODE_BATCH;
const isDelayFlow = flowMode === FLOW_MODE_DELAY || flowMode === FLOW_MODE_DELAY_DEPARTMENT;
const rows = useMemo(
() => {
if (flowMode === FLOW_MODE_DELAY || flowMode === FLOW_MODE_DELAY_DEPARTMENT) {
return buildDelayRows(records, metadata, flowMode);
return buildDelayRows(
records,
metadata,
flowMode,
flowMode === FLOW_MODE_DELAY_DEPARTMENT ? messageRecord : null,
);
}
return flowMode === FLOW_MODE_DEPARTMENT
? buildDepartmentRows(records)
: buildBatchRows(records, metadata);
},
[flowMode, metadata, records],
[flowMode, messageRecord, metadata, records],
);
const currentUserId = toId(userInfo?._id);
const senderId = toId(metadata.sender);
......@@ -379,20 +419,10 @@ export default function ProblemRectificationFlowTablePage() {
const nextMessage = await loadRecord(messageId);
const nextMetadata = nextMessage?.metadata || {};
const recordIds = normalizeRestrictionValue(nextMetadata.restriction_value);
const result = recordIds.length
? await queryRecords({
subject: nextMetadata.subject_name || PROBLEM_RECTIFICATION_SUBJECT,
page: 0,
page_size: 1000,
mode: 'full',
filter: {
_id: { $in: recordIds },
},
})
: { records: [] };
const relatedRecords = await Promise.all(recordIds.map((recordId) => loadRecord(recordId)));
setMessageRecord(nextMessage);
setRecords(result?.records || []);
setRecords(relatedRecords.filter(Boolean));
setDrafts({});
} catch (requestError) {
setError(requestError?.message || '流程表格加载失败,请稍后重试。');
......@@ -1070,17 +1100,6 @@ export default function ProblemRectificationFlowTablePage() {
) : null}
</div>
<div className="mt-1 text-xs text-slate-500">{row.projectName}</div>
{isSender ? (
<Button
plain
className="mt-2"
disabled={Boolean(submittingKey)}
onClick={() => setEditingRecordName(row.record?.name || row.recordId)}
>
<PencilSquareIcon data-slot="icon" />
{row.isLocked ? '查看问题记录' : '编辑问题记录'}
</Button>
) : null}
</TableCell>
<TableCell className="min-w-72 max-w-96">
<div className="whitespace-pre-wrap break-words text-sm text-slate-700">
......@@ -1292,21 +1311,6 @@ export default function ProblemRectificationFlowTablePage() {
)}
</section>
<RecordDetailDrawer
open={Boolean(editingRecordName)}
recordName={editingRecordName}
subjectName={PROBLEM_RECTIFICATION_SUBJECT}
readonly={!isSender}
disabledFields={
flowMode === FLOW_MODE_DEPARTMENT || isDelayFlow
? DEPARTMENT_MANAGED_PARENT_FIELDS
: FLOW_MANAGED_PARENT_FIELDS
}
onClose={() => {
setEditingRecordName('');
loadFlow();
}}
/>
</div>
);
}
......@@ -59,6 +59,7 @@ import {
PROBLEM_RECTIFICATION_PAGE_SIZE_OPTIONS,
buildDepartmentAssignmentsFromRecord,
buildDepartmentDelayRequestItems,
buildDelayRequestItemsBatchUpdateData,
buildDepartmentParentBatchUpdateData,
buildDepartmentRectificationItems,
buildGridViews,
......@@ -68,6 +69,8 @@ import {
buildProblemRectificationCreatePayload,
getProblemRectificationFlowTableUrl,
getRecordCellText,
delayRequestItemsMatch,
loadRecordAfterDelayRequestWrite,
loadProblemRectificationListData,
loadProblemRectificationListResources,
loadProblemRectificationViewCountMap,
......@@ -1154,8 +1157,15 @@ export default function ProblemRectificationListPage() {
},
});
const flowRecordId = messageRecord.name || messageRecord._id;
const returnUrl = getProblemRectificationFlowTableUrl(flowRecordId);
const createdMessageRecord = messageRecord?._id
? messageRecord
: await loadRecord(messageRecord?.name);
const flowRecordId = createdMessageRecord?._id || '';
if (!flowRecordId) {
throw new Error('流程主单创建后未返回 _id,不能发起延期申请。');
}
const flowRouteId = createdMessageRecord?.name || messageRecord?.name || flowRecordId;
const returnUrl = getProblemRectificationFlowTableUrl(flowRouteId);
const progressIdEditorByItemId = {};
for (const assignment of delayDepartmentAssignments) {
......@@ -1187,18 +1197,26 @@ export default function ProblemRectificationListPage() {
progressIdEditorByItemId,
});
await updateRecord(selectedRecord.id, {
metadata: { [DELAY_REQUEST_ITEMS_FIELD]: delayRequestItems },
await batchUpdateRecords({
subject: PROBLEM_RECTIFICATION_SUBJECT,
filter: {
_id: { $in: [selectedRecord.id] },
},
data: buildDelayRequestItemsBatchUpdateData(delayRequestItems),
});
await updateRecord(messageRecord._id || messageRecord.name, {
await updateRecord(flowRecordId, {
metadata: { progress_id_editor: Object.values(progressIdEditorByItemId).filter(Boolean) },
});
const savedRecord = await loadRecord(selectedRecord.id);
const savedRecord = await loadRecordAfterDelayRequestWrite({
recordName: selectedRecord.id,
expectedItems: delayRequestItems,
loadRecord,
});
const savedDelayRequestItems = normalizeDelayRequestItems(
savedRecord?.metadata?.[DELAY_REQUEST_ITEMS_FIELD],
);
if (savedDelayRequestItems.length !== delayRequestItems.length) {
if (!delayRequestItemsMatch(delayRequestItems, savedDelayRequestItems)) {
throw new Error('延期申请写入失败,请刷新后重试。');
}
......
......@@ -191,7 +191,8 @@
- 每次只允许选择 1 条问题记录,按该问题责任部门生成部门延期申请配置行;已有 `department_rectification_items` 时优先沿用其部门明细 ID
- 弹窗为每个保留的责任部门选择 1 个填写人和 1 个一级审批人,发起人为最终审批人
- 延期申请保存于 `metadata.problem_rectification_delay_request_items`,与 `department_rectification_items` 平级;历史 `metadata.problem_rectification_delay_request` 仅用于兼容旧问题级延期;每条问题最多保存 1 份延期申请流程
- 创建 `message_reference_id` 流程主单和 OA 填写待办,流程链接使用相对地址 `/problem_rectification_new/problem_rectification_flow_table?id=<message_record_name>`,不拼接浏览器当前域名
- 创建 `message_reference_id` 流程主单和 OA 填写待办;延期明细的 `flow_record_id` 必须使用流程主单 `_id`。创建接口未直接返回 `_id` 时,系统按返回的 `name` 回读流程主单后再写入;流程链接仍使用相对地址 `/problem_rectification_new/problem_rectification_flow_table?id=<message_record_name>`,不拼接浏览器当前域名
- 延期申请明细写入问题主记录时使用 `/record/batch/update` 的 `metadata.problem_rectification_delay_request_items` 点路径更新,避免覆盖主记录其它业务字段
- 历史 OA 待办若仍使用 `/self_inspection_form?id=<message_record_name>`,应用入口会保留原流程 ID 并重定向到上述表格处理页
- 已锁定、未选择流程人员或已有延期申请的问题不能发起
......@@ -340,7 +341,7 @@
- `rectification_proof_materials`
4. 填写人送审后,该行填写业务字段锁定;只有审批驳回后,填写人恢复编辑
5. 审批驳回后,原审批人也可以编辑该行整改业务字段,但不能代替填写人点击“送审”;待填写人重新送审后,审批人恢复审批操作
6. 流程推送人可以从问题列打开父问题详情抽屉,编辑本次推送关联的 `problem_rectification` 主记录;填写人和审批人只能查看父问题记录。部门流程的父级汇总字段、部门明细对象、审批状态和流程人员字段不在详情抽屉中直接编辑
6. 问题整改推送与延期申请推送的流程页均不提供父问题“编辑问题记录”入口或详情抽屉;流程参与人仅在当前表格行内执行其整改或延期填写、送审和审批操作
推荐新交互:
......@@ -403,8 +404,8 @@
2. 系统展示当前问题及主记录预计完成时间,并按责任部门生成延期申请配置行;已有 `department_rectification_items` 时优先使用其部门和 `item_id`,否则按 `metadata.responsible_department` 生成临时部门行
3. 发起人可删除本次不需要推送延期申请的部门配置行,但至少需保留 1 行;保留的每个部门必须选择 1 个填写人和 1 个一级审批人
4. 问题主记录已有 `problem_rectification_delay_request` 或 `problem_rectification_delay_request_items` 时不能重复推送
5. 确认推送后创建 `message_reference_id`,写入 `flow_mode = "delay_department"`、问题 ID、部门延期申请 `item_id` 列表、填写人、一级审批人和推送人
6. 在 `metadata.problem_rectification_delay_request_items` 初始化部门延期申请数组:原预计完成时间优先取对应部门明细当前 `the_time_of_rectification`,没有部门明细时取主记录当前 `the_time_of_rectification`;填写、一级审批、最终审批状态分别初始化为“未完成 / 未审批 / 未审批”
5. 确认推送后创建 `message_reference_id`,尝试写入 `flow_mode = "delay_department"`、问题 ID、部门延期申请 `item_id` 列表、填写人、一级审批人和推送人;创建响应未带 `_id` 时按 `name` 回读流程主单。若线上流程对象过滤了 `flow_mode` 或部门明细 ID,流程页先按 `restriction_value` 逐条读取关联问题详情,确保取得完整的延期嵌套字段,再按问题延期明细的 `flow_record_id._id` 与当前流程主单 `_id` 关联,或按二者共享的 `progress_id_editor` 自动识别为延期流程;这两类关联信息均被过滤时,仅对标题包含“延期申请”且关联问题确有延期明细的流程主单回退显示延期表单
6. 在 `metadata.problem_rectification_delay_request_items` 初始化部门延期申请数组:`flow_record_id` 写入流程主单 `_id`;原预计完成时间优先取对应部门明细当前 `the_time_of_rectification`,没有部门明细时取主记录当前 `the_time_of_rectification`;填写、一级审批、最终审批状态分别初始化为“未完成 / 未审批 / 未审批”
7. 系统为每个部门填写人创建 OA 待办并发送内部通知,链接使用相对地址 `/problem_rectification_new/problem_rectification_flow_table?id=<message_record_name>`
8. 填写人进入流程页后按部门填写 `requested_due_date`(申请完成时间)和 `delay_reason`(延期事由);草稿可以暂存,提交时两项均必填
......@@ -427,6 +428,7 @@
- `metadata.is_locked = true` 时,禁止发起、保存、提交或审批延期申请
- 延期申请的状态和待办 ID 只写入 `problem_rectification_delay_request_items` 对应部门行,不覆盖部门整改明细的审批状态和原整改待办 ID
- 后台问题记录在提交前发生变化时,操作失败并提示刷新后重试
- 延期申请主记录写入成功后,前端以部门延期明细条数和 `flow_record_id` 回读校验,并以退避间隔最多等待约 8 秒;服务端读取时可能将 `flow_record_id` 填充为引用对象,前端统一提取其 `_id`。若后端未返回 `item_id`,按保存数组顺序匹配;重试后仍不一致才提示“延期申请写入失败”。若回读返回 `401` 或 `403`,提示登录状态失效且写入已完成,避免误报写入失败
- OA 待办创建、业务记录写入或内部通知发送失败时展示错误,不显示成功提示
---
......@@ -479,7 +481,7 @@
- 父行可展开显示部门明细,明细中分开展示责任部门、填写人、审批人、整改进度、预计完成时间、审批状态、最终审批状态和附件状态
- 存在部门明细的父行在标题右侧显示“N 条部门明细”入口;单部门推送同样生成 1 条明细。展开后部门明细作为父行下方子行平铺展示,复用主表列顺序、列宽、固定列和列分组,与问题整改列头一一对齐;子行不参与主表勾选和批量操作,并将填写人、审批人 ID 转为用户显示名
- 转发后的整改填写、审批和最终审批均优先更新部门明细,父字段仅做兼容汇总展示
- 新流程页中,填写人只能维护本人负责行的整改业务字段,送审后锁定,复核驳回后恢复;复核驳回状态下对应审批人可以维护该行整改业务字段,审批通过后锁定,且审批人不能点击填写人的“送审”;流程推送人可以维护本次推送关联的父问题记录和全部部门明细整改业务字段,但不能直接编辑流程状态与待办 ID
- 新流程页中,填写人只能维护本人负责行的整改业务字段,送审后锁定,复核驳回后恢复;复核驳回状态下对应审批人可以维护该行整改业务字段,审批通过后锁定,且审批人不能点击填写人的“送审”;流程推送人可以维护全部关联部门明细整改业务字段,但流程页不提供父问题记录编辑或详情抽屉,且不能直接编辑流程状态与待办 ID
- 推送人的内容修改保持当前审批状态不变,不触发重新送审
### 8.4 延期申请状态约定
......
......@@ -40,6 +40,7 @@ export const EDITOR_STATUS_COMPLETED = '已完成';
export const AUDIT_STATUS_PENDING = '未审批';
export const AUDIT_STATUS_APPROVED = '已审批';
export const AUDIT_STATUS_REJECTED = '已驳回';
const DELAY_REQUEST_WRITE_VERIFY_DELAYS = [0, 200, 500, 1000, 2000, 4000];
const FIXED_PROGRESS_VALUES = new Set(['已整改', '已优化']);
const RECTIFICATION_PROGRESS_PRIORITY = ['整改中', '部分整改', '已整改', '已优化', '已完成', '不适用'];
const AUDIT_STATUS_PRIORITY = [AUDIT_STATUS_REJECTED, AUDIT_STATUS_PENDING, AUDIT_STATUS_APPROVED];
......@@ -124,6 +125,18 @@ function normalizeUserId(value) {
return String(value._id || value.id || value.name || '').trim();
}
function normalizeRecordId(value) {
if (!value) {
return '';
}
if (typeof value === 'object') {
return normalizeText(value._id || value.id || value.name);
}
return normalizeText(value);
}
function uniqueTruthy(values = []) {
return Array.from(new Set(values.map((item) => normalizeText(item)).filter(Boolean)));
}
......@@ -276,7 +289,7 @@ export function normalizeDelayRequest(value) {
progress_id_editor: parsedValue.progress_id_editor || '',
progress_id_auditor: parsedValue.progress_id_auditor || '',
progress_id_sender: parsedValue.progress_id_sender || '',
flow_record_id: parsedValue.flow_record_id || '',
flow_record_id: normalizeRecordId(parsedValue.flow_record_id),
submitted_at: parsedValue.submitted_at || null,
auditor_decided_at: parsedValue.auditor_decided_at || null,
sender_decided_at: parsedValue.sender_decided_at || null,
......@@ -304,6 +317,71 @@ export function normalizeDelayRequestItems(value) {
}));
}
function waitForDelayRequestWrite(delayMs) {
if (!delayMs) {
return Promise.resolve();
}
return new Promise((resolve) => {
setTimeout(resolve, delayMs);
});
}
export function delayRequestItemsMatch(expectedItems = [], savedItems = []) {
if (expectedItems.length !== savedItems.length) {
return false;
}
const savedItemsById = new Map(
savedItems
.map((item) => [normalizeText(item.item_id), item])
.filter(([itemId]) => Boolean(itemId)),
);
return expectedItems.every((expectedItem, index) => {
const itemId = normalizeText(expectedItem.item_id);
const savedItem = savedItemsById.get(itemId) || savedItems[index];
return Boolean(
savedItem &&
normalizeRecordId(savedItem.flow_record_id) === normalizeRecordId(expectedItem.flow_record_id),
);
});
}
export async function loadRecordAfterDelayRequestWrite({
recordName,
expectedItems,
loadRecord,
} = {}) {
let latestRecord = null;
for (const delayMs of DELAY_REQUEST_WRITE_VERIFY_DELAYS) {
await waitForDelayRequestWrite(delayMs);
latestRecord = await loadRecord(recordName);
const responseCode = Number(latestRecord?.code);
if (Number.isFinite(responseCode) && responseCode >= 400) {
if (responseCode === 401 || responseCode === 403) {
throw new Error('延期申请已写入,但登录状态已失效,无法完成回读校验。请重新登录后刷新页面确认。');
}
throw new Error(
latestRecord?.message || `延期申请已写入,但后台回读校验失败(${responseCode})。`,
);
}
const savedItems = normalizeDelayRequestItems(
latestRecord?.metadata?.[DELAY_REQUEST_ITEMS_FIELD],
);
if (delayRequestItemsMatch(expectedItems, savedItems)) {
return latestRecord;
}
}
return latestRecord;
}
export function buildDepartmentAssignmentsFromRecord(record) {
const metadata = record?.metadata || record?.raw?.metadata || {};
const departmentItems = normalizeDepartmentRectificationItems(record);
......@@ -439,6 +517,12 @@ export function buildDepartmentParentBatchUpdateData(items = []) {
);
}
export function buildDelayRequestItemsBatchUpdateData(items = []) {
return {
[`metadata.${DELAY_REQUEST_ITEMS_FIELD}`]: items.filter(Boolean),
};
}
function normalizeFlagText(value) {
const text = normalizeText(value);
return text || '否';
......
......@@ -181,7 +181,7 @@ type Record = {
- `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 = "已审批"`)不再允许编辑,审批驳回后(`audit_status = "已驳回"`)审批人可以编辑该明细的整改业务字段,填写人也恢复编辑。审批人恢复的只是业务字段编辑权限,不获得填写人的送审权限。流程推送人可以编辑本次推送关联的全部部门明细整改业务字段,但流程页不提供父问题记录编辑或详情抽屉。审批状态、待办 ID、明细 ID 等流程控制字段不作为普通表单字段开放
- 推送人修改父问题记录或部门明细时,不自动重置该明细已经形成的审批状态,也不自动重新生成流程待办
### 3.1.3 `department_rectification_items` 子结构
......@@ -229,7 +229,7 @@ type Record = {
| 填写待办 ID | `progress_id_editor` | text | 否 | 当前延期申请填写 OA 待办 ID |
| 一级审批待办 ID | `progress_id_auditor` | text | 否 | 当前延期申请一级审批 OA 待办 ID |
| 最终审批待办 ID | `progress_id_sender` | text | 否 | 当前延期申请最终审批 OA 待办 ID |
| 流程主单 ID | `flow_record_id` | text | 否 | 对应 `message_reference_id` 的 `name` 或 `_id` |
| 流程主单 ID | `flow_record_id` | text | 否 | 对应 `message_reference_id` 的 `_id` |
| 提交时间 | `submitted_at` | date | 否 | 填写人最近一次提交时间 |
| 一级审批时间 | `auditor_decided_at` | date | 否 | 一级审批人最近一次审批时间 |
| 最终审批时间 | `sender_decided_at` | date | 否 | 最终审批人最近一次审批时间 |
......@@ -371,6 +371,7 @@ type Record = {
- `subject_name` 保存被推送记录所属对象名,当前整改流程固定为 `problem_rectification`
- `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` 匹配,不能将其当作普通整改流程
- `progress_id_editor`、`progress_id_auditor`、`progress_id_sender` 用于记录每一步用户确认动作生成的流程 ID,用来区分当前流程推进到了哪一步
- `editor_status`:当前项目统一使用 `未完成`、`已完成`
- `auditor_status`:当前项目统一使用 `未完成`、`已完成`
......
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