Commit 426a2639 authored by David Yang's avatar David Yang

feat: support single and multiple audit materials

parent 5206d42b
......@@ -9,6 +9,7 @@ export default function FileUploadField({
description = '将文件拖到此处,或从本地选择。',
isDisabled = false,
fileName = '',
multiple = false,
onFilesChange,
triggerLabel = '选择文件',
}) {
......@@ -29,9 +30,9 @@ export default function FileUploadField({
if (fileItems.length === 0) return;
const files = await Promise.all(fileItems.map((item) => item.getFile()));
emitFiles(files.slice(0, 1));
emitFiles(multiple ? files : files.slice(0, 1));
},
[emitFiles],
[emitFiles, multiple],
);
return (
......@@ -44,6 +45,7 @@ export default function FileUploadField({
<DropZone.Input
accept={accept}
disabled={isDisabled}
multiple={multiple}
onSelect={emitFiles}
/>
<DropZone.Icon>
......@@ -51,7 +53,9 @@ export default function FileUploadField({
</DropZone.Icon>
<DropZone.Label>{fileName || '将文件拖到此处'}</DropZone.Label>
<DropZone.Description>
{fileName ? '已选择文件,可重新选择进行替换。' : description}
{fileName
? `已选择${multiple ? '文件,可继续添加。' : '文件,可重新选择进行替换。'}`
: description}
</DropZone.Description>
<DropZone.Trigger isDisabled={isDisabled}>
<Upload aria-hidden="true" />
......
......@@ -399,6 +399,7 @@ function ControlledFieldEditor({
<ProblemRectificationMaterialField
editSessionId={editSessionId}
fieldPath={fieldPath}
multiple={Boolean(fieldDef?.multiple)}
onChange={onChange}
onPendingLocalMaterialChange={onPendingLocalMaterialChange}
onValidationChange={onValidationChange}
......
......@@ -268,12 +268,33 @@ const RecordDetailDrawer = (props, ref) => {
}, []);
const handlePendingLocalMaterialChange = useCallback(
(fieldPath, pendingMaterial, editSessionId) => {
(fieldPath, change, editSessionId) => {
if (editSessionId !== editSessionRef.current) return;
const nextPendingMaterials = { ...pendingLocalMaterialsRef.current };
if (pendingMaterial) {
nextPendingMaterials[fieldPath] = pendingMaterial;
const currentFieldMaterials = Array.isArray(nextPendingMaterials[fieldPath])
? nextPendingMaterials[fieldPath]
: [];
let nextFieldMaterials = currentFieldMaterials;
if (change?.type === 'replace') {
nextFieldMaterials = change.materials || [];
} else if (change?.type === 'add') {
const materialsById = new Map(
currentFieldMaterials.map((material) => [material.clientId, material]),
);
(change.materials || []).forEach((material) => {
materialsById.set(material.clientId, material);
});
nextFieldMaterials = [...materialsById.values()];
} else if (change?.type === 'remove') {
nextFieldMaterials = currentFieldMaterials.filter(
(material) => material.clientId !== change.clientId,
);
}
if (nextFieldMaterials.length > 0) {
nextPendingMaterials[fieldPath] = nextFieldMaterials;
} else {
delete nextPendingMaterials[fieldPath];
}
......@@ -292,21 +313,25 @@ const RecordDetailDrawer = (props, ref) => {
const pendingMaterials = Object.entries(pendingLocalMaterialsRef.current)
.filter(([fieldPath]) => dirtyFields[fieldPath]);
for (const [fieldPath, pendingMaterial] of pendingMaterials) {
for (const [fieldPath, fieldMaterials] of pendingMaterials) {
for (const pendingMaterial of fieldMaterials) {
if (pendingMaterial.materialRecordId) continue;
const createdMaterial = await createProblemRectificationMaterial({
fileAsset: pendingMaterial.fileAsset,
currentUser,
});
const currentFieldMaterials = pendingLocalMaterialsRef.current[fieldPath] || [];
pendingLocalMaterialsRef.current = {
...pendingLocalMaterialsRef.current,
[fieldPath]: {
...pendingMaterial,
materialRecordId: createdMaterial.id,
},
[fieldPath]: currentFieldMaterials.map((material) =>
material.clientId === pendingMaterial.clientId
? { ...material, materialRecordId: createdMaterial.id }
: material,
),
};
}
}
await handleFieldsUpdate(Object.values(dirtyFields));
setIsEditing(false);
......
......@@ -146,7 +146,7 @@
- `am_material`:审计材料中心共享主档。问题清单详情可查询已有主档;从本地上传证明材料并保存详情时同步创建主档
问题记录仍在 `metadata.rectification_proof_materials` 保存 `{name,type,size,url}` 文件快照,不保存材料主档 `_id`,不建立自动联动关系。
问题记录仍在 `metadata.rectification_proof_materials` 保存文件快照,不保存材料主档 `_id`,不建立自动联动关系。字段单值时保存 `{name,type,size,url}`,字段配置为多值时保存上述文件对象数组。
### 4.4 流程辅助对象
......@@ -706,7 +706,7 @@
- 原主表中的多值选择 `responsible_department` 在旧实现中被转成逗号拼接文本
- `fill_in_the_people`、`review_people` 继续保存为用户 `_id[]`
- `rectification_proof_materials` 作为文件对象原样保留
- `rectification_proof_materials` 按线上字段的单值或多值形态保留文件对象或文件对象数组
- 归档是快照,后续原问题记录再变化,不应反向污染归档结果
- 归档明细导入失败时不创建归档批次;归档批次创建失败时应提示用户归档未完整完成,避免出现无法从批次入口追溯的孤立明细
......@@ -756,11 +756,11 @@
整改证明材料交互限定在问题清单详情弹框,不改变整改处理页、整改流程表格和归档详情:
1. “材料中心”首次读取当前用户可访问的最近 20 条 `am_material`,支持按材料标题或文件名远程搜索;选中后仅把 `metadata.file_asset` 快照写入详情草稿。
2. “本地上传”选择文件后立即调用 `/upload`,上传期间禁止保存;成功后将文件对象写入详情草稿,并提示保存后同步到材料中心。
3. 用户点击详情“保存”时,先创建 `am_material`,写入 `source_app = issue_rectification`、当前用户 `_id` 和 ISO 上传时间,再更新 `problem_rectification.metadata.rectification_proof_materials`。
1. “材料中心”首次读取当前用户可访问的最近 20 条 `am_material`,支持按材料标题或文件名远程搜索;选中后仅把 `metadata.file_asset` 快照写入详情草稿。控件运行时读取字段 `multiple`:单值选择会替换原附件,多值选择会追加并按文件地址去重,附件可逐项删除。
2. “本地上传”选择文件后立即调用 `/upload`,上传期间禁止保存及修改当前附件;单值只接收一个文件,多值可一次选择或拖入多个文件。批量上传成功项追加到详情草稿,失败项显示文件级汇总错误并允许重新选择。
3. 用户点击详情“保存”时,为每个本次新增且仍保留在草稿中的本地文件分别创建 `am_material`,写入 `source_app = issue_rectification`、当前用户 `_id` 和 ISO 上传时间;所有待建档项成功后,再按单值对象或多值对象数组更新 `problem_rectification.metadata.rectification_proof_materials`。
4. 用户取消编辑时不创建材料主档;选择已有材料不重复上传、不创建新主档。
5. 材料创建失败时保留编辑草稿且不更新问题;材料创建成功但问题更新失败时保留已建档标记,再次保存只重试问题更新,避免重复材料。
5. 任一材料创建失败时保留编辑草稿且不更新问题;同一批次已创建成功的材料,以及材料创建成功但问题更新失败的情况,均保留逐项建档标记,再次保存只创建尚未成功的材料并重试问题更新,避免重复材料。
6. 清空或替换问题附件不删除材料中心主档或底层文件;已有材料后续修改或软删除不联动历史问题快照。
建议字段分组:
......
......@@ -269,7 +269,7 @@ Query:`calculated=true|false`
## 5. 问题详情与审计材料中心接入
问题清单详情弹框中的 `metadata.rectification_proof_materials` 支持从统一材料池选择已有文件,或上传本地文件后同步建档。整改处理页、整改流程表格和归档详情不使用本节流程。
问题清单详情弹框中的 `metadata.rectification_proof_materials` 支持从统一材料池选择已有文件,或上传本地文件后同步建档。控件必须运行时读取 Subject 字段的 `multiple` 配置:单值字段保存一个文件对象,多值字段保存文件对象数组。整改处理页、整改流程表格和归档详情不使用本节流程。
### 5.1 查询已有材料
......@@ -301,12 +301,12 @@ Query:`calculated=true|false`
}
```
搜索关键词必须先转义正则特殊字符。选中材料后,只将 `metadata.file_asset` 的 `{name,type,size,url}` 快照写入问题详情草稿,不保存 `am_material` `_id`。
搜索关键词必须先转义正则特殊字符。选中材料后,只将 `metadata.file_asset` 的 `{name,type,size,url}` 快照写入问题详情草稿,不保存 `am_material` `_id`。单值字段选择后替换原值;多值字段允许连续选择并追加,按文件地址去重,支持逐项删除。
### 5.2 本地上传并写入材料中心
1. 选择文件后立即调用 `POST /upload`,将返回结果规范化为 `{name,type,size,url}` 并保留在详情草稿。
2. 用户点击详情“保存”时调用 `POST /record` 创建材料主档:
1. 选择文件后立即调用 `POST /upload`,将返回结果规范化为 `{name,type,size,url}` 并保留在详情草稿。单值字段只接收一个文件并替换原值;多值字段允许一次选择或拖入多个文件,每个文件分别上传,成功项追加到草稿,失败项保留错误提示并可重新选择。
2. 用户点击详情“保存”时,为每个本次本地上传且仍保留在草稿中的文件分别调用 `POST /record` 创建材料主档:
```json
{
......@@ -328,6 +328,6 @@ Query:`calculated=true|false`
}
```
3. 材料主档创建成功后,再调用 `PUT /record/:name` 更新问题记录的 `metadata.rectification_proof_materials`。
4. 材料创建失败时不更新问题记录;材料创建成功但问题更新失败时,当前编辑会话记住已创建的材料,重试保存不得重复创建。
5. 取消详情编辑不创建 `am_material`;已经上传但未建档的二进制文件遵循上传服务自身的清理策略。
3. 所有待建档材料创建成功后,再调用 `PUT /record/:name` 更新问题记录的 `metadata.rectification_proof_materials`,单值写对象,多值写对象数组。
4. 任一材料创建失败时不更新问题记录;一批材料中已创建成功的条目和“材料已创建、问题更新失败”的条目都在当前编辑会话中保留建档标记,重试保存只处理尚未建档的条目,不得重复创建。
5. 用户从草稿逐项删除本地附件后,该附件不再参与材料建档;取消详情编辑不创建 `am_material`。已经上传但未建档的二进制文件遵循上传服务自身的清理策略。
......@@ -78,7 +78,7 @@ type Record = {
- `number`:数字
- `boolean`:`true / false`
- `select`:传选项的存储值,不传显示文案
- `file`:对象,单文件时建议结构为 `{ name, type, size, url }`
- `file`:单值字段保存 `{ name, type, size, url }`,`multiple=true` 时保存上述文件对象数组
- `object`:对象或对象数组,前端优先使用结构化表单编辑,无现成交互时再退回 JSON 校验方案
### 2.4 `reference`
......@@ -187,7 +187,7 @@ type Record = {
- 新增驾驶舱扩展字段当前在线上 Subject 中大多仍为 `text` 类型,前端工作台按标准值引导录入,用于支持后续严重程度、问题分类、问题原因和整改质效分析
- 其余 `select` 类型的可选值必须以线上 Subject 配置中的 `settings.options` 为准,禁止在页面内硬编码
- `problem_discovery_situation` 曾在历史文档和归档对象中出现,但本次主对象字段定义未包含;新页面只应在 Subject View 或线上字段配置存在时展示,不能作为 `problem_rectification` 固定字段依赖
- 问题清单详情弹框中的 `rectification_proof_materials` 支持从 `am_material.metadata.file_asset` 复制文件快照,或上传本地文件后创建 `am_material`。问题字段仍只保存 `{name,type,size,url}`,不保存材料主档 `_id`,材料后续修改或软删除不反向联动问题记录
- 问题清单详情弹框中的 `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` 以明细为事实来源,父字段仅做兼容汇总展示;整改进度、审批状态和最终审批状态按部门明细中最落后的状态汇总,例如“整改中”优先于“已整改”,“未审批”优先于“已审批”
- `problem_rectification_delay_request` 为历史问题级单次延期申请嵌套对象,与 `department_rectification_items` 平级;新发起延期默认按部门写入 `problem_rectification_delay_request_items`
- `problem_rectification_delay_request_items` 为部门级延期申请明细数组;每个责任部门 1 条延期申请,独立保存填写人、一级审批人、最终审批人、申请完成时间、延期事由、状态和待办 ID
......@@ -308,7 +308,7 @@ type Record = {
| 材料分类 | `archive_category` | select | 否 | 问题详情不写入 |
| 所属周期/年份 | `archive_year` | text | 否 | 问题详情不写入 |
问题详情选择已有材料时读取 `material_title` 与 `file_asset`;搜索范围为 `metadata.material_title` 和 `metadata.file_asset.name`。列表只展示当前登录用户通过 Record 权限可读取且 `file_asset.url` 有效的记录。
问题详情选择已有材料时读取 `material_title` 与 `file_asset`;搜索范围为 `metadata.material_title` 和 `metadata.file_asset.name`。列表只展示当前登录用户通过 Record 权限可读取且 `file_asset.url` 有效的记录。业务文件字段为多值时,每个本地上传文件仍分别创建一条 `am_material`,业务记录保存对应的文件快照数组。
## 3.3 问题整改归档明细
......
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