Commit 508a8e17 authored by David Yang's avatar David Yang

fix: map department proof materials in issue list

parent 4b89489e
......@@ -32,6 +32,11 @@ export default defineConfig({
layout: false,
component: './Login',
},
{
path: ROUTE_PATHS.problemRectificationFlowTable,
wrappers: ['@/wrapper/Site', '@/wrapper/Auth'],
component: './problem_rectification/flow-table/index',
},
{
path: '/',
wrappers: ['@/wrapper/Site', '@/wrapper/Auth'],
......@@ -65,10 +70,6 @@ export default defineConfig({
path: ROUTE_PATHS.problemRectificationForm,
component: './problem_rectification/form/index',
},
{
path: ROUTE_PATHS.problemRectificationFlowTable,
component: './problem_rectification/flow-table/index',
},
],
},
],
......
{
"name": "problem_rectification_new",
"version": "1.0.7",
"version": "1.0.9",
"build": "abc",
"description": "merge master",
"date": "2025-07-14 19:27:20",
......
......@@ -5,6 +5,7 @@ import {
CalendarDaysIcon,
ChevronRightIcon,
Close,
Edit,
LockClosedIcon,
LockOpenIcon,
} from '@/components/AppIcons';
......@@ -25,6 +26,7 @@ import {
executeAction,
loadRecord,
loadUser,
queryRecords,
queryUsers,
sendNotice,
updateRecord,
......@@ -52,6 +54,7 @@ import {
getSelectOptions,
normalizeText,
shouldHideViewTitleColumn,
toArray,
} from '@/utils/subjectView';
import {
DEFAULT_PROBLEM_RECTIFICATION_PAGE_SIZE,
......@@ -126,6 +129,11 @@ const CREATE_FIELD_TYPE_OVERRIDES = {
actual_rectification_completed_at: 'date',
inspection_completed_at: 'date',
};
function getPushedProjectName(record) {
const projectName = normalizeText(record?.projectName);
return projectName && projectName !== '-' ? projectName : '未填写检查项目名称';
}
const CREATE_FIELD_OPTION_OVERRIDES = {
has_economic_loss: ['是', '否'],
has_external_impact: ['是', '否'],
......@@ -583,7 +591,10 @@ function UserIdentityCell({ fieldName, record, value }) {
function FieldValuePreview({ column, record }) {
const fieldName = getFieldNameFromPath(column.field);
const rawRecord = record.raw || record;
const value = getFieldValue(rawRecord, column.field);
const value =
fieldName === 'rectification_proof_materials' && !record.isDepartmentDetailRow
? record.rectificationProofMaterials
: getFieldValue(rawRecord, column.field);
if (fieldName === 'title') {
if (record.isDepartmentDetailRow) {
......@@ -802,7 +813,16 @@ export default function ProblemRectificationListPage() {
});
const [gridViewCounts, setGridViewCounts] = useState({});
const [workflowUserLabels, setWorkflowUserLabels] = useState({});
const [maintenanceFlowByRecordId, setMaintenanceFlowByRecordId] = useState({});
const createFieldVariant = 'secondary';
const currentUserId = useMemo(() => {
try {
const currentUser = JSON.parse(window.localStorage.getItem('current_user') || '{}');
return currentUser?._id || currentUser?.id || '';
} catch {
return '';
}
}, []);
const reloadResources = useCallback(async () => {
setLoading(true);
......@@ -880,6 +900,61 @@ export default function ProblemRectificationListPage() {
};
}, [records]);
useEffect(() => {
const recordIds = records.map((record) => record.id).filter(Boolean);
if (!currentUserId || recordIds.length === 0) {
setMaintenanceFlowByRecordId({});
return undefined;
}
let cancelled = false;
queryRecords({
subject: 'message_reference_id',
page: 0,
page_size: 100,
filter: {
'metadata.subject_name': PROBLEM_RECTIFICATION_SUBJECT,
'metadata.sender': currentUserId,
'metadata.restriction_value': { $in: recordIds },
},
sort: '-created_at',
mode: 'full',
})
.then((result) => {
if (cancelled) return;
const recordIdSet = new Set(recordIds);
const nextFlowByRecordId = {};
(result?.records || []).forEach((flowRecord) => {
const flowMetadata = flowRecord?.metadata || {};
if (
[FLOW_MODE_DELAY_DEPARTMENT, 'delay'].includes(flowMetadata.flow_mode) ||
String(flowRecord?.title || '').includes('延期申请')
) {
return;
}
const flowName = flowRecord?.name || flowRecord?._id;
toArray(flowMetadata.restriction_value).forEach((recordId) => {
const normalizedRecordId = String(recordId?._id || recordId || '');
if (recordIdSet.has(normalizedRecordId) && !nextFlowByRecordId[normalizedRecordId]) {
nextFlowByRecordId[normalizedRecordId] = flowName;
}
});
});
setMaintenanceFlowByRecordId(nextFlowByRecordId);
})
.catch(() => {
if (!cancelled) {
setMaintenanceFlowByRecordId({});
}
});
return () => {
cancelled = true;
};
}, [currentUserId, records]);
const gridViews = useMemo(() => buildGridViews(views), [views]);
useEffect(() => {
......@@ -1882,6 +1957,19 @@ export default function ProblemRectificationListPage() {
hidden: ({ record }) => !resolveDelayRequestDisplay(record),
onClick: ({ record }) => setDelayDetailRecord(record),
},
{
key: 'maintain-rectification',
label: '维护整改内容',
icon: Edit,
variant: 'secondary',
hidden: ({ record }) => !maintenanceFlowByRecordId[record.id],
onClick: ({ record }) => {
window.location.href = getProblemRectificationFlowTableUrl(
maintenanceFlowByRecordId[record.id],
{ recordId: record.id },
);
},
},
{
key: 'lock',
label: '锁定',
......@@ -2297,12 +2385,12 @@ export default function ProblemRectificationListPage() {
<Card className="mt-6" variant="secondary">
<Card.Header>
<Card.Title>本次推送记录</Card.Title>
<Card.Title>本次推送检查项目名称</Card.Title>
</Card.Header>
<Card.Content className="flex flex-wrap gap-2">
{selectedRecords.map((record) => (
<Chip key={record.id} size="sm">
{record.title || record.projectName || record.name}
{getPushedProjectName(record)}
</Chip>
))}
</Card.Content>
......
......@@ -187,15 +187,54 @@ function summarizeDepartmentAuditStatus(values = []) {
}
function summarizeDate(values = []) {
const normalizedValues = uniqueTruthy(values);
return normalizedValues.length === 1 ? normalizedValues[0] : null;
return values.reduce((latestValue, value) => {
const normalizedValue = normalizeText(value);
if (!normalizedValue) {
return latestValue;
}
const parsedValue = dayjs(normalizedValue);
if (!parsedValue.isValid()) {
return latestValue;
}
return !latestValue || parsedValue.isAfter(dayjs(latestValue))
? normalizedValue
: latestValue;
}, null);
}
export function getProblemRectificationFlowTableUrl(messageName) {
const query = messageName ? `?id=${encodeURIComponent(messageName)}` : '';
function collectDepartmentProofMaterials(items = []) {
const seenKeys = new Set();
return items
.flatMap((item) => toArray(item?.rectification_proof_materials))
.filter((file) => {
if (!file) {
return false;
}
const fileKey = normalizeText(
file?.url || `${file?.name || file?.title || ''}-${file?.size || ''}`,
);
if (!fileKey || seenKeys.has(fileKey)) {
return false;
}
seenKeys.add(fileKey);
return true;
});
}
export function getProblemRectificationFlowTableUrl(messageName, { recordId = '' } = {}) {
const query = new URLSearchParams();
if (messageName) {
query.set('id', messageName);
}
if (recordId) {
query.set('record', recordId);
}
const basePath = String(PROBLEM_RECTIFICATION_APP_BASE_PATH || '/')
.replace(/\/+$/, '');
const appPath = `${basePath}${PROBLEM_RECTIFICATION_FLOW_TABLE_PATH}${query}`;
const queryString = query.toString();
const appPath = `${basePath}${PROBLEM_RECTIFICATION_FLOW_TABLE_PATH}${queryString ? `?${queryString}` : ''}`;
if (typeof window === 'undefined') {
return appPath;
......@@ -966,6 +1005,9 @@ export function normalizeProblemRectificationRecord(record) {
const summarizedDueDate = hasDepartmentItems
? summarizeDate(departmentItems.map((item) => item.the_time_of_rectification))
: metadata.the_time_of_rectification;
const rectificationProofMaterials = hasDepartmentItems
? collectDepartmentProofMaterials(departmentItems)
: metadata.rectification_proof_materials;
const dueDate = summarizedDueDate
? dayjs(summarizedDueDate)
: null;
......@@ -1043,6 +1085,7 @@ export function normalizeProblemRectificationRecord(record) {
.map(normalizeUserLabel)
.filter(Boolean),
dueDateLabel: formatDateText(summarizedDueDate) || (hasDepartmentItems ? '见部门明细' : '-'),
rectificationProofMaterials,
departmentItems,
hasDepartmentItems,
delayRequest: normalizeDelayRequest(metadata[DELAY_REQUEST_FIELD]),
......
......@@ -188,11 +188,11 @@ type Record = {
- 其余 `select` 类型的可选值必须以线上 Subject 配置中的 `settings.options` 为准,禁止在页面内硬编码
- `problem_discovery_situation` 曾在历史文档和归档对象中出现,但本次主对象字段定义未包含;新页面只应在 Subject View 或线上字段配置存在时展示,不能作为 `problem_rectification` 固定字段依赖
- 问题清单详情弹框中的 `rectification_proof_materials` 支持从 `am_material.metadata.file_asset` 复制文件快照,或上传本地文件后创建 `am_material`。控件运行时读取线上字段的 `multiple`:当前定义为单值时保存 `{name,type,size,url}`;若后续配置为多值则保存文件对象数组,并支持追加、逐项删除及为每个本地文件分别建档。问题字段不保存材料主档 `_id`,材料后续修改或软删除不反向联动问题记录
- `department_rectification_items` 用于新转发流程的部门级整改明细。无论单条还是多条推送,每个问题都按责任部门生成明细;单条问题即使只有 1 个责任部门,也生成 1 条明细。已推送过的问题再次推送时,必须保留原部门明细记录和 `item_id`,只在本次参与推送的原明细行上更新填写人、审批人、流程状态和待办 ID;未参与本次推送的原明细行保持不变,不因弹窗删除部门配置而从父记录移除。转发后 `rectification_progress`、`rectification_situation`、`the_time_of_rectification`、`rectification_proof_materials`、`audit_status`、`final_audit_status` 以明细为事实来源,父字段仅做兼容汇总展示;整改进度、审批状态和最终审批状态按部门明细中最落后的状态汇总,例如“整改中”优先于“已整改”,“未审批”优先于“已审批”
- `department_rectification_items` 用于新转发流程的部门级整改明细。无论单条还是多条推送,每个问题都按责任部门生成明细;单条问题即使只有 1 个责任部门,也生成 1 条明细。已推送过的问题再次推送时,必须保留原部门明细记录和 `item_id`,只在本次参与推送的原明细行上更新填写人、审批人、流程状态和待办 ID;未参与本次推送的原明细行保持不变,不因弹窗删除部门配置而从父记录移除。转发后 4 个业务字段均以明细为事实来源:父对象 `rectification_progress` 汇总最落后的部门进度,`the_time_of_rectification` 汇总最晚的部门预计完成时间;问题清单父行直接收集全部部门明细的 `rectification_proof_materials` 用于展示,不写回主对象,也不要求主对象文件字段改为多值;`rectification_situation` 不汇总到父对象,继续按部门明细展示。审批状态和最终审批状态也按部门明细中最落后的状态汇总,例如“未审批”优先于“已审批”
- `problem_rectification_delay_request` 为历史问题级单次延期申请嵌套对象,与 `department_rectification_items` 平级;新发起延期默认按部门写入 `problem_rectification_delay_request_items`
- `problem_rectification_delay_request_items` 为部门级延期申请明细数组;每个责任部门 1 条延期申请,独立保存填写人、一级审批人、最终审批人、申请完成时间、延期事由、状态和待办 ID
- `is_locked` 为问题整改主记录的状态锁定标记;仅整改进度汇总值为 `已整改` 的记录允许写入 `true`,锁定后所有业务字段和流程状态均不可修改,只有解锁操作可以将其写回 `false`
- 部门明细的填写人只能编辑自己负责明细的整改业务字段,送审后(`editor_status = "已完成"`)不再允许编辑;审批人审批通过后(`audit_status = "已审批"`)不再允许编辑,审批驳回后(`audit_status = "已驳回"`)审批人可以编辑该明细的整改业务字段,填写人也恢复编辑。审批人恢复的只是业务字段编辑权限,不获得填写人的送审权限。流程推送人可以编辑本次推送关联的全部部门明细整改业务字段,但流程页不提供父问题记录编辑或详情抽屉。审批状态、待办 ID、明细 ID 等流程控制字段不作为普通表单字段开放
- 部门明细的填写人只能编辑自己负责明细的整改业务字段,流程推送人可以编辑本次推送关联的全部部门明细整改业务字段;填写人或流程推送人均可提交对应明细或整批流程审批。提交后(`editor_status = "已完成"`)不再允许填写人编辑;在一级审批人尚未审批且 `audit_status = "未审批"`、`final_audit_status = "未审批"` 时,填写人或流程推送人可以撤回送审。撤回后该明细回到 `editor_status = "未完成"`、`audit_status = "未审批"`、`final_audit_status = "未审批"`,清空审批与最终审批待办 ID,并将已创建的审批 OA 待办置为完成,审批人无需继续审批。审批人审批通过后(`audit_status = "已审批"`)不再允许编辑,审批驳回后(`audit_status = "已驳回"`)审批人可以编辑该明细的整改业务字段,填写人也恢复编辑。审批人恢复的只是业务字段编辑权限,不获得提交审批权限。流程页不提供父问题记录编辑或详情抽屉,审批状态、待办 ID、明细 ID 等流程控制字段不作为普通表单字段开放
- 推送人修改父问题记录或部门明细时,不自动重置该明细已经形成的审批状态,也不自动重新生成流程待办
### 3.1.3 `department_rectification_items` 子结构
......@@ -207,7 +207,7 @@ type Record = {
| 整改情况 | `rectification_situation` | long_text | 否 | 填写人维护 |
| 延期事由(兼容) | `delay_reason` | long_text | 否 | 已存在的明细字段;新延期流程改为写入问题主记录平级嵌套对象的同名字段,本字段仅保留兼容数据 |
| 预计整改完成时间 | `the_time_of_rectification` | date | 否 | 填写人维护,提交时保存 ISO 8601 |
| 整改证明材料 | `rectification_proof_materials` | file | 否 | 填写人上传,按部门明细独立保存 |
| 整改证明材料 | `rectification_proof_materials` | file | 否 | 填写人或推送人上传,按部门明细独立保存;问题清单父行直接汇总展示全部部门材料,不写回主对象字段 |
| 填写状态 | `editor_status` | text | 否 | `未完成`、`已完成` |
| 审批状态 | `audit_status` | text | 否 | `未审批`、`已审批`、`已驳回` |
| 最终审批状态 | `final_audit_status` | text | 否 | `未审批`、`已审批`、`已驳回` |
......@@ -402,11 +402,11 @@ type Record = {
- `flow_mode` 区分流程:`department` 表示单条问题按责任部门生成整改明细行,`batch` 表示多选问题的单填写人 / 单审批人简单推送,`delay` 表示历史问题级延期申请,`delay_department` 表示按责任部门生成延期申请明细
- `department_item_ids` 保存本次流程涉及的部门级 `item_id`;单条和多条整改流程对应 `department_rectification_items[].item_id`,延期流程对应 `problem_rectification_delay_request_items[].item_id`
- 部分线上 `message_reference_id` 对象会过滤未配置的 `flow_mode` 或 `department_item_ids`;延期流程页须回退按关联问题 `problem_rectification_delay_request_items[].flow_record_id._id` 匹配当前流程主单 `_id`,或按共享的 `progress_id_editor` 匹配,不能将其当作普通整改流程
- 批量流程的填写、复核、最终审批状态及待办 ID 直接使用流程主单顶层的 `editor_status`、`auditor_status`、`sender_status` 和 `progress_id_*`,同一批次的全部问题共享流程状态
- 批量流程以各 `department_rectification_items[]` 的填写、复核、最终审批状态及待办 ID 为行级事实来源,支持逐行提交和审批;流程主单顶层的 `editor_status`、`auditor_status`、`sender_status` 仅汇总全部关联明细是否完成,`progress_id_*` 用于保存该批次仍有效的待办 ID
- `editor_status`:当前项目统一使用 `未完成`、`已完成`
- `auditor_status`:当前项目统一使用 `未完成`、`已完成`
- `sender_status`:当前项目统一使用 `未完成`、`已完成`
- 当批量流程复核或最终审核驳回时,流程主单 `editor_status` 重置为 `未完成` 并回写新的 `progress_id_editor`,同一批次的全部问题共同重新进入填写阶段
- 当批量流程单行复核或最终审核驳回时,只将该部门明细 `editor_status` 重置为 `未完成` 并回写新的 `progress_id_editor`;流程主单顶层 `editor_status` 汇总为 `未完成`,其它已提交或已审批明细保持原状态
## 3.6 项目管理
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment