吸收数字员工日报物理事实发送记录预览

This commit is contained in:
baiyanyun
2026-06-15 17:20:42 +08:00
parent fd06f3bce3
commit 3a41eb40d0
9 changed files with 164 additions and 4 deletions

View File

@@ -133,6 +133,10 @@ public class DigitalEmployeeAdminWorkbenchRowDto {
private String reportId;
@JsonProperty("daily_report_fact_id")
private String dailyReportFactId;
@JsonProperty("daily_report_table")
private String dailyReportTable;
@JsonProperty("daily_report_materialized")
private Boolean dailyReportMaterialized;
@JsonProperty("daily_report_physical_fact_status")
private String dailyReportPhysicalFactStatus;
@JsonProperty("send_record_id")
@@ -141,6 +145,10 @@ public class DigitalEmployeeAdminWorkbenchRowDto {
private String sendChannel;
@JsonProperty("send_request_status")
private String sendRequestStatus;
@JsonProperty("send_record_status")
private String sendRecordStatus;
@JsonProperty("formal_daily_report_page_disabled")
private Boolean formalDailyReportPageDisabled;
@JsonProperty("external_send_disabled")
private Boolean externalSendDisabled;
@JsonProperty("third_party_channel_disabled")

View File

@@ -25,12 +25,20 @@ public class DigitalEmployeeDailyReportFact {
private String clientKey;
private String deviceId;
private String reportId;
private String dailyReportFactId;
private String planId;
private String status;
private String physicalFactStatus;
private String deliveryStatus;
private String approvalStatus;
private String approvalDecisionStatus;
private String confirmedAt;
private String reportDate;
private String sendRecordId;
private String sendChannel;
private String sendRequestStatus;
private String sendRecordStatus;
private String materializedSource;
private String businessView;
private Date syncedAt;
private Date modified;

View File

@@ -300,12 +300,20 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
.clientKey(record.getClientKey())
.deviceId(record.getDeviceId())
.reportId(reportId)
.dailyReportFactId(firstString(view.get("daily_report_fact_id"), view.get("dailyReportFactId")))
.planId(row.getPlanId())
.status(row.getStatus())
.physicalFactStatus(firstString(view.get("daily_report_physical_fact_status"), view.get("dailyReportPhysicalFactStatus"), view.get("physical_fact_status"), view.get("physicalFactStatus")))
.deliveryStatus(firstString(view.get("delivery_status"), view.get("deliveryStatus")))
.approvalStatus(firstString(view.get("approval_status"), view.get("approvalStatus")))
.approvalDecisionStatus(firstString(view.get("approval_decision_status"), view.get("approvalDecisionStatus")))
.confirmedAt(firstString(view.get("confirmed_at"), view.get("confirmedAt")))
.reportDate(firstString(view.get("report_date"), view.get("reportDate")))
.sendRecordId(firstString(view.get("send_record_id"), view.get("sendRecordId")))
.sendChannel(firstString(view.get("send_channel"), view.get("sendChannel")))
.sendRequestStatus(firstString(view.get("send_request_status"), view.get("sendRequestStatus")))
.sendRecordStatus(firstString(view.get("send_record_status"), view.get("sendRecordStatus"), view.get("send_request_status"), view.get("sendRequestStatus")))
.materializedSource(firstString(view.get("materialized_source"), view.get("materializedSource"), "digital_employee_daily_report_fact"))
.businessView(record.getBusinessView())
.syncedAt(record.getSyncedAt())
.modified(now)
@@ -1160,10 +1168,14 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
.conflictStatus(firstString(businessView.get("conflict_status"), businessView.get("conflictStatus"), payload.get("conflict_status"), payload.get("conflictStatus")))
.reportId(firstString(businessView.get("report_id"), payload.get("report_id")))
.dailyReportFactId(firstString(businessView.get("daily_report_fact_id"), businessView.get("dailyReportFactId"), payload.get("daily_report_fact_id"), payload.get("dailyReportFactId")))
.dailyReportTable(firstString(businessView.get("daily_report_table"), businessView.get("dailyReportTable"), payload.get("daily_report_table"), payload.get("dailyReportTable"), "digital_employee_daily_report_fact"))
.dailyReportMaterialized(nullableBooleanValue(firstPresent(businessView.get("daily_report_materialized"), businessView.get("dailyReportMaterialized"), payload.get("daily_report_materialized"), payload.get("dailyReportMaterialized"))))
.dailyReportPhysicalFactStatus(firstString(businessView.get("daily_report_physical_fact_status"), businessView.get("dailyReportPhysicalFactStatus"), payload.get("daily_report_physical_fact_status"), payload.get("dailyReportPhysicalFactStatus")))
.sendRecordId(firstString(businessView.get("send_record_id"), businessView.get("sendRecordId"), payload.get("send_record_id"), payload.get("sendRecordId")))
.sendChannel(firstString(businessView.get("send_channel"), businessView.get("sendChannel"), payload.get("send_channel"), payload.get("sendChannel")))
.sendRequestStatus(firstString(businessView.get("send_request_status"), businessView.get("sendRequestStatus"), payload.get("send_request_status"), payload.get("sendRequestStatus")))
.sendRecordStatus(firstString(businessView.get("send_record_status"), businessView.get("sendRecordStatus"), payload.get("send_record_status"), payload.get("sendRecordStatus"), businessView.get("send_request_status"), payload.get("send_request_status")))
.formalDailyReportPageDisabled(nullableBooleanValue(firstPresent(businessView.get("formal_daily_report_page_disabled"), businessView.get("formalDailyReportPageDisabled"), payload.get("formal_daily_report_page_disabled"), payload.get("formalDailyReportPageDisabled"))))
.externalSendDisabled(nullableBooleanValue(firstPresent(businessView.get("external_send_disabled"), businessView.get("externalSendDisabled"), payload.get("external_send_disabled"), payload.get("externalSendDisabled"))))
.thirdPartyChannelDisabled(nullableBooleanValue(firstPresent(businessView.get("third_party_channel_disabled"), businessView.get("thirdPartyChannelDisabled"), payload.get("third_party_channel_disabled"), payload.get("thirdPartyChannelDisabled"))))
.approvalDecisionStatus(firstString(businessView.get("approval_decision_status"), businessView.get("approvalDecisionStatus"), payload.get("approval_decision_status"), payload.get("approvalDecisionStatus")))
@@ -1448,8 +1460,12 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|| startsWithAny(row.getKind(), "daily_report")
|| StringUtils.isNotBlank(row.getReportId())
|| StringUtils.isNotBlank(row.getDailyReportPhysicalFactStatus())
|| StringUtils.isNotBlank(row.getDailyReportTable())
|| row.getDailyReportMaterialized() != null
|| StringUtils.isNotBlank(row.getSendChannel())
|| StringUtils.isNotBlank(row.getSendRequestStatus())
|| StringUtils.isNotBlank(row.getSendRecordStatus())
|| row.getFormalDailyReportPageDisabled() != null
|| row.getExternalSendDisabled() != null
|| row.getThirdPartyChannelDisabled() != null
|| StringUtils.isNotBlank(row.getDailyReportApprovalId())

View File

@@ -27,6 +27,7 @@ import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeDailyReportFac
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeSyncRecordRepository;
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeAdminAction;
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeBusinessFact;
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeDailyReportFact;
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeSyncRecord;
import com.xspaceagi.system.spec.common.RequestContext;
import org.junit.jupiter.api.AfterEach;
@@ -123,6 +124,59 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
assertThat(factCaptor.getValue().getStatus()).isEqualTo("active");
}
@Test
void syncOutboxMaterializesDailyReportPhysicalFactAndSendRecord() {
DigitalEmployeeSyncItemDto item = new DigitalEmployeeSyncItemDto();
item.setOutboxId("outbox-daily-report-1");
item.setEntityType("daily_report");
item.setEntityId("report-1");
item.setOperation("upsert");
item.setBusinessView(Map.ofEntries(
Map.entry("entity_type", "daily_report"),
Map.entry("category", "daily_report"),
Map.entry("kind", "daily_report_generated"),
Map.entry("status", "generated"),
Map.entry("title", "6 月 13 日日报"),
Map.entry("report_id", "report-1"),
Map.entry("daily_report_fact_id", "daily-report-fact-1"),
Map.entry("daily_report_physical_fact_status", "materialized"),
Map.entry("approval_decision_status", "approved"),
Map.entry("send_record_id", "send-record-1"),
Map.entry("send_channel", "email"),
Map.entry("send_request_status", "recorded"),
Map.entry("send_record_status", "materialized"),
Map.entry("materialized_source", "digital_employee_daily_report_fact"),
Map.entry("summary", "日报已物化")
));
item.setPayload(Map.of("id", "report-1", "kind", "daily_report_generated"));
DigitalEmployeeSyncRequestDto request = new DigitalEmployeeSyncRequestDto();
request.setClientKey("client-key-001");
request.setDeviceId("device-001");
request.setItems(List.of(item));
doAnswer(invocation -> {
DigitalEmployeeSyncRecord saved = invocation.getArgument(0);
saved.setId(1002L);
return true;
}).when(repository).save(any(DigitalEmployeeSyncRecord.class));
service.syncOutbox(request);
ArgumentCaptor<DigitalEmployeeDailyReportFact> factCaptor = ArgumentCaptor.forClass(DigitalEmployeeDailyReportFact.class);
verify(dailyReportFactRepository).upsertBySyncRecord(factCaptor.capture());
DigitalEmployeeDailyReportFact fact = factCaptor.getValue();
assertThat(fact.getSyncRecordId()).isEqualTo(1002L);
assertThat(fact.getReportId()).isEqualTo("report-1");
assertThat(fact.getDailyReportFactId()).isEqualTo("daily-report-fact-1");
assertThat(fact.getPhysicalFactStatus()).isEqualTo("materialized");
assertThat(fact.getApprovalDecisionStatus()).isEqualTo("approved");
assertThat(fact.getSendRecordId()).isEqualTo("send-record-1");
assertThat(fact.getSendChannel()).isEqualTo("email");
assertThat(fact.getSendRequestStatus()).isEqualTo("recorded");
assertThat(fact.getSendRecordStatus()).isEqualTo("materialized");
assertThat(fact.getMaterializedSource()).isEqualTo("digital_employee_daily_report_fact");
}
@Test
void queryRecordsReturnsStoredBusinessMetadata() {
DigitalEmployeeSyncRecord record = planRecord("plan-1", "客户回访", "active", 101L, new Date(1_000));
@@ -2053,10 +2107,14 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
Map.entry("title", "6 13 日日报"),
Map.entry("report_id", "report-1"),
Map.entry("daily_report_fact_id", "daily-report-fact-1"),
Map.entry("daily_report_table", "digital_employee_daily_report_fact"),
Map.entry("daily_report_materialized", true),
Map.entry("daily_report_physical_fact_status", "pending_materialization"),
Map.entry("send_record_id", "send-record-1"),
Map.entry("send_channel", "email"),
Map.entry("send_request_status", "recorded"),
Map.entry("send_record_status", "materialized"),
Map.entry("formal_daily_report_page_disabled", true),
Map.entry("external_send_disabled", true),
Map.entry("third_party_channel_disabled", true),
Map.entry("approval_decision_status", "pending"),
@@ -2128,10 +2186,14 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
assertThat(result.getRecords().get(0).getStatus()).isEqualTo("generated");
assertThat(result.getRecords().get(0).getSummary()).isEqualTo("日报已生成");
assertThat(result.getRecords().get(0).getDailyReportFactId()).isEqualTo("daily-report-fact-1");
assertThat(result.getRecords().get(0).getDailyReportTable()).isEqualTo("digital_employee_daily_report_fact");
assertThat(result.getRecords().get(0).getDailyReportMaterialized()).isTrue();
assertThat(result.getRecords().get(0).getDailyReportPhysicalFactStatus()).isEqualTo("pending_materialization");
assertThat(result.getRecords().get(0).getSendRecordId()).isEqualTo("send-record-1");
assertThat(result.getRecords().get(0).getSendChannel()).isEqualTo("email");
assertThat(result.getRecords().get(0).getSendRequestStatus()).isEqualTo("recorded");
assertThat(result.getRecords().get(0).getSendRecordStatus()).isEqualTo("materialized");
assertThat(result.getRecords().get(0).getFormalDailyReportPageDisabled()).isTrue();
assertThat(result.getRecords().get(0).getExternalSendDisabled()).isTrue();
assertThat(result.getRecords().get(0).getThirdPartyChannelDisabled()).isTrue();
assertThat(result.getRecords().get(0).getApprovalDecisionStatus()).isEqualTo("pending");

View File

@@ -92,12 +92,20 @@ CREATE TABLE IF NOT EXISTS `digital_employee_daily_report_fact` (
`client_key` VARCHAR(255) DEFAULT NULL COMMENT '客户端Key',
`device_id` VARCHAR(255) DEFAULT NULL COMMENT '设备ID',
`report_id` VARCHAR(255) NOT NULL COMMENT '日报ID',
`daily_report_fact_id` VARCHAR(255) DEFAULT NULL COMMENT '日报事实ID',
`plan_id` VARCHAR(255) DEFAULT NULL COMMENT '计划ID',
`status` VARCHAR(128) DEFAULT NULL COMMENT '状态',
`physical_fact_status` VARCHAR(128) DEFAULT NULL COMMENT '物理事实状态',
`delivery_status` VARCHAR(128) DEFAULT NULL COMMENT '交付状态',
`approval_status` VARCHAR(128) DEFAULT NULL COMMENT '审批状态',
`approval_decision_status` VARCHAR(128) DEFAULT NULL COMMENT '审批裁决状态',
`confirmed_at` VARCHAR(64) DEFAULT NULL COMMENT '确认时间',
`report_date` VARCHAR(64) DEFAULT NULL COMMENT '日报日期',
`send_record_id` VARCHAR(255) DEFAULT NULL COMMENT '发送记录ID',
`send_channel` VARCHAR(128) DEFAULT NULL COMMENT '发送渠道',
`send_request_status` VARCHAR(128) DEFAULT NULL COMMENT '发送请求状态',
`send_record_status` VARCHAR(128) DEFAULT NULL COMMENT '发送记录状态',
`materialized_source` VARCHAR(128) DEFAULT NULL COMMENT '物化来源',
`business_view` JSON DEFAULT NULL COMMENT '业务视图快照',
`synced_at` DATETIME DEFAULT NULL COMMENT '同步时间',
`modified` DATETIME NOT NULL COMMENT '修改时间',

View File

@@ -1223,6 +1223,10 @@ const DigitalEmployeeOps: React.FC = () => {
{detailRecord.report_id || detailRecord.entity_id || '--'}
{detailRecord.daily_report_fact_id &&
` · 事实 ${detailRecord.daily_report_fact_id}`}
{detailRecord.daily_report_table &&
` · 正式日报物理表 ${detailRecord.daily_report_table}`}
{detailRecord.daily_report_materialized != null &&
` · 物化 ${detailRecord.daily_report_materialized ? '已写入' : '未写入'}`}
{detailRecord.daily_report_physical_fact_status &&
` · 物理事实 ${detailRecord.daily_report_physical_fact_status}`}
{detailRecord.send_record_id &&
@@ -1230,8 +1234,11 @@ const DigitalEmployeeOps: React.FC = () => {
{detailRecord.send_channel && ` · 渠道 ${detailRecord.send_channel}`}
{detailRecord.send_request_status &&
` · 发送请求状态 ${detailRecord.send_request_status}`}
{detailRecord.send_record_status &&
` · 发送记录物化 ${detailRecord.send_record_status}`}
{detailRecord.approval_decision_status &&
` · 审批状态 ${detailRecord.approval_decision_status}`}
{detailRecord.formal_daily_report_page_disabled && ' · 正式外部日报页面禁用'}
{detailRecord.external_send_disabled && ' · 外部发送禁用'}
{detailRecord.third_party_channel_disabled && ' · 第三方发送禁用'}
{detailRecord.daily_report_approval_id &&
@@ -1246,7 +1253,7 @@ const DigitalEmployeeOps: React.FC = () => {
` · 裁决记录 ${detailRecord.daily_report_approval_recorded_at}`}
<Typography.Text type="secondary">
{' '}
· ////
· ////
</Typography.Text>
</Typography.Paragraph>
)}

View File

@@ -652,10 +652,14 @@ export interface DigitalEmployeeAdminWorkbenchRow {
conflict_status?: string;
report_id?: string;
daily_report_fact_id?: string;
daily_report_table?: string;
daily_report_materialized?: boolean;
daily_report_physical_fact_status?: string;
send_record_id?: string;
send_channel?: string;
send_request_status?: string;
send_record_status?: string;
formal_daily_report_page_disabled?: boolean;
external_send_disabled?: boolean;
third_party_channel_disabled?: boolean;
approval_decision_status?: string;

View File

@@ -3129,20 +3129,66 @@ function checkDigitalAdminDailyReportWorkbench() {
const qimingOps = fs.readFileSync(qimingOpsPath, "utf8");
const qimingTypes = fs.readFileSync(qimingTypesPath, "utf8");
const docs = fs.readFileSync(docsPath, "utf8");
const dailyReportFactEntity = fs.readFileSync(
path.join(
backendRoot,
"app-platform-modules",
"app-platform-agent",
"app-platform-agent-core-adapter",
"src",
"main",
"java",
"com",
"xspaceagi",
"agent",
"core",
"adapter",
"repository",
"entity",
"DigitalEmployeeDailyReportFact.java",
),
"utf8",
);
const sql = fs.readFileSync(path.join(backendRoot, "sql", "update-20260612.sql"), "utf8");
const requiredSnippets = [
[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"],
[service, "daily_report_table", "backend daily report table projection"],
[service, "daily_report_materialized", "backend daily report materialized projection"],
[service, "send_record_status", "backend send record status projection"],
[service, "formal_daily_report_page_disabled", "backend formal daily report page guard projection"],
[dto, "report_id", "backend report id row field"],
[dto, "daily_report_table", "backend daily report table row field"],
[dto, "daily_report_materialized", "backend daily report materialized row field"],
[dto, "send_record_status", "backend send record status row field"],
[dto, "formal_daily_report_page_disabled", "backend formal daily report page guard 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"],
[dailyReportFactEntity, "dailyReportFactId", "daily report fact entity fact id field"],
[dailyReportFactEntity, "physicalFactStatus", "daily report fact entity physical status field"],
[dailyReportFactEntity, "sendRecordStatus", "daily report fact entity send status field"],
[dailyReportFactEntity, "materializedSource", "daily report fact entity materialized source field"],
[sql, "daily_report_fact_id", "daily report fact sql fact id column"],
[sql, "physical_fact_status", "daily report fact sql physical status column"],
[sql, "send_record_status", "daily report fact sql send status column"],
[sql, "materialized_source", "daily report fact sql materialized source column"],
[test, "queryAdminWorkbenchDailyReportsReturnsDailyReportRowsOnly", "backend daily report workbench test"],
[test, "syncOutboxMaterializesDailyReportPhysicalFactAndSendRecord", "backend daily report physical fact materialization test"],
[test, "queryAdminWorkbenchDailyReportsReturnsPhysicalFactAndSendShellRows", "backend daily report physical send shell test"],
[test, "getDailyReportApprovalId", "backend daily report approval assertion"],
[qimingTypes, "daily_reports", "qiming daily report view type"],
[qimingTypes, "daily_report_table", "qiming daily report table type field"],
[qimingTypes, "daily_report_materialized", "qiming daily report materialized type field"],
[qimingTypes, "send_record_status", "qiming send record status type field"],
[qimingTypes, "formal_daily_report_page_disabled", "qiming formal daily report page guard type field"],
[qimingTypes, "daily_report_approval_id", "qiming daily report approval id type field"],
[qimingOps, "日报运营", "qiming daily report tab"],
[qimingOps, "正式日报物理表", "qiming formal daily report physical table copy"],
[qimingOps, "发送记录物化", "qiming send record materialized copy"],
[qimingOps, "正式外部日报页面禁用", "qiming formal daily report page disabled copy"],
[qimingOps, "记录已送达", "qiming daily report delivered action"],
[qimingOps, "确认回执", "qiming daily report confirmed action"],
[qimingOps, "请求审批", "qiming daily report approval action"],
@@ -3150,6 +3196,7 @@ function checkDigitalAdminDailyReportWorkbench() {
[qimingOps, "记录发送结果", "qiming daily report send record action"],
[docs, "正式管理端日报运营预览", "docs daily report absorption"],
[docs, "管理端日报审批裁决回写预览", "docs daily report approval decision absorption"],
[docs, "正式日报物理事实与发送记录预览已开始吸收", "docs daily report physical fact send record absorption"],
];
const missing = requiredSnippets
.filter(([content, snippet]) => !content.includes(snippet))

View File

@@ -614,9 +614,9 @@ 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`,用于解释事实来源、发送记录和审批状态。管理端日报审批裁决回写预览已开始吸收,日报运营详情可展示 `daily_report_approval_id`、裁决动作、裁决人、原因和记录时间,并只记录审批裁决意图,不触发真实发送或自动审批。资产通知日报业务查询总收口已开始吸收,日报运营详情会补充 `daily_report_physical_fact_status`、发送渠道、发送请求状态、外部发送禁用和第三方发送禁用说明。
- 缺口:没有正式 DailyReport 物理表、真实管理端发送通道和正式外部管理端日报页面。
- 建议:先稳定 Artifact/Event 派生日报交付模型,再根据管理端联调需要决定是否物化 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`、裁决动作、裁决人、原因和记录时间,并只记录审批裁决意图,不触发真实发送或自动审批。资产通知日报业务查询总收口已开始吸收,日报运营详情会补充 `daily_report_physical_fact_status`、发送渠道、发送请求状态、外部发送禁用和第三方发送禁用说明。正式日报物理事实与发送记录预览已开始吸收,后端复用 `digital_employee_daily_report_fact` 增量物化日报事实、物理事实状态、审批裁决状态、发送记录状态和物化来源qiming 日报运营详情展示正式日报物理表、发送记录物化和正式外部日报页面禁用状态。
- 缺口:真实管理端发送通道、第三方发送通道、更完整 DailyReport 物理模型和正式外部管理端日报页面。
- 建议:先稳定 `digital_employee_daily_report_fact` 增量物化与 Artifact/Event 派生交付模型,再根据管理端联调需要决定是否扩展正式 DailyReport 表发送记录表和外部页面
## 管理端联动原则