diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/report/ReportController.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/report/ReportController.java new file mode 100644 index 0000000..80250fd --- /dev/null +++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/report/ReportController.java @@ -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 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 getProjectHealth() { + return reportService.getProjectHealth(); + } +} diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/ReportService.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/ReportService.java new file mode 100644 index 0000000..ecc2acc --- /dev/null +++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/ReportService.java @@ -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 getContractSnReport(Long projectId, Long contractId) { + LambdaQueryWrapper q = Wrappers.lambdaQuery(PlatformContract.class) + .eq(projectId != null, PlatformContract::getProjectId, projectId) + .eq(contractId != null, PlatformContract::getId, contractId) + .orderByDesc(PlatformContract::getId); + List contracts = contractMapper.selectList(q); + List rows = new ArrayList<>(); + for (PlatformContract contract : contracts) { + String customerName = resolveCustomerName(contract.getCustomerId()); + LambdaQueryWrapper lineQ = Wrappers.lambdaQuery(PlatformContractLine.class) + .eq(PlatformContractLine::getContractId, contract.getId()); + List lines = contractLineMapper.selectList(lineQ); + for (PlatformContractLine line : lines) { + LambdaQueryWrapper snQ = Wrappers.lambdaQuery(PlatformLicenseSn.class) + .eq(PlatformLicenseSn::getContractLineId, line.getId()); + List 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 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 typeGroup = all.stream() + .collect(Collectors.groupingBy(PlatformCallbackInbox::getEventType, Collectors.counting())); + List> eventTypeDistribution = new ArrayList<>(); + for (Map.Entry entry : typeGroup.entrySet()) { + Map 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 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; + } +} diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/CallbackStatsResponse.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/CallbackStatsResponse.java new file mode 100644 index 0000000..852c360 --- /dev/null +++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/CallbackStatsResponse.java @@ -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> eventTypeDistribution; + private List> 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> getEventTypeDistribution() { + return eventTypeDistribution; + } + + public void setEventTypeDistribution(List> eventTypeDistribution) { + this.eventTypeDistribution = eventTypeDistribution; + } + + public List> getTopFailureReasons() { + return topFailureReasons; + } + + public void setTopFailureReasons(List> topFailureReasons) { + this.topFailureReasons = topFailureReasons; + } +} diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/ContractSnReportRow.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/ContractSnReportRow.java new file mode 100644 index 0000000..c15f4ea --- /dev/null +++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/ContractSnReportRow.java @@ -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; + } +} diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/ProjectHealthRow.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/ProjectHealthRow.java new file mode 100644 index 0000000..075e925 --- /dev/null +++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/ProjectHealthRow.java @@ -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; + } +}