吸收数字员工管理端日报审批裁决回写预览

This commit is contained in:
baiyanyun
2026-06-15 09:54:56 +08:00
parent b64f8e35c3
commit 642521035b
7 changed files with 75 additions and 11 deletions

View File

@@ -109,6 +109,16 @@ public class DigitalEmployeeAdminWorkbenchRowDto {
private String sendRecordId;
@JsonProperty("approval_decision_status")
private String approvalDecisionStatus;
@JsonProperty("daily_report_approval_id")
private String dailyReportApprovalId;
@JsonProperty("daily_report_approval_action")
private String dailyReportApprovalAction;
@JsonProperty("daily_report_approval_actor")
private String dailyReportApprovalActor;
@JsonProperty("daily_report_approval_reason")
private String dailyReportApprovalReason;
@JsonProperty("daily_report_approval_recorded_at")
private String dailyReportApprovalRecordedAt;
@JsonProperty("route_decision_id")
private String routeDecisionId;
@JsonProperty("service_id")

View File

@@ -71,7 +71,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
"notification:mark_read", "notification:dismiss",
"notification_policy:pull_policy", "notification_policy:mark_reviewed", "notification_policy:dismiss",
"artifact:mark_keep", "artifact:mark_expire", "artifact:mark_reviewed", "artifact:record_cleanup_request",
"daily_report:mark_delivered", "daily_report:mark_confirmed", "daily_report:request_approval",
"daily_report:mark_delivered", "daily_report:mark_confirmed", "daily_report:request_approval", "daily_report:record_approval_decision",
"memory:archive_memory", "memory:restore_memory", "memory:mark_reviewed",
"skill:mark_reviewed", "skill:dismiss",
"policy_snapshot:pull_policy", "policy_snapshot:mark_reviewed", "policy_snapshot:dismiss",
@@ -1123,6 +1123,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
.dailyReportFactId(firstString(businessView.get("daily_report_fact_id"), businessView.get("dailyReportFactId"), payload.get("daily_report_fact_id"), payload.get("dailyReportFactId")))
.sendRecordId(firstString(businessView.get("send_record_id"), businessView.get("sendRecordId"), payload.get("send_record_id"), payload.get("sendRecordId")))
.approvalDecisionStatus(firstString(businessView.get("approval_decision_status"), businessView.get("approvalDecisionStatus"), payload.get("approval_decision_status"), payload.get("approvalDecisionStatus")))
.dailyReportApprovalId(firstString(businessView.get("daily_report_approval_id"), businessView.get("dailyReportApprovalId"), payload.get("daily_report_approval_id"), payload.get("dailyReportApprovalId")))
.dailyReportApprovalAction(firstString(businessView.get("daily_report_approval_action"), businessView.get("dailyReportApprovalAction"), payload.get("daily_report_approval_action"), payload.get("dailyReportApprovalAction")))
.dailyReportApprovalActor(firstString(businessView.get("daily_report_approval_actor"), businessView.get("dailyReportApprovalActor"), payload.get("daily_report_approval_actor"), payload.get("dailyReportApprovalActor")))
.dailyReportApprovalReason(firstString(businessView.get("daily_report_approval_reason"), businessView.get("dailyReportApprovalReason"), payload.get("daily_report_approval_reason"), payload.get("dailyReportApprovalReason")))
.dailyReportApprovalRecordedAt(firstString(businessView.get("daily_report_approval_recorded_at"), businessView.get("dailyReportApprovalRecordedAt"), payload.get("daily_report_approval_recorded_at"), payload.get("dailyReportApprovalRecordedAt")))
.routeDecisionId(firstString(businessView.get("route_decision_id"), payload.get("route_decision_id")))
.serviceId(firstString(businessView.get("service_id"), businessView.get("serviceId"), payload.get("service_id"), payload.get("serviceId")))
.serviceLeaseId(firstString(businessView.get("service_lease_id"), businessView.get("serviceLeaseId"), payload.get("service_lease_id"), payload.get("serviceLeaseId")))
@@ -1321,7 +1326,9 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
private boolean isDailyReportRow(DigitalEmployeeAdminWorkbenchRowDto row) {
return StringUtils.equals(row.getCategory(), "daily_report")
|| startsWithAny(row.getKind(), "daily_report")
|| StringUtils.isNotBlank(row.getReportId());
|| StringUtils.isNotBlank(row.getReportId())
|| StringUtils.isNotBlank(row.getDailyReportApprovalId())
|| StringUtils.isNotBlank(row.getDailyReportApprovalAction());
}
private boolean isGovernanceCommandRow(DigitalEmployeeAdminWorkbenchRowDto row) {

View File

@@ -1719,16 +1719,32 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
"confirmed_at", "2026-06-13T10:00:00+08:00"
),
Map.of("id", "event-daily-report-2", "kind", "daily_report_confirmed")),
record("outbox-task", "task", "task-1", 101L, new Date(13_000),
record("outbox-daily-report-approval", "event", "event-daily-report-3", 101L, new Date(13_000),
Map.ofEntries(
Map.entry("entity_type", "event"),
Map.entry("category", "daily_report"),
Map.entry("kind", "daily_report_approval_decision_recorded"),
Map.entry("status", "approved"),
Map.entry("title", "日报审批裁决已记录"),
Map.entry("report_id", "report-4"),
Map.entry("daily_report_approval_id", "daily-report-approval-1"),
Map.entry("daily_report_approval_action", "approved"),
Map.entry("daily_report_approval_actor", "operator-1"),
Map.entry("daily_report_approval_reason", "content_reviewed"),
Map.entry("daily_report_approval_recorded_at", "2026-06-13T10:30:00+08:00"),
Map.entry("approval_decision_status", "approved")
),
Map.of("id", "event-daily-report-3", "kind", "daily_report_approval_decision_recorded")),
record("outbox-task", "task", "task-1", 101L, new Date(14_000),
Map.of("entity_type", "task", "title", "普通任务", "status", "running"),
Map.of("id", "task-1", "status", "running")),
record("outbox-approval", "approval", "approval-1", 101L, new Date(14_000),
record("outbox-approval", "approval", "approval-1", 101L, new Date(15_000),
Map.of("entity_type", "approval", "title", "普通审批", "status", "pending"),
Map.of("id", "approval-1", "status", "pending")),
record("outbox-notification", "notification", "notification-1", 101L, new Date(15_000),
record("outbox-notification", "notification", "notification-1", 101L, new Date(16_000),
Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"),
Map.of("id", "notification-1", "status", "unread")),
record("outbox-audit", "event", "event-audit-1", 101L, new Date(16_000),
record("outbox-audit", "event", "event-audit-1", 101L, new Date(17_000),
Map.of("entity_type", "event", "category", "tool_call_audit", "title", "审计", "status", "warn"),
Map.of("id", "event-audit-1", "status", "warn"))
)));
@@ -1737,9 +1753,9 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
"daily_reports", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L
);
assertThat(result.getTotal()).isEqualTo(3);
assertThat(result.getTotal()).isEqualTo(4);
assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId)
.containsExactly("report-1", "event-daily-report-1", "event-daily-report-2");
.containsExactly("report-1", "event-daily-report-1", "event-daily-report-2", "event-daily-report-3");
assertThat(result.getRecords().get(0).getView()).isEqualTo("daily_reports");
assertThat(result.getRecords().get(0).getReportId()).isEqualTo("report-1");
assertThat(result.getRecords().get(0).getCategory()).isEqualTo("daily_report");
@@ -1751,6 +1767,11 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
assertThat(result.getRecords().get(1).getReportId()).isEqualTo("report-2");
assertThat(result.getRecords().get(1).getBusinessView()).containsEntry("delivery_status", "delivered");
assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("confirmed_at", "2026-06-13T10:00:00+08:00");
assertThat(result.getRecords().get(3).getDailyReportApprovalId()).isEqualTo("daily-report-approval-1");
assertThat(result.getRecords().get(3).getDailyReportApprovalAction()).isEqualTo("approved");
assertThat(result.getRecords().get(3).getDailyReportApprovalActor()).isEqualTo("operator-1");
assertThat(result.getRecords().get(3).getDailyReportApprovalReason()).isEqualTo("content_reviewed");
assertThat(result.getRecords().get(3).getDailyReportApprovalRecordedAt()).isEqualTo("2026-06-13T10:30:00+08:00");
}
@Test

