吸收数字员工业务事实物化查询
This commit is contained in:
@@ -58,4 +58,5 @@ public class DigitalEmployeeBusinessViewRowDto {
|
||||
private String latestEventAt;
|
||||
@JsonProperty("last_event_message")
|
||||
private String lastEventMessage;
|
||||
private Boolean materialized;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeArtifactLifecycle;
|
||||
|
||||
public interface DigitalEmployeeArtifactLifecycleRepository extends IService<DigitalEmployeeArtifactLifecycle> {
|
||||
void upsertBySyncRecord(DigitalEmployeeArtifactLifecycle lifecycle);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeBusinessFact;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public interface DigitalEmployeeBusinessFactRepository extends IService<DigitalEmployeeBusinessFact> {
|
||||
void upsertBySyncRecord(DigitalEmployeeBusinessFact fact);
|
||||
|
||||
IPage<DigitalEmployeeBusinessFact> queryFacts(
|
||||
Long tenantId,
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String entityType,
|
||||
String entityId,
|
||||
Date updatedFrom,
|
||||
Date updatedTo,
|
||||
String syncStatus,
|
||||
long pageNo,
|
||||
long pageSize
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeDailyReportFact;
|
||||
|
||||
public interface DigitalEmployeeDailyReportFactRepository extends IService<DigitalEmployeeDailyReportFact> {
|
||||
void upsertBySyncRecord(DigitalEmployeeDailyReportFact fact);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@TableName("digital_employee_artifact_lifecycle")
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DigitalEmployeeArtifactLifecycle {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
@TableField(value = "_tenant_id")
|
||||
private Long tenantId;
|
||||
private Long syncRecordId;
|
||||
private String clientKey;
|
||||
private String deviceId;
|
||||
private String artifactId;
|
||||
private String planId;
|
||||
private String taskId;
|
||||
private String runId;
|
||||
private String status;
|
||||
private String retentionStatus;
|
||||
private String cleanupStatus;
|
||||
private String deletedAt;
|
||||
private String versionKey;
|
||||
private String source;
|
||||
private String businessView;
|
||||
private Date syncedAt;
|
||||
private Date modified;
|
||||
private Date created;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@TableName("digital_employee_business_fact")
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DigitalEmployeeBusinessFact {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
@TableField(value = "_tenant_id")
|
||||
private Long tenantId;
|
||||
private Long userId;
|
||||
private Long syncRecordId;
|
||||
private String clientKey;
|
||||
private String deviceId;
|
||||
private String outboxId;
|
||||
private String entityType;
|
||||
private String entityId;
|
||||
private String operation;
|
||||
private String title;
|
||||
private String status;
|
||||
private String summary;
|
||||
private String planId;
|
||||
private String taskId;
|
||||
private String runId;
|
||||
private String kind;
|
||||
private String category;
|
||||
private String occurredAt;
|
||||
private String startedAt;
|
||||
private String finishedAt;
|
||||
private Long durationMs;
|
||||
private String syncStatus;
|
||||
private String businessView;
|
||||
private String payload;
|
||||
private Date syncedAt;
|
||||
private Date modified;
|
||||
private Date created;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@TableName("digital_employee_daily_report_fact")
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DigitalEmployeeDailyReportFact {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
@TableField(value = "_tenant_id")
|
||||
private Long tenantId;
|
||||
private Long syncRecordId;
|
||||
private String clientKey;
|
||||
private String deviceId;
|
||||
private String reportId;
|
||||
private String planId;
|
||||
private String status;
|
||||
private String deliveryStatus;
|
||||
private String approvalStatus;
|
||||
private String confirmedAt;
|
||||
private String reportDate;
|
||||
private String businessView;
|
||||
private Date syncedAt;
|
||||
private Date modified;
|
||||
private Date created;
|
||||
}
|
||||
@@ -25,8 +25,14 @@ import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRecordPag
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRequestDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncResponseDto;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeAdminActionRepository;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeArtifactLifecycleRepository;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeBusinessFactRepository;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeDailyReportFactRepository;
|
||||
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.DigitalEmployeeArtifactLifecycle;
|
||||
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 jakarta.annotation.Resource;
|
||||
@@ -70,6 +76,15 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
@Resource
|
||||
private DigitalEmployeeAdminActionRepository digitalEmployeeAdminActionRepository;
|
||||
|
||||
@Resource
|
||||
private DigitalEmployeeBusinessFactRepository digitalEmployeeBusinessFactRepository;
|
||||
|
||||
@Resource
|
||||
private DigitalEmployeeArtifactLifecycleRepository digitalEmployeeArtifactLifecycleRepository;
|
||||
|
||||
@Resource
|
||||
private DigitalEmployeeDailyReportFactRepository digitalEmployeeDailyReportFactRepository;
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@@ -116,6 +131,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
} else {
|
||||
digitalEmployeeSyncRecordRepository.updateById(record);
|
||||
}
|
||||
materializeSyncRecord(record, now);
|
||||
|
||||
acked.add(DigitalEmployeeSyncAckDto.builder()
|
||||
.outboxId(item.getOutboxId())
|
||||
@@ -190,6 +206,94 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
.build();
|
||||
}
|
||||
|
||||
private void materializeSyncRecord(DigitalEmployeeSyncRecord record, Date now) {
|
||||
if (record == null || record.getId() == null || digitalEmployeeBusinessFactRepository == null) return;
|
||||
DigitalEmployeeBusinessViewRowDto row = toBusinessRow(record);
|
||||
digitalEmployeeBusinessFactRepository.upsertBySyncRecord(DigitalEmployeeBusinessFact.builder()
|
||||
.tenantId(record.getTenantId())
|
||||
.userId(record.getUserId())
|
||||
.syncRecordId(record.getId())
|
||||
.clientKey(record.getClientKey())
|
||||
.deviceId(record.getDeviceId())
|
||||
.outboxId(record.getOutboxId())
|
||||
.entityType(row.getEntityType())
|
||||
.entityId(row.getEntityId())
|
||||
.operation(row.getOperation())
|
||||
.title(row.getTitle())
|
||||
.status(row.getStatus())
|
||||
.summary(row.getSummary())
|
||||
.planId(row.getPlanId())
|
||||
.taskId(row.getTaskId())
|
||||
.runId(row.getRunId())
|
||||
.kind(row.getKind())
|
||||
.category(firstString(row.getBusinessView().get("category"), row.getEntityType()))
|
||||
.occurredAt(row.getOccurredAt())
|
||||
.startedAt(row.getStartedAt())
|
||||
.finishedAt(row.getFinishedAt())
|
||||
.durationMs(row.getDurationMs())
|
||||
.syncStatus(row.getSyncStatus())
|
||||
.businessView(record.getBusinessView())
|
||||
.payload(record.getPayload())
|
||||
.syncedAt(record.getSyncedAt())
|
||||
.modified(now)
|
||||
.created(now)
|
||||
.build());
|
||||
materializeArtifactLifecycle(record, row, now);
|
||||
materializeDailyReportFact(record, row, now);
|
||||
}
|
||||
|
||||
private void materializeArtifactLifecycle(DigitalEmployeeSyncRecord record, DigitalEmployeeBusinessViewRowDto row, Date now) {
|
||||
if (digitalEmployeeArtifactLifecycleRepository == null || !StringUtils.equals(row.getEntityType(), "artifact")) return;
|
||||
Map<String, Object> view = row.getBusinessView();
|
||||
String artifactId = firstString(view.get("artifact_id"), view.get("artifactId"), row.getEntityId());
|
||||
digitalEmployeeArtifactLifecycleRepository.upsertBySyncRecord(DigitalEmployeeArtifactLifecycle.builder()
|
||||
.tenantId(record.getTenantId())
|
||||
.syncRecordId(record.getId())
|
||||
.clientKey(record.getClientKey())
|
||||
.deviceId(record.getDeviceId())
|
||||
.artifactId(artifactId)
|
||||
.planId(row.getPlanId())
|
||||
.taskId(row.getTaskId())
|
||||
.runId(row.getRunId())
|
||||
.status(row.getStatus())
|
||||
.retentionStatus(firstString(view.get("retention_status"), view.get("retentionStatus")))
|
||||
.cleanupStatus(firstString(view.get("cleanup_status"), view.get("cleanupStatus")))
|
||||
.deletedAt(firstString(view.get("deleted_at"), view.get("deletedAt")))
|
||||
.versionKey(firstString(view.get("version_key"), view.get("versionKey")))
|
||||
.source(firstString(view.get("source"), view.get("artifact_source"), view.get("artifactSource")))
|
||||
.businessView(record.getBusinessView())
|
||||
.syncedAt(record.getSyncedAt())
|
||||
.modified(now)
|
||||
.created(now)
|
||||
.build());
|
||||
}
|
||||
|
||||
private void materializeDailyReportFact(DigitalEmployeeSyncRecord record, DigitalEmployeeBusinessViewRowDto row, Date now) {
|
||||
Map<String, Object> view = row.getBusinessView();
|
||||
if (digitalEmployeeDailyReportFactRepository == null
|
||||
|| !(StringUtils.equals(row.getEntityType(), "daily_report") || StringUtils.equals(firstString(view.get("category")), "daily_report"))) {
|
||||
return;
|
||||
}
|
||||
String reportId = firstString(view.get("report_id"), view.get("reportId"), row.getEntityId());
|
||||
digitalEmployeeDailyReportFactRepository.upsertBySyncRecord(DigitalEmployeeDailyReportFact.builder()
|
||||
.tenantId(record.getTenantId())
|
||||
.syncRecordId(record.getId())
|
||||
.clientKey(record.getClientKey())
|
||||
.deviceId(record.getDeviceId())
|
||||
.reportId(reportId)
|
||||
.planId(row.getPlanId())
|
||||
.status(row.getStatus())
|
||||
.deliveryStatus(firstString(view.get("delivery_status"), view.get("deliveryStatus")))
|
||||
.approvalStatus(firstString(view.get("approval_status"), view.get("approvalStatus")))
|
||||
.confirmedAt(firstString(view.get("confirmed_at"), view.get("confirmedAt")))
|
||||
.reportDate(firstString(view.get("report_date"), view.get("reportDate")))
|
||||
.businessView(record.getBusinessView())
|
||||
.syncedAt(record.getSyncedAt())
|
||||
.modified(now)
|
||||
.created(now)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DigitalEmployeeBusinessViewPageDto queryBusinessViews(
|
||||
String entityType,
|
||||
@@ -214,6 +318,28 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
|
||||
String normalizedEntityType = normalizeBusinessEntityType(entityType);
|
||||
String repositoryEntityType = "plan".equals(normalizedEntityType) ? null : normalizedEntityType;
|
||||
List<DigitalEmployeeBusinessViewRowDto> materializedRows = queryMaterializedBusinessRows(
|
||||
tenantId,
|
||||
clientKey,
|
||||
deviceId,
|
||||
repositoryEntityType,
|
||||
entityId,
|
||||
updatedFrom,
|
||||
updatedTo,
|
||||
syncStatus,
|
||||
currentPage,
|
||||
currentPageSize
|
||||
);
|
||||
if (materializedRows != null) {
|
||||
List<DigitalEmployeeBusinessViewRowDto> rows = "plan".equals(normalizedEntityType)
|
||||
? enrichPlanRows(materializedRows)
|
||||
: materializedRows.stream().filter(row -> StringUtils.equals(row.getEntityType(), normalizedEntityType)).toList();
|
||||
rows = rows.stream()
|
||||
.filter(row -> matchesText(status, row.getStatus()))
|
||||
.filter(row -> matchesText(syncStatus, row.getSyncStatus()))
|
||||
.toList();
|
||||
return businessPage(rows, currentPage, currentPageSize);
|
||||
}
|
||||
IPage<DigitalEmployeeSyncRecord> page = digitalEmployeeSyncRecordRepository.queryBusinessRecords(
|
||||
tenantId,
|
||||
clientKey,
|
||||
@@ -239,6 +365,35 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
return businessPage(rows, currentPage, currentPageSize);
|
||||
}
|
||||
|
||||
private List<DigitalEmployeeBusinessViewRowDto> queryMaterializedBusinessRows(
|
||||
Long tenantId,
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String entityType,
|
||||
String entityId,
|
||||
Date updatedFrom,
|
||||
Date updatedTo,
|
||||
String syncStatus,
|
||||
long pageNo,
|
||||
long pageSize
|
||||
) {
|
||||
if (digitalEmployeeBusinessFactRepository == null) return null;
|
||||
IPage<DigitalEmployeeBusinessFact> factPage = digitalEmployeeBusinessFactRepository.queryFacts(
|
||||
tenantId,
|
||||
clientKey,
|
||||
deviceId,
|
||||
entityType,
|
||||
entityId,
|
||||
updatedFrom,
|
||||
updatedTo,
|
||||
syncStatus,
|
||||
pageNo,
|
||||
pageSize
|
||||
);
|
||||
if (factPage == null || factPage.getTotal() <= 0) return null;
|
||||
return factPage.getRecords().stream().map(this::toBusinessRow).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DigitalEmployeePlanBusinessViewDetailDto queryPlanBusinessViewDetail(
|
||||
String planId,
|
||||
@@ -814,6 +969,38 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
.businessView(businessView)
|
||||
.payload(payload)
|
||||
.lastEventMessage(lastEventMessage(entityType, businessView, payload))
|
||||
.materialized(false)
|
||||
.build();
|
||||
}
|
||||
|
||||
private DigitalEmployeeBusinessViewRowDto toBusinessRow(DigitalEmployeeBusinessFact fact) {
|
||||
return DigitalEmployeeBusinessViewRowDto.builder()
|
||||
.syncRecordId(fact.getSyncRecordId())
|
||||
.syncRecordUrl(syncRecordUrl(fact.getOutboxId()))
|
||||
.entityType(fact.getEntityType())
|
||||
.entityId(fact.getEntityId())
|
||||
.operation(fact.getOperation())
|
||||
.title(fact.getTitle())
|
||||
.status(fact.getStatus())
|
||||
.summary(fact.getSummary())
|
||||
.planId(fact.getPlanId())
|
||||
.taskId(fact.getTaskId())
|
||||
.runId(fact.getRunId())
|
||||
.kind(fact.getKind())
|
||||
.occurredAt(fact.getOccurredAt())
|
||||
.startedAt(fact.getStartedAt())
|
||||
.finishedAt(fact.getFinishedAt())
|
||||
.durationMs(fact.getDurationMs())
|
||||
.syncStatus(fact.getSyncStatus())
|
||||
.clientKeyMasked(maskClientKey(fact.getClientKey()))
|
||||
.deviceId(fact.getDeviceId())
|
||||
.syncedAt(fact.getSyncedAt())
|
||||
.modified(fact.getModified())
|
||||
.created(fact.getCreated())
|
||||
.businessView(readJsonObject(fact.getBusinessView()))
|
||||
.payload(readJsonObject(fact.getPayload()))
|
||||
.lastEventMessage(lastEventMessage(fact.getEntityType(), readJsonObject(fact.getBusinessView()), readJsonObject(fact.getPayload())))
|
||||
.materialized(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,12 @@ import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncItemDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRecordPageDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRequestDto;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeAdminActionRepository;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeArtifactLifecycleRepository;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeBusinessFactRepository;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeDailyReportFactRepository;
|
||||
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.DigitalEmployeeSyncRecord;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -35,6 +39,7 @@ import java.util.Map;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -44,6 +49,9 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
||||
|
||||
private DigitalEmployeeSyncRecordRepository repository;
|
||||
private DigitalEmployeeAdminActionRepository adminActionRepository;
|
||||
private DigitalEmployeeBusinessFactRepository businessFactRepository;
|
||||
private DigitalEmployeeArtifactLifecycleRepository artifactLifecycleRepository;
|
||||
private DigitalEmployeeDailyReportFactRepository dailyReportFactRepository;
|
||||
private DigitalEmployeeSyncApplicationServiceImpl service;
|
||||
|
||||
@BeforeEach
|
||||
@@ -55,9 +63,15 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
||||
|
||||
repository = mock(DigitalEmployeeSyncRecordRepository.class);
|
||||
adminActionRepository = mock(DigitalEmployeeAdminActionRepository.class);
|
||||
businessFactRepository = mock(DigitalEmployeeBusinessFactRepository.class);
|
||||
artifactLifecycleRepository = mock(DigitalEmployeeArtifactLifecycleRepository.class);
|
||||
dailyReportFactRepository = mock(DigitalEmployeeDailyReportFactRepository.class);
|
||||
service = new DigitalEmployeeSyncApplicationServiceImpl();
|
||||
ReflectionTestUtils.setField(service, "digitalEmployeeSyncRecordRepository", repository);
|
||||
ReflectionTestUtils.setField(service, "digitalEmployeeAdminActionRepository", adminActionRepository);
|
||||
ReflectionTestUtils.setField(service, "digitalEmployeeBusinessFactRepository", businessFactRepository);
|
||||
ReflectionTestUtils.setField(service, "digitalEmployeeArtifactLifecycleRepository", artifactLifecycleRepository);
|
||||
ReflectionTestUtils.setField(service, "digitalEmployeeDailyReportFactRepository", dailyReportFactRepository);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper());
|
||||
}
|
||||
|
||||
@@ -82,6 +96,11 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
||||
request.setClientKey("client-key-001");
|
||||
request.setDeviceId("device-001");
|
||||
request.setItems(List.of(item));
|
||||
doAnswer(invocation -> {
|
||||
DigitalEmployeeSyncRecord saved = invocation.getArgument(0);
|
||||
saved.setId(1001L);
|
||||
return true;
|
||||
}).when(repository).save(any(DigitalEmployeeSyncRecord.class));
|
||||
|
||||
service.syncOutbox(request);
|
||||
|
||||
@@ -94,6 +113,11 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
||||
assertThat(record.getPayload()).contains("plan-1");
|
||||
assertThat(record.getTenantId()).isEqualTo(101L);
|
||||
assertThat(record.getUserId()).isEqualTo(202L);
|
||||
ArgumentCaptor<DigitalEmployeeBusinessFact> factCaptor = ArgumentCaptor.forClass(DigitalEmployeeBusinessFact.class);
|
||||
verify(businessFactRepository).upsertBySyncRecord(factCaptor.capture());
|
||||
assertThat(factCaptor.getValue().getSyncRecordId()).isEqualTo(1001L);
|
||||
assertThat(factCaptor.getValue().getTitle()).isEqualTo("客户回访");
|
||||
assertThat(factCaptor.getValue().getStatus()).isEqualTo("active");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -140,6 +164,22 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
||||
assertThat(plan.getClientKeyMasked()).isEqualTo("clie*********001");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryBusinessViewsPrefersMaterializedFactsWhenAvailable() {
|
||||
DigitalEmployeeBusinessFact fact = businessFact("plan-1", "客户回访", "active", new Date(10_000));
|
||||
Page<DigitalEmployeeBusinessFact> page = new Page<>(1, 20, 1);
|
||||
page.setRecords(List.of(fact));
|
||||
when(businessFactRepository.queryFacts(eq(101L), eq("client-key-001"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
||||
.thenReturn(page);
|
||||
|
||||
DigitalEmployeeBusinessViewPageDto result = service.queryBusinessViews("plan", "client-key-001", null, null, null, null, null, null, 1L, 20L);
|
||||
|
||||
assertThat(result.getRecords()).hasSize(1);
|
||||
assertThat(result.getRecords().get(0).getTitle()).isEqualTo("客户回访");
|
||||
assertThat(result.getRecords().get(0).getMaterialized()).isTrue();
|
||||
verify(repository, never()).queryBusinessRecords(any(), any(), any(), any(), any(), any(), any(), any(), any(Long.class), any(Long.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryEventsFiltersStatusEntityAndTimeRangeFromBusinessView() {
|
||||
Date from = new Date(10_000);
|
||||
@@ -416,6 +456,30 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
||||
return record;
|
||||
}
|
||||
|
||||
private DigitalEmployeeBusinessFact businessFact(String planId, String title, String status, Date syncedAt) {
|
||||
DigitalEmployeeBusinessFact fact = new DigitalEmployeeBusinessFact();
|
||||
fact.setId(7001L);
|
||||
fact.setTenantId(101L);
|
||||
fact.setUserId(202L);
|
||||
fact.setSyncRecordId(1001L);
|
||||
fact.setClientKey("client-key-001");
|
||||
fact.setDeviceId("device-001");
|
||||
fact.setOutboxId("outbox-" + planId);
|
||||
fact.setEntityType("plan");
|
||||
fact.setEntityId(planId);
|
||||
fact.setOperation("upsert");
|
||||
fact.setTitle(title);
|
||||
fact.setStatus(status);
|
||||
fact.setSummary("计划摘要");
|
||||
fact.setPlanId(planId);
|
||||
fact.setBusinessView(toJson(Map.of("entity_type", "plan", "local_id", planId, "title", title, "status", status)));
|
||||
fact.setPayload(toJson(Map.of("id", planId, "title", title, "status", status)));
|
||||
fact.setSyncedAt(syncedAt);
|
||||
fact.setCreated(syncedAt);
|
||||
fact.setModified(syncedAt);
|
||||
return fact;
|
||||
}
|
||||
|
||||
private DigitalEmployeeSyncRecord planRecord(String planId, String title, String status, Long tenantId, Date syncedAt) {
|
||||
return record("outbox-" + planId, "plan", planId, tenantId, syncedAt,
|
||||
Map.of("entity_type", "plan", "local_id", planId, "title", title, "status", status, "summary", "计划摘要"),
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.xspaceagi.agent.core.infra.dao.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeArtifactLifecycle;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface DigitalEmployeeArtifactLifecycleMapper extends BaseMapper<DigitalEmployeeArtifactLifecycle> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.xspaceagi.agent.core.infra.dao.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeBusinessFact;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface DigitalEmployeeBusinessFactMapper extends BaseMapper<DigitalEmployeeBusinessFact> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.xspaceagi.agent.core.infra.dao.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeDailyReportFact;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface DigitalEmployeeDailyReportFactMapper extends BaseMapper<DigitalEmployeeDailyReportFact> {
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.xspaceagi.agent.core.infra.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeArtifactLifecycleRepository;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeArtifactLifecycle;
|
||||
import com.xspaceagi.agent.core.infra.dao.mapper.DigitalEmployeeArtifactLifecycleMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class DigitalEmployeeArtifactLifecycleRepositoryImpl
|
||||
extends ServiceImpl<DigitalEmployeeArtifactLifecycleMapper, DigitalEmployeeArtifactLifecycle>
|
||||
implements DigitalEmployeeArtifactLifecycleRepository {
|
||||
|
||||
@Override
|
||||
public void upsertBySyncRecord(DigitalEmployeeArtifactLifecycle lifecycle) {
|
||||
if (lifecycle == null || lifecycle.getTenantId() == null || lifecycle.getSyncRecordId() == null) return;
|
||||
DigitalEmployeeArtifactLifecycle existing = getOne(new LambdaQueryWrapper<DigitalEmployeeArtifactLifecycle>()
|
||||
.eq(DigitalEmployeeArtifactLifecycle::getTenantId, lifecycle.getTenantId())
|
||||
.eq(DigitalEmployeeArtifactLifecycle::getSyncRecordId, lifecycle.getSyncRecordId()), false);
|
||||
if (existing == null) {
|
||||
save(lifecycle);
|
||||
} else {
|
||||
lifecycle.setId(existing.getId());
|
||||
lifecycle.setCreated(existing.getCreated());
|
||||
updateById(lifecycle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.xspaceagi.agent.core.infra.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeBusinessFactRepository;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeBusinessFact;
|
||||
import com.xspaceagi.agent.core.infra.dao.mapper.DigitalEmployeeBusinessFactMapper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Service
|
||||
public class DigitalEmployeeBusinessFactRepositoryImpl
|
||||
extends ServiceImpl<DigitalEmployeeBusinessFactMapper, DigitalEmployeeBusinessFact>
|
||||
implements DigitalEmployeeBusinessFactRepository {
|
||||
|
||||
@Override
|
||||
public void upsertBySyncRecord(DigitalEmployeeBusinessFact fact) {
|
||||
if (fact == null || fact.getTenantId() == null || fact.getSyncRecordId() == null) return;
|
||||
DigitalEmployeeBusinessFact existing = getOne(new LambdaQueryWrapper<DigitalEmployeeBusinessFact>()
|
||||
.eq(DigitalEmployeeBusinessFact::getTenantId, fact.getTenantId())
|
||||
.eq(DigitalEmployeeBusinessFact::getSyncRecordId, fact.getSyncRecordId()), false);
|
||||
if (existing == null) {
|
||||
save(fact);
|
||||
} else {
|
||||
fact.setId(existing.getId());
|
||||
fact.setCreated(existing.getCreated());
|
||||
updateById(fact);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<DigitalEmployeeBusinessFact> queryFacts(
|
||||
Long tenantId,
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String entityType,
|
||||
String entityId,
|
||||
Date updatedFrom,
|
||||
Date updatedTo,
|
||||
String syncStatus,
|
||||
long pageNo,
|
||||
long pageSize
|
||||
) {
|
||||
LambdaQueryWrapper<DigitalEmployeeBusinessFact> queryWrapper = new LambdaQueryWrapper<DigitalEmployeeBusinessFact>()
|
||||
.eq(tenantId != null, DigitalEmployeeBusinessFact::getTenantId, tenantId)
|
||||
.eq(StringUtils.isNotBlank(clientKey), DigitalEmployeeBusinessFact::getClientKey, clientKey)
|
||||
.eq(StringUtils.isNotBlank(deviceId), DigitalEmployeeBusinessFact::getDeviceId, deviceId)
|
||||
.eq(StringUtils.isNotBlank(entityType), DigitalEmployeeBusinessFact::getEntityType, entityType)
|
||||
.eq(StringUtils.isNotBlank(entityId), DigitalEmployeeBusinessFact::getEntityId, entityId)
|
||||
.eq(StringUtils.isNotBlank(syncStatus), DigitalEmployeeBusinessFact::getSyncStatus, syncStatus)
|
||||
.ge(updatedFrom != null, DigitalEmployeeBusinessFact::getSyncedAt, updatedFrom)
|
||||
.le(updatedTo != null, DigitalEmployeeBusinessFact::getSyncedAt, updatedTo)
|
||||
.orderByDesc(DigitalEmployeeBusinessFact::getSyncedAt)
|
||||
.orderByDesc(DigitalEmployeeBusinessFact::getId);
|
||||
return page(new Page<>(pageNo, pageSize), queryWrapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.xspaceagi.agent.core.infra.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeDailyReportFactRepository;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeDailyReportFact;
|
||||
import com.xspaceagi.agent.core.infra.dao.mapper.DigitalEmployeeDailyReportFactMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class DigitalEmployeeDailyReportFactRepositoryImpl
|
||||
extends ServiceImpl<DigitalEmployeeDailyReportFactMapper, DigitalEmployeeDailyReportFact>
|
||||
implements DigitalEmployeeDailyReportFactRepository {
|
||||
|
||||
@Override
|
||||
public void upsertBySyncRecord(DigitalEmployeeDailyReportFact fact) {
|
||||
if (fact == null || fact.getTenantId() == null || fact.getSyncRecordId() == null) return;
|
||||
DigitalEmployeeDailyReportFact existing = getOne(new LambdaQueryWrapper<DigitalEmployeeDailyReportFact>()
|
||||
.eq(DigitalEmployeeDailyReportFact::getTenantId, fact.getTenantId())
|
||||
.eq(DigitalEmployeeDailyReportFact::getSyncRecordId, fact.getSyncRecordId()), false);
|
||||
if (existing == null) {
|
||||
save(fact);
|
||||
} else {
|
||||
fact.setId(existing.getId());
|
||||
fact.setCreated(existing.getCreated());
|
||||
updateById(fact);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user