Commit 2ebc3759 authored by drigle's avatar drigle

fix: 修复自查底稿推送校验与流程链接

parent 46ec095b
...@@ -34,6 +34,11 @@ export default defineConfig({ ...@@ -34,6 +34,11 @@ export default defineConfig({
'react-dom': resolvePackageRoot('react-dom'), 'react-dom': resolvePackageRoot('react-dom'),
}, },
routes: [ routes: [
{
path: '/self_inspection_form',
wrappers: ['@/wrapper/Site', '@/wrapper/Auth'],
component: './integrity_practice_management/SelfInspectionFlowPage',
},
{ {
path: ROUTE_PATHS.login, path: ROUTE_PATHS.login,
layout: false, layout: false,
......
{ {
"name": "integrity_practice_management_new", "name": "integrity_practice_management_new",
"version": "1.0.8", "version": "1.0.9",
"build": "abc", "build": "abc",
"description": "廉洁从业管理", "description": "廉洁从业管理",
"date": "2025-07-14 19:27:20", "date": "2025-07-14 19:27:20",
......
...@@ -29,6 +29,8 @@ const push = records => exportsObject.executeConfiguredAction('integrityRiskMana ...@@ -29,6 +29,8 @@ const push = records => exportsObject.executeConfiguredAction('integrityRiskMana
await push([{ _id: 'p1', metadata: {} }]); await push([{ _id: 'p1', metadata: {} }]);
const created = writes.find(([kind]) => kind === 'create')[1]; const created = writes.find(([kind]) => kind === 'create')[1];
assert.equal(JSON.stringify(created.metadata.restriction_value), '["r1","r2"]'); assert.equal(JSON.stringify(created.metadata.restriction_value), '["r1","r2"]');
assert.equal(writes.find(([kind]) => kind === 'oa')[2].returnUrl, '/flow');
assert.match(writes.find(([kind]) => kind === 'notice')[1].content, /\/flow$/);
writes = []; empty = true; writes = []; empty = true;
await assert.rejects(() => push([{ _id: 'p1' }, { _id: 'p2' }]), /没有关联自查项目/); await assert.rejects(() => push([{ _id: 'p1' }, { _id: 'p2' }]), /没有关联自查项目/);
assert.equal(writes.length, 0); assert.equal(writes.length, 0);
......
...@@ -25,6 +25,7 @@ import PlusOutlined from '@ant-design/icons/PlusOutlined'; ...@@ -25,6 +25,7 @@ import PlusOutlined from '@ant-design/icons/PlusOutlined';
import WarningOutlined from '@ant-design/icons/WarningOutlined'; import WarningOutlined from '@ant-design/icons/WarningOutlined';
import MoreOutlined from '@ant-design/icons/MoreOutlined'; import MoreOutlined from '@ant-design/icons/MoreOutlined';
import ExportOutlined from '@ant-design/icons/ExportOutlined'; import ExportOutlined from '@ant-design/icons/ExportOutlined';
import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined';
function iconStyle(size, style) { function iconStyle(size, style) {
return size === undefined ? style : { fontSize: size, ...style }; return size === undefined ? style : { fontSize: size, ...style };
...@@ -48,6 +49,7 @@ function withSize(Icon) { ...@@ -48,6 +49,7 @@ function withSize(Icon) {
} }
export const AppGrid = withSize(AppstoreOutlined); export const AppGrid = withSize(AppstoreOutlined);
export const Info = withSize(InfoCircleOutlined);
export const Bell = withSize(BellOutlined); export const Bell = withSize(BellOutlined);
export const Archive = withSize(ArchiveOutlined); export const Archive = withSize(ArchiveOutlined);
export const Calendar = withSize(CalendarOutlined); export const Calendar = withSize(CalendarOutlined);
......
...@@ -2,7 +2,16 @@ export const APPLICATION_DISPLAY_NAME = '廉洁从业管理'; ...@@ -2,7 +2,16 @@ export const APPLICATION_DISPLAY_NAME = '廉洁从业管理';
export const APPLICATION_NAME = 'integrity_practice_management'; export const APPLICATION_NAME = 'integrity_practice_management';
export const APPLICATION_ROUTE_NAME = APPLICATION_NAME; export const APPLICATION_ROUTE_NAME = APPLICATION_NAME;
export const AUTHORIZATION_APPLICATION_NAME = APPLICATION_NAME; export const AUTHORIZATION_APPLICATION_NAME = APPLICATION_NAME;
export const APPLICATION_BASE_PATH = `/${APPLICATION_NAME}/`; export const APPLICATION_BASE_PATH = typeof INTEGRITY_PRACTICE_APP_BASE_PATH !== 'undefined'
? INTEGRITY_PRACTICE_APP_BASE_PATH
: `/${APPLICATION_NAME}/`;
export const SELF_INSPECTION_FLOW_PATH = '/self_inspection_form';
export function getSelfInspectionFlowUrl(flowName, origin = window.location.origin) {
const basePath = APPLICATION_BASE_PATH.replace(/\/+$/, '');
return `${origin}${basePath}${SELF_INSPECTION_FLOW_PATH}?id=${encodeURIComponent(flowName)}`;
}
export const ROUTE_TITLES = Object.freeze({ export const ROUTE_TITLES = Object.freeze({
'/self_check_the_results_of_the_working_draft': '自查底稿填写表', '/self_check_the_results_of_the_working_draft': '自查底稿填写表',
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
## 1. 模块范围与入口 ## 1. 模块范围与入口
- 应用基路径:`/integrity_practice_management/` - 应用基路径:`.umirc.ts` 中的 `APP_BASE_PATH`,当前为 `/integrity_practice_management_new/`
- 默认入口:`/self_check_the_results_of_the_working_draft` - 默认入口:`/self_check_the_results_of_the_working_draft`
- 页面菜单与 Subject 动作配置:`integrityPracticeConfig.js` - 页面菜单与 Subject 动作配置:`integrityPracticeConfig.js`
- 页面统一使用鑫元 HeroUI Pro Shell、`RecordWorkbench` 和 `RecordDetailDrawer`;Sidebar 为 offcanvas,移动端通过可访问性 Dialog 打开 - 页面统一使用鑫元 HeroUI Pro Shell、`RecordWorkbench` 和 `RecordDetailDrawer`;Sidebar 为 offcanvas,移动端通过可访问性 Dialog 打开
...@@ -72,18 +72,25 @@ ...@@ -72,18 +72,25 @@
入口:自查底稿填写表 -> “推送自查底稿填写表”。 入口:自查底稿填写表 -> “推送自查底稿填写表”。
1. 通过 HeroUI Modal 中的远程 ComboBox 搜索选择填报人和审核人;两组人员都不能为空,只保存用户 `_id[]`。 1. 通过 HeroUI Modal 中的远程 ComboBox 搜索选择填报人和审核人;两组人员都不能为空,只保存用户 `_id[]`。
2. 将填报人写入关联 `self_check_the_project_results.metadata.informant`,同时写入填写表 `metadata.editor`。 2. 推送前逐条调用 `GET /record/:name` 读取所选底稿完整详情,从 `metadata.project_outcome` 提取并去重结果 ID;全部底稿通过非空校验后才执行写入。将填报人写入关联 `self_check_the_project_results.metadata.informant`,同时写入填写表 `metadata.editor`。
3. 创建 `message_reference_id`,保存发送人、填报人、审核人、Subject、View、关联记录和限制条件。 3. 创建 `message_reference_id`,保存发送人、填报人、审核人、Subject、View、关联记录和限制条件。
4. 为每个填报人调用 `oa_notice:createProgress` 创建 OA 待办,并将任务 ID 写入 `metadata.progress_id_editor`。 4. 为每个填报人调用 `oa_notice:createProgress` 创建 OA 待办,并将任务 ID 写入 `metadata.progress_id_editor`。
5. 创建并发送站内通知,流程链接继续使用历史兼容入口 `/self_inspection_form?id=:flowName`。 5. 创建并发送站内通知。首次推送及后续审批通知统一使用当前域名、应用部署基路径和兼容流程入口 `/self_inspection_form?id=:flowName`;当前完整路径为 `/integrity_practice_management_new/self_inspection_form?id=:flowName`,其中 ID 为流程主单 `name`。
填写人提交后,在审核人尚未处理前,填写人可在流程页点击“撤回提交”。撤回会将流程主单的 `editor_status`、`auditor_status` 重置为“未完成”,并调用 `oa_notice:updateProgressStatus` 将审核人待办标记为已完成;撤回不发送站内通知。审核人已完成审批或驳回后不可撤回。 填写人提交后,在审核人尚未处理前,填写人可在流程页点击“撤回提交”。撤回会将流程主单的 `editor_status`、`auditor_status` 重置为“未完成”,并调用 `oa_notice:updateProgressStatus` 将审核人待办标记为已完成;撤回不发送站内通知。审核人已完成审批或驳回后不可撤回。
限制与异常: 限制与异常:
- 未选择记录、填报人或审核人时不创建流程。 - 未选择记录、填报人或审核人时不创建流程。
- 任一所选底稿没有关联自查项目时,整批推送在写入前停止,不创建空流程、待办或通知。
- 任一接口失败时显示错误,不把当前列表直接标记为成功;已经由后端成功创建的数据按历史非事务协议保留。 - 任一接口失败时显示错误,不把当前列表直接标记为成功;已经由后端成功创建的数据按历史非事务协议保留。
### 5.1 流程页路由与链接兼容
- `SelfInspectionFlowPage.jsx` 作为独立填写与审核流程页,挂载 Site/Auth 登录校验,不增加业务菜单。
- `/self_inspection_form` 是应用内兼容路由名,不代表旧表单实现;新推送链接必须带当前部署基路径。
- 已经发出的 OA 待办 URL 不会被前端批量改写;旧根路径链接是否继续可用取决于部署侧是否将该路径转发到本应用。
## 6. 归档流程 ## 6. 归档流程
### 6.1 自查底稿填写表 ### 6.1 自查底稿填写表
......
...@@ -32,6 +32,7 @@ import { ...@@ -32,6 +32,7 @@ import {
sortRecordsByView, sortRecordsByView,
toArray, toArray,
} from '@/utils/subjectView'; } from '@/utils/subjectView';
import { getSelfInspectionFlowUrl } from '@/config/application';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
import JSZip from 'jszip'; import JSZip from 'jszip';
...@@ -585,8 +586,19 @@ async function pushSelfInspectionForm(records, userInfo, assignment) { ...@@ -585,8 +586,19 @@ async function pushSelfInspectionForm(records, userInfo, assignment) {
throw new Error('请同时选择填报人和审核人'); throw new Error('请同时选择填报人和审核人');
} }
for (const record of records) { const preparedRecords = [];
const outcomeIds = toArray(record?.metadata?.project_outcome).map((item) => item?._id || item); for (const selectedRecord of records) {
const record = await loadRecord(selectedRecord.name || selectedRecord._id);
const outcomeIds = [...new Set(toArray(record?.metadata?.project_outcome)
.map((item) => typeof item === 'string' ? item : item?._id)
.filter(Boolean))];
if (!outcomeIds.length) {
throw new Error(`底稿“${record.title || record.name}”没有关联自查项目,请检查项目结果后再推送。`);
}
preparedRecords.push({ record, outcomeIds });
}
for (const { record, outcomeIds } of preparedRecords) {
const views = await getSubjectView('self_check_the_project_results'); const views = await getSubjectView('self_check_the_project_results');
await batchUpdateRecords({ await batchUpdateRecords({
subject: 'self_check_the_project_results', subject: 'self_check_the_project_results',
...@@ -625,14 +637,14 @@ async function pushSelfInspectionForm(records, userInfo, assignment) { ...@@ -625,14 +637,14 @@ async function pushSelfInspectionForm(records, userInfo, assignment) {
sendUserId: userInfo?._id, sendUserId: userInfo?._id,
sendUserName: userInfo?.username, sendUserName: userInfo?.username,
sendDisplayName: userInfo?.display_name, sendDisplayName: userInfo?.display_name,
returnUrl: `${window.location.origin}/self_inspection_form?id=${flowRecord.name}`, returnUrl: getSelfInspectionFlowUrl(flowRecord.name),
}); });
progressIds.push(progress?.data?.taskId); progressIds.push(progress?.data?.taskId);
} }
await updateRecord(flowRecord._id, { metadata: { progress_id_editor: progressIds } }); await updateRecord(flowRecord._id, { metadata: { progress_id_editor: progressIds } });
const notice = await createNotice({ const notice = await createNotice({
title: `${dayjs().format('YYYY-MM-DD')} 填写廉洁从业自查底稿`, title: `${dayjs().format('YYYY-MM-DD')} 填写廉洁从业自查底稿`,
content: `请点击链接填写自查底稿:${window.location.origin}/self_inspection_form?id=${flowRecord.name}`, content: `请点击链接填写自查底稿:${getSelfInspectionFlowUrl(flowRecord.name)}`,
type: 'internal', type: 'internal',
from: userInfo?._id, from: userInfo?._id,
to: fillers, to: fillers,
......
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