Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
P
problem_rectification
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
problem_rectification
Commits
004e66ec
Commit
004e66ec
authored
Aug 03, 2026
by
David Yang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 支持部门级延期申请
parent
3917767f
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
611 additions
and
222 deletions
+611
-222
index.jsx
src/components/record-workbench/index.jsx
+95
-2
index.jsx
src/pages/problem_rectification/flow-table/index.jsx
+105
-2
index.jsx
src/pages/problem_rectification/list/index.jsx
+248
-151
problem-rectification.business-flow.md
...blem_rectification/problem-rectification.business-flow.md
+29
-27
problemRectificationListService.js
.../problem_rectification/problemRectificationListService.js
+118
-30
subjectsDefinition.md
src/services/subjectsDefinition.md
+16
-10
No files found.
src/components/record-workbench/index.jsx
View file @
004e66ec
...
@@ -466,6 +466,7 @@ export default function RecordWorkbench({
...
@@ -466,6 +466,7 @@ export default function RecordWorkbench({
states
=
{},
states
=
{},
renderCell
,
renderCell
,
renderExpandedRow
,
renderExpandedRow
,
getExpandedRecordRows
,
isRowExpandable
,
isRowExpandable
,
getRecordId
=
defaultGetRecordId
,
getRecordId
=
defaultGetRecordId
,
getRecordLabel
=
defaultGetRecordLabel
,
getRecordLabel
=
defaultGetRecordLabel
,
...
@@ -534,7 +535,8 @@ export default function RecordWorkbench({
...
@@ -534,7 +535,8 @@ export default function RecordWorkbench({
);
);
const
hasColumnGroups
=
columnGroupMeta
.
groups
.
length
>
0
;
const
hasColumnGroups
=
columnGroupMeta
.
groups
.
length
>
0
;
const
showColumnGroupHeader
=
columnGroupSegments
.
some
((
segment
)
=>
segment
.
group
);
const
showColumnGroupHeader
=
columnGroupSegments
.
some
((
segment
)
=>
segment
.
group
);
const
hasExpandableRows
=
typeof
renderExpandedRow
===
'function'
;
const
hasExpandedRecordRows
=
typeof
getExpandedRecordRows
===
'function'
;
const
hasExpandableRows
=
hasExpandedRecordRows
||
typeof
renderExpandedRow
===
'function'
;
const
hasExpandColumn
=
hasExpandableRows
&&
showExpandColumn
;
const
hasExpandColumn
=
hasExpandableRows
&&
showExpandColumn
;
const
tableColSpan
=
const
tableColSpan
=
stickyColumns
.
orderedColumns
.
length
+
stickyColumns
.
orderedColumns
.
length
+
...
@@ -997,6 +999,21 @@ export default function RecordWorkbench({
...
@@ -997,6 +999,21 @@ export default function RecordWorkbench({
openDetail
,
openDetail
,
};
};
const
visibleRowActions
=
rowActions
.
filter
((
action
)
=>
!
isActionHidden
(
action
,
rowContext
));
const
visibleRowActions
=
rowActions
.
filter
((
action
)
=>
!
isActionHidden
(
action
,
rowContext
));
const
expandedRecordRows
=
(()
=>
{
if
(
!
expanded
||
!
expandable
||
!
hasExpandedRecordRows
)
{
return
EMPTY_ARRAY
;
}
const
nextExpandedRecordRows
=
getExpandedRecordRows
({
record
,
rowContext
,
columns
:
stickyColumns
.
orderedColumns
,
});
return
Array
.
isArray
(
nextExpandedRecordRows
)
?
nextExpandedRecordRows
:
EMPTY_ARRAY
;
})();
return
(
return
(
<
Fragment
key=
{
recordId
}
>
<
Fragment
key=
{
recordId
}
>
...
@@ -1101,7 +1118,83 @@ export default function RecordWorkbench({
...
@@ -1101,7 +1118,83 @@ export default function RecordWorkbench({
</
TableCell
>
</
TableCell
>
)
:
null
}
)
:
null
}
</
TableRow
>
</
TableRow
>
{
expanded
&&
expandable
?
(
{
expanded
&&
expandable
&&
hasExpandedRecordRows
?
(
expandedRecordRows
.
map
((
expandedRecord
,
expandedIndex
)
=>
{
const
expandedRecordId
=
expandedRecord
?.
key
||
expandedRecord
?.
id
||
expandedRecord
?.
_id
||
`${recordId}-expanded-${expandedIndex}`
;
return
(
<
TableRow
key=
{
expandedRecordId
}
className=
"align-top bg-rose-50/35"
>
{
selectable
?
(
<
TableCell
className=
"align-top sticky left-0 z-30 bg-rose-50/95 shadow-[10px_0_20px_rgba(15,23,42,0.05)]"
style=
{
{
width
:
SELECTION_COLUMN_WIDTH
,
minWidth
:
SELECTION_COLUMN_WIDTH
}
}
/>
)
:
null
}
{
hasExpandColumn
?
(
<
TableCell
className=
"w-12 align-top bg-rose-50/70"
style=
{
{
width
:
48
,
minWidth
:
48
}
}
/>
)
:
null
}
{
stickyColumns
.
orderedColumns
.
map
((
column
)
=>
{
const
stickyMeta
=
stickyColumns
.
stickyMeta
[
column
.
field
];
return
(
<
TableCell
key=
{
column
.
field
}
style=
{
{
width
:
column
.
width
||
180
,
minWidth
:
Math
.
min
(
Math
.
max
(
column
.
width
||
180
,
150
),
360
),
left
:
stickyMeta
?.
side
===
'left'
?
`${stickyMeta.offset}px`
:
undefined
,
right
:
stickyMeta
?.
side
===
'right'
?
`${stickyMeta.offset}px`
:
undefined
,
}
}
className=
{
clsx
(
'align-top'
,
getStickyCellClass
(
stickyMeta
),
)
}
>
{
(()
=>
{
if
(
column
.
render
)
{
return
column
.
render
(
expandedRecord
,
column
);
}
if
(
renderCell
)
{
const
customNode
=
renderCell
({
record
:
expandedRecord
,
column
,
expandable
:
false
,
expanded
:
false
,
toggleExpanded
:
()
=>
{},
parentRecord
:
record
,
isExpandedRecord
:
true
,
});
if
(
customNode
!==
undefined
)
{
return
customNode
;
}
}
return
defaultRenderCell
(
expandedRecord
,
column
);
})()
}
</
TableCell
>
);
})
}
{
visibleRowActions
.
length
>
0
?
(
<
TableCell
className=
"align-top sticky right-0 z-30 bg-rose-50/95 shadow-[-10px_0_20px_rgba(15,23,42,0.05)]"
style=
{
{
width
:
actionColumnWidth
,
minWidth
:
actionColumnWidth
}
}
/>
)
:
null
}
</
TableRow
>
);
})
)
:
null
}
{
expanded
&&
expandable
&&
!
hasExpandedRecordRows
?
(
<
TableRow
key=
{
`${recordId}-expanded`
}
className=
"bg-zinc-50/70"
>
<
TableRow
key=
{
`${recordId}-expanded`
}
className=
"bg-zinc-50/70"
>
<
TableCell
colSpan=
{
tableColSpan
}
className=
"px-6 py-4"
>
<
TableCell
colSpan=
{
tableColSpan
}
className=
"px-6 py-4"
>
{
renderExpandedRow
({
record
,
rowContext
})
}
{
renderExpandedRow
({
record
,
rowContext
})
}
...
...
src/pages/problem_rectification/flow-table/index.jsx
View file @
004e66ec
...
@@ -34,6 +34,7 @@ import {
...
@@ -34,6 +34,7 @@ import {
AUDIT_STATUS_PENDING
,
AUDIT_STATUS_PENDING
,
AUDIT_STATUS_REJECTED
,
AUDIT_STATUS_REJECTED
,
DELAY_REQUEST_FIELD
,
DELAY_REQUEST_FIELD
,
DELAY_REQUEST_ITEMS_FIELD
,
EDITOR_STATUS_COMPLETED
,
EDITOR_STATUS_COMPLETED
,
EDITOR_STATUS_PENDING
,
EDITOR_STATUS_PENDING
,
FLOW_MODE_BATCH
,
FLOW_MODE_BATCH
,
...
@@ -41,9 +42,11 @@ import {
...
@@ -41,9 +42,11 @@ import {
FLOW_MODE_DELAY_DEPARTMENT
,
FLOW_MODE_DELAY_DEPARTMENT
,
FLOW_MODE_DEPARTMENT
,
FLOW_MODE_DEPARTMENT
,
PROBLEM_RECTIFICATION_SUBJECT
,
PROBLEM_RECTIFICATION_SUBJECT
,
buildDepartmentParentMetadataPatch
,
buildDepartmentParentBatchUpdateData
,
buildDepartmentParentBatchUpdateData
,
getProblemRectificationFlowTableUrl
,
getProblemRectificationFlowTableUrl
,
normalizeDelayRequest
,
normalizeDelayRequest
,
normalizeDelayRequestItems
,
normalizeDepartmentRectificationItems
,
normalizeDepartmentRectificationItems
,
}
from
'../problemRectificationListService'
;
}
from
'../problemRectificationListService'
;
...
@@ -54,6 +57,7 @@ const FLOW_MANAGED_PARENT_FIELDS = [
...
@@ -54,6 +57,7 @@ const FLOW_MANAGED_PARENT_FIELDS = [
'final_audit_status'
,
'final_audit_status'
,
'department_rectification_items'
,
'department_rectification_items'
,
DELAY_REQUEST_FIELD
,
DELAY_REQUEST_FIELD
,
DELAY_REQUEST_ITEMS_FIELD
,
];
];
const
DEPARTMENT_MANAGED_PARENT_FIELDS
=
[
const
DEPARTMENT_MANAGED_PARENT_FIELDS
=
[
...
FLOW_MANAGED_PARENT_FIELDS
,
...
FLOW_MANAGED_PARENT_FIELDS
,
...
@@ -248,12 +252,50 @@ function buildBatchRows(records = [], metadata = {}) {
...
@@ -248,12 +252,50 @@ function buildBatchRows(records = [], metadata = {}) {
});
});
}
}
function
buildDelayRows
(
records
=
[],
metadata
=
{})
{
function
buildDelayRows
(
records
=
[],
metadata
=
{}
,
flowMode
=
FLOW_MODE_DELAY
)
{
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
.
flatMap
((
record
)
=>
{
return
records
.
flatMap
((
record
)
=>
{
const
recordMetadata
=
record
.
metadata
||
{};
const
recordMetadata
=
record
.
metadata
||
{};
if
(
flowMode
===
FLOW_MODE_DELAY_DEPARTMENT
)
{
return
normalizeDelayRequestItems
(
recordMetadata
[
DELAY_REQUEST_ITEMS_FIELD
])
.
filter
(
(
request
)
=>
scopedItemIds
.
size
===
0
||
scopedItemIds
.
has
(
request
.
item_id
)
||
scopedItemIds
.
has
(
request
.
source_department_item_id
),
)
.
map
((
request
)
=>
({
key
:
`
${
record
.
_id
||
record
.
name
}
-delay-
${
request
.
item_id
}
`
,
flowMode
:
FLOW_MODE_DELAY_DEPARTMENT
,
record
,
item
:
request
,
itemId
:
request
.
item_id
,
sourceDepartmentItemId
:
request
.
source_department_item_id
||
request
.
item_id
,
recordId
:
record
.
_id
||
record
.
name
,
title
:
record
.
title
||
recordMetadata
.
project_name
||
'-'
,
projectName
:
recordMetadata
.
project_name
||
'-'
,
problemBrief
:
recordMetadata
.
problem_brief
||
''
,
relatedIssueDomain
:
toArray
(
recordMetadata
.
related_issue_domain
).
join
(
'、'
),
department
:
request
.
responsible_department
||
'-'
,
editor
:
request
.
editor
||
editor
,
auditor
:
request
.
auditor
||
auditor
,
sender
:
request
.
sender
||
toId
(
metadata
.
sender
),
originalDueDate
:
request
.
original_due_date
||
''
,
requestedDueDate
:
request
.
requested_due_date
||
''
,
delayReason
:
request
.
delay_reason
||
''
,
editorStatus
:
request
.
editor_status
||
EDITOR_STATUS_PENDING
,
auditStatus
:
request
.
auditor_status
||
AUDIT_STATUS_PENDING
,
finalAuditStatus
:
request
.
sender_status
||
AUDIT_STATUS_PENDING
,
progressIdEditor
:
request
.
progress_id_editor
||
''
,
progressIdAuditor
:
request
.
progress_id_auditor
||
''
,
progressIdSender
:
request
.
progress_id_sender
||
''
,
isLocked
:
recordMetadata
.
is_locked
===
true
,
}));
}
const
request
=
normalizeDelayRequest
(
recordMetadata
[
DELAY_REQUEST_FIELD
]);
const
request
=
normalizeDelayRequest
(
recordMetadata
[
DELAY_REQUEST_FIELD
]);
if
(
!
request
)
{
if
(
!
request
)
{
return
[];
return
[];
...
@@ -308,7 +350,7 @@ export default function ProblemRectificationFlowTablePage() {
...
@@ -308,7 +350,7 @@ export default function ProblemRectificationFlowTablePage() {
const
rows
=
useMemo
(
const
rows
=
useMemo
(
()
=>
{
()
=>
{
if
(
flowMode
===
FLOW_MODE_DELAY
||
flowMode
===
FLOW_MODE_DELAY_DEPARTMENT
)
{
if
(
flowMode
===
FLOW_MODE_DELAY
||
flowMode
===
FLOW_MODE_DELAY_DEPARTMENT
)
{
return
buildDelayRows
(
records
,
metadata
);
return
buildDelayRows
(
records
,
metadata
,
flowMode
);
}
}
return
flowMode
===
FLOW_MODE_DEPARTMENT
return
flowMode
===
FLOW_MODE_DEPARTMENT
?
buildDepartmentRows
(
records
)
?
buildDepartmentRows
(
records
)
...
@@ -453,6 +495,67 @@ export default function ProblemRectificationFlowTablePage() {
...
@@ -453,6 +495,67 @@ export default function ProblemRectificationFlowTablePage() {
throw
new
Error
(
'当前问题整改记录已锁定,不允许继续修改或审批。'
);
throw
new
Error
(
'当前问题整改记录已锁定,不允许继续修改或审批。'
);
}
}
if
(
row
.
flowMode
===
FLOW_MODE_DELAY_DEPARTMENT
)
{
const
currentRequests
=
normalizeDelayRequestItems
(
latestRecord
?.
metadata
?.[
DELAY_REQUEST_ITEMS_FIELD
],
);
const
currentRequest
=
currentRequests
.
find
(
(
request
)
=>
request
.
item_id
===
row
.
itemId
||
request
.
source_department_item_id
===
row
.
sourceDepartmentItemId
,
);
if
(
!
currentRequest
)
{
throw
new
Error
(
'当前部门延期申请不存在,请刷新后重试。'
);
}
const
currentFlowIds
=
[
messageRecord
?.
name
,
messageRecord
?.
_id
,
messageId
].
filter
(
Boolean
);
if
(
currentRequest
.
flow_record_id
&&
!
currentFlowIds
.
includes
(
currentRequest
.
flow_record_id
)
)
{
throw
new
Error
(
'当前部门延期申请已关联其他流程,请刷新后重试。'
);
}
const
nextRequests
=
currentRequests
.
map
((
request
)
=>
request
.
item_id
===
currentRequest
.
item_id
?
{
...
request
,
...
requestPatch
,
}
:
request
,
);
const
metadataPatch
=
{
[
DELAY_REQUEST_ITEMS_FIELD
]:
nextRequests
,
};
if
(
recordPatch
.
the_time_of_rectification
)
{
const
currentDepartmentItems
=
normalizeDepartmentRectificationItems
(
latestRecord
);
const
sourceDepartmentItemId
=
row
.
sourceDepartmentItemId
||
row
.
itemId
;
if
(
currentDepartmentItems
.
length
>
0
)
{
const
nextDepartmentItems
=
currentDepartmentItems
.
map
((
item
)
=>
item
.
item_id
===
sourceDepartmentItemId
?
{
...
item
,
the_time_of_rectification
:
recordPatch
.
the_time_of_rectification
,
updated_by
:
currentUserId
,
updated_at
:
dayjs
().
toISOString
(),
}
:
item
,
);
Object
.
assign
(
metadataPatch
,
buildDepartmentParentMetadataPatch
(
nextDepartmentItems
));
}
else
{
metadataPatch
.
the_time_of_rectification
=
recordPatch
.
the_time_of_rectification
;
}
}
await
updateRecord
(
row
.
recordId
,
{
metadata
:
metadataPatch
,
});
return
;
}
const
currentRequest
=
normalizeDelayRequest
(
const
currentRequest
=
normalizeDelayRequest
(
latestRecord
?.
metadata
?.[
DELAY_REQUEST_FIELD
],
latestRecord
?.
metadata
?.[
DELAY_REQUEST_FIELD
],
);
);
...
...
src/pages/problem_rectification/list/index.jsx
View file @
004e66ec
...
@@ -34,7 +34,7 @@ import {
...
@@ -34,7 +34,7 @@ import {
}
from
'@/services/DataService'
;
}
from
'@/services/DataService'
;
import
*
as
Headless
from
'@headlessui/react'
;
import
*
as
Headless
from
'@headlessui/react'
;
import
{
ChevronRightIcon
}
from
'@heroicons/react/20/solid'
;
import
{
ChevronRightIcon
}
from
'@heroicons/react/20/solid'
;
import
{
LockClosedIcon
,
LockOpenIcon
}
from
'@heroicons/react/24/outline'
;
import
{
LockClosedIcon
,
LockOpenIcon
,
TrashIcon
}
from
'@heroicons/react/24/outline'
;
import
clsx
from
'clsx'
;
import
clsx
from
'clsx'
;
import
dayjs
from
'dayjs'
;
import
dayjs
from
'dayjs'
;
import
{
useCallback
,
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
useCallback
,
useEffect
,
useMemo
,
useState
}
from
'react'
;
...
@@ -50,13 +50,15 @@ import {
...
@@ -50,13 +50,15 @@ import {
PROBLEM_RECTIFICATION_SUBJECT
,
PROBLEM_RECTIFICATION_SUBJECT
,
AUDIT_STATUS_PENDING
,
AUDIT_STATUS_PENDING
,
DELAY_REQUEST_FIELD
,
DELAY_REQUEST_FIELD
,
DELAY_REQUEST_ITEMS_FIELD
,
DEPARTMENT_RECTIFICATION_FIELD
,
DEPARTMENT_RECTIFICATION_FIELD
,
EDITOR_STATUS_PENDING
,
EDITOR_STATUS_PENDING
,
FLOW_MODE_BATCH
,
FLOW_MODE_BATCH
,
FLOW_MODE_DELAY
,
FLOW_MODE_DELAY
_DEPARTMENT
,
FLOW_MODE_DEPARTMENT
,
FLOW_MODE_DEPARTMENT
,
PROBLEM_RECTIFICATION_PAGE_SIZE_OPTIONS
,
PROBLEM_RECTIFICATION_PAGE_SIZE_OPTIONS
,
buildDepartmentAssignmentsFromRecord
,
buildDepartmentAssignmentsFromRecord
,
buildDepartmentDelayRequestItems
,
buildDepartmentParentBatchUpdateData
,
buildDepartmentParentBatchUpdateData
,
buildDepartmentRectificationItems
,
buildDepartmentRectificationItems
,
buildGridViews
,
buildGridViews
,
...
@@ -70,6 +72,7 @@ import {
...
@@ -70,6 +72,7 @@ import {
loadProblemRectificationListResources
,
loadProblemRectificationListResources
,
loadProblemRectificationViewCountMap
,
loadProblemRectificationViewCountMap
,
normalizeDelayRequest
,
normalizeDelayRequest
,
normalizeDelayRequestItems
,
normalizeDepartmentRectificationItems
,
normalizeDepartmentRectificationItems
,
}
from
'../problemRectificationListService'
;
}
from
'../problemRectificationListService'
;
...
@@ -470,6 +473,17 @@ function FieldValuePreview({ column, record }) {
...
@@ -470,6 +473,17 @@ function FieldValuePreview({ column, record }) {
const value = getFieldValue(rawRecord, column.field);
const value = getFieldValue(rawRecord, column.field);
if (fieldName === 'title') {
if (fieldName === 'title') {
if (record.isDepartmentDetailRow) {
return (
<div className="flex flex-wrap items-center gap-2">
<span className="inline-flex rounded-full bg-rose-50 px-2.5 py-1 text-xs font-medium text-rose-700 ring-1 ring-rose-100">
明细
</span>
<span className="font-medium text-slate-800">{record.title || '部门整改明细'}</span>
</div>
);
}
return <div className="font-medium text-slate-900">{record.title || '(无标题)'}</div>;
return <div className="font-medium text-slate-900">{record.title || '(无标题)'}</div>;
}
}
...
@@ -559,13 +573,52 @@ function FieldValuePreview({ column, record }) {
...
@@ -559,13 +573,52 @@ function FieldValuePreview({ column, record }) {
);
);
}
}
function DepartmentDetailField({ label, children }) {
function buildUserDisplayValue(userId, userLabels = {}) {
return (
if (!userId) {
<div className="min-w-0">
return [];
<div className="text-xs font-medium text-slate-400">{label}</div>
}
<div className="mt-1.5 break-words text-sm text-slate-800">{children || '-'}</div>
</div>
return [
);
{
_id: userId,
display_name: userLabels[userId] || userId,
},
];
}
function buildDepartmentDetailRecord(parentRecord, item, userLabels = {}) {
const parentRaw = parentRecord.raw || parentRecord;
const parentMetadata = parentRaw.metadata || {};
const departmentName = item.responsible_department || '未指定责任部门';
const metadata = {
...parentMetadata,
responsible_department: [departmentName],
fill_in_the_people: buildUserDisplayValue(item.editor, userLabels),
review_people: buildUserDisplayValue(item.auditor, userLabels),
rectification_progress: item.rectification_progress || '',
rectification_situation: item.rectification_situation || '',
the_time_of_rectification: item.the_time_of_rectification || null,
rectification_proof_materials: item.rectification_proof_materials || null,
audit_status: item.audit_status || AUDIT_STATUS_PENDING,
final_audit_status: item.final_audit_status || AUDIT_STATUS_PENDING,
};
return {
...parentRecord,
id: `
$
{
parentRecord
.
id
||
parentRecord
.
name
||
parentRaw
.
_id
||
parentRaw
.
name
}
-
$
{
item
.
item_id
}
`,
key: `
$
{
parentRecord
.
id
||
parentRecord
.
name
||
parentRaw
.
_id
||
parentRaw
.
name
}
-
$
{
item
.
item_id
}
`,
title: `
$
{
departmentName
}
部门明细
`,
fillUsers: [userLabels[item.editor] || item.editor].filter(Boolean),
reviewUsers: [userLabels[item.auditor] || item.auditor].filter(Boolean),
departmentItems: [],
hasDepartmentItems: false,
isDepartmentDetailRow: true,
parentRecord,
raw: {
...parentRaw,
metadata,
},
};
}
}
export default function ProblemRectificationListPage() {
export default function ProblemRectificationListPage() {
...
@@ -598,8 +651,7 @@ export default function ProblemRectificationListPage() {
...
@@ -598,8 +651,7 @@ export default function ProblemRectificationListPage() {
const [fillerUsers, setFillerUsers] = useState([]);
const [fillerUsers, setFillerUsers] = useState([]);
const [auditorUsers, setAuditorUsers] = useState([]);
const [auditorUsers, setAuditorUsers] = useState([]);
const [departmentAssignments, setDepartmentAssignments] = useState([]);
const [departmentAssignments, setDepartmentAssignments] = useState([]);
const [delayEditorUsers, setDelayEditorUsers] = useState([]);
const [delayDepartmentAssignments, setDelayDepartmentAssignments] = useState([]);
const [delayAuditorUsers, setDelayAuditorUsers] = useState([]);
const [successMessage, setSuccessMessage] = useState('');
const [successMessage, setSuccessMessage] = useState('');
const [page, setPage] = useState(0);
const [page, setPage] = useState(0);
const [pageSize, setPageSize] = useState(DEFAULT_PROBLEM_RECTIFICATION_PAGE_SIZE);
const [pageSize, setPageSize] = useState(DEFAULT_PROBLEM_RECTIFICATION_PAGE_SIZE);
...
@@ -986,10 +1038,30 @@ export default function ProblemRectificationListPage() {
...
@@ -986,10 +1038,30 @@ export default function ProblemRectificationListPage() {
);
);
};
};
const updateDelayDepartmentAssignment = (itemId, patch) => {
setDelayDepartmentAssignments((current) =>
current.map((item) =>
item.item_id === itemId
? {
...item,
...patch,
}
: item,
),
);
};
const removeDepartmentAssignment = (itemId) => {
setDepartmentAssignments((current) => current.filter((item) => item.item_id !== itemId));
};
const removeDelayDepartmentAssignment = (itemId) => {
setDelayDepartmentAssignments((current) => current.filter((item) => item.item_id !== itemId));
};
const closeDelayPushModal = () => {
const closeDelayPushModal = () => {
setShowDelayPushModal(false);
setShowDelayPushModal(false);
setDelayEditorUsers([]);
setDelayDepartmentAssignments([]);
setDelayAuditorUsers([]);
};
};
const openDelayPushModal = () => {
const openDelayPushModal = () => {
...
@@ -1004,26 +1076,32 @@ export default function ProblemRectificationListPage() {
...
@@ -1004,26 +1076,32 @@ export default function ProblemRectificationListPage() {
return;
return;
}
}
if (record.delayRequest) {
if (record.delayRequest
|| record.delayRequestItems?.length > 0
) {
setError('当前问题已发起延期申请,不能重复推送。');
setError('当前问题已发起延期申请,不能重复推送。');
return;
return;
}
}
setError('');
setError('');
setDelayEditorUsers([]);
setDelayDepartmentAssignments(buildDepartmentAssignmentsFromRecord(record.raw || record));
setDelayAuditorUsers([]);
setShowDelayPushModal(true);
setShowDelayPushModal(true);
};
};
const handleDelayPush = async () => {
const handleDelayPush = async () => {
const editorProfile = delayEditorUsers[0];
const auditorProfile = delayAuditorUsers[0];
if (selectedRecords.length !== 1) {
if (selectedRecords.length !== 1) {
setError('延期申请每次只能选择 1 条问题整改记录。');
setError('延期申请每次只能选择 1 条问题整改记录。');
return;
return;
}
}
if (!editorProfile?._id || !auditorProfile?._id) {
setError('请分别选择 1 个填写人和 1 个一级审批人。');
if (delayDepartmentAssignments.length === 0) {
setError('请至少保留 1 个责任部门配置后再推送延期申请。');
return;
}
const hasMissingDepartmentUser = delayDepartmentAssignments.some(
(item) => !item.editor?._id || !item.auditor?._id,
);
if (hasMissingDepartmentUser) {
setError('请为每个责任部门选择 1 个延期申请填写人和 1 个一级审批人。');
return;
return;
}
}
...
@@ -1043,26 +1121,33 @@ export default function ProblemRectificationListPage() {
...
@@ -1043,26 +1121,33 @@ export default function ProblemRectificationListPage() {
if (latestRecord?.metadata?.is_locked === true) {
if (latestRecord?.metadata?.is_locked === true) {
throw new Error('当前问题整改记录已锁定,不允许发起延期申请。');
throw new Error('当前问题整改记录已锁定,不允许发起延期申请。');
}
}
if (normalizeDelayRequest(latestRecord?.metadata?.[DELAY_REQUEST_FIELD])) {
if (
normalizeDelayRequest(latestRecord?.metadata?.[DELAY_REQUEST_FIELD]) ||
normalizeDelayRequestItems(latestRecord?.metadata?.[DELAY_REQUEST_ITEMS_FIELD]).length > 0
) {
throw new Error('当前问题已发起延期申请,不能重复推送。');
throw new Error('当前问题已发起延期申请,不能重复推送。');
}
}
const editorId = editorProfile._id;
const editorIds = Array.from(
const auditorId = auditorProfile._id;
new Set(delayDepartmentAssignments.map((item) => item.editor?._id).filter(Boolean)),
);
const auditorIds = Array.from(
new Set(delayDepartmentAssignments.map((item) => item.auditor?._id).filter(Boolean)),
);
const messageRecord = await createRecord({
const messageRecord = await createRecord({
subject: 'message_reference_id',
subject: 'message_reference_id',
title: `
推送延期申请填写
$
{
dayjs
().
format
(
'YYYY-MM-DD HH:mm'
)}
`,
title: `
推送延期申请填写
$
{
dayjs
().
format
(
'YYYY-MM-DD HH:mm'
)}
`,
metadata: {
metadata: {
sender: currentUserId,
sender: currentUserId,
editor:
[editorId]
,
editor:
editorIds
,
auditor:
[auditorId]
,
auditor:
auditorIds
,
restriction_key: '_id',
restriction_key: '_id',
restriction_value: [selectedRecord.id],
restriction_value: [selectedRecord.id],
reference_record_id: [selectedRecord.id],
reference_record_id: [selectedRecord.id],
view_name: activeGridView?.name || '',
view_name: activeGridView?.name || '',
subject_name: PROBLEM_RECTIFICATION_SUBJECT,
subject_name: PROBLEM_RECTIFICATION_SUBJECT,
flow_mode: FLOW_MODE_DELAY,
flow_mode: FLOW_MODE_DELAY
_DEPARTMENT
,
department_item_ids:
[]
,
department_item_ids:
delayDepartmentAssignments.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,
...
@@ -1071,50 +1156,49 @@ export default function ProblemRectificationListPage() {
...
@@ -1071,50 +1156,49 @@ export default function ProblemRectificationListPage() {
const flowRecordId = messageRecord.name || messageRecord._id;
const flowRecordId = messageRecord.name || messageRecord._id;
const returnUrl = getProblemRectificationFlowTableUrl(flowRecordId);
const returnUrl = getProblemRectificationFlowTableUrl(flowRecordId);
const oaProcessResult = await executeAction('oa_notice:createProgress', {
const progressIdEditorByItemId = {};
title: `
$
{
dayjs
().
format
(
'YYYY-MM-DD'
)}
填写延期申请
-
$
{
selectedRecord
.
title
||
selectedRecord
.
projectName
}
`,
classify: '流程待办',
for (const assignment of delayDepartmentAssignments) {
contentType: '流程待办',
const editorProfile = assignment.editor?._id ? await loadUser(assignment.editor._id) : assignment.editor;
receiveUserId: editorId,
const oaProcessResult = await executeAction('oa_notice:createProgress', {
receiveUserName: editorProfile.username || editorId,
title: `
$
{
dayjs
().
format
(
'YYYY-MM-DD'
)}
填写延期申请
-
$
{
assignment
.
responsible_department
}
`,
receiveDisplayName: editorProfile.display_name || editorProfile.username || editorId,
classify: '流程待办',
sendUserId: currentUserId,
contentType: '流程待办',
sendUserName: currentUserInfo?.username || '',
receiveUserId: editorProfile?._id || assignment.editor?._id,
sendDisplayName: currentUserInfo?.display_name || '',
receiveUserName: editorProfile?.username || assignment.editor?.username || assignment.editor?._id,
returnUrl,
receiveDisplayName:
editorProfile?.display_name ||
assignment.editor?.display_name ||
assignment.editor?.username ||
assignment.editor?._id,
sendUserId: currentUserId,
sendUserName: currentUserInfo?.username || '',
sendDisplayName: currentUserInfo?.display_name || '',
returnUrl,
});
progressIdEditorByItemId[assignment.item_id] = oaProcessResult?.data?.taskId || '';
}
const delayRequestItems = buildDepartmentDelayRequestItems({
record: latestRecord,
assignments: delayDepartmentAssignments,
currentUser: currentUserInfo,
flowRecordId,
progressIdEditorByItemId,
});
});
const progressIdEditor = oaProcessResult?.data?.taskId || '';
const delayRequest = {
original_due_date: latestRecord?.metadata?.the_time_of_rectification || null,
requested_due_date: null,
delay_reason: '',
editor: editorId,
auditor: auditorId,
sender: currentUserId,
editor_status: EDITOR_STATUS_PENDING,
auditor_status: AUDIT_STATUS_PENDING,
sender_status: AUDIT_STATUS_PENDING,
progress_id_editor: progressIdEditor,
progress_id_auditor: '',
progress_id_sender: '',
flow_record_id: flowRecordId,
submitted_at: null,
auditor_decided_at: null,
sender_decided_at: null,
};
await updateRecord(selectedRecord.id, {
await updateRecord(selectedRecord.id, {
metadata: { [DELAY_REQUEST_
FIELD]: delayRequest
},
metadata: { [DELAY_REQUEST_
ITEMS_FIELD]: delayRequestItems
},
});
});
await updateRecord(messageRecord._id || messageRecord.name, {
await updateRecord(messageRecord._id || messageRecord.name, {
metadata: { progress_id_editor:
[progressIdEditor]
},
metadata: { progress_id_editor:
Object.values(progressIdEditorByItemId).filter(Boolean)
},
});
});
const savedRecord = await loadRecord(selectedRecord.id);
const savedRecord = await loadRecord(selectedRecord.id);
const savedDelayRequest
= normalizeDelayRequest
(
const savedDelayRequest
Items = normalizeDelayRequestItems
(
savedRecord?.metadata?.[DELAY_REQUEST_FIELD],
savedRecord?.metadata?.[DELAY_REQUEST_
ITEMS_
FIELD],
);
);
if (savedDelayRequest
?.flow_record_id !== flowRecordId
) {
if (savedDelayRequest
Items.length !== delayRequestItems.length
) {
throw new Error('延期申请写入失败,请刷新后重试。');
throw new Error('延期申请写入失败,请刷新后重试。');
}
}
...
@@ -1123,7 +1207,7 @@ export default function ProblemRectificationListPage() {
...
@@ -1123,7 +1207,7 @@ export default function ProblemRectificationListPage() {
content: `
请点击链接填写延期申请:
$
{
returnUrl
}
`,
content: `
请点击链接填写延期申请:
$
{
returnUrl
}
`,
type: 'internal',
type: 'internal',
from: currentUserId,
from: currentUserId,
to:
[editorId]
,
to:
editorIds
,
settings: {},
settings: {},
});
});
await sendNotice(noticeResult.name);
await sendNotice(noticeResult.name);
...
@@ -1131,7 +1215,7 @@ export default function ProblemRectificationListPage() {
...
@@ -1131,7 +1215,7 @@ export default function ProblemRectificationListPage() {
closeDelayPushModal();
closeDelayPushModal();
setSelectedIds([]);
setSelectedIds([]);
await reloadList({ keepSuccessMessage: true });
await reloadList({ keepSuccessMessage: true });
setSuccessMessage(
'延期申请已推送,当前问题已进入填写流程。'
);
setSuccessMessage(
`
延期申请已按部门推送,已生成
$
{
delayRequestItems
.
length
}
条部门延期申请。
`
);
} catch (requestError) {
} catch (requestError) {
setError(requestError?.message || '推送延期申请失败,请稍后重试。');
setError(requestError?.message || '推送延期申请失败,请稍后重试。');
} finally {
} finally {
...
@@ -1155,11 +1239,16 @@ export default function ProblemRectificationListPage() {
...
@@ -1155,11 +1239,16 @@ export default function ProblemRectificationListPage() {
}
}
if (isDepartmentFlow) {
if (isDepartmentFlow) {
if (departmentAssignments.length === 0) {
setError('请至少保留 1 个责任部门配置后再推送。');
return;
}
const hasMissingDepartmentUser = departmentAssignments.some(
const hasMissingDepartmentUser = departmentAssignments.some(
(item) => !item.editor?._id || !item.auditor?._id,
(item) => !item.editor?._id || !item.auditor?._id,
);
);
if (
departmentAssignments.length === 0 ||
hasMissingDepartmentUser) {
if (hasMissingDepartmentUser) {
setError('请为每个责任部门选择 1 个填报人和 1 个复核人。');
setError('请为每个责任部门选择 1 个填报人和 1 个复核人。');
return;
return;
}
}
...
@@ -1429,66 +1518,10 @@ export default function ProblemRectificationListPage() {
...
@@ -1429,66 +1518,10 @@ export default function ProblemRectificationListPage() {
selectable={quickSelectEnabled}
selectable={quickSelectEnabled}
showExpandColumn={false}
showExpandColumn={false}
isRowExpandable={(record) => record.departmentItems?.length > 0}
isRowExpandable={(record) => record.departmentItems?.length > 0}
renderExpandedRow={({ record }) => (
getExpandedRecordRows={({ record }) => (
<div className="sticky left-4 w-[calc(100vw-8rem)] max-w-[1320px] overflow-hidden border-y border-rose-100 bg-rose-50/25">
(record.departmentItems || []).map((item) =>
<div className="flex items-center justify-between gap-4 border-b border-rose-100 px-5 py-3">
buildDepartmentDetailRecord(record, item, departmentUserLabels),
<div className="text-sm font-semibold text-slate-900">部门整改明细</div>
)
<div className="text-xs text-slate-500">共 {record.departmentItems.length} 条</div>
</div>
<div className="divide-y divide-rose-100/80">
{(record.departmentItems || []).map((item) => (
<div
key={item.item_id}
className="grid gap-x-5 gap-y-4 px-5 py-4 sm:grid-cols-2 lg:grid-cols-4 2xl:grid-cols-8"
>
<DepartmentDetailField label="责任部门">
{item.responsible_department || '-'}
</DepartmentDetailField>
<DepartmentDetailField label="填写人">
{departmentUserLabels[item.editor] || item.editor || '-'}
</DepartmentDetailField>
<DepartmentDetailField label="审批人">
{departmentUserLabels[item.auditor] || item.auditor || '-'}
</DepartmentDetailField>
<DepartmentDetailField label="整改进度">
<StatusPill
text={item.rectification_progress || '未填写'}
tone={resolveProgressTone(item.rectification_progress)}
/>
</DepartmentDetailField>
<DepartmentDetailField label="预计完成时间">
{item.the_time_of_rectification
? dayjs(item.the_time_of_rectification).format('YYYY-MM-DD')
: '-'}
</DepartmentDetailField>
<DepartmentDetailField label="审批状态">
<StatusPill
text={item.audit_status || '未审批'}
tone={resolveAuditTone(item.audit_status)}
/>
</DepartmentDetailField>
<DepartmentDetailField label="最终审批">
<StatusPill
text={item.final_audit_status || '未审批'}
tone={resolveAuditTone(item.final_audit_status)}
/>
</DepartmentDetailField>
<DepartmentDetailField label="整改附件">
{item.rectification_proof_materials?.url ? (
<a
href={item.rectification_proof_materials.url}
target="_blank"
rel="noreferrer"
className="font-medium text-sky-700 hover:text-sky-900"
>
{item.rectification_proof_materials.name || '查看附件'}
</a>
) : '未上传'}
</DepartmentDetailField>
</div>
))}
</div>
</div>
)}
)}
toolbarActions={[
toolbarActions={[
{
{
...
@@ -1585,7 +1618,7 @@ export default function ProblemRectificationListPage() {
...
@@ -1585,7 +1618,7 @@ export default function ProblemRectificationListPage() {
recordName={recordName}
recordName={recordName}
onClose={onClose}
onClose={onClose}
onDataChange={onDataChange}
onDataChange={onDataChange}
disabledFields={[DELAY_REQUEST_FIELD]}
disabledFields={[DELAY_REQUEST_FIELD
, DELAY_REQUEST_ITEMS_FIELD
]}
preferredViewName={activeGridView?.title || activeGridView?.name}
preferredViewName={activeGridView?.title || activeGridView?.name}
/>
/>
),
),
...
@@ -1627,10 +1660,10 @@ export default function ProblemRectificationListPage() {
...
@@ -1627,10 +1660,10 @@ export default function ProblemRectificationListPage() {
return (
return (
<div className="flex flex-wrap items-center gap-x-2 gap-y-1">
<div className="flex flex-wrap items-center gap-x-2 gap-y-1">
{fieldPreview}
{fieldPreview}
{record.isLocked ? (
{
!record.isDepartmentDetailRow &&
record.isLocked ? (
<StatusPill text="已锁定" tone="amber" />
<StatusPill text="已锁定" tone="amber" />
) : null}
) : null}
{expandable ? (
{
!record.isDepartmentDetailRow &&
expandable ? (
<Button
<Button
plain
plain
className="text-rose-700"
className="text-rose-700"
...
@@ -1724,13 +1757,18 @@ export default function ProblemRectificationListPage() {
...
@@ -1724,13 +1757,18 @@ export default function ProblemRectificationListPage() {
</DialogDescription>
</DialogDescription>
<DialogBody>
<DialogBody>
{isDepartmentPushMode ? (
{isDepartmentPushMode ? (
<div className="overflow-hidden rounded-[24px] border border-zinc-200 bg-white">
<div className="space-y-3">
<div className="text-sm text-slate-600">
当前保留 {departmentAssignments.length} 条部门配置;可删除无需本次推送的部门,至少保留 1 条。
</div>
<div className="overflow-hidden rounded-[24px] border border-zinc-200 bg-white">
<Table bleed dense className="[--gutter:--spacing(4)]">
<Table bleed dense className="[--gutter:--spacing(4)]">
<TableHead>
<TableHead>
<TableRow className="bg-zinc-50">
<TableRow className="bg-zinc-50">
<TableHeader>责任部门</TableHeader>
<TableHeader>责任部门</TableHeader>
<TableHeader>填报人</TableHeader>
<TableHeader>填报人</TableHeader>
<TableHeader>复核人</TableHeader>
<TableHeader>复核人</TableHeader>
<TableHeader className="w-20 text-right">操作</TableHeader>
</TableRow>
</TableRow>
</TableHead>
</TableHead>
<TableBody>
<TableBody>
...
@@ -1766,10 +1804,23 @@ export default function ProblemRectificationListPage() {
...
@@ -1766,10 +1804,23 @@ export default function ProblemRectificationListPage() {
placeholder="搜索复核人"
placeholder="搜索复核人"
/>
/>
</TableCell>
</TableCell>
<TableCell className="w-20 align-top text-right">
<button
type="button"
onClick={() => removeDepartmentAssignment(assignment.item_id)}
disabled={actionLoading}
className="inline-flex size-9 items-center justify-center rounded-lg text-slate-400 transition hover:bg-rose-50 hover:text-rose-600 disabled:cursor-not-allowed disabled:opacity-50"
aria-label={`
删除
$
{
assignment
.
responsible_department
}
部门配置
`}
title="删除"
>
<TrashIcon className="size-4" aria-hidden="true" />
</button>
</TableCell>
</TableRow>
</TableRow>
))}
))}
</TableBody>
</TableBody>
</Table>
</Table>
</div>
</div>
</div>
) : (
) : (
<div className="grid gap-6 md:grid-cols-2">
<div className="grid gap-6 md:grid-cols-2">
...
@@ -1824,10 +1875,10 @@ export default function ProblemRectificationListPage() {
...
@@ -1824,10 +1875,10 @@ export default function ProblemRectificationListPage() {
) : null}
) : null}
{showDelayPushModal ? (
{showDelayPushModal ? (
<Dialog open={showDelayPushModal} onClose={closeDelayPushModal} size="
2
xl">
<Dialog open={showDelayPushModal} onClose={closeDelayPushModal} size="
5
xl">
<DialogTitle>推送延期申请</DialogTitle>
<DialogTitle>推送延期申请</DialogTitle>
<DialogDescription>
<DialogDescription>
填写人将在流程页补充申请完成时间和延期事由,最终审批通过前不会修改当前预计完成时间。
按责任部门生成延期申请,
填写人将在流程页补充申请完成时间和延期事由,最终审批通过前不会修改当前预计完成时间。
</DialogDescription>
</DialogDescription>
<DialogBody>
<DialogBody>
<div className="rounded-lg border border-zinc-200 bg-zinc-50/80 p-4">
<div className="rounded-lg border border-zinc-200 bg-zinc-50/80 p-4">
...
@@ -1842,21 +1893,71 @@ export default function ProblemRectificationListPage() {
...
@@ -1842,21 +1893,71 @@ export default function ProblemRectificationListPage() {
) || '未设置'}
) || '未设置'}
</div>
</div>
</div>
</div>
<div className="mt-6 grid gap-6 md:grid-cols-2">
<div className="mt-6 space-y-3">
<RemoteUserSelect
<div className="text-sm text-slate-600">
label="填写人"
当前保留 {delayDepartmentAssignments.length} 条部门配置;可删除无需本次推送延期申请的部门,至少保留 1 条。
value={delayEditorUsers}
</div>
onChange={(users) => setDelayEditorUsers(users.slice(-1))}
<div className="overflow-hidden rounded-[24px] border border-zinc-200 bg-white">
onSearch={handleSearchUsers}
<Table bleed dense className="[--gutter:--spacing(4)]">
placeholder="搜索延期申请填写人"
<TableHead>
/>
<TableRow className="bg-zinc-50">
<RemoteUserSelect
<TableHeader>责任部门</TableHeader>
label="一级审批人"
<TableHeader>填写人</TableHeader>
value={delayAuditorUsers}
<TableHeader>一级审批人</TableHeader>
onChange={(users) => setDelayAuditorUsers(users.slice(-1))}
<TableHeader className="w-20 text-right">操作</TableHeader>
onSearch={handleSearchUsers}
</TableRow>
placeholder="搜索延期申请一级审批人"
</TableHead>
/>
<TableBody>
{delayDepartmentAssignments.map((assignment) => (
<TableRow key={assignment.item_id}>
<TableCell className="w-56 align-top">
<div className="font-medium text-slate-900">
{assignment.responsible_department}
</div>
</TableCell>
<TableCell className="align-top">
<RemoteUserSelect
label="填写人"
value={assignment.editor ? [assignment.editor] : []}
onChange={(users) =>
updateDelayDepartmentAssignment(assignment.item_id, {
editor: users[users.length - 1] || null,
})
}
onSearch={handleSearchUsers}
placeholder="搜索延期申请填写人"
/>
</TableCell>
<TableCell className="align-top">
<RemoteUserSelect
label="一级审批人"
value={assignment.auditor ? [assignment.auditor] : []}
onChange={(users) =>
updateDelayDepartmentAssignment(assignment.item_id, {
auditor: users[users.length - 1] || null,
})
}
onSearch={handleSearchUsers}
placeholder="搜索延期申请一级审批人"
/>
</TableCell>
<TableCell className="w-20 align-top text-right">
<button
type="button"
onClick={() => removeDelayDepartmentAssignment(assignment.item_id)}
disabled={actionLoading}
className="inline-flex size-9 items-center justify-center rounded-lg text-slate-400 transition hover:bg-rose-50 hover:text-rose-600 disabled:cursor-not-allowed disabled:opacity-50"
aria-label={`
删除
$
{
assignment
.
responsible_department
}
延期申请部门配置
`}
title="删除"
>
<TrashIcon className="size-4" aria-hidden="true" />
</button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</div>
</DialogBody>
</DialogBody>
<DialogActions>
<DialogActions>
...
@@ -1865,11 +1966,7 @@ export default function ProblemRectificationListPage() {
...
@@ -1865,11 +1966,7 @@ export default function ProblemRectificationListPage() {
</Button>
</Button>
<Button
<Button
color="blue"
color="blue"
disabled={
disabled={actionLoading || delayDepartmentAssignments.length === 0}
actionLoading ||
delayEditorUsers.length !== 1 ||
delayAuditorUsers.length !== 1
}
onClick={handleDelayPush}
onClick={handleDelayPush}
>
>
{actionLoading ? '推送中...' : '确认推送'}
{actionLoading ? '推送中...' : '确认推送'}
...
...
src/pages/problem_rectification/problem-rectification.business-flow.md
View file @
004e66ec
...
@@ -188,9 +188,9 @@
...
@@ -188,9 +188,9 @@
### 5.5 推送延期申请
### 5.5 推送延期申请
-
每次只允许选择 1 条问题记录,
不要求该问题已生成
`department_rectification_items`
-
每次只允许选择 1 条问题记录,
按该问题责任部门生成部门延期申请配置行;已有
`department_rectification_items`
时优先沿用其部门明细 ID
-
弹窗为
本次问题级延期申请
选择 1 个填写人和 1 个一级审批人,发起人为最终审批人
-
弹窗为
每个保留的责任部门
选择 1 个填写人和 1 个一级审批人,发起人为最终审批人
-
延期申请保存于
`metadata.problem_rectification_delay_request
`
,与
`department_rectification_items`
平级;每条问题最多保存 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 填写待办,流程链接使用相对地址
`/problem_rectification_new/problem_rectification_flow_table?id=<message_record_name>`
,不拼接浏览器当前域名
-
历史 OA 待办若仍使用
`/self_inspection_form?id=<message_record_name>`
,应用入口会保留原流程 ID 并重定向到上述表格处理页
-
历史 OA 待办若仍使用
`/self_inspection_form?id=<message_record_name>`
,应用入口会保留原流程 ID 并重定向到上述表格处理页
-
已锁定、未选择流程人员或已有延期申请的问题不能发起
-
已锁定、未选择流程人员或已有延期申请的问题不能发起
...
@@ -300,7 +300,7 @@
...
@@ -300,7 +300,7 @@
1.
在问题整改列表中勾选一条或多条问题
1.
在问题整改列表中勾选一条或多条问题
2.
点击“推送问题整改表”
2.
点击“推送问题整改表”
3.
如果只选择 1 条问题,系统按该问题的
`metadata.responsible_department`
生成部门配置行;每个责任部门必须选择 1 个填写人和 1 个审批人
3.
如果只选择 1 条问题,系统按该问题的
`metadata.responsible_department`
生成部门配置行;
用户可删除本次无需推送的部门配置行,但至少需保留 1 行;保留的
每个责任部门必须选择 1 个填写人和 1 个审批人
4.
如果选择多条问题,不拆分责任部门;本次推送只允许选择 1 个填写人和 1 个审批人,并批量应用到选中问题
4.
如果选择多条问题,不拆分责任部门;本次推送只允许选择 1 个填写人和 1 个审批人,并批量应用到选中问题
5.
创建
`message_reference_id`
(信息关联id)流程主单,写入:
5.
创建
`message_reference_id`
(信息关联id)流程主单,写入:
-
`sender`
-
`sender`
...
@@ -325,7 +325,8 @@
...
@@ -325,7 +325,8 @@
-
顶部批量操作栏出现“推送整改表”
-
顶部批量操作栏出现“推送整改表”
-
点击后打开推送弹窗;单条问题展示部门配置表格,多选问题展示单填写人 / 单复核人配置
-
点击后打开推送弹窗;单条问题展示部门配置表格,多选问题展示单填写人 / 单复核人配置
-
弹窗底部明确展示本次选中记录数量和生成的部门明细数量
-
弹窗中部门配置表展示自动生成的部门行数量,并提供行级删除操作;确认推送时只按保留的部门行生成
`department_rectification_items`
、
`department_item_ids`
、OA 待办和内部通知
-
弹窗底部明确展示本次选中记录数量和最终生成的部门明细数量
-
成功后列表即时刷新,并给出已推送结果提示
-
成功后列表即时刷新,并给出已推送结果提示
### 7.3 填报人补录整改内容
### 7.3 填报人补录整改内容
...
@@ -399,31 +400,32 @@
...
@@ -399,31 +400,32 @@
#### 7.6.1 发起与填写
#### 7.6.1 发起与填写
1.
在问题整改列表中只勾选 1 条问题,点击“推送延期申请”
1.
在问题整改列表中只勾选 1 条问题,点击“推送延期申请”
2.
系统展示当前问题及主记录预计完成时间,由发起人选择 1 个填写人和 1 个一级审批人
2.
系统展示当前问题及主记录预计完成时间,并按责任部门生成延期申请配置行;已有
`department_rectification_items`
时优先使用其部门和
`item_id`
,否则按
`metadata.responsible_department`
生成临时部门行
3.
问题主记录已有
`problem_rectification_delay_request`
时不能重复推送;是否存在部门整改明细不影响延期发起
3.
发起人可删除本次不需要推送延期申请的部门配置行,但至少需保留 1 行;保留的每个部门必须选择 1 个填写人和 1 个一级审批人
4.
确认推送后创建
`message_reference_id`
,写入
`flow_mode = "delay"`
、问题 ID、填写人、一级审批人和推送人
4.
问题主记录已有
`problem_rectification_delay_request`
或
`problem_rectification_delay_request_items`
时不能重复推送
5.
在
`metadata.problem_rectification_delay_request`
初始化延期申请:原预计完成时间取主记录当前
`the_time_of_rectification`
,填写、一级审批、最终审批状态分别初始化为“未完成 / 未审批 / 未审批”
5.
确认推送后创建
`message_reference_id`
,写入
`flow_mode = "delay_department"`
、问题 ID、部门延期申请
`item_id`
列表、填写人、一级审批人和推送人
6.
系统为填写人创建 OA 待办并发送内部通知,链接使用相对地址
`/problem_rectification_new/problem_rectification_flow_table?id=<message_record_name>`
6.
在
`metadata.problem_rectification_delay_request_items`
初始化部门延期申请数组:原预计完成时间优先取对应部门明细当前
`the_time_of_rectification`
,没有部门明细时取主记录当前
`the_time_of_rectification`
;填写、一级审批、最终审批状态分别初始化为“未完成 / 未审批 / 未审批”
7.
填写人进入流程页后填写
`requested_due_date`
(申请完成时间)和
`delay_reason`
(延期事由);草稿可以暂存,提交时两项均必填
7.
系统为每个部门填写人创建 OA 待办并发送内部通知,链接使用相对地址
`/problem_rectification_new/problem_rectification_flow_table?id=<message_record_name>`
8.
填写人进入流程页后按部门填写
`requested_due_date`
(申请完成时间)和
`delay_reason`
(延期事由);草稿可以暂存,提交时两项均必填
#### 7.6.2 一级审批
#### 7.6.2 一级审批
1.
填写人提交后,系统完成填写 OA 待办,写入
`editor_status = "已完成"`
、
`submitted_at`
,并创建
一级审批人 OA 待办
1.
部门填写人提交后,系统完成该部门填写 OA 待办,写入该部门延期申请
`editor_status = "已完成"`
、
`submitted_at`
,并创建该部门
一级审批人 OA 待办
2.
一级审批通过时写入
`auditor_status = "已审批"`
、
`auditor_decided_at`
,并立即给推送发起人创建最终审批 OA 待办
2.
部门一级审批通过时写入该部门
`auditor_status = "已审批"`
、
`auditor_decided_at`
,并立即给推送发起人创建最终审批 OA 待办
3.
一级审批驳回时写入
`auditor_status = "已驳回"`
,把
`editor_status`
重置为“未完成”,重新给
填写人创建 OA 待办
3.
部门一级审批驳回时写入该部门
`auditor_status = "已驳回"`
,把该部门
`editor_status`
重置为“未完成”,重新给该部门
填写人创建 OA 待办
#### 7.6.3 最终审批与生效
#### 7.6.3 最终审批与生效
1.
推送发起人作为最终审批人,
处理已通过一级审批的问题
延期申请
1.
推送发起人作为最终审批人,
按部门处理已通过一级审批的
延期申请
2.
最终审批通过时写入
`sender_status = "已审批"`
、
`sender_decided_at`
,再把
`requested_due_date`
写回问题主记录的
`the_time_of_rectification`
2.
部门最终审批通过时写入该部门
`sender_status = "已审批"`
、
`sender_decided_at`
,再把该部门
`requested_due_date`
写回对应
`department_rectification_items[].the_time_of_rectification`
;如果不存在对应部门整改明细,则回退写回问题主记录
`the_time_of_rectification`
3.
延期申请不修改已有
`department_rectification_items`
的预计完成时间
或整改审批状态
3.
部门延期申请不修改整改进度、整改情况、证明材料
或整改审批状态
4.
最终审批驳回时写入
`sender_status = "已驳回"`
,把
`editor_status`
重置为“未完成”,重新给
填写人创建 OA 待办;原预计完成时间保持不变
4.
部门最终审批驳回时写入该部门
`sender_status = "已驳回"`
,把该部门
`editor_status`
重置为“未完成”,重新给该部门
填写人创建 OA 待办;原预计完成时间保持不变
5.
驳回后
填写人修改并重新提交时,
流程再次经过一级审批和最终审批
5.
驳回后
部门填写人修改并重新提交时,该部门
流程再次经过一级审批和最终审批
限制与异常处理:
限制与异常处理:
-
`metadata.is_locked = true`
时,禁止发起、保存、提交或审批延期申请
-
`metadata.is_locked = true`
时,禁止发起、保存、提交或审批延期申请
-
延期申请的状态和待办 ID 只写入
问题主记录的嵌套对象,不覆盖部门
明细的审批状态和原整改待办 ID
-
延期申请的状态和待办 ID 只写入
`problem_rectification_delay_request_items`
对应部门行,不覆盖部门整改
明细的审批状态和原整改待办 ID
-
后台问题记录在提交前发生变化时,操作失败并提示刷新后重试
-
后台问题记录在提交前发生变化时,操作失败并提示刷新后重试
-
OA 待办创建、业务记录写入或内部通知发送失败时展示错误,不显示成功提示
-
OA 待办创建、业务记录写入或内部通知发送失败时展示错误,不显示成功提示
...
@@ -455,8 +457,8 @@
...
@@ -455,8 +457,8 @@
-
`sender`
记录推送发起人
-
`sender`
记录推送发起人
-
`editor`
记录填写人列表
-
`editor`
记录填写人列表
-
`auditor`
记录审核人列表
-
`auditor`
记录审核人列表
-
`flow_mode`
记录流程模式:
`department`
表示单条问题按责任部门拆分整改明细,
`batch`
表示多选问题的单填写人 / 单审批人简单推送,
`delay`
表示
问题级延期申请;历史
`delay_department`
仅保留页面读取兼容
-
`flow_mode`
记录流程模式:
`department`
表示单条问题按责任部门拆分整改明细,
`batch`
表示多选问题的单填写人 / 单审批人简单推送,
`delay`
表示
历史问题级延期申请,
`delay_department`
表示按责任部门拆分延期申请
-
`department_item_ids`
记录本次部门流程关联的
`department_rectification
_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`
一致
...
@@ -473,20 +475,20 @@
...
@@ -473,20 +475,20 @@
`department_rectification_items`
存放在父问题
`metadata`
中:
`department_rectification_items`
存放在父问题
`metadata`
中:
-
问题整改主列表父行仍显示一条问题
-
问题整改主列表父行仍显示一条问题
-
父行的整改进度、审批状态、最终审批状态按部门明细
汇总;全部一致则显示该状态,不一致则显示“部分整改”或“部分
审批”
-
父行的整改进度、审批状态、最终审批状态按部门明细
中最落后的状态汇总;例如 1 条“整改中”与 1 条“已整改”汇总为“整改中”,1 条“未审批”与 1 条“已审批”汇总为“未
审批”
-
父行可展开显示部门明细,明细中分开展示责任部门、填写人、审批人、整改进度、预计完成时间、审批状态、最终审批状态和附件状态
-
父行可展开显示部门明细,明细中分开展示责任部门、填写人、审批人、整改进度、预计完成时间、审批状态、最终审批状态和附件状态
-
存在部门明细的父行在标题右侧显示“N 条部门明细”入口;单部门推送同样生成 1 条明细。展开
区使用贴合当前视口的响应式扁平信息网格,不嵌套横向子表格
,并将填写人、审批人 ID 转为用户显示名
-
存在部门明细的父行在标题右侧显示“N 条部门明细”入口;单部门推送同样生成 1 条明细。展开
后部门明细作为父行下方子行平铺展示,复用主表列顺序、列宽、固定列和列分组,与问题整改列头一一对齐;子行不参与主表勾选和批量操作
,并将填写人、审批人 ID 转为用户显示名
-
转发后的整改填写、审批和最终审批均优先更新部门明细,父字段仅做兼容汇总展示
-
转发后的整改填写、审批和最终审批均优先更新部门明细,父字段仅做兼容汇总展示
-
新流程页中,填写人只能维护本人负责行的整改业务字段,送审后锁定,复核驳回后恢复;复核驳回状态下对应审批人可以维护该行整改业务字段,审批通过后锁定,且审批人不能点击填写人的“送审”;流程推送人可以维护本次推送关联的父问题记录和全部部门明细整改业务字段,但不能直接编辑流程状态与待办 ID
-
新流程页中,填写人只能维护本人负责行的整改业务字段,送审后锁定,复核驳回后恢复;复核驳回状态下对应审批人可以维护该行整改业务字段,审批通过后锁定,且审批人不能点击填写人的“送审”;流程推送人可以维护本次推送关联的父问题记录和全部部门明细整改业务字段,但不能直接编辑流程状态与待办 ID
-
推送人的内容修改保持当前审批状态不变,不触发重新送审
-
推送人的内容修改保持当前审批状态不变,不触发重新送审
### 8.4 延期申请状态约定
### 8.4 延期申请状态约定
-
每条问题主记录最多保存 1 个
`problem_rectification_delay_request`
嵌套对象,路径为
`metadata.problem_rectification_delay_request`
-
新延期流程按部门保存
`problem_rectification_delay_request_items`
数组,路径为
`metadata.problem_rectification_delay_request_items[]`
;历史问题级延期对象
`metadata.problem_rectification_delay_request`
继续兼容读取
-
`editor_status`
使用“未完成 / 已完成”;
`auditor_status`
、
`sender_status`
使用“未审批 / 已审批 / 已驳回”
-
`editor_status`
使用“未完成 / 已完成”;
`auditor_status`
、
`sender_status`
使用“未审批 / 已审批 / 已驳回”
-
`progress_id_editor`
、
`progress_id_auditor`
、
`progress_id_sender`
记录延期申请各阶段 OA 待办 ID
-
`progress_id_editor`
、
`progress_id_auditor`
、
`progress_id_sender`
记录
每条部门
延期申请各阶段 OA 待办 ID
-
最终审批通过前,
`original_due_date`
和问题主记录原
`the_time_of_rectification`
保持不变
-
最终审批通过前,
`original_due_date`
和问题主记录原
`the_time_of_rectification`
保持不变
-
最终审批通过后,以
`requested_due_date`
更新问题主记录
`the_time_of_rectification`
,嵌套对象
继续保留完整审批记录
-
最终审批通过后,以
该部门
`requested_due_date`
更新对应部门明细预计完成时间;无对应部门明细时才回退更新问题主记录
`the_time_of_rectification`
,延期申请明细
继续保留完整审批记录
### 8.5 主记录状态锁定约定
### 8.5 主记录状态锁定约定
...
...
src/pages/problem_rectification/problemRectificationListService.js
View file @
004e66ec
...
@@ -29,6 +29,7 @@ export const PROBLEM_RECTIFICATION_PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
...
@@ -29,6 +29,7 @@ export const PROBLEM_RECTIFICATION_PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
export
const
DEFAULT_PROBLEM_RECTIFICATION_PAGE_SIZE
=
20
;
export
const
DEFAULT_PROBLEM_RECTIFICATION_PAGE_SIZE
=
20
;
export
const
DEPARTMENT_RECTIFICATION_FIELD
=
'department_rectification_items'
;
export
const
DEPARTMENT_RECTIFICATION_FIELD
=
'department_rectification_items'
;
export
const
DELAY_REQUEST_FIELD
=
'problem_rectification_delay_request'
;
export
const
DELAY_REQUEST_FIELD
=
'problem_rectification_delay_request'
;
export
const
DELAY_REQUEST_ITEMS_FIELD
=
'problem_rectification_delay_request_items'
;
export
const
FLOW_MODE_DEPARTMENT
=
'department'
;
export
const
FLOW_MODE_DEPARTMENT
=
'department'
;
export
const
FLOW_MODE_BATCH
=
'batch'
;
export
const
FLOW_MODE_BATCH
=
'batch'
;
export
const
FLOW_MODE_DELAY
=
'delay'
;
export
const
FLOW_MODE_DELAY
=
'delay'
;
...
@@ -40,6 +41,8 @@ export const AUDIT_STATUS_PENDING = '未审批';
...
@@ -40,6 +41,8 @@ export const AUDIT_STATUS_PENDING = '未审批';
export
const
AUDIT_STATUS_APPROVED
=
'已审批'
;
export
const
AUDIT_STATUS_APPROVED
=
'已审批'
;
export
const
AUDIT_STATUS_REJECTED
=
'已驳回'
;
export
const
AUDIT_STATUS_REJECTED
=
'已驳回'
;
const
FIXED_PROGRESS_VALUES
=
new
Set
([
'已整改'
,
'已优化'
]);
const
FIXED_PROGRESS_VALUES
=
new
Set
([
'已整改'
,
'已优化'
]);
const
RECTIFICATION_PROGRESS_PRIORITY
=
[
'整改中'
,
'部分整改'
,
'已整改'
,
'已优化'
,
'已完成'
,
'不适用'
];
const
AUDIT_STATUS_PRIORITY
=
[
AUDIT_STATUS_REJECTED
,
AUDIT_STATUS_PENDING
,
AUDIT_STATUS_APPROVED
];
const
LEVEL_ORDER
=
[
'高'
,
'中'
,
'低'
];
const
LEVEL_ORDER
=
[
'高'
,
'中'
,
'低'
];
const
FILTER_FIELD_PATHS
=
{
const
FILTER_FIELD_PATHS
=
{
progress
:
'metadata.rectification_progress'
,
progress
:
'metadata.rectification_progress'
,
...
@@ -132,18 +135,36 @@ function makeDepartmentItemId(record, department, index) {
...
@@ -132,18 +135,36 @@ function makeDepartmentItemId(record, department, index) {
return
`dept_
${
recordId
}
_
${
index
+
1
}
_
${
departmentSlug
}
_
${
Date
.
now
()}
_
${
randomPart
}
`
;
return
`dept_
${
recordId
}
_
${
index
+
1
}
_
${
departmentSlug
}
_
${
Date
.
now
()}
_
${
randomPart
}
`
;
}
}
function
summarize
TextStatus
(
values
=
[],
partialText
=
'部分审批'
,
emptyText
=
''
)
{
function
summarize
LaggingStatus
(
values
=
[],
priority
=
[],
emptyText
=
''
,
unknownRank
=
1
)
{
const
normalizedValues
=
uniqueTruthy
(
values
);
const
normalizedValues
=
values
.
map
((
value
)
=>
normalizeText
(
value
)
);
if
(
normalizedValues
.
length
===
0
)
{
if
(
normalizedValues
.
length
===
0
)
{
return
emptyText
;
return
emptyText
;
}
}
if
(
normalizedValues
.
length
===
1
)
{
const
priorityRank
=
new
Map
(
priority
.
map
((
status
,
index
)
=>
[
status
,
index
]));
return
normalizedValues
[
0
];
const
getStatusRank
=
(
status
)
=>
{
}
if
(
!
status
)
{
return
-
1
;
}
return
priorityRank
.
has
(
status
)
?
priorityRank
.
get
(
status
)
:
unknownRank
;
};
return
partialText
;
return
normalizedValues
.
reduce
((
laggingStatus
,
status
)
=>
{
const
nextStatus
=
status
||
emptyText
;
return
getStatusRank
(
nextStatus
)
<
getStatusRank
(
laggingStatus
)
?
nextStatus
:
laggingStatus
;
},
normalizedValues
[
0
]
||
emptyText
);
}
function
summarizeDepartmentProgress
(
values
=
[])
{
return
summarizeLaggingStatus
(
values
,
RECTIFICATION_PROGRESS_PRIORITY
,
''
,
1
);
}
function
summarizeDepartmentAuditStatus
(
values
=
[])
{
return
summarizeLaggingStatus
(
values
,
AUDIT_STATUS_PRIORITY
,
AUDIT_STATUS_PENDING
,
1
);
}
}
function
summarizeDate
(
values
=
[])
{
function
summarizeDate
(
values
=
[])
{
...
@@ -240,6 +261,9 @@ export function normalizeDelayRequest(value) {
...
@@ -240,6 +261,9 @@ export function normalizeDelayRequest(value) {
return
{
return
{
...
parsedValue
,
...
parsedValue
,
item_id
:
normalizeText
(
parsedValue
.
item_id
),
source_department_item_id
:
normalizeText
(
parsedValue
.
source_department_item_id
),
responsible_department
:
normalizeText
(
parsedValue
.
responsible_department
),
original_due_date
:
parsedValue
.
original_due_date
||
null
,
original_due_date
:
parsedValue
.
original_due_date
||
null
,
requested_due_date
:
parsedValue
.
requested_due_date
||
null
,
requested_due_date
:
parsedValue
.
requested_due_date
||
null
,
delay_reason
:
normalizeText
(
parsedValue
.
delay_reason
),
delay_reason
:
normalizeText
(
parsedValue
.
delay_reason
),
...
@@ -259,8 +283,41 @@ export function normalizeDelayRequest(value) {
...
@@ -259,8 +283,41 @@ export function normalizeDelayRequest(value) {
};
};
}
}
export
function
normalizeDelayRequestItems
(
value
)
{
let
parsedValue
=
value
;
if
(
typeof
value
===
'string'
)
{
try
{
parsedValue
=
JSON
.
parse
(
value
);
}
catch
(
error
)
{
return
[];
}
}
return
toArray
(
parsedValue
)
.
map
((
item
)
=>
normalizeDelayRequest
(
item
))
.
filter
(
Boolean
)
.
map
((
item
,
index
)
=>
({
...
item
,
item_id
:
item
.
item_id
||
item
.
source_department_item_id
||
`delay_item_
${
index
+
1
}
`
,
responsible_department
:
item
.
responsible_department
||
'未指定责任部门'
,
}));
}
export
function
buildDepartmentAssignmentsFromRecord
(
record
)
{
export
function
buildDepartmentAssignmentsFromRecord
(
record
)
{
const
metadata
=
record
?.
metadata
||
record
?.
raw
?.
metadata
||
{};
const
metadata
=
record
?.
metadata
||
record
?.
raw
?.
metadata
||
{};
const
departmentItems
=
normalizeDepartmentRectificationItems
(
record
);
if
(
departmentItems
.
length
>
0
)
{
return
departmentItems
.
map
((
item
,
index
)
=>
({
item_id
:
item
.
item_id
||
makeDepartmentItemId
(
record
?.
raw
||
record
,
item
.
responsible_department
,
index
),
source_department_item_id
:
item
.
item_id
||
''
,
responsible_department
:
normalizeText
(
item
.
responsible_department
)
||
'未指定责任部门'
,
editor
:
null
,
auditor
:
null
,
}));
}
const
departments
=
uniqueTruthy
(
const
departments
=
uniqueTruthy
(
toArray
(
metadata
.
responsible_department
).
length
>
0
toArray
(
metadata
.
responsible_department
).
length
>
0
?
toArray
(
metadata
.
responsible_department
)
?
toArray
(
metadata
.
responsible_department
)
...
@@ -301,6 +358,54 @@ export function buildDepartmentRectificationItems(record, assignments = [], curr
...
@@ -301,6 +358,54 @@ export function buildDepartmentRectificationItems(record, assignments = [], curr
}));
}));
}
}
export
function
buildDepartmentDelayRequestItems
({
record
,
assignments
=
[],
currentUser
=
null
,
flowRecordId
=
''
,
progressIdEditorByItemId
=
{},
}
=
{})
{
const
metadata
=
record
?.
metadata
||
record
?.
raw
?.
metadata
||
{};
const
departmentItemsById
=
new
Map
(
normalizeDepartmentRectificationItems
(
record
).
map
((
item
)
=>
[
item
.
item_id
,
item
]),
);
const
currentUserId
=
normalizeUserId
(
currentUser
);
return
assignments
.
map
((
assignment
,
index
)
=>
{
const
sourceDepartmentItemId
=
normalizeText
(
assignment
.
source_department_item_id
||
assignment
.
item_id
);
const
sourceDepartmentItem
=
departmentItemsById
.
get
(
sourceDepartmentItemId
);
const
itemId
=
normalizeText
(
assignment
.
item_id
)
||
sourceDepartmentItemId
||
makeDepartmentItemId
(
record
?.
raw
||
record
,
assignment
.
responsible_department
,
index
);
return
{
item_id
:
itemId
,
source_department_item_id
:
sourceDepartmentItemId
,
responsible_department
:
normalizeText
(
assignment
.
responsible_department
)
||
'未指定责任部门'
,
original_due_date
:
sourceDepartmentItem
?.
the_time_of_rectification
||
metadata
.
the_time_of_rectification
||
null
,
requested_due_date
:
null
,
delay_reason
:
''
,
editor
:
normalizeUserId
(
assignment
.
editor
),
auditor
:
normalizeUserId
(
assignment
.
auditor
),
sender
:
currentUserId
,
editor_status
:
EDITOR_STATUS_PENDING
,
auditor_status
:
AUDIT_STATUS_PENDING
,
sender_status
:
AUDIT_STATUS_PENDING
,
progress_id_editor
:
progressIdEditorByItemId
[
itemId
]
||
''
,
progress_id_auditor
:
''
,
progress_id_sender
:
''
,
flow_record_id
:
flowRecordId
,
submitted_at
:
null
,
auditor_decided_at
:
null
,
sender_decided_at
:
null
,
};
});
}
export
function
buildDepartmentParentMetadataPatch
(
items
=
[])
{
export
function
buildDepartmentParentMetadataPatch
(
items
=
[])
{
const
normalizedItems
=
items
.
filter
(
Boolean
);
const
normalizedItems
=
items
.
filter
(
Boolean
);
const
editorIds
=
uniqueTruthy
(
normalizedItems
.
map
((
item
)
=>
item
.
editor
));
const
editorIds
=
uniqueTruthy
(
normalizedItems
.
map
((
item
)
=>
item
.
editor
));
...
@@ -310,20 +415,14 @@ export function buildDepartmentParentMetadataPatch(items = []) {
...
@@ -310,20 +415,14 @@ export function buildDepartmentParentMetadataPatch(items = []) {
[
DEPARTMENT_RECTIFICATION_FIELD
]:
normalizedItems
,
[
DEPARTMENT_RECTIFICATION_FIELD
]:
normalizedItems
,
fill_in_the_people
:
editorIds
,
fill_in_the_people
:
editorIds
,
review_people
:
auditorIds
,
review_people
:
auditorIds
,
rectification_progress
:
summarize
TextStatu
s
(
rectification_progress
:
summarize
DepartmentProgres
s
(
normalizedItems
.
map
((
item
)
=>
item
.
rectification_progress
),
normalizedItems
.
map
((
item
)
=>
item
.
rectification_progress
),
'部分整改'
,
''
,
),
),
audit_status
:
summarize
Tex
tStatus
(
audit_status
:
summarize
DepartmentAudi
tStatus
(
normalizedItems
.
map
((
item
)
=>
item
.
audit_status
),
normalizedItems
.
map
((
item
)
=>
item
.
audit_status
),
'部分审批'
,
AUDIT_STATUS_PENDING
,
),
),
final_audit_status
:
summarize
Tex
tStatus
(
final_audit_status
:
summarize
DepartmentAudi
tStatus
(
normalizedItems
.
map
((
item
)
=>
item
.
final_audit_status
),
normalizedItems
.
map
((
item
)
=>
item
.
final_audit_status
),
'部分审批'
,
AUDIT_STATUS_PENDING
,
),
),
the_time_of_rectification
:
summarizeDate
(
the_time_of_rectification
:
summarizeDate
(
normalizedItems
.
map
((
item
)
=>
item
.
the_time_of_rectification
),
normalizedItems
.
map
((
item
)
=>
item
.
the_time_of_rectification
),
...
@@ -668,25 +767,13 @@ export function normalizeProblemRectificationRecord(record) {
...
@@ -668,25 +767,13 @@ export function normalizeProblemRectificationRecord(record) {
const
departmentItems
=
normalizeDepartmentRectificationItems
(
record
);
const
departmentItems
=
normalizeDepartmentRectificationItems
(
record
);
const
hasDepartmentItems
=
departmentItems
.
length
>
0
;
const
hasDepartmentItems
=
departmentItems
.
length
>
0
;
const
summarizedProgress
=
hasDepartmentItems
const
summarizedProgress
=
hasDepartmentItems
?
summarizeTextStatus
(
?
summarizeDepartmentProgress
(
departmentItems
.
map
((
item
)
=>
item
.
rectification_progress
))
departmentItems
.
map
((
item
)
=>
item
.
rectification_progress
),
'部分整改'
,
''
,
)
:
normalizeText
(
metadata
.
rectification_progress
);
:
normalizeText
(
metadata
.
rectification_progress
);
const
summarizedAuditStatus
=
hasDepartmentItems
const
summarizedAuditStatus
=
hasDepartmentItems
?
summarizeTextStatus
(
?
summarizeDepartmentAuditStatus
(
departmentItems
.
map
((
item
)
=>
item
.
audit_status
))
departmentItems
.
map
((
item
)
=>
item
.
audit_status
),
'部分审批'
,
AUDIT_STATUS_PENDING
,
)
:
normalizeText
(
metadata
.
audit_status
);
:
normalizeText
(
metadata
.
audit_status
);
const
summarizedFinalAuditStatus
=
hasDepartmentItems
const
summarizedFinalAuditStatus
=
hasDepartmentItems
?
summarizeTextStatus
(
?
summarizeDepartmentAuditStatus
(
departmentItems
.
map
((
item
)
=>
item
.
final_audit_status
))
departmentItems
.
map
((
item
)
=>
item
.
final_audit_status
),
'部分审批'
,
AUDIT_STATUS_PENDING
,
)
:
normalizeText
(
metadata
.
final_audit_status
);
:
normalizeText
(
metadata
.
final_audit_status
);
const
summarizedDueDate
=
hasDepartmentItems
const
summarizedDueDate
=
hasDepartmentItems
?
summarizeDate
(
departmentItems
.
map
((
item
)
=>
item
.
the_time_of_rectification
))
?
summarizeDate
(
departmentItems
.
map
((
item
)
=>
item
.
the_time_of_rectification
))
...
@@ -771,6 +858,7 @@ export function normalizeProblemRectificationRecord(record) {
...
@@ -771,6 +858,7 @@ export function normalizeProblemRectificationRecord(record) {
departmentItems
,
departmentItems
,
hasDepartmentItems
,
hasDepartmentItems
,
delayRequest
:
normalizeDelayRequest
(
metadata
[
DELAY_REQUEST_FIELD
]),
delayRequest
:
normalizeDelayRequest
(
metadata
[
DELAY_REQUEST_FIELD
]),
delayRequestItems
:
normalizeDelayRequestItems
(
metadata
[
DELAY_REQUEST_ITEMS_FIELD
]),
isFixed
,
isFixed
,
isLocked
:
metadata
.
is_locked
===
true
,
isLocked
:
metadata
.
is_locked
===
true
,
isOverdue
,
isOverdue
,
...
...
src/services/subjectsDefinition.md
View file @
004e66ec
...
@@ -152,6 +152,7 @@ type Record = {
...
@@ -152,6 +152,7 @@ type Record = {
| 主责部门 |
`primary_responsible_department`
| text |
`null`
| 否 | 否 | 否 | 否 | 否 |
| 主责部门 |
`primary_responsible_department`
| text |
`null`
| 否 | 否 | 否 | 否 | 否 |
| 部门整改明细 |
`department_rectification_items`
| object |
`[]`
| 否 | 是 | 否 | 否 | 否 |
| 部门整改明细 |
`department_rectification_items`
| object |
`[]`
| 否 | 是 | 否 | 否 | 否 |
| 延期申请 |
`problem_rectification_delay_request`
| subject |
`null`
| 否 | 否 | 否 | 否 | 否 |
| 延期申请 |
`problem_rectification_delay_request`
| subject |
`null`
| 否 | 否 | 否 | 否 | 否 |
| 部门延期申请明细 |
`problem_rectification_delay_request_items`
| object |
`[]`
| 否 | 是 | 否 | 否 | 否 |
| 状态锁定 |
`is_locked`
| boolean |
`false`
| 否 | 否 | 否 | 否 | 否 |
| 状态锁定 |
`is_locked`
| boolean |
`false`
| 否 | 否 | 否 | 否 | 否 |
### 3.1.1 select 字段选项
### 3.1.1 select 字段选项
...
@@ -176,8 +177,9 @@ type Record = {
...
@@ -176,8 +177,9 @@ 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`
为历史问题级单次延期申请嵌套对象,与
`department_rectification_items`
平级;新发起延期默认按部门写入
`problem_rectification_delay_request_items`
-
`problem_rectification_delay_request_items`
为部门级延期申请明细数组;每个责任部门 1 条延期申请,独立保存填写人、一级审批人、最终审批人、申请完成时间、延期事由、状态和待办 ID
-
`is_locked`
为问题整改主记录的状态锁定标记;仅整改进度汇总值为
`已整改`
的记录允许写入
`true`
,锁定后所有业务字段和流程状态均不可修改,只有解锁操作可以将其写回
`false`
-
`is_locked`
为问题整改主记录的状态锁定标记;仅整改进度汇总值为
`已整改`
的记录允许写入
`true`
,锁定后所有业务字段和流程状态均不可修改,只有解锁操作可以将其写回
`false`
-
部门明细的填写人只能编辑自己负责明细的整改业务字段,送审后(
`editor_status = "已完成"`
)不再允许编辑;审批人审批通过后(
`audit_status = "已审批"`
)不再允许编辑,审批驳回后(
`audit_status = "已驳回"`
)审批人可以编辑该明细的整改业务字段,填写人也恢复编辑。审批人恢复的只是业务字段编辑权限,不获得填写人的送审权限。流程推送人可以编辑本次推送关联的父问题记录及全部部门明细整改业务字段。审批状态、待办 ID、明细 ID 等流程控制字段不作为普通表单字段开放
-
部门明细的填写人只能编辑自己负责明细的整改业务字段,送审后(
`editor_status = "已完成"`
)不再允许编辑;审批人审批通过后(
`audit_status = "已审批"`
)不再允许编辑,审批驳回后(
`audit_status = "已驳回"`
)审批人可以编辑该明细的整改业务字段,填写人也恢复编辑。审批人恢复的只是业务字段编辑权限,不获得填写人的送审权限。流程推送人可以编辑本次推送关联的父问题记录及全部部门明细整改业务字段。审批状态、待办 ID、明细 ID 等流程控制字段不作为普通表单字段开放
-
推送人修改父问题记录或部门明细时,不自动重置该明细已经形成的审批状态,也不自动重新生成流程待办
-
推送人修改父问题记录或部门明细时,不自动重置该明细已经形成的审批状态,也不自动重新生成流程待办
...
@@ -204,14 +206,18 @@ type Record = {
...
@@ -204,14 +206,18 @@ type Record = {
| 更新人 |
`updated_by`
| user | 否 | 最近更新用户
`_id`
|
| 更新人 |
`updated_by`
| user | 否 | 最近更新用户
`_id`
|
| 更新时间 |
`updated_at`
| date | 否 | 最近更新时间 |
| 更新时间 |
`updated_at`
| date | 否 | 最近更新时间 |
### 3.1.4 `problem_rectification_delay_request` 嵌套对象
### 3.1.4 `problem_rectification_delay_request`
/ `problem_rectification_delay_request_items`
嵌套对象
-
**subject**
:
`problem_rectification_delay_request`
-
**subject**
:
`problem_rectification_delay_request`
-
**父级路径**
:
`metadata.problem_rectification_delay_request`
-
**父级路径**
:
`metadata.problem_rectification_delay_request`
-
**部门级父级路径**
:
`metadata.problem_rectification_delay_request_items[]`
| 字段标签 | 字段别名 | 字段类型 | 是否必填 | 说明 |
| 字段标签 | 字段别名 | 字段类型 | 是否必填 | 说明 |
| --- | --- | --- | --- | --- |
| --- | --- | --- | --- | --- |
| 原预计完成时间 |
`original_due_date`
| date | 否 | 发起申请时从问题主记录
`the_time_of_rectification`
复制,保存 ISO 8601 |
| 明细 ID |
`item_id`
| text | 部门级必填 | 部门延期申请唯一标识,用于行级保存、审批和待办回写 |
| 关联部门整改明细 ID |
`source_department_item_id`
| text | 否 | 有
`department_rectification_items`
时保存对应
`item_id`
,最终审批通过后用于回写该部门预计完成时间 |
| 责任部门 |
`responsible_department`
| text | 部门级必填 | 当前延期申请对应的责任部门 |
| 原预计完成时间 |
`original_due_date`
| date | 否 | 发起申请时优先从对应部门明细
`the_time_of_rectification`
复制,无部门明细时从问题主记录复制,保存 ISO 8601 |
| 申请完成时间 |
`requested_due_date`
| date | 否 | 填写人填写;提交申请时必填并保存 ISO 8601 |
| 申请完成时间 |
`requested_due_date`
| date | 否 | 填写人填写;提交申请时必填并保存 ISO 8601 |
| 延期事由 |
`delay_reason`
| long_text | 否 | 填写人填写;提交申请时必填 |
| 延期事由 |
`delay_reason`
| long_text | 否 | 填写人填写;提交申请时必填 |
| 填写人 |
`editor`
| user | 否 | 保存推送时选择的填写人
`_id`
|
| 填写人 |
`editor`
| user | 否 | 保存推送时选择的填写人
`_id`
|
...
@@ -230,11 +236,11 @@ type Record = {
...
@@ -230,11 +236,11 @@ type Record = {
写入约定:
写入约定:
-
延期申请发起时
只初始化问题主记录的嵌套对象和填写人 OA 待办,不修改主记录
`the_time_of_rectification`
-
延期申请发起时
初始化问题主记录的部门级延期申请数组和填写人 OA 待办,不修改主记录或部门明细的
`the_time_of_rectification`
-
`requested_due_date`
、
`delay_reason`
在保存草稿时可以为空,在提交申请时由前端强制校验必填
-
`requested_due_date`
、
`delay_reason`
在保存草稿时可以为空,在提交申请时由前端强制校验必填
-
一级审批或最终审批驳回后,
`editor_status`
重置为
`未完成`
,并重新创建
填写人 OA 待办
-
一级审批或最终审批驳回后,
仅对应部门延期申请的
`editor_status`
重置为
`未完成`
,并重新创建该部门
填写人 OA 待办
-
只有
`sender_status = "已审批"`
时,才把
`requested_due_date`
写回问题主记录
`the_time_of_rectification`
-
只有
部门延期申请
`sender_status = "已审批"`
时,才把该行
`requested_due_date`
写回对应
`department_rectification_items[].the_time_of_rectification`
;如果不存在对应部门整改明细,则回退
写回问题主记录
`the_time_of_rectification`
-
同一问题主记录已有
该嵌套对象
时,不允许再次创建另一份延期申请
-
同一问题主记录已有
`problem_rectification_delay_request`
或
`problem_rectification_delay_request_items`
时,不允许再次创建另一份延期申请
### 3.1.5 metadata 示例
### 3.1.5 metadata 示例
...
@@ -363,8 +369,8 @@ type Record = {
...
@@ -363,8 +369,8 @@ type Record = {
-
`restriction_value`
保存被推送整改记录的
`_id[]`
-
`restriction_value`
保存被推送整改记录的
`_id[]`
-
`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`
保存本次部门流程涉及的
`department_rectification
_items[].item_id`
-
`department_item_ids`
保存本次部门流程涉及的
部门级
`item_id`
;整改流程对应
`department_rectification_items[].item_id`
,延期流程对应
`problem_rectification_delay_request
_items[].item_id`
-
`progress_id_editor`
、
`progress_id_auditor`
、
`progress_id_sender`
用于记录每一步用户确认动作生成的流程 ID,用来区分当前流程推进到了哪一步
-
`progress_id_editor`
、
`progress_id_auditor`
、
`progress_id_sender`
用于记录每一步用户确认动作生成的流程 ID,用来区分当前流程推进到了哪一步
-
`editor_status`
:当前项目统一使用
`未完成`
、
`已完成`
-
`editor_status`
:当前项目统一使用
`未完成`
、
`已完成`
-
`auditor_status`
:当前项目统一使用
`未完成`
、
`已完成`
-
`auditor_status`
:当前项目统一使用
`未完成`
、
`已完成`
...
...
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