For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: 补齐 S8 中"处理人提交已修复 → 检验人检验 → RESOLVED → 提交关闭申请"的完整业务闭环,引入 PENDING_VERIFICATION 中间状态,检验人由处理人提交复检时手选。
Architecture: 新增 PENDING_VERIFICATION 状态和 VerifierId 等字段到主表;在 S8TaskFlowService 增加三个方法(submit-verification / approve-verification / reject-verification);前端详情页在 v-else 区域按角色+状态控制按钮显隐,检验动作与审批流无关,不受 hasActiveFlow 影响。
Tech Stack: C# / SqlSugar(后端)、Vue 3 Composition API + Element Plus(前端)、PostgreSQL(DB 迁移用 SQL 文件,非 CodeFirst)
关键文件路径(执行时直接用绝对路径):
| 文件 | 用途 |
|---|---|
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/S8/S8StatusRules.cs |
状态机边集 |
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/S8/S8Labels.cs |
状态中文标签 + 选项列表 |
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8Exception.cs |
主表实体 |
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8TaskFlowService.cs |
业务流转服务 |
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8DictionaryService.cs |
字典接口 |
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8ExceptionService.cs |
查询服务(pendingStatuses 在此) |
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S8/AdoS8Dtos.cs |
DTO 定义 |
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S8/AdoS8ExceptionsController.cs |
HTTP 控制器 |
AiDOPWarehouse/Web/src/views/aidop/s8/api/s8ExceptionApi.ts |
前端 API 封装 |
AiDOPWarehouse/Web/src/views/aidop/s8/exceptions/S8TaskDetailPage.vue |
前端详情页 |
现有状态机边集(修改前):
NEW->ASSIGNED, NEW->REJECTED
ASSIGNED->IN_PROGRESS, ASSIGNED->ESCALATED, ASSIGNED->REJECTED
IN_PROGRESS->RESOLVED, IN_PROGRESS->ESCALATED, IN_PROGRESS->REJECTED
RESOLVED->CLOSED, RESOLVED->IN_PROGRESS
ESCALATED->ASSIGNED, ESCALATED->IN_PROGRESS
REJECTED->NEW
注意:IN_PROGRESS->RESOLVED 边已存在,但目前无后端方法能触发它。
Files:
AiDOPWarehouse/doc/migrations/2026-04-18_s8_verification_fields.sqlStep 1: 编写迁移 SQL
-- 2026-04-18: S8 复检闭环 - 新增检验相关字段
ALTER TABLE ado_s8_exception
ADD COLUMN IF NOT EXISTS verifier_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS verification_assigned_at TIMESTAMPTZ NULL,
ADD COLUMN IF NOT EXISTS verified_at TIMESTAMPTZ NULL,
ADD COLUMN IF NOT EXISTS verification_result VARCHAR(32) NULL,
ADD COLUMN IF NOT EXISTS verification_remark VARCHAR(2000) NULL;
COMMENT ON COLUMN ado_s8_exception.verifier_id IS '检验人ID(处理人提交复检时手选)';
COMMENT ON COLUMN ado_s8_exception.verification_assigned_at IS '提交复检时间';
COMMENT ON COLUMN ado_s8_exception.verified_at IS '检验完成时间';
COMMENT ON COLUMN ado_s8_exception.verification_result IS 'APPROVED / REJECTED';
COMMENT ON COLUMN ado_s8_exception.verification_remark IS '检验意见';
Step 2: 在开发数据库执行该脚本
psql -U <db_user> -d <db_name> -f AiDOPWarehouse/doc/migrations/2026-04-18_s8_verification_fields.sql
预期:无报错,\d ado_s8_exception 能看到 5 个新列。
Step 3: Commit
git add AiDOPWarehouse/doc/migrations/2026-04-18_s8_verification_fields.sql
git commit -m "chore: add s8 verification fields migration"
Files:
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8Exception.csAiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/S8/S8StatusRules.csAiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/S8/S8Labels.csAiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8DictionaryService.csAiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8ExceptionService.csStep 1: 在 AdoS8Exception.cs 末尾(IsDeleted 字段之前)新增 5 个字段
在 SourcePayload 属性后、类的最后一个 } 前插入:
/// <summary>检验人ID(提交复检时手选)</summary>
[SugarColumn(ColumnName = "verifier_id", ColumnDataType = "bigint", IsNullable = true)]
public long? VerifierId { get; set; }
/// <summary>提交复检时间</summary>
[SugarColumn(ColumnName = "verification_assigned_at", IsNullable = true)]
public DateTime? VerificationAssignedAt { get; set; }
/// <summary>检验完成时间</summary>
[SugarColumn(ColumnName = "verified_at", IsNullable = true)]
public DateTime? VerifiedAt { get; set; }
/// <summary>检验结果(APPROVED / REJECTED)</summary>
[SugarColumn(ColumnName = "verification_result", Length = 32, IsNullable = true)]
public string? VerificationResult { get; set; }
/// <summary>检验意见</summary>
[SugarColumn(ColumnName = "verification_remark", Length = 2000, IsNullable = true)]
public string? VerificationRemark { get; set; }
Step 2: 在 S8StatusRules.cs 的 Edges HashSet 中新增两条边
在 ("IN_PROGRESS", "RESOLVED"), 这行后面插入:
("IN_PROGRESS", "PENDING_VERIFICATION"),
("PENDING_VERIFICATION", "RESOLVED"),
("PENDING_VERIFICATION", "IN_PROGRESS"),
Step 3: 在 S8Labels.cs 的 StatusLabel 方法中新增分支
在 "ESCALATED" => "已升级", 后面插入:
"PENDING_VERIFICATION" => "待检验",
同时在 StatusOptions() 方法的数组中新增 "PENDING_VERIFICATION",放在 "RESOLVED" 之后:
将:
public static object[] StatusOptions() =>
new[] { "NEW", "ASSIGNED", "IN_PROGRESS", "RESOLVED", "CLOSED", "REJECTED", "ESCALATED" }
改为:
public static object[] StatusOptions() =>
new[] { "NEW", "ASSIGNED", "IN_PROGRESS", "PENDING_VERIFICATION", "RESOLVED", "CLOSED", "REJECTED", "ESCALATED" }
Step 4: 在 S8DictionaryService.cs 的 S8_EXCEPTION_STATUS case 中新增一项
在 new { value = "RESOLVED", label = "已处理" }, 后面插入:
new { value = "PENDING_VERIFICATION", label = "待检验" },
Step 5: 在 S8ExceptionService.cs 的 pendingStatuses 数组中新增 PENDING_VERIFICATION
将:
var pendingStatuses = new[] { "NEW", "ASSIGNED", "IN_PROGRESS" };
改为:
var pendingStatuses = new[] { "NEW", "ASSIGNED", "IN_PROGRESS", "PENDING_VERIFICATION" };
Step 6: 编译验证
cd AiDOPWarehouse/server
dotnet build --no-restore 2>&1 | tail -20
预期:Build succeeded,无 error。
Step 7: Commit
git add AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8Exception.cs \
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/S8/S8StatusRules.cs \
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/S8/S8Labels.cs \
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8DictionaryService.cs \
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8ExceptionService.cs
git commit -m "feat: add PENDING_VERIFICATION status to S8 state machine"
Files:
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S8/AdoS8Dtos.csStep 1: 在文件末尾追加三个 DTO 类
public class AdoS8SubmitVerificationDto
{
public long VerifierId { get; set; }
public string? Remark { get; set; }
}
public class AdoS8ApproveVerificationDto
{
public string? Remark { get; set; }
}
public class AdoS8RejectVerificationDto
{
public string Remark { get; set; } = string.Empty;
}
同时在 AdoS8ExceptionDetailDto 类中(ActiveFlowBizType 属性后)新增检验字段的输出:
public long? VerifierId { get; set; }
public string? VerifierName { get; set; }
public DateTime? VerificationAssignedAt { get; set; }
public DateTime? VerifiedAt { get; set; }
public string? VerificationResult { get; set; }
public string? VerificationRemark { get; set; }
Step 2: 编译验证
cd AiDOPWarehouse/server
dotnet build --no-restore 2>&1 | tail -5
Step 3: Commit
git add AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S8/AdoS8Dtos.cs
git commit -m "feat: add verification DTOs for S8"
Files:
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8TaskFlowService.csAiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8ExceptionService.csStep 1: 在 S8TaskFlowService.cs 的 CommentAsync 方法前(即 private Task<AdoS8Exception?> LoadAsync 之前)插入三个新方法
public async Task<AdoS8Exception> SubmitVerificationAsync(
long id, long tenantId, long factoryId,
long currentUserId, long verifierId, string? remark)
{
var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
if (e.AssigneeId != currentUserId)
throw new S8BizException("只有当前处理人才能提交复检");
if (verifierId <= 0)
throw new S8BizException("请选择检验人");
if (!S8StatusRules.IsAllowedTransition(e.Status, "PENDING_VERIFICATION"))
throw new S8BizException($"状态 {e.Status} 不可提交复检");
var from = e.Status;
e.Status = "PENDING_VERIFICATION";
e.VerifierId = verifierId;
e.VerificationAssignedAt = DateTime.Now;
e.UpdatedAt = DateTime.Now;
await _rep.AsTenant().UseTranAsync(async () =>
{
await _rep.UpdateAsync(e);
await InsertTimelineAsync(e.Id, "VERIFY_SUBMITTED", "提交复检", from, "PENDING_VERIFICATION",
currentUserId, null, remark);
}, ex => throw ex);
return e;
}
public async Task<AdoS8Exception> ApproveVerificationAsync(
long id, long tenantId, long factoryId,
long currentUserId, string? remark)
{
var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
if (e.VerifierId != currentUserId)
throw new S8BizException("只有指定检验人才能检验通过");
if (!S8StatusRules.IsAllowedTransition(e.Status, "RESOLVED"))
throw new S8BizException($"状态 {e.Status} 不可检验通过");
var from = e.Status;
e.Status = "RESOLVED";
e.VerifiedAt = DateTime.Now;
e.VerificationResult = "APPROVED";
e.VerificationRemark = remark;
e.UpdatedAt = DateTime.Now;
await _rep.AsTenant().UseTranAsync(async () =>
{
await _rep.UpdateAsync(e);
await InsertTimelineAsync(e.Id, "VERIFY_APPROVED", "检验通过", from, "RESOLVED",
currentUserId, null, remark);
}, ex => throw ex);
return e;
}
public async Task<AdoS8Exception> RejectVerificationAsync(
long id, long tenantId, long factoryId,
long currentUserId, string remark)
{
var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
if (e.VerifierId != currentUserId)
throw new S8BizException("只有指定检验人才能检验退回");
if (!S8StatusRules.IsAllowedTransition(e.Status, "IN_PROGRESS"))
throw new S8BizException($"状态 {e.Status} 不可检验退回");
if (string.IsNullOrWhiteSpace(remark))
throw new S8BizException("检验退回必须填写退回原因");
var from = e.Status;
e.Status = "IN_PROGRESS";
e.VerifiedAt = DateTime.Now;
e.VerificationResult = "REJECTED";
e.VerificationRemark = remark;
e.UpdatedAt = DateTime.Now;
await _rep.AsTenant().UseTranAsync(async () =>
{
await _rep.UpdateAsync(e);
await InsertTimelineAsync(e.Id, "VERIFY_REJECTED", "检验退回", from, "IN_PROGRESS",
currentUserId, null, remark);
}, ex => throw ex);
return e;
}
Step 2: 在 S8ExceptionService.GetDetailAsync 的 Select 投影中新增检验字段
找到 Select((e, sc) => new AdoS8ExceptionDetailDto 投影块,在 ActiveFlowBizType = e.ActiveFlowBizType 后追加:
VerifierId = e.VerifierId,
VerificationAssignedAt = e.VerificationAssignedAt,
VerifiedAt = e.VerifiedAt,
VerificationResult = e.VerificationResult,
VerificationRemark = e.VerificationRemark,
Step 3: 在 S8ExceptionService.FillDisplayNamesAsync 中填充 VerifierName
在 empIds 收集处,将:
var empIds = list
.Select(x => x.AssigneeId ?? 0L)
.Concat(list.OfType<AdoS8ExceptionDetailDto>().Select(x => x.ReporterId ?? 0L))
改为:
var empIds = list
.Select(x => x.AssigneeId ?? 0L)
.Concat(list.OfType<AdoS8ExceptionDetailDto>().Select(x => x.ReporterId ?? 0L))
.Concat(list.OfType<AdoS8ExceptionDetailDto>().Select(x => x.VerifierId ?? 0L))
在 FillDisplayNamesAsync 的 foreach 里,在 detail.ReporterName = ... 后追加:
detail.VerifierName = detail.VerifierId.HasValue ? empMap.GetValueOrDefault(detail.VerifierId.Value) : null;
Step 4: 编译验证
cd AiDOPWarehouse/server
dotnet build --no-restore 2>&1 | tail -5
预期:Build succeeded。
Step 5: Commit
git add AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8TaskFlowService.cs \
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8ExceptionService.cs
git commit -m "feat: add submit/approve/reject verification methods to S8TaskFlowService"
Files:
AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S8/AdoS8ExceptionsController.csStep 1: 在 CommentAsync Action 前插入三个新 Action
[HttpPost("{id:long}/submit-verification")]
public async Task<IActionResult> SubmitVerificationAsync(long id,
[FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1,
[FromBody] AdoS8SubmitVerificationDto? body = null)
{
try
{
// currentUserId 实际项目接入 JWT 后从 HttpContext.User 取;Demo 阶段从 body 或 query 临时传入
var currentUserId = body?.VerifierId > 0 ? 0L : 0L; // placeholder,见 Step 2
var e = await _taskFlowSvc.SubmitVerificationAsync(
id, tenantId, factoryId,
/* currentUserId */ HttpContext.Request.Query.TryGetValue("currentUserId", out var v) && long.TryParse(v, out var uid) ? uid : 0L,
body?.VerifierId ?? 0,
body?.Remark);
return Ok(new { id = e.Id, status = e.Status, verifierId = e.VerifierId });
}
catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); }
}
[HttpPost("{id:long}/approve-verification")]
public async Task<IActionResult> ApproveVerificationAsync(long id,
[FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1,
[FromQuery] long currentUserId = 0,
[FromBody] AdoS8ApproveVerificationDto? body = null)
{
try
{
var e = await _taskFlowSvc.ApproveVerificationAsync(id, tenantId, factoryId, currentUserId, body?.Remark);
return Ok(new { id = e.Id, status = e.Status });
}
catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); }
}
[HttpPost("{id:long}/reject-verification")]
public async Task<IActionResult> RejectVerificationAsync(long id,
[FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1,
[FromQuery] long currentUserId = 0,
[FromBody] AdoS8RejectVerificationDto? body = null)
{
try
{
var e = await _taskFlowSvc.RejectVerificationAsync(id, tenantId, factoryId, currentUserId, body?.Remark ?? string.Empty);
return Ok(new { id = e.Id, status = e.Status });
}
catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); }
}
说明: Demo 阶段没有 JWT 鉴权,
currentUserId通过 query 参数传入,与现有claim/transfer等接口的tenantId/factoryId风格保持一致。生产接入 JWT 后改从HttpContext.User取即可,服务层逻辑不用改。
Step 2: 清理 submit-verification 中的 placeholder 注释
将上面 Step 1 中 submit-verification Action 的 currentUserId 提取行简化为:
[HttpPost("{id:long}/submit-verification")]
public async Task<IActionResult> SubmitVerificationAsync(long id,
[FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1,
[FromQuery] long currentUserId = 0,
[FromBody] AdoS8SubmitVerificationDto? body = null)
{
try
{
var e = await _taskFlowSvc.SubmitVerificationAsync(
id, tenantId, factoryId, currentUserId, body?.VerifierId ?? 0, body?.Remark);
return Ok(new { id = e.Id, status = e.Status, verifierId = e.VerifierId });
}
catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); }
}
Step 3: 编译并确认 Swagger 中有三个新端点
cd AiDOPWarehouse/server
dotnet build --no-restore 2>&1 | tail -5
启动后访问 /swagger,搜索 verification,应看到:
POST /api/aidop/s8/exceptions/{id}/submit-verificationPOST /api/aidop/s8/exceptions/{id}/approve-verificationPOST /api/aidop/s8/exceptions/{id}/reject-verificationStep 4: Commit
git add AiDOPWarehouse/server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S8/AdoS8ExceptionsController.cs
git commit -m "feat: add submit/approve/reject-verification endpoints to S8 controller"
Files:
AiDOPWarehouse/Web/src/views/aidop/s8/api/s8ExceptionApi.tsStep 1: 在 S8ExceptionRow 接口新增检验字段
在 activeFlowBizType?: string | null; 后追加:
verifierId?: number | null;
verifierName?: string | null;
verificationAssignedAt?: string | null;
verifiedAt?: string | null;
verificationResult?: string | null;
verificationRemark?: string | null;
Step 2: 在 s8ExceptionApi 对象末尾追加三个方法
submitVerification: (id: number, body: { verifierId: number; remark?: string }, currentUserId: number) =>
service.post(`/api/aidop/s8/exceptions/${id}/submit-verification?currentUserId=${currentUserId}`, body).then(unwrap),
approveVerification: (id: number, body: { remark?: string }, currentUserId: number) =>
service.post(`/api/aidop/s8/exceptions/${id}/approve-verification?currentUserId=${currentUserId}`, body).then(unwrap),
rejectVerification: (id: number, body: { remark: string }, currentUserId: number) =>
service.post(`/api/aidop/s8/exceptions/${id}/reject-verification?currentUserId=${currentUserId}`, body).then(unwrap),
Step 3: Commit
git add AiDOPWarehouse/Web/src/views/aidop/s8/api/s8ExceptionApi.ts
git commit -m "feat: add verification API methods to s8ExceptionApi"
Files:
AiDOPWarehouse/Web/src/views/aidop/s8/exceptions/S8TaskDetailPage.vueStep 1: 在 <script setup> 里的 computed 区域新增检验相关 computed
在 const canClose = computed(...) 后追加:
// Demo 阶段:currentUserId 从 detail 里的 assigneeId 模拟(实际接入 JWT 后改 useUserInfo)
const currentUserId = computed(() => Number(detail.value?.assigneeId ?? 0));
const canSubmitVerification = computed(
() => currentStatus.value === 'IN_PROGRESS' && !!detail.value?.assigneeId
);
const canApproveVerification = computed(
() => currentStatus.value === 'PENDING_VERIFICATION' && !!detail.value?.verifierId
);
const canRejectVerification = computed(
() => currentStatus.value === 'PENDING_VERIFICATION' && !!detail.value?.verifierId
);
说明: Demo 阶段无真实登录态,所有人都能看到所有按钮(按状态控制);实际接入 JWT 后把
currentUserId改成useUserInfo().userInfos.id并对比assigneeId/verifierId即可。
Step 2: 在 actionTitle() 函数的映射对象中新增三项
将:
{
claim: '认领',
transfer: '转派',
upgrade: '升级',
reject: '驳回',
close: '提交关闭申请',
comment: '补充说明',
}[dialogMode.value] ?? '动作'
改为:
{
claim: '认领',
transfer: '转派',
upgrade: '升级',
reject: '驳回',
close: '提交关闭申请',
comment: '补充说明',
submitVerification: '提交复检',
approveVerification: '检验通过',
rejectVerification: '检验退回',
}[dialogMode.value] ?? '动作'
Step 3: 在 submitAction() 函数的 if/else 链末尾,comment 之前插入三个新分支
} else if (dialogMode.value === 'submitVerification') {
if (!actionForm.assigneeId) { ElMessage.warning('请选择检验人'); return; }
await s8ExceptionApi.submitVerification(
id,
{ verifierId: actionForm.assigneeId, remark: actionForm.remark || undefined },
currentUserId.value
);
} else if (dialogMode.value === 'approveVerification') {
await s8ExceptionApi.approveVerification(
id,
{ remark: actionForm.remark || undefined },
currentUserId.value
);
} else if (dialogMode.value === 'rejectVerification') {
if (!actionForm.remark) { ElMessage.warning('检验退回必须填写退回原因'); return; }
await s8ExceptionApi.rejectVerification(
id,
{ remark: actionForm.remark },
currentUserId.value
);
} else if (dialogMode.value === 'comment') {
注意:将原来的 } else if (dialogMode.value === 'comment') { 行保持不变,只在其前面插入三个新分支。
Step 4: Commit
git add AiDOPWarehouse/Web/src/views/aidop/s8/exceptions/S8TaskDetailPage.vue
git commit -m "feat: add verification computed and action handlers to S8TaskDetailPage"
Files:
AiDOPWarehouse/Web/src/views/aidop/s8/exceptions/S8TaskDetailPage.vueStep 1: 在 <template> 的 v-else 操作面板区新增三个检验按钮
找到:
<div class="action-grid">
<el-button type="primary" :disabled="!canClaim" @click="openAction('claim')">认领</el-button>
<el-button :disabled="!canTransfer" @click="openAction('transfer')">转派</el-button>
<el-button type="warning" :disabled="!canUpgrade" @click="openAction('upgrade')">升级</el-button>
<el-button type="danger" :disabled="!canReject" @click="openAction('reject')">驳回</el-button>
<el-button type="success" :disabled="!canClose" @click="openAction('close')">提交关闭申请</el-button>
<el-button @click="openAction('comment')">补充说明</el-button>
</div>
替换为:
<div class="action-grid">
<el-button type="primary" :disabled="!canClaim" @click="openAction('claim')">认领</el-button>
<el-button :disabled="!canTransfer" @click="openAction('transfer')">转派</el-button>
<el-button type="warning" :disabled="!canUpgrade" @click="openAction('upgrade')">升级</el-button>
<el-button type="danger" :disabled="!canReject" @click="openAction('reject')">驳回</el-button>
<el-button type="primary" :disabled="!canSubmitVerification" @click="openAction('submitVerification')">提交复检</el-button>
<el-button type="success" :disabled="!canApproveVerification" @click="openAction('approveVerification')">检验通过</el-button>
<el-button type="warning" :disabled="!canRejectVerification" @click="openAction('rejectVerification')">检验退回</el-button>
<el-button type="success" :disabled="!canClose" @click="openAction('close')">提交关闭申请</el-button>
<el-button @click="openAction('comment')">补充说明</el-button>
</div>
Step 2: 在弹窗 <el-form> 中新增"检验人"选择项(仅 submitVerification 模式显示)
找到已有的处理人选择:
<el-form-item v-if="dialogMode === 'claim' || dialogMode === 'transfer'" label="处理人" required>
改为:
<el-form-item v-if="dialogMode === 'claim' || dialogMode === 'transfer'" label="处理人" required>
<el-select v-model="actionForm.assigneeId" style="width: 100%" filterable clearable>
<el-option v-for="item in employees" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item v-if="dialogMode === 'submitVerification'" label="检验人" required>
<el-select v-model="actionForm.assigneeId" style="width: 100%" filterable clearable>
<el-option v-for="item in employees" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
Step 3: 更新弹窗备注区的 :required 逻辑
找到:
<el-form-item
:label="dialogMode === 'close' ? '处置措施' : dialogMode === 'upgrade' ? '升级原因' : '备注'"
:required="dialogMode === 'upgrade' || dialogMode === 'close'"
>
改为:
<el-form-item
:label="dialogMode === 'close' ? '处置措施' : dialogMode === 'upgrade' ? '升级原因' : dialogMode === 'rejectVerification' ? '退回原因' : '备注'"
:required="dialogMode === 'upgrade' || dialogMode === 'close' || dialogMode === 'rejectVerification'"
>
Step 4: 在详情卡片中展示检验人信息
找到 <el-descriptions-item label="处理人"> 的那行,在其后插入:
<el-descriptions-item label="检验人">{{ detail.verifierName || detail.verifierId || '—' }}</el-descriptions-item>
<el-descriptions-item label="检验结果">{{ detail.verificationResult || '—' }}</el-descriptions-item>
Step 5: Commit
git add AiDOPWarehouse/Web/src/views/aidop/s8/exceptions/S8TaskDetailPage.vue
git commit -m "feat: add verification buttons and verifier info to S8TaskDetailPage"
Step 1: 启动后端
cd AiDOPWarehouse/server
dotnet run --project Admin.NET.Web.Entry
Step 2: 在 Swagger 中执行完整链路
按以下顺序操作(每步记录返回的 status):
| 步骤 | 接口 | 预期 status |
|---|---|---|
| 1 | 已有 IN_PROGRESS 异常(或先 claim + 手动改状态) | IN_PROGRESS |
| 2 | POST /{id}/submit-verification body: {"verifierId":2} query: currentUserId=assigneeId值 |
PENDING_VERIFICATION |
| 3 | GET /{id} |
返回含 verifierId、verificationAssignedAt |
| 4 | GET /{id}/timeline |
末尾出现 VERIFY_SUBMITTED 条目 |
| 5 | POST /{id}/reject-verification body: {"remark":"问题未修复"} query: currentUserId=verifierId值 |
IN_PROGRESS |
| 6 | GET /{id}/timeline |
末尾出现 VERIFY_REJECTED 条目 |
| 7 | 重复步骤 2 | PENDING_VERIFICATION |
| 8 | POST /{id}/approve-verification query: currentUserId=verifierId值 |
RESOLVED |
| 9 | GET /{id}/timeline |
末尾出现 VERIFY_APPROVED |
| 10 | POST /{id}/close body: {"remark":"已修复关闭"} |
触发 EXCEPTION_CLOSURE 审批流,status 视 handler 而定 |
Step 3: 前端验证
启动前端开发服务器:
cd AiDOPWarehouse/Web
npm run dev
打开详情页,按上述 10 步在页面上点击验证:
Step 4: 验证非法操作被拒绝
currentUserId(非 assigneeId)调 submit-verification → 预期 400 只有当前处理人才能提交复检currentUserId(非 verifierId)调 approve-verification → 预期 400 只有指定检验人才能检验通过approve-verification 在状态非 PENDING_VERIFICATION 时调用 → 预期 400Step 5: Final Commit
git add .
git commit -m "feat: S8 verification loop complete - submit/approve/reject verification with PENDING_VERIFICATION state"
NEW → ASSIGNED → IN_PROGRESS → PENDING_VERIFICATION ⇄ IN_PROGRESS
↓
RESOLVED → CLOSED(经 EXCEPTION_CLOSURE 审批)
IN_PROGRESS → ESCALATED → ASSIGNED(经 EXCEPTION_ESCALATION 审批)
各状态 → REJECTED → NEW
PENDING_VERIFICATION 在字典接口 GET /api/aidop/s8/exceptions/filter-options 的 statuses 中出现VERIFY_SUBMITTED / VERIFY_APPROVED / VERIFY_REJECTED 三种条目PENDING_VERIFICATION 期间调 /close 返回 400(RESOLVED->CLOSED 边要求当前状态为 RESOLVED)IN_PROGRESS,可以再次提交复检EXCEPTION_CLOSURE 仍只在 RESOLVED 状态发起,与检验环节无关