吸收数字员工跨设备审批策略冲突预览

This commit is contained in:
baiyanyun
2026-06-14 20:20:25 +08:00
parent c01c699410
commit 39371861f3
7 changed files with 122 additions and 3 deletions

View File

@@ -39,6 +39,18 @@ public class DigitalEmployeePolicySnapshotRowDto {
private Integer notMappedCount;
@JsonProperty("intervention_conflict_status")
private String interventionConflictStatus;
@JsonProperty("approval_policy_id")
private String approvalPolicyId;
@JsonProperty("conflict_policy_id")
private String conflictPolicyId;
@JsonProperty("decision_guard_status")
private String decisionGuardStatus;
@JsonProperty("batch_accept_remote_disabled")
private Boolean batchAcceptRemoteDisabled;
@JsonProperty("auto_decision_disabled")
private Boolean autoDecisionDisabled;
@JsonProperty("policy_reason")
private String policyReason;
@JsonProperty("sync_record_url")
private String syncRecordUrl;
@JsonProperty("client_key_masked")

View File

@@ -608,6 +608,12 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
.missingContextCount(routeInterventionCount(routeInterventionViews, "missing_context"))
.notMappedCount(routeInterventionCount(routeInterventionViews, "not_mapped"))
.interventionConflictStatus(interventionConflictStatus)
.approvalPolicyId(firstString(latestView.get("approval_policy_id"), latestView.get("approvalPolicyId")))
.conflictPolicyId(firstString(latestView.get("conflict_policy_id"), latestView.get("conflictPolicyId")))
.decisionGuardStatus(firstString(latestView.get("decision_guard_status"), latestView.get("decisionGuardStatus")))
.batchAcceptRemoteDisabled(nullableBooleanValue(firstPresent(latestView.get("batch_accept_remote_disabled"), latestView.get("batchAcceptRemoteDisabled"))))
.autoDecisionDisabled(nullableBooleanValue(firstPresent(latestView.get("auto_decision_disabled"), latestView.get("autoDecisionDisabled"))))
.policyReason(firstString(latestView.get("policy_reason"), latestView.get("policyReason")))
.syncRecordUrl(syncRecordUrl(latest.getOutboxId()))
.clientKeyMasked(maskClientKey(latest.getClientKey()))
.deviceId(latest.getDeviceId())
@@ -684,7 +690,13 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
"catch_up_on_startup",
"approval_required_risk_levels",
"approval_required_actions",
"auto_reject_actions"
"auto_reject_actions",
"approval_policy_id",
"conflict_policy_id",
"decision_guard_status",
"batch_accept_remote_disabled",
"auto_decision_disabled",
"policy_reason"
);
}

View File

@@ -1816,6 +1816,72 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
assertThat(row.getConflictKeys()).contains("route_intervention_state");
}
@Test
void queryAdminPolicySnapshotsSummarizesApprovalPolicyConflicts() {
DigitalEmployeeSyncRecord localApprovalPolicy = record("outbox-approval-policy-a", "event", "event-approval-policy-a", 101L, new Date(10_000),
Map.ofEntries(
Map.entry("entity_type", "event"),
Map.entry("category", "policy_snapshot"),
Map.entry("kind", "approval_decision_policy_snapshot"),
Map.entry("policy_kind", "approval_decision"),
Map.entry("policy_key", "approval_decision"),
Map.entry("title", "审批裁决策略"),
Map.entry("approval_policy_id", "approval-policy-local"),
Map.entry("conflict_policy_id", "conflict-policy-local"),
Map.entry("decision_guard_status", "protected"),
Map.entry("batch_accept_remote_disabled", true),
Map.entry("auto_decision_disabled", true),
Map.entry("policy_reason", "manual_review_required")
),
Map.of("kind", "approval_decision_policy_snapshot"));
localApprovalPolicy.setClientKey("client-key-a");
localApprovalPolicy.setDeviceId("device-a");
DigitalEmployeeSyncRecord remoteApprovalPolicy = record("outbox-approval-policy-b", "event", "event-approval-policy-b", 101L, new Date(11_000),
Map.ofEntries(
Map.entry("entity_type", "event"),
Map.entry("category", "policy_snapshot"),
Map.entry("kind", "approval_decision_policy_snapshot"),
Map.entry("policy_kind", "approval_decision"),
Map.entry("policy_key", "approval_decision"),
Map.entry("title", "审批裁决策略"),
Map.entry("approval_policy_id", "approval-policy-remote"),
Map.entry("conflict_policy_id", "conflict-policy-remote"),
Map.entry("decision_guard_status", "manual_only"),
Map.entry("batch_accept_remote_disabled", true),
Map.entry("auto_decision_disabled", false),
Map.entry("policy_reason", "remote_policy_changed")
),
Map.of("kind", "approval_decision_policy_snapshot"));
remoteApprovalPolicy.setClientKey("client-key-b");
remoteApprovalPolicy.setDeviceId("device-b");
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq("event"), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(100L)))
.thenReturn(page(List.of(
localApprovalPolicy,
remoteApprovalPolicy,
record("outbox-notification", "notification", "notification-1", 101L, new Date(12_000),
Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"),
Map.of("id", "notification-1", "status", "unread"))
)));
DigitalEmployeePolicySnapshotPageDto result = service.queryAdminPolicySnapshots(null, null, "conflict", "approval_decision", null, null, 1L, 20L);
assertThat(result.getTotal()).isEqualTo(1);
DigitalEmployeePolicySnapshotRowDto row = result.getRecords().get(0);
assertThat(row.getPolicyKind()).isEqualTo("approval_decision");
assertThat(row.getStatus()).isEqualTo("conflict");
assertThat(row.getClientCount()).isEqualTo(2);
assertThat(row.getDeviceCount()).isEqualTo(2);
assertThat(row.getConflictKeys()).contains("approval_policy_id", "conflict_policy_id", "decision_guard_status", "auto_decision_disabled", "policy_reason");
assertThat(row.getApprovalPolicyId()).isEqualTo("approval-policy-remote");
assertThat(row.getConflictPolicyId()).isEqualTo("conflict-policy-remote");
assertThat(row.getDecisionGuardStatus()).isEqualTo("manual_only");
assertThat(row.getBatchAcceptRemoteDisabled()).isTrue();
assertThat(row.getAutoDecisionDisabled()).isFalse();
assertThat(row.getPolicyReason()).isEqualTo("remote_policy_changed");
assertThat(row.getRecommendedResolution()).isEqualTo("review_latest_policy_snapshot");
}
@Test
void recordDailyReportSendResultStoresRecordedObservation() {
DigitalEmployeeAdminRecordRequestDto request = adminRecordRequest("daily_report", "report-1");