View File

@@ -339,6 +339,7 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE
{ action: 'mark_delivered', label: '记录已送达' },
{ action: 'mark_confirmed', label: '确认回执' },
{ action: 'request_approval', label: '请求审批' },
{ action: 'record_approval_decision', label: '记录审批裁决' },
];
}
if (view === 'audits') {
@@ -908,6 +909,8 @@ const DigitalEmployeeOps: React.FC = () => {
`ReportFact ${detailRecord.daily_report_fact_id}`,
detailRecord.send_record_id &&
`SendRecord ${detailRecord.send_record_id}`,
detailRecord.daily_report_approval_id &&
`ReportApproval ${detailRecord.daily_report_approval_id}`,
detailRecord.route_decision_id &&
`Route ${detailRecord.route_decision_id}`,
detailRecord.policy_kind && `Policy ${detailRecord.policy_kind}`,
@@ -1104,9 +1107,19 @@ const DigitalEmployeeOps: React.FC = () => {
` · 发送记录 ${detailRecord.send_record_id}`}
{detailRecord.approval_decision_status &&
` · 审批状态 ${detailRecord.approval_decision_status}`}
{detailRecord.daily_report_approval_id &&
` · 审批裁决 ${detailRecord.daily_report_approval_id}`}
{detailRecord.daily_report_approval_action &&
` · 裁决动作 ${detailRecord.daily_report_approval_action}`}
{detailRecord.daily_report_approval_actor &&
` · 裁决人 ${detailRecord.daily_report_approval_actor}`}
{detailRecord.daily_report_approval_reason &&
` · 裁决原因 ${detailRecord.daily_report_approval_reason}`}
{detailRecord.daily_report_approval_recorded_at &&
` · 裁决记录 ${detailRecord.daily_report_approval_recorded_at}`}
<Typography.Text type="secondary">
{' '}
· //
· ///
</Typography.Text>
</Typography.Paragraph>
)}

