refactor(clerk-performance):make better performance analysis
Some checks failed
Build and Push Backend / docker (push) Failing after 6s
Some checks failed
Build and Push Backend / docker (push) Failing after 6s
This commit is contained in:
@@ -9,6 +9,10 @@ import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
||||
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
|
||||
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceDetailQueryVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceDetailResponseVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceOverviewQueryVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceOverviewResponseVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoQueryVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoReturnVo;
|
||||
import com.starry.admin.modules.statistics.service.IPlayClerkPerformanceService;
|
||||
@@ -81,6 +85,20 @@ public class PlayClerkPerformanceController {
|
||||
return R.ok(voPage);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "店员业绩概览", notes = "汇总店员业绩榜单和汇总数据")
|
||||
@PostMapping("/overview")
|
||||
public R overview(
|
||||
@ApiParam(value = "查询条件", required = true) @Validated @RequestBody ClerkPerformanceOverviewQueryVo vo) {
|
||||
return R.ok(playClerkPerformanceService.queryOverview(vo));
|
||||
}
|
||||
|
||||
@ApiOperation(value = "店员业绩详情", notes = "查看单个店员的业绩构成和趋势")
|
||||
@PostMapping("/detail")
|
||||
public R detail(
|
||||
@ApiParam(value = "查询条件", required = true) @Validated @RequestBody ClerkPerformanceDetailQueryVo vo) {
|
||||
return R.ok(playClerkPerformanceService.queryDetail(vo));
|
||||
}
|
||||
|
||||
@ApiOperation(value = "按日查询业绩", notes = "按日期查询店员业绩统计信息")
|
||||
@ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
|
||||
@PostMapping("/listByTime")
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.starry.admin.modules.statistics.module.enums;
|
||||
|
||||
/**
|
||||
* 排序指标字段。
|
||||
*
|
||||
* @author admin
|
||||
* @since 2024/08/30
|
||||
*/
|
||||
public enum ClerkPerformanceSortField {
|
||||
/**
|
||||
* 根据成交金额(GMV)排序。
|
||||
*/
|
||||
GMV,
|
||||
/**
|
||||
* 根据订单数量排序。
|
||||
*/
|
||||
ORDER_COUNT,
|
||||
/**
|
||||
* 根据续单率排序。
|
||||
*/
|
||||
CONTINUE_RATE,
|
||||
/**
|
||||
* 根据预估收入排序。
|
||||
*/
|
||||
ESTIMATED_REVENUE
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.starry.admin.modules.statistics.module.enums;
|
||||
|
||||
/**
|
||||
* 排序方向。
|
||||
*/
|
||||
public enum SortDirection {
|
||||
/**
|
||||
* 升序。
|
||||
*/
|
||||
ASC,
|
||||
/**
|
||||
* 降序。
|
||||
*/
|
||||
DESC;
|
||||
|
||||
/**
|
||||
* 默认排序方向。
|
||||
*/
|
||||
public static final SortDirection DEFAULT = DESC;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.starry.admin.modules.statistics.module.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 店员业绩详情组成。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("店员业绩详情组成数据")
|
||||
public class ClerkPerformanceDetailCompositionVo {
|
||||
|
||||
@ApiModelProperty("订单构成")
|
||||
private List<CompositionSlice> orderComposition = Collections.emptyList();
|
||||
|
||||
@ApiModelProperty("金额构成")
|
||||
private List<CompositionSlice> amountComposition = Collections.emptyList();
|
||||
|
||||
@ApiModelProperty("顾客构成")
|
||||
private List<CompositionSlice> customerComposition = Collections.emptyList();
|
||||
|
||||
@Data
|
||||
@ApiModel("业绩组成占比")
|
||||
public static class CompositionSlice {
|
||||
|
||||
@ApiModelProperty("标识")
|
||||
private String key;
|
||||
|
||||
@ApiModelProperty("名称")
|
||||
private String label;
|
||||
|
||||
@ApiModelProperty("数量")
|
||||
private Integer count;
|
||||
|
||||
@ApiModelProperty("数量占比")
|
||||
private BigDecimal countRatio = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("金额")
|
||||
private BigDecimal amount = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("金额占比")
|
||||
private BigDecimal amountRatio = BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.starry.admin.modules.statistics.module.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 店员业绩详情查询参数。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel("店员业绩详情查询参数")
|
||||
public class ClerkPerformanceDetailQueryVo extends PlayClerkPerformanceInfoQueryVo {
|
||||
|
||||
@Override
|
||||
@NotBlank(message = "店员ID不能为空")
|
||||
@ApiModelProperty(value = "店员ID", required = true)
|
||||
public String getClerkId() {
|
||||
return super.getClerkId();
|
||||
}
|
||||
|
||||
@ApiModelProperty(value = "是否返回趋势数据")
|
||||
private Boolean includeTrend = Boolean.TRUE;
|
||||
|
||||
@ApiModelProperty(value = "趋势天数,默认7")
|
||||
private Integer trendDays = 7;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.starry.admin.modules.statistics.module.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 店员业绩详情响应。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("店员业绩详情响应")
|
||||
public class ClerkPerformanceDetailResponseVo {
|
||||
|
||||
@ApiModelProperty("基础信息")
|
||||
private ClerkPerformanceProfileVo profile;
|
||||
|
||||
@ApiModelProperty("业绩快照")
|
||||
private ClerkPerformanceSnapshotVo snapshot;
|
||||
|
||||
@ApiModelProperty("业绩组成")
|
||||
private ClerkPerformanceDetailCompositionVo composition;
|
||||
|
||||
@ApiModelProperty("趋势数据")
|
||||
private List<ClerkPerformanceTrendPointVo> trend = Collections.emptyList();
|
||||
|
||||
@ApiModelProperty("趋势维度")
|
||||
private String trendDimension = "DAY";
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.starry.admin.modules.statistics.module.vo;
|
||||
|
||||
import com.starry.admin.modules.statistics.module.enums.ClerkPerformanceSortField;
|
||||
import com.starry.admin.modules.statistics.module.enums.SortDirection;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 店员业绩概览查询参数。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel("店员业绩概览查询参数")
|
||||
public class ClerkPerformanceOverviewQueryVo extends PlayClerkPerformanceInfoQueryVo {
|
||||
|
||||
@ApiModelProperty(value = "排序字段", allowableValues = "GMV,ORDER_COUNT,CONTINUE_RATE,ESTIMATED_REVENUE")
|
||||
private ClerkPerformanceSortField sortField = ClerkPerformanceSortField.GMV;
|
||||
|
||||
@ApiModelProperty(value = "排序方向", allowableValues = "ASC,DESC")
|
||||
private SortDirection sortDirection = SortDirection.DEFAULT;
|
||||
|
||||
@ApiModelProperty(value = "返回前N名数据,未设置则返回分页数据")
|
||||
private Integer limit;
|
||||
|
||||
@ApiModelProperty(value = "是否包含汇总数据")
|
||||
private Boolean includeSummary = Boolean.TRUE;
|
||||
|
||||
@ApiModelProperty(value = "是否包含排行列表数据")
|
||||
private Boolean includeRankings = Boolean.TRUE;
|
||||
|
||||
@Override
|
||||
public void setEndOrderTime(List<String> endOrderTime) {
|
||||
super.setEndOrderTime(endOrderTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.starry.admin.modules.statistics.module.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 店员业绩概览响应。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("店员业绩概览响应")
|
||||
public class ClerkPerformanceOverviewResponseVo {
|
||||
|
||||
@ApiModelProperty("汇总信息")
|
||||
private ClerkPerformanceOverviewSummaryVo summary;
|
||||
|
||||
@ApiModelProperty("业绩排行榜")
|
||||
private List<ClerkPerformanceSnapshotVo> rankings = Collections.emptyList();
|
||||
|
||||
@ApiModelProperty("榜单总人数")
|
||||
private Integer total = 0;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.starry.admin.modules.statistics.module.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 店员业绩概览汇总。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("店员业绩概览汇总")
|
||||
public class ClerkPerformanceOverviewSummaryVo {
|
||||
|
||||
@ApiModelProperty("GMV汇总")
|
||||
private BigDecimal totalGmv = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("订单总数")
|
||||
private Integer totalOrderCount = 0;
|
||||
|
||||
@ApiModelProperty("首单总数")
|
||||
private Integer totalFirstOrderCount = 0;
|
||||
|
||||
@ApiModelProperty("续单总数")
|
||||
private Integer totalContinuedOrderCount = 0;
|
||||
|
||||
@ApiModelProperty("退款总数")
|
||||
private Integer totalRefundOrderCount = 0;
|
||||
|
||||
@ApiModelProperty("续单率(加权)")
|
||||
private BigDecimal continuedRate = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("续费率(续单金额/GMV)")
|
||||
private BigDecimal continuedAmountRate = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("退单率")
|
||||
private BigDecimal refundRate = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("客单价")
|
||||
private BigDecimal averageTicketPrice = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("预估工资汇总")
|
||||
private BigDecimal totalEstimatedRevenue = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("复购用户数")
|
||||
private Integer totalContinuedUserCount = 0;
|
||||
|
||||
@ApiModelProperty("用户总数")
|
||||
private Integer totalUserCount = 0;
|
||||
|
||||
@ApiModelProperty("续客率")
|
||||
private BigDecimal continuedCustomerRate = BigDecimal.ZERO;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.starry.admin.modules.statistics.module.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 店员基础信息。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("店员业绩详情-基础信息")
|
||||
public class ClerkPerformanceProfileVo {
|
||||
|
||||
@ApiModelProperty("店员ID")
|
||||
private String clerkId;
|
||||
|
||||
@ApiModelProperty("店员昵称")
|
||||
private String nickname;
|
||||
|
||||
@ApiModelProperty("头像地址")
|
||||
private String avatar;
|
||||
|
||||
@ApiModelProperty("性别")
|
||||
private String sex;
|
||||
|
||||
@ApiModelProperty("所属组名称")
|
||||
private String groupName;
|
||||
|
||||
@ApiModelProperty("等级名称")
|
||||
private String levelName;
|
||||
|
||||
@ApiModelProperty("上架状态")
|
||||
private String listingState;
|
||||
|
||||
@ApiModelProperty("在线状态")
|
||||
private String onlineState;
|
||||
|
||||
@ApiModelProperty("角色")
|
||||
private String role;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.starry.admin.modules.statistics.module.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 单个店员的业绩快照。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("店员业绩快照")
|
||||
public class ClerkPerformanceSnapshotVo {
|
||||
|
||||
@ApiModelProperty("店员ID")
|
||||
private String clerkId;
|
||||
|
||||
@ApiModelProperty("店员昵称")
|
||||
private String clerkNickname;
|
||||
|
||||
@ApiModelProperty("头像地址")
|
||||
private String avatar;
|
||||
|
||||
@ApiModelProperty("店员性别")
|
||||
private String sex;
|
||||
|
||||
@ApiModelProperty("所属组名称")
|
||||
private String groupName;
|
||||
|
||||
@ApiModelProperty("等级名称")
|
||||
private String levelName;
|
||||
|
||||
@ApiModelProperty("GMV(支付金额)")
|
||||
private BigDecimal gmv = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("首单金额")
|
||||
private BigDecimal firstOrderAmount = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("续单金额")
|
||||
private BigDecimal continuedOrderAmount = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("打赏金额")
|
||||
private BigDecimal rewardAmount = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("退款金额")
|
||||
private BigDecimal refundAmount = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("预估工资")
|
||||
private BigDecimal estimatedRevenue = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("订单数量")
|
||||
private Integer orderCount = 0;
|
||||
|
||||
@ApiModelProperty("首单数量")
|
||||
private Integer firstOrderCount = 0;
|
||||
|
||||
@ApiModelProperty("续单数量")
|
||||
private Integer continuedOrderCount = 0;
|
||||
|
||||
@ApiModelProperty("退款订单数量")
|
||||
private Integer refundOrderCount = 0;
|
||||
|
||||
@ApiModelProperty("超时未接单数量")
|
||||
private Integer expiredOrderCount = 0;
|
||||
|
||||
@ApiModelProperty("用户数量")
|
||||
private Integer userCount = 0;
|
||||
|
||||
@ApiModelProperty("复购用户数量")
|
||||
private Integer continuedUserCount = 0;
|
||||
|
||||
@ApiModelProperty("续单率")
|
||||
private BigDecimal continuedRate = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("续费率(续单金额/GMV)")
|
||||
private BigDecimal continuedAmountRate = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("退单率")
|
||||
private BigDecimal refundRate = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("续客率")
|
||||
private BigDecimal continuedCustomerRate = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("客单价")
|
||||
private BigDecimal averageTicketPrice = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("在线时长(单位:秒)")
|
||||
private Long onlineDuration = 0L;
|
||||
|
||||
@ApiModelProperty("排行榜名次")
|
||||
private Integer rank;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.starry.admin.modules.statistics.module.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 店员业绩趋势点。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("店员业绩趋势点")
|
||||
public class ClerkPerformanceTrendPointVo {
|
||||
|
||||
@ApiModelProperty("日期")
|
||||
private LocalDate date;
|
||||
|
||||
@ApiModelProperty("GMV")
|
||||
private BigDecimal gmv = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("订单数量")
|
||||
private Integer orderCount = 0;
|
||||
|
||||
@ApiModelProperty("续单率")
|
||||
private BigDecimal continuedRate = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("续费金额")
|
||||
private BigDecimal continuedAmount = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("首单金额")
|
||||
private BigDecimal firstAmount = BigDecimal.ZERO;
|
||||
}
|
||||
@@ -4,6 +4,10 @@ import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceDetailQueryVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceDetailResponseVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceOverviewQueryVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceOverviewResponseVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoReturnVo;
|
||||
import java.util.List;
|
||||
|
||||
@@ -29,4 +33,8 @@ public interface IPlayClerkPerformanceService {
|
||||
PlayClerkPerformanceInfoReturnVo getClerkPerformanceInfo(PlayClerkUserInfoEntity userInfo,
|
||||
List<PlayOrderInfoEntity> orderInfoEntities, List<PlayClerkLevelInfoEntity> clerkLevelInfoEntity,
|
||||
List<PlayPersonnelGroupInfoEntity> groupInfoEntities);
|
||||
|
||||
ClerkPerformanceOverviewResponseVo queryOverview(ClerkPerformanceOverviewQueryVo vo);
|
||||
|
||||
ClerkPerformanceDetailResponseVo queryDetail(ClerkPerformanceDetailQueryVo vo);
|
||||
}
|
||||
|
||||
@@ -1,29 +1,75 @@
|
||||
package com.starry.admin.modules.statistics.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.starry.admin.common.exception.ServiceException;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
||||
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
|
||||
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
|
||||
import com.starry.admin.modules.statistics.module.enums.ClerkPerformanceSortField;
|
||||
import com.starry.admin.modules.statistics.module.enums.SortDirection;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceDetailCompositionVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceDetailQueryVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceDetailResponseVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceOverviewQueryVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceOverviewResponseVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceOverviewSummaryVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceProfileVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceSnapshotVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceTrendPointVo;
|
||||
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoReturnVo;
|
||||
import com.starry.admin.modules.statistics.service.IPlayClerkPerformanceService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
* @since 2024/6/15 下午3:29
|
||||
**/
|
||||
* 店员业绩数据服务。
|
||||
*/
|
||||
@Service
|
||||
public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceService {
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
@Resource
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Resource
|
||||
private IPlayOrderInfoService playOrderInfoService;
|
||||
|
||||
@Resource
|
||||
private IPlayClerkLevelInfoService playClerkLevelInfoService;
|
||||
|
||||
@Resource
|
||||
private IPlayPersonnelGroupInfoService playPersonnelGroupInfoService;
|
||||
|
||||
@Override
|
||||
public PlayClerkPerformanceInfoReturnVo getClerkPerformanceInfo(PlayClerkUserInfoEntity userInfo,
|
||||
List<PlayOrderInfoEntity> orderInfoEntities, List<PlayClerkLevelInfoEntity> clerkLevelInfoEntities,
|
||||
List<PlayPersonnelGroupInfoEntity> groupInfoEntities) {
|
||||
|
||||
Set<String> customIds = new HashSet<>();
|
||||
int orderContinueNumber = 0;
|
||||
int orderRefundNumber = 0;
|
||||
@@ -36,35 +82,36 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
|
||||
BigDecimal estimatedRevenue = BigDecimal.ZERO;
|
||||
for (PlayOrderInfoEntity orderInfoEntity : orderInfoEntities) {
|
||||
customIds.add(orderInfoEntity.getPurchaserBy());
|
||||
finalAmount = finalAmount.add(orderInfoEntity.getFinalAmount());
|
||||
finalAmount = finalAmount.add(defaultZero(orderInfoEntity.getFinalAmount()));
|
||||
if ("1".equals(orderInfoEntity.getFirstOrder())) {
|
||||
orderFirstAmount = orderFirstAmount.add(orderInfoEntity.getFinalAmount());
|
||||
orderFirstAmount = orderFirstAmount.add(defaultZero(orderInfoEntity.getFinalAmount()));
|
||||
} else {
|
||||
orderContinueNumber++;
|
||||
orderTotalAmount = orderTotalAmount.add(orderInfoEntity.getFinalAmount());
|
||||
orderTotalAmount = orderTotalAmount.add(defaultZero(orderInfoEntity.getFinalAmount()));
|
||||
}
|
||||
if ("2".equals(orderInfoEntity.getPlaceType())) {
|
||||
orderRewardAmount = orderRewardAmount.add(orderInfoEntity.getFinalAmount());
|
||||
orderRewardAmount = orderRewardAmount.add(defaultZero(orderInfoEntity.getFinalAmount()));
|
||||
}
|
||||
if ("1".equals(orderInfoEntity.getRefundType())) {
|
||||
orderRefundNumber++;
|
||||
orderRefundAmount = orderRefundAmount.add(orderInfoEntity.getRefundAmount());
|
||||
orderRefundAmount = orderRefundAmount.add(defaultZero(orderInfoEntity.getRefundAmount()));
|
||||
}
|
||||
if ("1".equals(orderInfoEntity.getOrdersExpiredState())) {
|
||||
ordersExpiredNumber++;
|
||||
}
|
||||
estimatedRevenue = estimatedRevenue.add(defaultZero(orderInfoEntity.getEstimatedRevenue()));
|
||||
}
|
||||
PlayClerkPerformanceInfoReturnVo returnVo = new PlayClerkPerformanceInfoReturnVo();
|
||||
returnVo.setClerkId(userInfo.getId());
|
||||
returnVo.setClerkNickname(userInfo.getNickname());
|
||||
returnVo.setClerkSex(userInfo.getSex());
|
||||
for (PlayClerkLevelInfoEntity infoEntity : clerkLevelInfoEntities) {
|
||||
if (infoEntity.getId().equals(userInfo.getLevelId())) {
|
||||
if (Objects.equals(infoEntity.getId(), userInfo.getLevelId())) {
|
||||
returnVo.setLevelName(infoEntity.getName());
|
||||
}
|
||||
}
|
||||
for (PlayPersonnelGroupInfoEntity infoEntity : groupInfoEntities) {
|
||||
if (infoEntity.getId().equals(userInfo.getGroupId())) {
|
||||
if (Objects.equals(infoEntity.getId(), userInfo.getGroupId())) {
|
||||
returnVo.setGroupName(infoEntity.getGroupName());
|
||||
}
|
||||
}
|
||||
@@ -79,7 +126,456 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
|
||||
returnVo.setOrderRefundAmount(orderRefundAmount);
|
||||
returnVo.setCustomNumber(customIds.size());
|
||||
returnVo.setEstimatedRevenue(estimatedRevenue);
|
||||
|
||||
return returnVo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClerkPerformanceOverviewResponseVo queryOverview(ClerkPerformanceOverviewQueryVo vo) {
|
||||
DateRange range = resolveDateRange(vo.getEndOrderTime());
|
||||
List<PlayClerkUserInfoEntity> clerks = loadAccessibleClerks(vo);
|
||||
ClerkPerformanceOverviewResponseVo responseVo = new ClerkPerformanceOverviewResponseVo();
|
||||
if (CollectionUtil.isEmpty(clerks)) {
|
||||
responseVo.setSummary(new ClerkPerformanceOverviewSummaryVo());
|
||||
responseVo.setRankings(Collections.emptyList());
|
||||
responseVo.setTotal(0);
|
||||
return responseVo;
|
||||
}
|
||||
Map<String, String> levelNameMap = playClerkLevelInfoService.selectAll().stream().collect(
|
||||
Collectors.toMap(PlayClerkLevelInfoEntity::getId, PlayClerkLevelInfoEntity::getName, (a, b) -> a));
|
||||
Map<String, String> groupNameMap = playPersonnelGroupInfoService.selectAll().stream().collect(
|
||||
Collectors.toMap(PlayPersonnelGroupInfoEntity::getId, PlayPersonnelGroupInfoEntity::getGroupName,
|
||||
(a, b) -> a));
|
||||
List<ClerkPerformanceSnapshotVo> snapshots = new ArrayList<>(clerks.size());
|
||||
for (PlayClerkUserInfoEntity clerk : clerks) {
|
||||
List<PlayOrderInfoEntity> orders = playOrderInfoService.clerkSelectOrderInfoList(clerk.getId(),
|
||||
range.startTime, range.endTime);
|
||||
snapshots.add(buildSnapshot(clerk, orders, levelNameMap, groupNameMap));
|
||||
}
|
||||
int total = snapshots.size();
|
||||
ClerkPerformanceOverviewSummaryVo summary = aggregateSummary(snapshots);
|
||||
List<ClerkPerformanceSnapshotVo> sorted = new ArrayList<>(snapshots);
|
||||
applySortAndRank(sorted, vo.getSortField(), vo.getSortDirection());
|
||||
if (vo.getLimit() != null && vo.getLimit() > 0 && vo.getLimit() < sorted.size()) {
|
||||
sorted = new ArrayList<>(sorted.subList(0, vo.getLimit()));
|
||||
applySequentialRank(sorted);
|
||||
}
|
||||
responseVo.setSummary(Boolean.TRUE.equals(vo.getIncludeSummary()) ? summary : null);
|
||||
responseVo.setRankings(Boolean.TRUE.equals(vo.getIncludeRankings()) ? sorted : Collections.emptyList());
|
||||
responseVo.setTotal(total);
|
||||
return responseVo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClerkPerformanceDetailResponseVo queryDetail(ClerkPerformanceDetailQueryVo vo) {
|
||||
DateRange range = resolveDateRange(vo.getEndOrderTime());
|
||||
PlayClerkUserInfoEntity clerk = clerkUserInfoService.getById(vo.getClerkId());
|
||||
if (clerk == null) {
|
||||
throw new ServiceException("店员不存在");
|
||||
}
|
||||
List<String> accessibleIds =
|
||||
playPersonnelGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
|
||||
if (CollectionUtil.isEmpty(accessibleIds) || !accessibleIds.contains(clerk.getId())) {
|
||||
throw new ServiceException("无权查看该店员业绩");
|
||||
}
|
||||
Map<String, String> levelNameMap = playClerkLevelInfoService.selectAll().stream().collect(
|
||||
Collectors.toMap(PlayClerkLevelInfoEntity::getId, PlayClerkLevelInfoEntity::getName, (a, b) -> a));
|
||||
Map<String, String> groupNameMap = playPersonnelGroupInfoService.selectAll().stream().collect(
|
||||
Collectors.toMap(PlayPersonnelGroupInfoEntity::getId, PlayPersonnelGroupInfoEntity::getGroupName,
|
||||
(a, b) -> a));
|
||||
List<PlayOrderInfoEntity> orders = playOrderInfoService.clerkSelectOrderInfoList(clerk.getId(),
|
||||
range.startTime, range.endTime);
|
||||
ClerkPerformanceSnapshotVo snapshot = buildSnapshot(clerk, orders, levelNameMap, groupNameMap);
|
||||
ClerkPerformanceDetailResponseVo responseVo = new ClerkPerformanceDetailResponseVo();
|
||||
responseVo.setProfile(buildProfile(clerk, levelNameMap, groupNameMap));
|
||||
responseVo.setSnapshot(snapshot);
|
||||
responseVo.setComposition(buildComposition(snapshot));
|
||||
if (Boolean.TRUE.equals(vo.getIncludeTrend())) {
|
||||
responseVo.setTrend(buildTrend(orders, range,
|
||||
vo.getTrendDays() == null || vo.getTrendDays() <= 0 ? 7 : vo.getTrendDays()));
|
||||
responseVo.setTrendDimension("DAY");
|
||||
} else {
|
||||
responseVo.setTrend(Collections.emptyList());
|
||||
}
|
||||
return responseVo;
|
||||
}
|
||||
|
||||
private ClerkPerformanceProfileVo buildProfile(PlayClerkUserInfoEntity clerk, Map<String, String> levelNameMap,
|
||||
Map<String, String> groupNameMap) {
|
||||
ClerkPerformanceProfileVo profile = new ClerkPerformanceProfileVo();
|
||||
profile.setClerkId(clerk.getId());
|
||||
profile.setNickname(clerk.getNickname());
|
||||
profile.setAvatar(clerk.getAvatar());
|
||||
profile.setSex(clerk.getSex());
|
||||
profile.setGroupName(groupNameMap.getOrDefault(clerk.getGroupId(), ""));
|
||||
profile.setLevelName(levelNameMap.getOrDefault(clerk.getLevelId(), ""));
|
||||
profile.setListingState(clerk.getListingState());
|
||||
profile.setOnlineState(clerk.getOnlineState());
|
||||
profile.setRole(null);
|
||||
return profile;
|
||||
}
|
||||
|
||||
private List<ClerkPerformanceTrendPointVo> buildTrend(List<PlayOrderInfoEntity> orders, DateRange range,
|
||||
int trendDays) {
|
||||
if (CollectionUtil.isEmpty(orders)) {
|
||||
return buildEmptyTrend(range, trendDays);
|
||||
}
|
||||
LocalDate end = range.endDate;
|
||||
LocalDate start = end.minusDays(trendDays - 1);
|
||||
if (start.isBefore(range.startDate)) {
|
||||
start = range.startDate;
|
||||
}
|
||||
Map<LocalDate, List<PlayOrderInfoEntity>> grouped = orders.stream()
|
||||
.filter(order -> order.getPurchaserTime() != null)
|
||||
.collect(Collectors.groupingBy(order -> order.getPurchaserTime().toLocalDate()));
|
||||
List<ClerkPerformanceTrendPointVo> points = new ArrayList<>();
|
||||
LocalDate cursor = start;
|
||||
while (!cursor.isAfter(end)) {
|
||||
List<PlayOrderInfoEntity> dayOrders = grouped.getOrDefault(cursor, Collections.emptyList());
|
||||
points.add(buildTrendPoint(cursor, dayOrders));
|
||||
cursor = cursor.plusDays(1);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
private List<ClerkPerformanceTrendPointVo> buildEmptyTrend(DateRange range, int trendDays) {
|
||||
List<ClerkPerformanceTrendPointVo> points = new ArrayList<>();
|
||||
LocalDate end = range.endDate;
|
||||
LocalDate start = end.minusDays(trendDays - 1);
|
||||
if (start.isBefore(range.startDate)) {
|
||||
start = range.startDate;
|
||||
}
|
||||
LocalDate cursor = start;
|
||||
while (!cursor.isAfter(end)) {
|
||||
points.add(buildTrendPoint(cursor, Collections.emptyList()));
|
||||
cursor = cursor.plusDays(1);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
private ClerkPerformanceTrendPointVo buildTrendPoint(LocalDate date, List<PlayOrderInfoEntity> orders) {
|
||||
ClerkPerformanceTrendPointVo point = new ClerkPerformanceTrendPointVo();
|
||||
point.setDate(date);
|
||||
BigDecimal gmv = BigDecimal.ZERO;
|
||||
BigDecimal continuedAmount = BigDecimal.ZERO;
|
||||
BigDecimal firstAmount = BigDecimal.ZERO;
|
||||
int continuedCount = 0;
|
||||
for (PlayOrderInfoEntity order : orders) {
|
||||
BigDecimal finalAmount = defaultZero(order.getFinalAmount());
|
||||
gmv = gmv.add(finalAmount);
|
||||
if ("1".equals(order.getFirstOrder())) {
|
||||
firstAmount = firstAmount.add(finalAmount);
|
||||
} else {
|
||||
continuedAmount = continuedAmount.add(finalAmount);
|
||||
continuedCount++;
|
||||
}
|
||||
}
|
||||
point.setGmv(gmv);
|
||||
point.setOrderCount(orders.size());
|
||||
point.setContinuedAmount(continuedAmount);
|
||||
point.setFirstAmount(firstAmount);
|
||||
point.setContinuedRate(calcPercentage(continuedCount, orders.size()));
|
||||
return point;
|
||||
}
|
||||
|
||||
private ClerkPerformanceDetailCompositionVo buildComposition(ClerkPerformanceSnapshotVo snapshot) {
|
||||
ClerkPerformanceDetailCompositionVo composition = new ClerkPerformanceDetailCompositionVo();
|
||||
int orderTotal = snapshot.getOrderCount();
|
||||
BigDecimal gmv = snapshot.getGmv();
|
||||
List<ClerkPerformanceDetailCompositionVo.CompositionSlice> orderSlices = new ArrayList<>();
|
||||
orderSlices.add(createSlice("FIRST_ORDER", "首单", snapshot.getFirstOrderCount(), orderTotal,
|
||||
snapshot.getFirstOrderAmount(), gmv));
|
||||
orderSlices.add(createSlice("CONTINUED_ORDER", "续单", snapshot.getContinuedOrderCount(), orderTotal,
|
||||
snapshot.getContinuedOrderAmount(), gmv));
|
||||
orderSlices.add(createSlice("REFUND_ORDER", "退款", snapshot.getRefundOrderCount(), orderTotal,
|
||||
snapshot.getRefundAmount(), gmv));
|
||||
orderSlices.add(createSlice("EXPIRED_ORDER", "超时", snapshot.getExpiredOrderCount(), orderTotal,
|
||||
BigDecimal.ZERO, gmv));
|
||||
composition.setOrderComposition(orderSlices);
|
||||
List<ClerkPerformanceDetailCompositionVo.CompositionSlice> amountSlices = new ArrayList<>();
|
||||
amountSlices.add(createSlice("FIRST_AMOUNT", "首单金额", 0, orderTotal, snapshot.getFirstOrderAmount(), gmv));
|
||||
amountSlices.add(
|
||||
createSlice("CONTINUED_AMOUNT", "续单金额", 0, orderTotal, snapshot.getContinuedOrderAmount(), gmv));
|
||||
amountSlices.add(createSlice("REWARD_AMOUNT", "打赏金额", 0, orderTotal, snapshot.getRewardAmount(), gmv));
|
||||
amountSlices.add(createSlice("REFUND_AMOUNT", "退款金额", 0, orderTotal, snapshot.getRefundAmount(), gmv));
|
||||
composition.setAmountComposition(amountSlices);
|
||||
int continuedCustomers = snapshot.getContinuedUserCount();
|
||||
int totalCustomers = snapshot.getUserCount();
|
||||
int newCustomers = Math.max(totalCustomers - continuedCustomers, 0);
|
||||
List<ClerkPerformanceDetailCompositionVo.CompositionSlice> customerSlices = new ArrayList<>();
|
||||
customerSlices.add(createSlice("NEW_CUSTOMER", "新顾客", newCustomers, totalCustomers, BigDecimal.ZERO, gmv));
|
||||
customerSlices
|
||||
.add(createSlice("CONTINUED_CUSTOMER", "复购顾客", continuedCustomers, totalCustomers, BigDecimal.ZERO,
|
||||
gmv));
|
||||
composition.setCustomerComposition(customerSlices);
|
||||
return composition;
|
||||
}
|
||||
|
||||
private ClerkPerformanceDetailCompositionVo.CompositionSlice createSlice(String key, String label, int count,
|
||||
int totalCount, BigDecimal amount, BigDecimal totalAmount) {
|
||||
ClerkPerformanceDetailCompositionVo.CompositionSlice slice =
|
||||
new ClerkPerformanceDetailCompositionVo.CompositionSlice();
|
||||
slice.setKey(key);
|
||||
slice.setLabel(label);
|
||||
slice.setCount(count);
|
||||
slice.setCountRatio(calcPercentage(count, totalCount));
|
||||
slice.setAmount(amount);
|
||||
slice.setAmountRatio(calcPercentage(amount, totalAmount));
|
||||
return slice;
|
||||
}
|
||||
|
||||
private void applySortAndRank(List<ClerkPerformanceSnapshotVo> snapshots, ClerkPerformanceSortField sortField,
|
||||
SortDirection direction) {
|
||||
if (CollectionUtil.isEmpty(snapshots)) {
|
||||
return;
|
||||
}
|
||||
Comparator<ClerkPerformanceSnapshotVo> comparator;
|
||||
if (sortField == null) {
|
||||
sortField = ClerkPerformanceSortField.GMV;
|
||||
}
|
||||
switch (sortField) {
|
||||
case ORDER_COUNT:
|
||||
comparator = Comparator.comparing(ClerkPerformanceSnapshotVo::getOrderCount, Comparator.nullsLast(Integer::compareTo));
|
||||
break;
|
||||
case CONTINUE_RATE:
|
||||
comparator = Comparator.comparing(ClerkPerformanceSnapshotVo::getContinuedRate, Comparator.nullsLast(BigDecimal::compareTo));
|
||||
break;
|
||||
case ESTIMATED_REVENUE:
|
||||
comparator = Comparator.comparing(ClerkPerformanceSnapshotVo::getEstimatedRevenue, Comparator.nullsLast(BigDecimal::compareTo));
|
||||
break;
|
||||
case GMV:
|
||||
default:
|
||||
comparator = Comparator.comparing(ClerkPerformanceSnapshotVo::getGmv, Comparator.nullsLast(BigDecimal::compareTo));
|
||||
break;
|
||||
}
|
||||
comparator = comparator.thenComparing(ClerkPerformanceSnapshotVo::getClerkNickname,
|
||||
Comparator.nullsFirst(String::compareTo));
|
||||
if (direction == null) {
|
||||
direction = SortDirection.DEFAULT;
|
||||
}
|
||||
if (direction == SortDirection.DESC) {
|
||||
comparator = comparator.reversed();
|
||||
}
|
||||
snapshots.sort(comparator);
|
||||
applySequentialRank(snapshots);
|
||||
}
|
||||
|
||||
private void applySequentialRank(List<ClerkPerformanceSnapshotVo> snapshots) {
|
||||
for (int i = 0; i < snapshots.size(); i++) {
|
||||
snapshots.get(i).setRank(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private ClerkPerformanceOverviewSummaryVo aggregateSummary(List<ClerkPerformanceSnapshotVo> snapshots) {
|
||||
ClerkPerformanceOverviewSummaryVo summary = new ClerkPerformanceOverviewSummaryVo();
|
||||
if (CollectionUtil.isEmpty(snapshots)) {
|
||||
return summary;
|
||||
}
|
||||
BigDecimal totalGmv = BigDecimal.ZERO;
|
||||
BigDecimal continuedAmount = BigDecimal.ZERO;
|
||||
BigDecimal refundAmount = BigDecimal.ZERO;
|
||||
BigDecimal estimatedRevenue = BigDecimal.ZERO;
|
||||
int orderTotal = 0;
|
||||
int firstOrderTotal = 0;
|
||||
int continuedOrderTotal = 0;
|
||||
int refundOrderTotal = 0;
|
||||
int userTotal = 0;
|
||||
int continuedUserTotal = 0;
|
||||
for (ClerkPerformanceSnapshotVo snapshot : snapshots) {
|
||||
totalGmv = totalGmv.add(defaultZero(snapshot.getGmv()));
|
||||
continuedAmount = continuedAmount.add(defaultZero(snapshot.getContinuedOrderAmount()));
|
||||
refundAmount = refundAmount.add(defaultZero(snapshot.getRefundAmount()));
|
||||
estimatedRevenue = estimatedRevenue.add(defaultZero(snapshot.getEstimatedRevenue()));
|
||||
orderTotal += snapshot.getOrderCount();
|
||||
firstOrderTotal += snapshot.getFirstOrderCount();
|
||||
continuedOrderTotal += snapshot.getContinuedOrderCount();
|
||||
refundOrderTotal += snapshot.getRefundOrderCount();
|
||||
userTotal += snapshot.getUserCount();
|
||||
continuedUserTotal += snapshot.getContinuedUserCount();
|
||||
}
|
||||
summary.setTotalGmv(totalGmv);
|
||||
summary.setTotalOrderCount(orderTotal);
|
||||
summary.setTotalFirstOrderCount(firstOrderTotal);
|
||||
summary.setTotalContinuedOrderCount(continuedOrderTotal);
|
||||
summary.setTotalRefundOrderCount(refundOrderTotal);
|
||||
summary.setTotalEstimatedRevenue(estimatedRevenue);
|
||||
summary.setTotalUserCount(userTotal);
|
||||
summary.setTotalContinuedUserCount(continuedUserTotal);
|
||||
summary.setContinuedRate(calcPercentage(continuedOrderTotal, orderTotal));
|
||||
summary.setContinuedAmountRate(calcPercentage(continuedAmount, totalGmv));
|
||||
summary.setRefundRate(calcPercentage(refundOrderTotal, orderTotal));
|
||||
summary.setAverageTicketPrice(calcAverage(totalGmv, orderTotal));
|
||||
summary.setContinuedCustomerRate(calcPercentage(continuedUserTotal, userTotal));
|
||||
return summary;
|
||||
}
|
||||
|
||||
private ClerkPerformanceSnapshotVo buildSnapshot(PlayClerkUserInfoEntity clerk, List<PlayOrderInfoEntity> orders,
|
||||
Map<String, String> levelNameMap, Map<String, String> groupNameMap) {
|
||||
ClerkPerformanceSnapshotVo snapshot = new ClerkPerformanceSnapshotVo();
|
||||
snapshot.setClerkId(clerk.getId());
|
||||
snapshot.setClerkNickname(clerk.getNickname());
|
||||
snapshot.setAvatar(clerk.getAvatar());
|
||||
snapshot.setSex(clerk.getSex());
|
||||
snapshot.setGroupName(groupNameMap.getOrDefault(clerk.getGroupId(), ""));
|
||||
snapshot.setLevelName(levelNameMap.getOrDefault(clerk.getLevelId(), ""));
|
||||
BigDecimal gmv = BigDecimal.ZERO;
|
||||
BigDecimal firstAmount = BigDecimal.ZERO;
|
||||
BigDecimal continuedAmount = BigDecimal.ZERO;
|
||||
BigDecimal rewardAmount = BigDecimal.ZERO;
|
||||
BigDecimal refundAmount = BigDecimal.ZERO;
|
||||
BigDecimal estimatedRevenue = BigDecimal.ZERO;
|
||||
int firstCount = 0;
|
||||
int continuedCount = 0;
|
||||
int refundCount = 0;
|
||||
int expiredCount = 0;
|
||||
Map<String, Integer> userOrderMap = new HashMap<>();
|
||||
for (PlayOrderInfoEntity order : orders) {
|
||||
BigDecimal finalAmount = defaultZero(order.getFinalAmount());
|
||||
gmv = gmv.add(finalAmount);
|
||||
userOrderMap.merge(order.getPurchaserBy(), 1, Integer::sum);
|
||||
if ("1".equals(order.getFirstOrder())) {
|
||||
firstCount++;
|
||||
firstAmount = firstAmount.add(finalAmount);
|
||||
} else {
|
||||
continuedCount++;
|
||||
continuedAmount = continuedAmount.add(finalAmount);
|
||||
}
|
||||
if ("2".equals(order.getPlaceType())) {
|
||||
rewardAmount = rewardAmount.add(finalAmount);
|
||||
}
|
||||
if ("1".equals(order.getRefundType())) {
|
||||
refundCount++;
|
||||
refundAmount = refundAmount.add(defaultZero(order.getRefundAmount()));
|
||||
}
|
||||
if ("1".equals(order.getOrdersExpiredState())) {
|
||||
expiredCount++;
|
||||
}
|
||||
estimatedRevenue = estimatedRevenue.add(defaultZero(order.getEstimatedRevenue()));
|
||||
}
|
||||
int orderCount = orders.size();
|
||||
int userCount = userOrderMap.size();
|
||||
int continuedUserCount = (int) userOrderMap.values().stream().filter(cnt -> cnt > 1).count();
|
||||
snapshot.setGmv(gmv);
|
||||
snapshot.setFirstOrderAmount(firstAmount);
|
||||
snapshot.setContinuedOrderAmount(continuedAmount);
|
||||
snapshot.setRewardAmount(rewardAmount);
|
||||
snapshot.setRefundAmount(refundAmount);
|
||||
snapshot.setEstimatedRevenue(estimatedRevenue);
|
||||
snapshot.setOrderCount(orderCount);
|
||||
snapshot.setFirstOrderCount(firstCount);
|
||||
snapshot.setContinuedOrderCount(continuedCount);
|
||||
snapshot.setRefundOrderCount(refundCount);
|
||||
snapshot.setExpiredOrderCount(expiredCount);
|
||||
snapshot.setUserCount(userCount);
|
||||
snapshot.setContinuedUserCount(continuedUserCount);
|
||||
snapshot.setContinuedRate(calcPercentage(continuedCount, orderCount));
|
||||
snapshot.setContinuedAmountRate(calcPercentage(continuedAmount, gmv));
|
||||
snapshot.setRefundRate(calcPercentage(refundCount, orderCount));
|
||||
snapshot.setContinuedCustomerRate(calcPercentage(continuedUserCount, userCount));
|
||||
snapshot.setAverageTicketPrice(calcAverage(gmv, orderCount));
|
||||
snapshot.setOnlineDuration(0L);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private List<PlayClerkUserInfoEntity> loadAccessibleClerks(ClerkPerformanceOverviewQueryVo vo) {
|
||||
List<String> accessibleIds =
|
||||
playPersonnelGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
|
||||
if (CollectionUtil.isEmpty(accessibleIds)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
LambdaQueryWrapper<PlayClerkUserInfoEntity> wrapper =
|
||||
Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class).in(PlayClerkUserInfoEntity::getId, accessibleIds)
|
||||
.eq(PlayClerkUserInfoEntity::getClerkState, "1");
|
||||
if (StrUtil.isNotBlank(vo.getClerkId())) {
|
||||
wrapper.eq(PlayClerkUserInfoEntity::getId, vo.getClerkId());
|
||||
}
|
||||
if (StrUtil.isNotBlank(vo.getGroupId())) {
|
||||
wrapper.eq(PlayClerkUserInfoEntity::getGroupId, vo.getGroupId());
|
||||
}
|
||||
if (StrUtil.isNotBlank(vo.getSex())) {
|
||||
wrapper.eq(PlayClerkUserInfoEntity::getSex, vo.getSex());
|
||||
}
|
||||
if (StrUtil.isNotBlank(vo.getListingState())) {
|
||||
wrapper.eq(PlayClerkUserInfoEntity::getListingState, vo.getListingState());
|
||||
}
|
||||
return clerkUserInfoService.list(wrapper);
|
||||
}
|
||||
|
||||
private DateRange resolveDateRange(List<String> endOrderTime) {
|
||||
String startStr;
|
||||
String endStr;
|
||||
if (CollectionUtil.isNotEmpty(endOrderTime) && endOrderTime.size() >= 2) {
|
||||
startStr = normalizeStart(endOrderTime.get(0));
|
||||
endStr = normalizeEnd(endOrderTime.get(1));
|
||||
} else {
|
||||
LocalDate end = LocalDate.now();
|
||||
LocalDate start = end.minusDays(6);
|
||||
startStr = start.format(DATE_FORMATTER) + " 00:00:00";
|
||||
endStr = end.format(DATE_FORMATTER) + " 23:59:59";
|
||||
}
|
||||
LocalDate startDate = LocalDate.parse(startStr.substring(0, 10), DATE_FORMATTER);
|
||||
LocalDate endDate = LocalDate.parse(endStr.substring(0, 10), DATE_FORMATTER);
|
||||
return new DateRange(startStr, endStr, startDate, endDate);
|
||||
}
|
||||
|
||||
private String normalizeStart(String raw) {
|
||||
if (StrUtil.isBlank(raw)) {
|
||||
return LocalDate.now().minusDays(6).format(DATE_FORMATTER) + " 00:00:00";
|
||||
}
|
||||
if (raw.length() > 10) {
|
||||
LocalDateTime.parse(raw, DATE_TIME_FORMATTER);
|
||||
return raw;
|
||||
}
|
||||
return raw + " 00:00:00";
|
||||
}
|
||||
|
||||
private String normalizeEnd(String raw) {
|
||||
if (StrUtil.isBlank(raw)) {
|
||||
return LocalDate.now().format(DATE_FORMATTER) + " 23:59:59";
|
||||
}
|
||||
if (raw.length() > 10) {
|
||||
LocalDateTime.parse(raw, DATE_TIME_FORMATTER);
|
||||
return raw;
|
||||
}
|
||||
return raw + " 23:59:59";
|
||||
}
|
||||
|
||||
private BigDecimal calcPercentage(int numerator, int denominator) {
|
||||
if (denominator <= 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return BigDecimal.valueOf(numerator).multiply(BigDecimal.valueOf(100))
|
||||
.divide(BigDecimal.valueOf(denominator), 2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
private BigDecimal calcPercentage(BigDecimal numerator, BigDecimal denominator) {
|
||||
if (denominator == null || BigDecimal.ZERO.compareTo(denominator) == 0 || numerator == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return numerator.multiply(BigDecimal.valueOf(100)).divide(denominator, 2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
private BigDecimal calcAverage(BigDecimal numerator, int denominator) {
|
||||
if (denominator <= 0 || numerator == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return numerator.divide(BigDecimal.valueOf(denominator), 2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
private BigDecimal defaultZero(BigDecimal value) {
|
||||
return value == null ? BigDecimal.ZERO : value;
|
||||
}
|
||||
|
||||
private static final class DateRange {
|
||||
private final String startTime;
|
||||
private final String endTime;
|
||||
private final LocalDate startDate;
|
||||
private final LocalDate endDate;
|
||||
|
||||
private DateRange(String startTime, String endTime, LocalDate startDate, LocalDate endDate) {
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
this.startDate = startDate;
|
||||
this.endDate = endDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user