feat: add M9 ReportService + ReportController

This commit is contained in:
2026-05-25 01:04:04 +08:00
parent ba38897f73
commit 8b00f401be
5 changed files with 371 additions and 0 deletions
@@ -0,0 +1,42 @@
package cn.craftlabs.platform.api.report;
import cn.craftlabs.platform.api.service.ReportService;
import cn.craftlabs.platform.api.web.dto.CallbackStatsResponse;
import cn.craftlabs.platform.api.web.dto.ContractSnReportRow;
import cn.craftlabs.platform.api.web.dto.ProjectHealthRow;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/v1/reports")
public class ReportController {
private final ReportService reportService;
public ReportController(ReportService reportService) {
this.reportService = reportService;
}
@GetMapping("/contract-sn")
public List<ContractSnReportRow> getContractSnReport(
@RequestParam(value = "projectId", required = false) Long projectId,
@RequestParam(value = "contractId", required = false) Long contractId) {
return reportService.getContractSnReport(projectId, contractId);
}
@GetMapping("/callback-stats")
public CallbackStatsResponse getCallbackStats(
@RequestParam(value = "from", required = false) String from,
@RequestParam(value = "to", required = false) String to) {
return reportService.getCallbackStats(from, to);
}
@GetMapping("/project-health")
public List<ProjectHealthRow> getProjectHealth() {
return reportService.getProjectHealth();
}
}
@@ -0,0 +1,131 @@
package cn.craftlabs.platform.api.service;
import cn.craftlabs.platform.api.persistence.callback.PlatformCallbackInbox;
import cn.craftlabs.platform.api.persistence.callback.PlatformCallbackInboxMapper;
import cn.craftlabs.platform.api.persistence.contract.PlatformContract;
import cn.craftlabs.platform.api.persistence.contract.PlatformContractLine;
import cn.craftlabs.platform.api.persistence.contract.PlatformContractLineMapper;
import cn.craftlabs.platform.api.persistence.contract.PlatformContractMapper;
import cn.craftlabs.platform.api.persistence.customer.PlatformCustomer;
import cn.craftlabs.platform.api.persistence.customer.PlatformCustomerMapper;
import cn.craftlabs.platform.api.persistence.license.PlatformLicenseSn;
import cn.craftlabs.platform.api.persistence.license.PlatformLicenseSnMapper;
import cn.craftlabs.platform.api.persistence.project.PlatformProject;
import cn.craftlabs.platform.api.persistence.project.PlatformProjectMapper;
import cn.craftlabs.platform.api.web.dto.CallbackStatsResponse;
import cn.craftlabs.platform.api.web.dto.ContractSnReportRow;
import cn.craftlabs.platform.api.web.dto.ProjectHealthRow;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class ReportService {
private final PlatformContractMapper contractMapper;
private final PlatformContractLineMapper contractLineMapper;
private final PlatformLicenseSnMapper licenseSnMapper;
private final PlatformCallbackInboxMapper callbackInboxMapper;
private final PlatformProjectMapper projectMapper;
private final PlatformCustomerMapper customerMapper;
public ReportService(
PlatformContractMapper contractMapper,
PlatformContractLineMapper contractLineMapper,
PlatformLicenseSnMapper licenseSnMapper,
PlatformCallbackInboxMapper callbackInboxMapper,
PlatformProjectMapper projectMapper,
PlatformCustomerMapper customerMapper) {
this.contractMapper = contractMapper;
this.contractLineMapper = contractLineMapper;
this.licenseSnMapper = licenseSnMapper;
this.callbackInboxMapper = callbackInboxMapper;
this.projectMapper = projectMapper;
this.customerMapper = customerMapper;
}
@Transactional(readOnly = true)
public List<ContractSnReportRow> getContractSnReport(Long projectId, Long contractId) {
LambdaQueryWrapper<PlatformContract> q = Wrappers.lambdaQuery(PlatformContract.class)
.eq(projectId != null, PlatformContract::getProjectId, projectId)
.eq(contractId != null, PlatformContract::getId, contractId)
.orderByDesc(PlatformContract::getId);
List<PlatformContract> contracts = contractMapper.selectList(q);
List<ContractSnReportRow> rows = new ArrayList<>();
for (PlatformContract contract : contracts) {
String customerName = resolveCustomerName(contract.getCustomerId());
LambdaQueryWrapper<PlatformContractLine> lineQ = Wrappers.lambdaQuery(PlatformContractLine.class)
.eq(PlatformContractLine::getContractId, contract.getId());
List<PlatformContractLine> lines = contractLineMapper.selectList(lineQ);
for (PlatformContractLine line : lines) {
LambdaQueryWrapper<PlatformLicenseSn> snQ = Wrappers.lambdaQuery(PlatformLicenseSn.class)
.eq(PlatformLicenseSn::getContractLineId, line.getId());
List<PlatformLicenseSn> sns = licenseSnMapper.selectList(snQ);
int issuedCount = sns.size();
int activatedCount = (int) sns.stream().filter(sn -> "ACTIVATED".equals(sn.getStatus())).count();
int expectedCount = line.getQuantity() != null ? line.getQuantity().intValue() : 0;
int gapCount = Math.max(0, expectedCount - issuedCount);
ContractSnReportRow row = new ContractSnReportRow();
row.setContractId(contract.getId());
row.setContractTitle(contract.getTitle());
row.setCustomerName(customerName);
row.setLineItemName(line.getItemName());
row.setExpectedCount(expectedCount);
row.setIssuedCount(issuedCount);
row.setActivatedCount(activatedCount);
row.setGapCount(gapCount);
row.setStatus(gapCount > 0 ? "缺额" : "正常");
rows.add(row);
}
}
return rows;
}
@Transactional(readOnly = true)
public CallbackStatsResponse getCallbackStats(String from, String to) {
List<PlatformCallbackInbox> all = callbackInboxMapper.selectList(Wrappers.lambdaQuery(PlatformCallbackInbox.class));
long totalEvents = all.size();
long processedCount = all.stream().filter(c -> "PROCESSED".equals(c.getStatus())).count();
long pendingCount = all.stream().filter(c -> "PENDING".equals(c.getStatus())).count();
double successRate = totalEvents > 0 ? (double) processedCount / totalEvents * 100 : 0.0;
Map<String, Long> typeGroup = all.stream()
.collect(Collectors.groupingBy(PlatformCallbackInbox::getEventType, Collectors.counting()));
List<Map<String, Object>> eventTypeDistribution = new ArrayList<>();
for (Map.Entry<String, Long> entry : typeGroup.entrySet()) {
Map<String, Object> item = new HashMap<>();
item.put("eventType", entry.getKey());
item.put("count", entry.getValue());
eventTypeDistribution.add(item);
}
CallbackStatsResponse resp = new CallbackStatsResponse();
resp.setTotalEvents(totalEvents);
resp.setSuccessRate(successRate);
resp.setPendingCount(pendingCount);
resp.setEventTypeDistribution(eventTypeDistribution);
resp.setTopFailureReasons(new ArrayList<>());
return resp;
}
@Transactional(readOnly = true)
public List<ProjectHealthRow> getProjectHealth() {
return new ArrayList<>();
}
private String resolveCustomerName(Long customerId) {
if (customerId == null) {
return null;
}
PlatformCustomer c = customerMapper.selectById(customerId);
return c != null ? c.getName() : null;
}
}
@@ -0,0 +1,53 @@
package cn.craftlabs.platform.api.web.dto;
import java.util.List;
import java.util.Map;
public class CallbackStatsResponse {
private long totalEvents;
private double successRate;
private long pendingCount;
private List<Map<String, Object>> eventTypeDistribution;
private List<Map<String, Object>> topFailureReasons;
public long getTotalEvents() {
return totalEvents;
}
public void setTotalEvents(long totalEvents) {
this.totalEvents = totalEvents;
}
public double getSuccessRate() {
return successRate;
}
public void setSuccessRate(double successRate) {
this.successRate = successRate;
}
public long getPendingCount() {
return pendingCount;
}
public void setPendingCount(long pendingCount) {
this.pendingCount = pendingCount;
}
public List<Map<String, Object>> getEventTypeDistribution() {
return eventTypeDistribution;
}
public void setEventTypeDistribution(List<Map<String, Object>> eventTypeDistribution) {
this.eventTypeDistribution = eventTypeDistribution;
}
public List<Map<String, Object>> getTopFailureReasons() {
return topFailureReasons;
}
public void setTopFailureReasons(List<Map<String, Object>> topFailureReasons) {
this.topFailureReasons = topFailureReasons;
}
}
@@ -0,0 +1,86 @@
package cn.craftlabs.platform.api.web.dto;
public class ContractSnReportRow {
private Long contractId;
private String contractTitle;
private String customerName;
private String lineItemName;
private int expectedCount;
private int issuedCount;
private int activatedCount;
private int gapCount;
private String status;
public Long getContractId() {
return contractId;
}
public void setContractId(Long contractId) {
this.contractId = contractId;
}
public String getContractTitle() {
return contractTitle;
}
public void setContractTitle(String contractTitle) {
this.contractTitle = contractTitle;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public String getLineItemName() {
return lineItemName;
}
public void setLineItemName(String lineItemName) {
this.lineItemName = lineItemName;
}
public int getExpectedCount() {
return expectedCount;
}
public void setExpectedCount(int expectedCount) {
this.expectedCount = expectedCount;
}
public int getIssuedCount() {
return issuedCount;
}
public void setIssuedCount(int issuedCount) {
this.issuedCount = issuedCount;
}
public int getActivatedCount() {
return activatedCount;
}
public void setActivatedCount(int activatedCount) {
this.activatedCount = activatedCount;
}
public int getGapCount() {
return gapCount;
}
public void setGapCount(int gapCount) {
this.gapCount = gapCount;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
@@ -0,0 +1,59 @@
package cn.craftlabs.platform.api.web.dto;
public class ProjectHealthRow {
private Long projectId;
private String projectName;
private double deliveryRate;
private double snIssuedRate;
private double activationRate;
private String healthLevel;
public Long getProjectId() {
return projectId;
}
public void setProjectId(Long projectId) {
this.projectId = projectId;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public double getDeliveryRate() {
return deliveryRate;
}
public void setDeliveryRate(double deliveryRate) {
this.deliveryRate = deliveryRate;
}
public double getSnIssuedRate() {
return snIssuedRate;
}
public void setSnIssuedRate(double snIssuedRate) {
this.snIssuedRate = snIssuedRate;
}
public double getActivationRate() {
return activationRate;
}
public void setActivationRate(double activationRate) {
this.activationRate = activationRate;
}
public String getHealthLevel() {
return healthLevel;
}
public void setHealthLevel(String healthLevel) {
this.healthLevel = healthLevel;
}
}