View File

@@ -637,6 +637,11 @@ export interface DigitalEmployeeAdminWorkbenchRow {
daily_report_fact_id?: string;
send_record_id?: string;
approval_decision_status?: string;
daily_report_approval_id?: string;
daily_report_approval_action?: string;
daily_report_approval_actor?: string;
daily_report_approval_reason?: string;
daily_report_approval_recorded_at?: string;
route_decision_id?: string;
service_id?: string;
service_lease_id?: string;

View File

@@ -2791,15 +2791,23 @@ function checkDigitalAdminDailyReportWorkbench() {
[service, "daily_reports", "backend daily report view"],
[service, "isDailyReportRow", "backend daily report matcher"],
[service, "daily_report", "backend daily report category"],
[service, "daily_report:record_approval_decision", "backend daily report approval decision action"],
[dto, "report_id", "backend report id row field"],
[dto, "daily_report_approval_id", "backend daily report approval id row field"],
[dto, "daily_report_approval_action", "backend daily report approval action row field"],
[dto, "daily_report_approval_recorded_at", "backend daily report approval recorded at row field"],
[test, "queryAdminWorkbenchDailyReportsReturnsDailyReportRowsOnly", "backend daily report workbench test"],
[test, "getDailyReportApprovalId", "backend daily report approval assertion"],
[qimingTypes, "daily_reports", "qiming daily report view type"],
[qimingTypes, "daily_report_approval_id", "qiming daily report approval id type field"],
[qimingOps, "日报运营", "qiming daily report tab"],
[qimingOps, "记录已送达", "qiming daily report delivered action"],
[qimingOps, "确认回执", "qiming daily report confirmed action"],
[qimingOps, "请求审批", "qiming daily report approval action"],
[qimingOps, "记录审批裁决", "qiming daily report approval decision action"],
[qimingOps, "记录发送结果", "qiming daily report send record action"],
[docs, "正式管理端日报运营预览", "docs daily report absorption"],
[docs, "管理端日报审批裁决回写预览", "docs daily report approval decision absorption"],
];
const missing = requiredSnippets
.filter(([content, snippet]) => !content.includes(snippet))

View File

@@ -614,8 +614,8 @@ GET /api/digital-employee/approvals
- 建议:先使用 sync record 派生业务查询模型和 embedded 业务视图预览承接联调,再按容量和查询压力决定是否拆 Plan / Task / Run / Event 物理业务表。
13. **日报实体与导出交付**
- 已吸收embedded 页面可基于当前 workday projection 导出本地文本日报;生成日报时会通过 qimingclaw bridge 写入 `daily_report` text / HTML / PDF Artifact 与 `daily_report_generated` Event并可通过 `/api/digital-employee/daily-reports` 和详情接口读取只读日报投影。日报 HTML/PDF 导出、发送记录、管理端确认回执和日报发送审批已开始吸收,交付动作会写入 `daily_report_send_recorded` / `daily_report_confirmed` / `daily_report_approval_requested` 事件sync `business_view.category = daily_report` 可被管理端筛选消费。管理端正式日报页面预览和正式管理端日报运营预览已开始吸收embedded 新增“日报运营”入口展示日报生成、交付、确认、审批和关联产物状态并复用现有生成、记录发送、确认回执和请求审批动作qiming-backend 的 `daily_reports` 运营台视图会聚合日报生成、发送记录、确认回执和审批请求qiming“数字员工运营台”的“日报运营”tab 可记录已送达、确认回执、请求审批和发送结果。发送账单审计外壳已开始吸收qiming-backend 新增 `/api/digital-employee/admin/daily-reports/send-records`qiming 运营台可在日报详情中“记录发送结果”,但不接真实第三方发送通道。正式管理端协作运营收口已开始吸收,日报运营详情会展示 `daily_report_fact_id``send_record_id``approval_decision_status`,用于解释事实来源、发送记录和审批状态。
- 缺口:没有正式 DailyReport 物理表、真实管理端发送通道正式外部管理端日报页面和审批裁决回写闭环
- 已吸收embedded 页面可基于当前 workday projection 导出本地文本日报;生成日报时会通过 qimingclaw bridge 写入 `daily_report` text / HTML / PDF Artifact 与 `daily_report_generated` Event并可通过 `/api/digital-employee/daily-reports` 和详情接口读取只读日报投影。日报 HTML/PDF 导出、发送记录、管理端确认回执和日报发送审批已开始吸收,交付动作会写入 `daily_report_send_recorded` / `daily_report_confirmed` / `daily_report_approval_requested` 事件sync `business_view.category = daily_report` 可被管理端筛选消费。管理端正式日报页面预览和正式管理端日报运营预览已开始吸收embedded 新增“日报运营”入口展示日报生成、交付、确认、审批和关联产物状态并复用现有生成、记录发送、确认回执和请求审批动作qiming-backend 的 `daily_reports` 运营台视图会聚合日报生成、发送记录、确认回执和审批请求qiming“数字员工运营台”的“日报运营”tab 可记录已送达、确认回执、请求审批和发送结果。发送账单审计外壳已开始吸收qiming-backend 新增 `/api/digital-employee/admin/daily-reports/send-records`qiming 运营台可在日报详情中“记录发送结果”,但不接真实第三方发送通道。正式管理端协作运营收口已开始吸收,日报运营详情会展示 `daily_report_fact_id``send_record_id``approval_decision_status`,用于解释事实来源、发送记录和审批状态。管理端日报审批裁决回写预览已开始吸收,日报运营详情可展示 `daily_report_approval_id`、裁决动作、裁决人、原因和记录时间,并只记录审批裁决意图,不触发真实发送或自动审批。
- 缺口:没有正式 DailyReport 物理表、真实管理端发送通道正式外部管理端日报页面。
- 建议:先稳定 Artifact/Event 派生日报交付模型,再根据管理端联调需要决定是否物化 DailyReport 表与发送记录表。
## 管理端联动原则