Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
M
model_analysis
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
David Yang
model_analysis
Commits
c0151ca8
Commit
c0151ca8
authored
Sep 17, 2026
by
drigle
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add forward OA and app notifications
parent
a2314f0c
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
424 additions
and
5 deletions
+424
-5
ForwardApprovals.jsx
src/pages/project_manager_xy/ForwardApprovals.jsx
+25
-1
ModelBatchData.jsx
src/pages/project_manager_xy/ModelBatchData.jsx
+5
-0
forwardNotificationService.js
src/pages/project_manager_xy/forwardNotificationService.js
+281
-0
model-monitor.business-flow.md
src/pages/project_manager_xy/model-monitor.business-flow.md
+7
-3
modelMonitorService.js
src/pages/project_manager_xy/modelMonitorService.js
+43
-0
record-api.md
src/services/record-api.md
+59
-0
subjectsDefinition.md
src/services/subjectsDefinition.md
+4
-1
No files found.
src/pages/project_manager_xy/ForwardApprovals.jsx
View file @
c0151ca8
...
@@ -27,6 +27,7 @@ import {
...
@@ -27,6 +27,7 @@ import {
loadForwardEditableRecords
,
loadForwardEditableRecords
,
loadForwardRecords
,
loadForwardRecords
,
saveForwardEditableRecords
,
saveForwardEditableRecords
,
dispatchForwardStateNotification
,
updateForwardRecordStatus
,
updateForwardRecordStatus
,
}
from
'./modelMonitorService'
;
}
from
'./modelMonitorService'
;
import
{
useContext
,
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
useContext
,
useEffect
,
useMemo
,
useState
}
from
'react'
;
...
@@ -94,6 +95,7 @@ export default function ForwardApprovals() {
...
@@ -94,6 +95,7 @@ export default function ForwardApprovals() {
const
[
editSaving
,
setEditSaving
]
=
useState
(
false
);
const
[
editSaving
,
setEditSaving
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
notice
,
setNotice
]
=
useState
(
''
);
const
[
notice
,
setNotice
]
=
useState
(
''
);
const
[
notificationWarning
,
setNotificationWarning
]
=
useState
(
''
);
const
[
activeRecordId
,
setActiveRecordId
]
=
useState
(
''
);
const
[
activeRecordId
,
setActiveRecordId
]
=
useState
(
''
);
const
[
isDetailDialogOpen
,
setIsDetailDialogOpen
]
=
useState
(
false
);
const
[
isDetailDialogOpen
,
setIsDetailDialogOpen
]
=
useState
(
false
);
const
[
isEditDialogOpen
,
setIsEditDialogOpen
]
=
useState
(
false
);
const
[
isEditDialogOpen
,
setIsEditDialogOpen
]
=
useState
(
false
);
...
@@ -186,8 +188,22 @@ export default function ForwardApprovals() {
...
@@ -186,8 +188,22 @@ export default function ForwardApprovals() {
async
function
handleUpdateRecord
(
record
,
nextStatuses
)
{
async
function
handleUpdateRecord
(
record
,
nextStatuses
)
{
setSaving
(
true
);
setSaving
(
true
);
setNotificationWarning
(
''
);
try
{
try
{
await
updateForwardRecordStatus
(
record
.
recordName
,
nextStatuses
);
const
updatedRecord
=
await
updateForwardRecordStatus
(
record
.
recordName
,
nextStatuses
);
const
notificationEvent
=
nextStatuses
.
approval_status
===
'approved'
?
'first_approval_pending'
:
nextStatuses
.
final_approval_status
===
'approved'
?
'final_approval_pending'
:
nextStatuses
.
approval_status
===
'rejected'
?
'first_rejected'
:
nextStatuses
.
final_approval_status
===
'rejected'
?
'final_rejected'
:
''
;
const
notificationResult
=
notificationEvent
?
await
dispatchForwardStateNotification
(
notificationEvent
,
updatedRecord
,
userInfo
)
:
{
warning
:
''
};
setNotificationWarning
(
notificationResult
.
warning
);
await
refreshRecords
(
'审批状态已更新。'
);
await
refreshRecords
(
'审批状态已更新。'
);
setError
(
''
);
setError
(
''
);
}
catch
(
updateError
)
{
}
catch
(
updateError
)
{
...
@@ -287,6 +303,7 @@ export default function ForwardApprovals() {
...
@@ -287,6 +303,7 @@ export default function ForwardApprovals() {
}
}
setEditSaving
(
true
);
setEditSaving
(
true
);
setNotificationWarning
(
''
);
try
{
try
{
if
(
editableRecords
.
length
>
0
)
{
if
(
editableRecords
.
length
>
0
)
{
const
saved
=
await
saveForwardEditableRecords
(
const
saved
=
await
saveForwardEditableRecords
(
...
@@ -309,6 +326,12 @@ export default function ForwardApprovals() {
...
@@ -309,6 +326,12 @@ export default function ForwardApprovals() {
});
});
setEditingRecord
(
updatedForwardRecord
);
setEditingRecord
(
updatedForwardRecord
);
const
notificationResult
=
await
dispatchForwardStateNotification
(
'first_approval_pending'
,
updatedForwardRecord
,
userInfo
,
);
setNotificationWarning
(
notificationResult
.
warning
);
setIsEditDialogOpen
(
false
);
setIsEditDialogOpen
(
false
);
await
refreshRecords
(
'填写已完成,当前批次已进入待审批。'
);
await
refreshRecords
(
'填写已完成,当前批次已进入待审批。'
);
setError
(
''
);
setError
(
''
);
...
@@ -460,6 +483,7 @@ export default function ForwardApprovals() {
...
@@ -460,6 +483,7 @@ export default function ForwardApprovals() {
{
loading
?
<
NoticeBanner
>
正在加载转发审批记录...
</
NoticeBanner
>
:
null
}
{
loading
?
<
NoticeBanner
>
正在加载转发审批记录...
</
NoticeBanner
>
:
null
}
{
!
loading
&&
error
?
<
NoticeBanner
tone=
"error"
>
{
error
}
</
NoticeBanner
>
:
null
}
{
!
loading
&&
error
?
<
NoticeBanner
tone=
"error"
>
{
error
}
</
NoticeBanner
>
:
null
}
{
!
error
&&
notice
?
<
NoticeBanner
tone=
"success"
>
{
notice
}
</
NoticeBanner
>
:
null
}
{
!
error
&&
notice
?
<
NoticeBanner
tone=
"success"
>
{
notice
}
</
NoticeBanner
>
:
null
}
{
notificationWarning
?
<
NoticeBanner
>
{
notificationWarning
}
</
NoticeBanner
>
:
null
}
<
div
className=
"min-w-0 max-w-full"
>
<
div
className=
"min-w-0 max-w-full"
>
<
Tabs
className=
"w-fit max-w-full min-w-0"
selectedKey=
{
activeFilter
}
variant=
"primary"
onSelectionChange=
{
(
key
)
=>
{
setActiveFilter
(
String
(
key
));
setPage
(
0
);
}
}
>
<
Tabs
className=
"w-fit max-w-full min-w-0"
selectedKey=
{
activeFilter
}
variant=
"primary"
onSelectionChange=
{
(
key
)
=>
{
setActiveFilter
(
String
(
key
));
setPage
(
0
);
}
}
>
...
...
src/pages/project_manager_xy/ModelBatchData.jsx
View file @
c0151ca8
...
@@ -39,6 +39,7 @@ import {
...
@@ -39,6 +39,7 @@ import {
loadModelBatch
,
loadModelBatch
,
resolveForwardRowStatus
,
resolveForwardRowStatus
,
saveForwardRecord
,
saveForwardRecord
,
dispatchForwardStateNotification
,
updateBatchHiddenRecords
,
updateBatchHiddenRecords
,
}
from
'./modelMonitorService'
;
}
from
'./modelMonitorService'
;
...
@@ -60,6 +61,7 @@ export default function ModelBatchData() {
...
@@ -60,6 +61,7 @@ export default function ModelBatchData() {
const
[
saving
,
setSaving
]
=
useState
(
false
);
const
[
saving
,
setSaving
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
notice
,
setNotice
]
=
useState
(
''
);
const
[
notice
,
setNotice
]
=
useState
(
''
);
const
[
notificationWarning
,
setNotificationWarning
]
=
useState
(
''
);
const
[
isHiddenDialogOpen
,
setIsHiddenDialogOpen
]
=
useState
(
false
);
const
[
isHiddenDialogOpen
,
setIsHiddenDialogOpen
]
=
useState
(
false
);
const
[
isForwardDialogOpen
,
setIsForwardDialogOpen
]
=
useState
(
false
);
const
[
isForwardDialogOpen
,
setIsForwardDialogOpen
]
=
useState
(
false
);
const
[
forwardForm
,
setForwardForm
]
=
useState
(
createEmptyForwardDraft
);
const
[
forwardForm
,
setForwardForm
]
=
useState
(
createEmptyForwardDraft
);
...
@@ -209,6 +211,8 @@ export default function ModelBatchData() {
...
@@ -209,6 +211,8 @@ export default function ModelBatchData() {
},
userInfo
);
},
userInfo
);
const
deliverRecords
=
selectedRecords
.
filter
((
item
)
=>
deliverIds
.
includes
(
item
.
recordName
||
item
.
id
));
const
deliverRecords
=
selectedRecords
.
filter
((
item
)
=>
deliverIds
.
includes
(
item
.
recordName
||
item
.
id
));
await
ensureForwardRecordItems
(
record
,
deliverRecords
,
userInfo
);
await
ensureForwardRecordItems
(
record
,
deliverRecords
,
userInfo
);
const
notificationResult
=
await
dispatchForwardStateNotification
(
'requested'
,
record
,
userInfo
);
setNotificationWarning
(
notificationResult
.
warning
);
await
refresh
();
await
refresh
();
setSelectedRecordIds
([]);
setSelectedRecordIds
([]);
setIsForwardDialogOpen
(
false
);
setIsForwardDialogOpen
(
false
);
...
@@ -227,6 +231,7 @@ export default function ModelBatchData() {
...
@@ -227,6 +231,7 @@ export default function ModelBatchData() {
{
loading
?
<
NoticeBanner
>
正在加载批次数据和转发状态...
</
NoticeBanner
>
:
null
}
{
loading
?
<
NoticeBanner
>
正在加载批次数据和转发状态...
</
NoticeBanner
>
:
null
}
{
error
?
<
NoticeBanner
tone=
"error"
>
{
error
}
</
NoticeBanner
>
:
null
}
{
error
?
<
NoticeBanner
tone=
"error"
>
{
error
}
</
NoticeBanner
>
:
null
}
{
!
error
&&
notice
?
<
NoticeBanner
tone=
"success"
>
{
notice
}
</
NoticeBanner
>
:
null
}
{
!
error
&&
notice
?
<
NoticeBanner
tone=
"success"
>
{
notice
}
</
NoticeBanner
>
:
null
}
{
notificationWarning
?
<
NoticeBanner
>
{
notificationWarning
}
</
NoticeBanner
>
:
null
}
{
batch
?.
syncStatus
===
'partial'
||
batch
?.
syncStatus
===
'failed'
?
<
NoticeBanner
tone=
"error"
>
最近一次同步未完整成功:
{
batch
.
errorMessage
||
'该批次不可重试,请联系管理员排查。'
}
</
NoticeBanner
>
:
null
}
{
batch
?.
syncStatus
===
'partial'
||
batch
?.
syncStatus
===
'failed'
?
<
NoticeBanner
tone=
"error"
>
最近一次同步未完整成功:
{
batch
.
errorMessage
||
'该批次不可重试,请联系管理员排查。'
}
</
NoticeBanner
>
:
null
}
{
!
loading
&&
batch
?
(
{
!
loading
&&
batch
?
(
...
...
src/pages/project_manager_xy/forwardNotificationService.js
0 → 100644
View file @
c0151ca8
import
{
createNotice
,
executeAction
,
loadUser
,
sendNotice
,
}
from
'@/services/DataService'
;
import
{
APP_BASE_PATH
}
from
'@/constants/app'
;
export
const
FORWARD_NOTIFICATION_TASK_FIELDS
=
{
recipients
:
'oa_recipient_task_ids'
,
firstApprover
:
'oa_first_approver_task_id'
,
finalApprover
:
'oa_final_approver_task_id'
,
};
const
FORWARD_TARGET_PATH
=
`
${
APP_BASE_PATH
}
/models/forwards`
;
function
uniqueIds
(
values
=
[])
{
return
[...
new
Set
(
values
.
filter
(
Boolean
).
map
(
String
))];
}
function
forwardTargetUrl
()
{
return
`
${
window
.
location
.
origin
}${
FORWARD_TARGET_PATH
}
`
;
}
function
unwrapResponse
(
response
)
{
return
response
?.
data
||
response
?.
result
||
response
;
}
function
unpackOaResult
(
result
)
{
return
result
.
status
===
'fulfilled'
?
result
.
value
:
{
taskIds
:
[],
errors
:
[
result
.
reason
?.
message
||
'OA 待办处理失败'
]
};
}
function
notificationAudience
(
record
,
role
)
{
if
(
role
===
'recipients'
)
return
uniqueIds
(
record
.
recipientIds
);
if
(
role
===
'firstApprover'
)
return
uniqueIds
([
record
.
firstApprover
]);
if
(
role
===
'finalApprover'
)
return
uniqueIds
([
record
.
finalApprover
]);
if
(
role
===
'participants'
)
{
return
uniqueIds
([
...(
record
.
recipientIds
||
[]),
record
.
initiatorId
,
record
.
firstApprover
,
record
.
finalApprover
,
]);
}
return
[];
}
function
contentLink
(
prefix
)
{
return
`
${
prefix
}
,请打开:
${
forwardTargetUrl
()}
`
;
}
async
function
sendInternalNotice
({
title
,
content
,
actor
,
audience
,
})
{
const
to
=
uniqueIds
(
audience
);
if
(
!
to
.
length
)
return
null
;
const
notice
=
unwrapResponse
(
await
createNotice
({
title
,
content
:
contentLink
(
content
),
type
:
'internal'
,
category
:
'message'
,
from
:
actor
.
id
,
to
,
settings
:
{
url
:
FORWARD_TARGET_PATH
},
}));
if
(
!
notice
?.
name
)
throw
new
Error
(
'站内通知创建结果缺少 name'
);
await
sendNotice
(
notice
.
name
);
return
notice
;
}
async
function
loadOaUser
(
userId
)
{
const
user
=
unwrapResponse
(
await
loadUser
(
userId
));
if
(
!
user
)
throw
new
Error
(
`OA 接收人不存在:
${
userId
}
`
);
return
user
;
}
async
function
createOaProgress
({
title
,
actor
,
userId
,
})
{
const
receiver
=
await
loadOaUser
(
userId
);
const
result
=
await
executeAction
(
'oa_notice:createProgress'
,
{
title
,
classify
:
'流程待办'
,
contentType
:
'流程待办'
,
receiveUserId
:
receiver
.
_id
||
receiver
.
id
||
userId
,
receiveUserName
:
receiver
.
username
||
''
,
receiveDisplayName
:
receiver
.
display_name
||
receiver
.
displayName
||
receiver
.
username
||
''
,
sendUserId
:
actor
.
id
,
sendUserName
:
actor
.
username
||
''
,
sendDisplayName
:
actor
.
name
||
''
,
returnUrl
:
forwardTargetUrl
(),
});
const
taskId
=
unwrapResponse
(
result
)?.
taskId
;
if
(
!
taskId
)
throw
new
Error
(
`OA 待办创建结果缺少 taskId:
${
userId
}
`
);
return
taskId
;
}
async
function
completeOaTasks
(
taskIds
=
[])
{
const
results
=
await
Promise
.
allSettled
(
uniqueIds
(
taskIds
).
map
((
taskId
)
=>
executeAction
(
'oa_notice:updateProgressStatus'
,
{
taskId
})),
);
return
results
.
filter
((
result
)
=>
result
.
status
===
'rejected'
)
.
map
((
result
)
=>
result
.
reason
?.
message
||
result
.
reason
||
'OA 待办关闭失败'
);
}
async
function
createOaProgressList
(
context
,
userIds
)
{
const
targetIds
=
uniqueIds
(
userIds
);
if
(
!
targetIds
.
length
)
return
{
taskIds
:
[],
errors
:
[]
};
const
results
=
await
Promise
.
allSettled
(
targetIds
.
map
((
userId
)
=>
createOaProgress
({
...
context
,
userId
,
})));
const
taskIds
=
[];
const
errors
=
[];
results
.
forEach
((
result
,
index
)
=>
{
if
(
result
.
status
===
'fulfilled'
)
taskIds
.
push
(
result
.
value
);
else
errors
.
push
(
result
.
reason
?.
message
||
`OA 待办创建失败:
${
targetIds
[
index
]}
`
);
});
return
{
taskIds
,
errors
};
}
export
async
function
dispatchForwardNotification
(
event
,
record
,
user
)
{
const
actor
=
{
id
:
user
?.
_id
||
user
?.
id
||
user
?.
username
||
''
,
username
:
user
?.
username
||
''
,
name
:
user
?.
display_name
||
user
?.
displayName
||
user
?.
username
||
''
,
};
const
modelLabel
=
`
${
record
.
modelName
||
'模型'
}
/
${
record
.
batchName
||
'批次'
}
`
;
const
context
=
{
actor
};
const
errors
=
[];
const
taskUpdates
=
{};
if
(
event
===
'requested'
)
{
const
[
oaResult
,
noticeResult
]
=
await
Promise
.
allSettled
([
createOaProgressList
({
...
context
,
title
:
`模型监控数据填写:
${
modelLabel
}
`
,
},
record
.
recipientIds
),
sendInternalNotice
({
title
:
'模型监控数据填写'
,
content
:
`
${
user
?.
display_name
||
user
?.
username
||
'发起人'
}
已向您分发
$
{
modelLabel
}
的整改数据
`,
actor,
audience: record.recipientIds,
}),
]);
const { taskIds, errors: oaErrors } = unpackOaResult(oaResult);
if (noticeResult.status === 'rejected') errors.push(noticeResult.reason?.message || '站内通知发送失败');
if (noticeResult.status === 'fulfilled' && Array.isArray(noticeResult.value?.errors)) errors.push(...noticeResult.value.errors);
if (oaErrors) errors.push(...oaErrors);
if (taskIds) taskUpdates[FORWARD_NOTIFICATION_TASK_FIELDS.recipients] = taskIds;
return { taskUpdates, errors };
}
if (event === 'first_approval_pending') {
const closeErrors = await completeOaTasks(record.oaRecipientTaskIds);
errors.push(...closeErrors);
const [oaResult, noticeResult] = await Promise.allSettled([
createOaProgressList({
...context,
title: `
模型监控数据一级审批:
$
{
modelLabel
}
`,
}, [record.firstApprover]),
sendInternalNotice({
title: '模型监控数据待一级审批',
content: `
$
{
modelLabel
}
已完成填写,等待您一级审批
`,
actor,
audience: [record.firstApprover],
}),
]);
const { taskIds, errors: oaErrors } = unpackOaResult(oaResult);
if (noticeResult.status === 'rejected') errors.push(noticeResult.reason?.message || '站内通知发送失败');
if (oaErrors) errors.push(...oaErrors);
if (taskIds) taskUpdates[FORWARD_NOTIFICATION_TASK_FIELDS.firstApprover] = taskIds[0] || '';
return { taskUpdates, errors };
}
if (event === 'final_approval_pending') {
const closeErrors = await completeOaTasks([record.oaFirstApproverTaskId]);
errors.push(...closeErrors);
const [oaResult, noticeResult] = await Promise.allSettled([
createOaProgressList({
...context,
title: `
模型监控数据终审:
$
{
modelLabel
}
`,
}, [record.finalApprover]),
sendInternalNotice({
title: '模型监控数据待终审',
content: `
$
{
modelLabel
}
已通过一级审批,等待您终审
`,
actor,
audience: [record.finalApprover],
}),
]);
const { taskIds, errors: oaErrors } = unpackOaResult(oaResult);
if (noticeResult.status === 'rejected') errors.push(noticeResult.reason?.message || '站内通知发送失败');
if (oaErrors) errors.push(...oaErrors);
if (taskIds) taskUpdates[FORWARD_NOTIFICATION_TASK_FIELDS.finalApprover] = taskIds[0] || '';
return { taskUpdates, errors };
}
if (event === 'first_rejected') {
const closeErrors = await completeOaTasks([
...record.oaRecipientTaskIds,
record.oaFirstApproverTaskId,
]);
errors.push(...closeErrors);
const [oaResult, noticeResult] = await Promise.allSettled([
createOaProgressList({
...context,
title: `
模型监控数据重新填写:
$
{
modelLabel
}
`,
}, record.recipientIds),
sendInternalNotice({
title: '模型监控数据已驳回',
content: `
$
{
modelLabel
}
一级审批已驳回,请重新填写
`,
actor,
audience: uniqueIds([...record.recipientIds, record.initiatorId]),
}),
]);
const { taskIds, errors: oaErrors } = unpackOaResult(oaResult);
if (noticeResult.status === 'rejected') errors.push(noticeResult.reason?.message || '站内通知发送失败');
if (oaErrors) errors.push(...oaErrors);
if (taskIds) taskUpdates[FORWARD_NOTIFICATION_TASK_FIELDS.recipients] = taskIds;
return { taskUpdates, errors };
}
if (event === 'final_rejected') {
const closeErrors = await completeOaTasks([
...record.oaRecipientTaskIds,
record.oaFinalApproverTaskId,
]);
errors.push(...closeErrors);
const [oaResult, noticeResult] = await Promise.allSettled([
createOaProgressList({
...context,
title: `
模型监控数据重新填写:
$
{
modelLabel
}
`,
}, record.recipientIds),
sendInternalNotice({
title: '模型监控数据终审驳回',
content: `
$
{
modelLabel
}
终审已驳回,请重新填写
`,
actor,
audience: uniqueIds([...record.recipientIds, record.initiatorId]),
}),
]);
const { taskIds, errors: oaErrors } = unpackOaResult(oaResult);
if (noticeResult.status === 'rejected') errors.push(noticeResult.reason?.message || '站内通知发送失败');
if (oaErrors) errors.push(...oaErrors);
if (taskIds) taskUpdates[FORWARD_NOTIFICATION_TASK_FIELDS.recipients] = taskIds;
return { taskUpdates, errors };
}
if (event === 'final_approved') {
const [closeResult, noticeResult] = await Promise.allSettled([
completeOaTasks(record.oaFinalApproverTaskId),
sendInternalNotice({
title: '模型监控流程已结束',
content: `
$
{
modelLabel
}
已通过终审
`,
actor,
audience: notificationAudience(record, 'participants'),
}),
]);
if (closeResult.status === 'fulfilled') errors.push(...closeResult.value);
else errors.push(closeResult.reason?.message || 'OA 待办关闭失败');
if (noticeResult.status === 'rejected') errors.push(noticeResult.reason?.message || '站内通知发送失败');
return { taskUpdates: {}, errors };
}
throw new Error(`
未知的通知环节:
$
{
event
}
`);
}
export function formatForwardNotificationIssues(result) {
const errors = [...new Set(result?.errors || [])].filter(Boolean);
return errors.length ? `
业务状态已更新,但通知处理存在异常:
$
{
errors
.
join
(
';'
)}
` : '';
}
src/pages/project_manager_xy/model-monitor.business-flow.md
View file @
c0151ca8
...
@@ -132,7 +132,8 @@ MySQL 联调数据可通过 `scripts/mysql/model-monitor-source-fixtures.sql`
...
@@ -132,7 +132,8 @@ MySQL 联调数据可通过 `scripts/mysql/model-monitor-source-fixtures.sql`
3.
填写接收人、转发说明、一级审批人和终审人。
3.
填写接收人、转发说明、一级审批人和终审人。
4.
创建
`model_monitor_forward_record`
,并为实际下发记录创建
`model_monitor_forward_item`
;发起时不写入尚未填写的是否属实、整改方案和预计整改完成时间字段。
4.
创建
`model_monitor_forward_record`
,并为实际下发记录创建
`model_monitor_forward_item`
;发起时不写入尚未填写的是否属实、整改方案和预计整改完成时间字段。
5.
初始化
`editor_status = editing`
、
`approval_status = pending`
、
`final_approval_status = pending`
。
5.
初始化
`editor_status = editing`
、
`approval_status = pending`
、
`final_approval_status = pending`
。
6.
未结束流程中的
`excluded_record_ids`
在来源批次页暂时隐藏,终审通过后恢复展示。
6.
发起成功后为每位接收人创建 OA 流程待办,并给接收人发送站内通知;接收人 OA 任务 ID 写入
`oa_recipient_task_ids`
。
7.
未结束流程中的
`excluded_record_ids`
在来源批次页暂时隐藏,终审通过后恢复展示。
### 4.8 填写与两级审批
### 4.8 填写与两级审批
...
@@ -143,8 +144,11 @@ MySQL 联调数据可通过 `scripts/mysql/model-monitor-source-fixtures.sql`
...
@@ -143,8 +144,11 @@ MySQL 联调数据可通过 `scripts/mysql/model-monitor-source-fixtures.sql`
5.
一级审批人通过或驳回;只有一级通过后终审人才可操作。
5.
一级审批人通过或驳回;只有一级通过后终审人才可操作。
6.
任一审批驳回后,填写状态恢复为
`editing`
,清空上次完成人和完成时间,接收人可修改原回填内容。
6.
任一审批驳回后,填写状态恢复为
`editing`
,清空上次完成人和完成时间,接收人可修改原回填内容。
7.
接收人重新完成填写时,
`approval_status`
和
`final_approval_status`
均重置为
`pending`
,流程从一级审批重新开始。
7.
接收人重新完成填写时,
`approval_status`
和
`final_approval_status`
均重置为
`pending`
,流程从一级审批重新开始。
8.
终审通过后状态为已最终审批,流程结束。
8.
终审通过后状态为已最终审批,流程结束;终审 OA 待办关闭,并给发起人、接收人和两级审批人发送结束通知。
9.
接收人填写、完成填写、一级审批和终审在前端同时执行按钮禁用与提交事件校验;用户身份或流程状态不满足时不发送更新请求,并提示状态已经变化或当前用户无权操作。
9.
接收人完成填写后关闭接收人 OA 待办,给一级审批人发送站内通知并创建其 OA 待办;一级通过后关闭一级 OA 待办,给终审人发送站内通知并创建其 OA 待办。
10.
一级或终审驳回后关闭当前环节 OA 待办,重新为接收人创建 OA 待办,并向接收人和发起人发送驳回站内通知。
11.
接收人填写、完成填写、一级审批和终审在前端同时执行按钮禁用与提交事件校验;用户身份或流程状态不满足时不发送更新请求,并提示状态已经变化或当前用户无权操作。
12.
通知和 OA 调用发生在业务状态成功更新之后;单个通知失败不会回滚填写、审批或转发状态,页面会显示“业务状态已更新,但通知处理存在异常”的提示,便于补发或排查。
## 5. 页面状态与异常处理
## 5. 页面状态与异常处理
...
...
src/pages/project_manager_xy/modelMonitorService.js
View file @
c0151ca8
...
@@ -17,6 +17,10 @@ import {
...
@@ -17,6 +17,10 @@ import {
listSourceModels
,
listSourceModels
,
loadSourceBatchData
,
loadSourceBatchData
,
}
from
'./ModelMonitorConnectorService'
;
}
from
'./ModelMonitorConnectorService'
;
import
{
dispatchForwardNotification
,
formatForwardNotificationIssues
,
}
from
'./forwardNotificationService'
;
export
const
MODEL_GROUP_SUBJECT
=
'model_monitor_group'
;
export
const
MODEL_GROUP_SUBJECT
=
'model_monitor_group'
;
export
const
MODEL_SUBJECT
=
'model_monitor_model'
;
export
const
MODEL_SUBJECT
=
'model_monitor_model'
;
...
@@ -172,6 +176,9 @@ const FIXED_SUBJECTS = [
...
@@ -172,6 +176,9 @@ const FIXED_SUBJECTS = [
{
name
:
'final_approver_name'
,
label
:
'终审人名称'
,
type
:
'text'
},
{
name
:
'final_approver_name'
,
label
:
'终审人名称'
,
type
:
'text'
},
{
name
:
'initiator_id'
,
label
:
'发起人 ID'
,
type
:
'text'
},
{
name
:
'initiator_id'
,
label
:
'发起人 ID'
,
type
:
'text'
},
{
name
:
'initiator_name'
,
label
:
'发起人名称'
,
type
:
'text'
},
{
name
:
'initiator_name'
,
label
:
'发起人名称'
,
type
:
'text'
},
{
name
:
'oa_recipient_task_ids'
,
label
:
'接收人 OA 任务 ID'
,
type
:
'text'
,
multiple
:
true
},
{
name
:
'oa_first_approver_task_id'
,
label
:
'一级审批人 OA 任务 ID'
,
type
:
'text'
},
{
name
:
'oa_final_approver_task_id'
,
label
:
'终审人 OA 任务 ID'
,
type
:
'text'
},
],
],
},
},
{
{
...
@@ -429,6 +436,11 @@ function normalizeForwardRecord(record = {}) {
...
@@ -429,6 +436,11 @@ function normalizeForwardRecord(record = {}) {
finalApproverName
:
metadata
.
final_approver_name
||
''
,
finalApproverName
:
metadata
.
final_approver_name
||
''
,
initiatorId
:
metadata
.
initiator_id
||
''
,
initiatorId
:
metadata
.
initiator_id
||
''
,
initiatorName
:
metadata
.
initiator_name
||
''
,
initiatorName
:
metadata
.
initiator_name
||
''
,
oaRecipientTaskIds
:
Array
.
isArray
(
metadata
.
oa_recipient_task_ids
)
?
metadata
.
oa_recipient_task_ids
:
[],
oaFirstApproverTaskId
:
metadata
.
oa_first_approver_task_id
||
''
,
oaFinalApproverTaskId
:
metadata
.
oa_final_approver_task_id
||
''
,
createdAt
:
record
.
created_at
||
''
,
createdAt
:
record
.
created_at
||
''
,
updatedAt
:
record
.
updated_at
||
''
,
updatedAt
:
record
.
updated_at
||
''
,
};
};
...
@@ -1141,6 +1153,9 @@ function createForwardPayload(values, user) {
...
@@ -1141,6 +1153,9 @@ function createForwardPayload(values, user) {
final_approver_name
:
values
.
finalApproverName
||
''
,
final_approver_name
:
values
.
finalApproverName
||
''
,
initiator_id
:
values
.
initiatorId
||
actor
.
id
,
initiator_id
:
values
.
initiatorId
||
actor
.
id
,
initiator_name
:
values
.
initiatorName
||
actor
.
name
,
initiator_name
:
values
.
initiatorName
||
actor
.
name
,
oa_recipient_task_ids
:
values
.
oaRecipientTaskIds
||
[],
oa_first_approver_task_id
:
values
.
oaFirstApproverTaskId
||
''
,
oa_final_approver_task_id
:
values
.
oaFinalApproverTaskId
||
''
,
},
},
};
};
}
}
...
@@ -1157,6 +1172,29 @@ export async function saveForwardRecord(values, user) {
...
@@ -1157,6 +1172,29 @@ export async function saveForwardRecord(values, user) {
return
normalizeForwardRecord
(
unwrapRecord
(
await
createRecord
(
createForwardPayload
(
values
,
user
))));
return
normalizeForwardRecord
(
unwrapRecord
(
await
createRecord
(
createForwardPayload
(
values
,
user
))));
}
}
async
function
saveForwardNotificationTasks
(
recordName
,
taskUpdates
=
{})
{
if
(
!
Object
.
keys
(
taskUpdates
).
length
)
return
normalizeForwardRecord
(
await
loadRecord
(
recordName
));
const
current
=
normalizeForwardRecord
(
await
loadRecord
(
recordName
));
return
normalizeForwardRecord
(
unwrapRecord
(
await
updateRecord
(
recordName
,
createForwardPayload
({
...
current
,
...
taskUpdates
,
}))));
}
async
function
dispatchForwardStateNotification
(
event
,
record
,
user
)
{
const
result
=
await
dispatchForwardNotification
(
event
,
record
,
user
);
let
updatedRecord
=
record
;
try
{
updatedRecord
=
await
saveForwardNotificationTasks
(
record
.
recordName
,
result
.
taskUpdates
);
}
catch
(
saveError
)
{
result
.
errors
.
push
(
saveError
?.
message
||
'OA 任务标识保存失败'
);
}
return
{
record
:
updatedRecord
,
warning
:
formatForwardNotificationIssues
(
result
),
};
}
function
createForwardItemPayload
(
values
,
user
)
{
function
createForwardItemPayload
(
values
,
user
)
{
const
actor
=
currentUserValues
(
user
);
const
actor
=
currentUserValues
(
user
);
const
hasFill
=
Boolean
(
values
.
truthStatus
||
values
.
rectificationPlan
||
values
.
expectedRectificationCompletedAt
);
const
hasFill
=
Boolean
(
values
.
truthStatus
||
values
.
rectificationPlan
||
values
.
expectedRectificationCompletedAt
);
...
@@ -1309,3 +1347,8 @@ export async function updateForwardRecordStatus(recordName, nextMetadata = {}) {
...
@@ -1309,3 +1347,8 @@ export async function updateForwardRecordStatus(recordName, nextMetadata = {}) {
});
});
return
normalizeForwardRecord
(
unwrapRecord
(
await
updateRecord
(
recordName
,
payload
)));
return
normalizeForwardRecord
(
unwrapRecord
(
await
updateRecord
(
recordName
,
payload
)));
}
}
export
{
dispatchForwardStateNotification
,
formatForwardNotificationIssues
,
};
src/services/record-api.md
View file @
c0151ca8
...
@@ -285,3 +285,62 @@ Navbar 读取真实未读数;0 不显示 Badge,1-99 显示原值,100 及
...
@@ -285,3 +285,62 @@ Navbar 读取真实未读数;0 不显示 Badge,1-99 显示原值,100 及
```
```
`:action`
与
`:methodName`
必须来自通知返回的动态
`actions`
配置,前端不得写死流程动作。
`:action`
与
`:methodName`
必须来自通知返回的动态
`actions`
配置,前端不得写死流程动作。
## 8. 模型监控流程通知
模型监控转发流程复用平台通知与已配置的
`oa_notice`
Action。业务 Record 状态更新成功后才调用本节接口;接口异常不回滚业务状态,但页面必须展示通知异常摘要。
### 8.1 创建站内通知
`POST /notice`
```
json
{
"title"
:
"模型监控数据填写"
,
"content"
:
"接收人已收到转发,请打开:https://example.com/model_analysis/models/forwards"
,
"type"
:
"internal"
,
"category"
:
"message"
,
"from"
:
"发起人 _id"
,
"to"
:
[
"接收人 _id"
],
"settings"
:
{
"url"
:
"/model_analysis/models/forwards"
}
}
```
### 8.2 发送站内通知
`PUT /notice/{name}/send`
请求路径中的
`name`
必须来自
`POST /notice`
返回值。
`internal`
通知不可重复发送。
### 8.3 创建 OA 待办
`POST /action/execute/oa_notice:createProgress`
```
json
{
"title"
:
"模型监控数据填写:模型 / 批次"
,
"classify"
:
"流程待办"
,
"contentType"
:
"流程待办"
,
"receiveUserId"
:
"接收人 _id"
,
"receiveUserName"
:
"接收人账号"
,
"receiveDisplayName"
:
"接收人显示名"
,
"sendUserId"
:
"发起人 _id"
,
"sendUserName"
:
"发起人账号"
,
"sendDisplayName"
:
"发起人显示名"
,
"returnUrl"
:
"https://example.com/model_analysis/models/forwards"
}
```
成功结果必须包含
`taskId`
,并按环节保存到
`model_monitor_forward_record.metadata.oa_recipient_task_ids`
、
`oa_first_approver_task_id`
或
`oa_final_approver_task_id`
。
### 8.4 关闭 OA 待办
`POST /action/execute/oa_notice:updateProgressStatus`
```
json
{
"taskId"
:
"待关闭的 OA 任务 ID"
}
```
src/services/subjectsDefinition.md
View file @
c0151ca8
...
@@ -150,7 +150,7 @@ type Record = {
...
@@ -150,7 +150,7 @@ type Record = {
### `model_monitor_forward_record`:模型转发审批记录
### `model_monitor_forward_record`:模型转发审批记录
-
类型:
`normal`
-
类型:
`normal`
-
字段数量:2
3
-
字段数量:2
6
-
说明:模型转发审批记录自动配置
-
说明:模型转发审批记录自动配置
| 字段标签 | 字段别名 | 字段类型 | 多值 | 必填 | 主键 | 关联/配置 |
| 字段标签 | 字段别名 | 字段类型 | 多值 | 必填 | 主键 | 关联/配置 |
...
@@ -178,6 +178,9 @@ type Record = {
...
@@ -178,6 +178,9 @@ type Record = {
| 终审人名称 |
`final_approver_name`
| text | 否 | 否 | 否 | |
| 终审人名称 |
`final_approver_name`
| text | 否 | 否 | 否 | |
| 发起人 ID |
`initiator_id`
| text | 否 | 否 | 否 | |
| 发起人 ID |
`initiator_id`
| text | 否 | 否 | 否 | |
| 发起人名称 |
`initiator_name`
| text | 否 | 否 | 否 | |
| 发起人名称 |
`initiator_name`
| text | 否 | 否 | 否 | |
| 接收人 OA 任务 ID |
`oa_recipient_task_ids`
| text | 是 | 否 | 否 | |
| 一级审批人 OA 任务 ID |
`oa_first_approver_task_id`
| text | 否 | 否 | 否 | |
| 终审人 OA 任务 ID |
`oa_final_approver_task_id`
| text | 否 | 否 | 否 | |
### `model_monitor_forward_item`:模型转发回填明细
### `model_monitor_forward_item`:模型转发回填明细
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment