feat(pk): implement PK (Player-Killer) system with lifecycle management
- Add PK entity fields: winner, scores, and setting_id - Implement force start/end API endpoints for clerk PK - Add PK lifecycle service with auto-start/end scheduling - Add Redis-based PK state management - Implement PK detail service with live/history/upcoming queries - Add WeChat PK controller with history and live PK endpoints - Add comprehensive PK integration tests - Create PK setting management with tenant-specific configs - Add database migrations for PK scores, winner, settings, and menu - Add PK-related DTOs and enums (status, menu paths) - Add TenantScope utility for tenant context management
This commit is contained in:
@@ -24,6 +24,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
public static final String PARAMETER_FORMAT_ERROR = "请求参数格式异常";
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
@@ -87,20 +88,20 @@ public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(MismatchedInputException.class)
|
||||
public R mismatchedInputException(MismatchedInputException e) {
|
||||
log.error("请求参数格式异常", e);
|
||||
return R.error("请求参数格式异常");
|
||||
log.error(PARAMETER_FORMAT_ERROR, e);
|
||||
return R.error(PARAMETER_FORMAT_ERROR);
|
||||
}
|
||||
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
public R httpMessageNotReadableException(HttpMessageNotReadableException e) {
|
||||
log.error("请求参数格式异常", e);
|
||||
return R.error("请求参数格式异常");
|
||||
log.error(PARAMETER_FORMAT_ERROR, e);
|
||||
return R.error(PARAMETER_FORMAT_ERROR);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
public R missingServletRequestParameterException(MissingServletRequestParameterException e) {
|
||||
log.error("请求参数格式异常", e);
|
||||
return R.error("请求参数格式异常");
|
||||
log.error(PARAMETER_FORMAT_ERROR, e);
|
||||
return R.error(PARAMETER_FORMAT_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkPkService;
|
||||
import com.starry.admin.modules.pk.dto.PkScoreBoardDto;
|
||||
import com.starry.admin.modules.pk.dto.PlayClerkPkForceStartRequest;
|
||||
import com.starry.admin.modules.pk.service.ClerkPkLifecycleService;
|
||||
import com.starry.admin.modules.pk.service.IPkScoreboardService;
|
||||
import com.starry.common.annotation.Log;
|
||||
@@ -100,6 +101,18 @@ public class PlayClerkPkController {
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制开始PK(无需排期,便于人工触发)
|
||||
*/
|
||||
@ApiOperation(value = "强制开始PK", notes = "人工触发PK开始并直接进入进行中状态")
|
||||
@ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
|
||||
@Log(title = "店员pk", businessType = BusinessType.INSERT)
|
||||
@PostMapping(value = "/force-start")
|
||||
public R forceStart(@ApiParam(value = "强制开始请求", required = true)
|
||||
@RequestBody PlayClerkPkForceStartRequest request) {
|
||||
return R.ok(clerkPkLifecycleService.forceStart(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增店员pk
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package com.starry.admin.modules.clerk.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
/**
|
||||
* 店员pkMapper接口
|
||||
@@ -11,4 +16,47 @@ import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
*/
|
||||
public interface PlayClerkPkMapper extends BaseMapper<PlayClerkPkEntity> {
|
||||
|
||||
@InterceptorIgnore(tenantLine = "1")
|
||||
@Select("SELECT * FROM play_clerk_pk "
|
||||
+ "WHERE status = #{status} "
|
||||
+ "AND pk_begin_time >= #{beginTime} "
|
||||
+ "AND pk_begin_time <= #{endTime}")
|
||||
List<PlayClerkPkEntity> selectUpcomingByStatus(
|
||||
@Param("status") String status,
|
||||
@Param("beginTime") Date beginTime,
|
||||
@Param("endTime") Date endTime);
|
||||
|
||||
@Select("<script>"
|
||||
+ "SELECT * FROM play_clerk_pk "
|
||||
+ "WHERE tenant_id = #{tenantId} "
|
||||
+ " AND status = #{status} "
|
||||
+ " AND ("
|
||||
+ " (clerk_a = #{clerkAId} AND clerk_b = #{clerkBId}) "
|
||||
+ " OR (clerk_a = #{clerkBId} AND clerk_b = #{clerkAId})"
|
||||
+ " ) "
|
||||
+ "ORDER BY pk_begin_time DESC "
|
||||
+ "LIMIT #{limit}"
|
||||
+ "</script>")
|
||||
List<PlayClerkPkEntity> selectRecentFinishedBetweenClerks(
|
||||
@Param("tenantId") String tenantId,
|
||||
@Param("clerkAId") String clerkAId,
|
||||
@Param("clerkBId") String clerkBId,
|
||||
@Param("status") String status,
|
||||
@Param("limit") int limit);
|
||||
|
||||
@Select("<script>"
|
||||
+ "SELECT * FROM play_clerk_pk "
|
||||
+ "WHERE tenant_id = #{tenantId} "
|
||||
+ " AND status = #{status} "
|
||||
+ " AND pk_begin_time >= #{beginTime} "
|
||||
+ " AND (clerk_a = #{clerkId} OR clerk_b = #{clerkId}) "
|
||||
+ "ORDER BY pk_begin_time ASC "
|
||||
+ "LIMIT #{limit}"
|
||||
+ "</script>")
|
||||
List<PlayClerkPkEntity> selectUpcomingForClerk(
|
||||
@Param("tenantId") String tenantId,
|
||||
@Param("clerkId") String clerkId,
|
||||
@Param("status") String status,
|
||||
@Param("beginTime") Date beginTime,
|
||||
@Param("limit") int limit);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,11 @@ public class PlayClerkPkEntity extends BaseEntity<PlayClerkPkEntity> {
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 排期设置ID
|
||||
*/
|
||||
private String settingId;
|
||||
|
||||
/**
|
||||
* 店员A得分
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -75,4 +77,6 @@ public interface IPlayClerkPkService extends IService<PlayClerkPkEntity> {
|
||||
* @return 存在则返回 PK 记录,否则返回空
|
||||
*/
|
||||
Optional<PlayClerkPkEntity> findActivePkForClerk(String clerkId, LocalDateTime occurredAt);
|
||||
|
||||
List<PlayClerkPkEntity> selectUpcomingForClerk(String tenantId, String clerkId, Date beginTime, int limit);
|
||||
}
|
||||
|
||||
@@ -7,19 +7,25 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.starry.admin.common.PageBuilder;
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.modules.clerk.mapper.PlayClerkPkMapper;
|
||||
import com.starry.admin.modules.clerk.module.entity.ClerkPkEnum;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkPkService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
import com.starry.admin.modules.pk.enums.PkLifecycleErrorCode;
|
||||
import com.starry.admin.modules.pk.redis.PkRedisKeyConstants;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
@@ -36,6 +42,8 @@ public class PlayClerkPkServiceImpl extends ServiceImpl<PlayClerkPkMapper, PlayC
|
||||
private PlayClerkPkMapper playClerkPkMapper;
|
||||
@Resource
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
/**
|
||||
* 查询店员pk
|
||||
@@ -58,8 +66,15 @@ public class PlayClerkPkServiceImpl extends ServiceImpl<PlayClerkPkMapper, PlayC
|
||||
*/
|
||||
@Override
|
||||
public IPage<PlayClerkPkEntity> selectPlayClerkPkByPage(PlayClerkPkEntity playClerkPk) {
|
||||
Page<PlayClerkPkEntity> page = new Page<>(1, 10);
|
||||
return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
|
||||
Page<PlayClerkPkEntity> page = PageBuilder.build();
|
||||
LambdaQueryWrapper<PlayClerkPkEntity> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(StrUtil.isNotBlank(playClerkPk.getStatus()), PlayClerkPkEntity::getStatus, playClerkPk.getStatus());
|
||||
wrapper.eq(StrUtil.isNotBlank(playClerkPk.getClerkA()), PlayClerkPkEntity::getClerkA, playClerkPk.getClerkA());
|
||||
wrapper.eq(StrUtil.isNotBlank(playClerkPk.getClerkB()), PlayClerkPkEntity::getClerkB, playClerkPk.getClerkB());
|
||||
wrapper.eq(StrUtil.isNotBlank(playClerkPk.getSettingId()), PlayClerkPkEntity::getSettingId,
|
||||
playClerkPk.getSettingId());
|
||||
wrapper.orderByDesc(PlayClerkPkEntity::getPkBeginTime);
|
||||
return this.baseMapper.selectPage(page, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,7 +116,11 @@ public class PlayClerkPkServiceImpl extends ServiceImpl<PlayClerkPkMapper, PlayC
|
||||
}
|
||||
|
||||
playClerkPk.setStatus(ClerkPkEnum.TO_BE_STARTED.name());
|
||||
return save(playClerkPk);
|
||||
boolean saved = save(playClerkPk);
|
||||
if (saved) {
|
||||
scheduleStart(playClerkPk);
|
||||
}
|
||||
return saved;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,6 +159,18 @@ public class PlayClerkPkServiceImpl extends ServiceImpl<PlayClerkPkMapper, PlayC
|
||||
return playClerkPkMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private void scheduleStart(PlayClerkPkEntity pk) {
|
||||
if (pk == null || pk.getPkBeginTime() == null || pk.getId() == null) {
|
||||
throw new CustomException(PkLifecycleErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
if (StrUtil.isBlank(pk.getTenantId())) {
|
||||
throw new CustomException(PkLifecycleErrorCode.TENANT_MISSING.getMessage());
|
||||
}
|
||||
String scheduleKey = PkRedisKeyConstants.startScheduleKey(pk.getTenantId());
|
||||
long startEpochSeconds = pk.getPkBeginTime().toInstant().getEpochSecond();
|
||||
stringRedisTemplate.opsForZSet().add(scheduleKey, pk.getId(), startEpochSeconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<PlayClerkPkEntity> findActivePkForClerk(String clerkId, LocalDateTime occurredAt) {
|
||||
if (StrUtil.isBlank(clerkId) || occurredAt == null) {
|
||||
@@ -158,4 +189,18 @@ public class PlayClerkPkServiceImpl extends ServiceImpl<PlayClerkPkMapper, PlayC
|
||||
PlayClerkPkEntity entity = this.getOne(wrapper, false);
|
||||
return Optional.ofNullable(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PlayClerkPkEntity> selectUpcomingForClerk(String tenantId, String clerkId, Date beginTime,
|
||||
int limit) {
|
||||
if (StrUtil.isBlank(tenantId) || StrUtil.isBlank(clerkId) || beginTime == null || limit <= 0) {
|
||||
throw new CustomException(PkLifecycleErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
return playClerkPkMapper.selectUpcomingForClerk(
|
||||
tenantId,
|
||||
clerkId,
|
||||
ClerkPkEnum.TO_BE_STARTED.name(),
|
||||
beginTime,
|
||||
limit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,12 @@ package com.starry.admin.modules.order.mapper;
|
||||
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.pk.dto.WxPkContributorDto;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
/**
|
||||
* 订单Mapper接口
|
||||
@@ -11,4 +17,31 @@ import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
*/
|
||||
public interface PlayOrderInfoMapper extends MPJBaseMapper<PlayOrderInfoEntity> {
|
||||
|
||||
@Select("<script>"
|
||||
+ "SELECT o.purchaser_by AS userId, "
|
||||
+ " u.nickname AS nickname, "
|
||||
+ " COALESCE(SUM(o.final_amount), 0) AS amount "
|
||||
+ "FROM play_order_info o "
|
||||
+ "LEFT JOIN play_custom_user_info u "
|
||||
+ " ON u.id = o.purchaser_by "
|
||||
+ " AND u.tenant_id = o.tenant_id "
|
||||
+ "WHERE o.tenant_id = #{tenantId} "
|
||||
+ " AND o.order_status = #{orderStatus} "
|
||||
+ " AND o.final_amount > #{minAmount} "
|
||||
+ " AND o.accept_by IN (#{clerkAId}, #{clerkBId}) "
|
||||
+ " AND o.order_end_time >= #{startTime} "
|
||||
+ " AND o.order_end_time <= #{endTime} "
|
||||
+ "GROUP BY o.purchaser_by, u.nickname "
|
||||
+ "ORDER BY amount DESC "
|
||||
+ "LIMIT #{limit}"
|
||||
+ "</script>")
|
||||
List<WxPkContributorDto> selectPkContributors(
|
||||
@Param("tenantId") String tenantId,
|
||||
@Param("clerkAId") String clerkAId,
|
||||
@Param("clerkBId") String clerkBId,
|
||||
@Param("startTime") LocalDateTime startTime,
|
||||
@Param("endTime") LocalDateTime endTime,
|
||||
@Param("orderStatus") String orderStatus,
|
||||
@Param("minAmount") BigDecimal minAmount,
|
||||
@Param("limit") int limit);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.starry.admin.modules.pk.constants;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public final class PkWxQueryConstants {
|
||||
|
||||
public static final int CONTRIBUTOR_LIMIT = 10;
|
||||
public static final int HISTORY_LIMIT = 10;
|
||||
public static final int CLERK_HISTORY_PAGE_NUM = 1;
|
||||
public static final int CLERK_HISTORY_PAGE_SIZE = 10;
|
||||
public static final int CLERK_HISTORY_MIN_PAGE = 1;
|
||||
public static final int CLERK_SCHEDULE_DEFAULT_LIMIT = 3;
|
||||
public static final int CLERK_SCHEDULE_MIN_LIMIT = 1;
|
||||
public static final int CLERK_SCHEDULE_MAX_LIMIT = 20;
|
||||
public static final int TOP_CONTRIBUTOR_LIMIT = 1;
|
||||
public static final int WIN_RATE_SCALE = 2;
|
||||
public static final String PERCENT_SUFFIX = "%";
|
||||
public static final BigDecimal MIN_CONTRIBUTION_AMOUNT = BigDecimal.ZERO;
|
||||
public static final BigDecimal WIN_RATE_MULTIPLIER = new BigDecimal("100");
|
||||
|
||||
private PkWxQueryConstants() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.starry.admin.modules.pk.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@ApiModel(value = "PlayClerkPkForceStartRequest", description = "强制开始PK请求")
|
||||
@Data
|
||||
public class PlayClerkPkForceStartRequest {
|
||||
|
||||
@ApiModelProperty(value = "店员A ID", required = true)
|
||||
private String clerkAId;
|
||||
|
||||
@ApiModelProperty(value = "店员B ID", required = true)
|
||||
private String clerkBId;
|
||||
|
||||
@ApiModelProperty(value = "持续分钟数", required = true)
|
||||
private Integer durationMinutes;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.starry.admin.modules.pk.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class WxPkClerkHistoryPageDto {
|
||||
private List<WxPkHistoryDto> items = new ArrayList<>();
|
||||
private WxPkClerkHistorySummaryDto summary = new WxPkClerkHistorySummaryDto();
|
||||
private long totalCount;
|
||||
private int pageNum;
|
||||
private int pageSize;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.starry.admin.modules.pk.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class WxPkClerkHistorySummaryDto {
|
||||
private static final String ZERO_PERCENT = "0%";
|
||||
|
||||
private long winCount;
|
||||
private long totalCount;
|
||||
private String winRate = ZERO_PERCENT;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.starry.admin.modules.pk.dto;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class WxPkContributorDto {
|
||||
private static final String EMPTY_TEXT = "";
|
||||
|
||||
private String userId = EMPTY_TEXT;
|
||||
private String nickname = EMPTY_TEXT;
|
||||
private BigDecimal amount = BigDecimal.ZERO;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.starry.admin.modules.pk.dto;
|
||||
|
||||
import com.starry.admin.modules.pk.enums.PkWxState;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class WxPkDetailDto {
|
||||
private static final String EMPTY_TEXT = "";
|
||||
private static final long ZERO_SECONDS = 0L;
|
||||
private static final Date EPOCH_DATE = Date.from(Instant.EPOCH);
|
||||
|
||||
private String id = EMPTY_TEXT;
|
||||
private String state = EMPTY_TEXT;
|
||||
private String clerkAId = EMPTY_TEXT;
|
||||
private String clerkBId = EMPTY_TEXT;
|
||||
private String clerkAName = EMPTY_TEXT;
|
||||
private String clerkBName = EMPTY_TEXT;
|
||||
private String clerkAAvatar = EMPTY_TEXT;
|
||||
private String clerkBAvatar = EMPTY_TEXT;
|
||||
private BigDecimal clerkAScore = BigDecimal.ZERO;
|
||||
private BigDecimal clerkBScore = BigDecimal.ZERO;
|
||||
private int clerkAOrderCount = 0;
|
||||
private int clerkBOrderCount = 0;
|
||||
private long remainingSeconds = ZERO_SECONDS;
|
||||
private Date pkBeginTime = EPOCH_DATE;
|
||||
private Date pkEndTime = EPOCH_DATE;
|
||||
private List<WxPkContributorDto> contributors = new ArrayList<>();
|
||||
private List<WxPkHistoryDto> history = new ArrayList<>();
|
||||
|
||||
public static WxPkDetailDto inactive() {
|
||||
WxPkDetailDto dto = new WxPkDetailDto();
|
||||
dto.setState(PkWxState.INACTIVE.getValue());
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.starry.admin.modules.pk.dto;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class WxPkHistoryDto {
|
||||
private static final String EMPTY_TEXT = "";
|
||||
private static final Date EPOCH_DATE = Date.from(Instant.EPOCH);
|
||||
|
||||
private String id = EMPTY_TEXT;
|
||||
private String clerkAId = EMPTY_TEXT;
|
||||
private String clerkBId = EMPTY_TEXT;
|
||||
private String winnerClerkId = EMPTY_TEXT;
|
||||
private String clerkAName = EMPTY_TEXT;
|
||||
private String clerkBName = EMPTY_TEXT;
|
||||
private BigDecimal clerkAScore = BigDecimal.ZERO;
|
||||
private BigDecimal clerkBScore = BigDecimal.ZERO;
|
||||
private Date pkBeginTime = EPOCH_DATE;
|
||||
private String topContributorName = EMPTY_TEXT;
|
||||
private BigDecimal topContributorAmount = BigDecimal.ZERO;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.starry.admin.modules.pk.dto;
|
||||
|
||||
import com.starry.admin.modules.pk.enums.PkWxState;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class WxPkLiveDto {
|
||||
private static final String EMPTY_TEXT = "";
|
||||
private static final long ZERO_SECONDS = 0L;
|
||||
|
||||
private String id = EMPTY_TEXT;
|
||||
private String state = EMPTY_TEXT;
|
||||
private String clerkAId = EMPTY_TEXT;
|
||||
private String clerkBId = EMPTY_TEXT;
|
||||
private String clerkAName = EMPTY_TEXT;
|
||||
private String clerkBName = EMPTY_TEXT;
|
||||
private String clerkAAvatar = EMPTY_TEXT;
|
||||
private String clerkBAvatar = EMPTY_TEXT;
|
||||
private BigDecimal clerkAScore = BigDecimal.ZERO;
|
||||
private BigDecimal clerkBScore = BigDecimal.ZERO;
|
||||
private int clerkAOrderCount = 0;
|
||||
private int clerkBOrderCount = 0;
|
||||
private long remainingSeconds = ZERO_SECONDS;
|
||||
private long pkEndEpochSeconds = ZERO_SECONDS;
|
||||
private long serverEpochSeconds = ZERO_SECONDS;
|
||||
|
||||
public static WxPkLiveDto inactive() {
|
||||
WxPkLiveDto dto = new WxPkLiveDto();
|
||||
dto.setState(PkWxState.INACTIVE.getValue());
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.starry.admin.modules.pk.dto;
|
||||
|
||||
import com.starry.admin.modules.pk.enums.PkWxState;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class WxPkUpcomingDto {
|
||||
private static final String EMPTY_TEXT = "";
|
||||
private static final Date EPOCH_DATE = Date.from(Instant.EPOCH);
|
||||
|
||||
private String id = EMPTY_TEXT;
|
||||
private String state = EMPTY_TEXT;
|
||||
private String clerkAId = EMPTY_TEXT;
|
||||
private String clerkBId = EMPTY_TEXT;
|
||||
private String clerkAName = EMPTY_TEXT;
|
||||
private String clerkBName = EMPTY_TEXT;
|
||||
private String clerkAAvatar = EMPTY_TEXT;
|
||||
private String clerkBAvatar = EMPTY_TEXT;
|
||||
private Date pkBeginTime = EPOCH_DATE;
|
||||
|
||||
public static WxPkUpcomingDto inactive() {
|
||||
WxPkUpcomingDto dto = new WxPkUpcomingDto();
|
||||
dto.setState(PkWxState.INACTIVE.getValue());
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.starry.admin.modules.pk.enums;
|
||||
|
||||
public enum PkLifecycleErrorCode {
|
||||
REQUEST_INVALID("PK手动开始参数非法"),
|
||||
CLERK_CONFLICT("店员排期冲突"),
|
||||
TENANT_MISSING("租户ID缺失");
|
||||
|
||||
private final String message;
|
||||
|
||||
PkLifecycleErrorCode(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.starry.admin.modules.pk.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
public enum PkWxState {
|
||||
ACTIVE("ACTIVE", "进行中"),
|
||||
UPCOMING("UPCOMING", "即将开始"),
|
||||
INACTIVE("INACTIVE", "无进行中PK");
|
||||
|
||||
@Getter
|
||||
private final String value;
|
||||
@Getter
|
||||
private final String desc;
|
||||
}
|
||||
@@ -8,11 +8,19 @@ public final class PkRedisKeyConstants {
|
||||
private static final String SCORE_HASH_PREFIX = "pk:";
|
||||
private static final String SCORE_HASH_SUFFIX = ":score";
|
||||
private static final String DEDUP_KEY_PREFIX = "pk:dedup:";
|
||||
private static final String UPCOMING_PREFIX = "pk:upcoming:";
|
||||
private static final String START_SCHEDULE_PREFIX = "pk:scheduler:start:";
|
||||
private static final String START_LOCK_PREFIX = "pk:scheduler:start:lock:";
|
||||
private static final String FINISH_SCHEDULE_PREFIX = "pk:scheduler:finish:";
|
||||
private static final String FINISH_RETRY_PREFIX = "pk:scheduler:finish:retry:";
|
||||
private static final String FINISH_FAILED_PREFIX = "pk:scheduler:finish:failed:";
|
||||
private static final String FINISH_LOCK_PREFIX = "pk:scheduler:finish:lock:";
|
||||
|
||||
/**
|
||||
* 贡献幂等记录的存活时间(秒)。
|
||||
*/
|
||||
public static final long CONTRIBUTION_DEDUP_TTL_SECONDS = 3600L;
|
||||
public static final long UPCOMING_REMINDER_TTL_SECONDS = 7200L;
|
||||
|
||||
public static final String FIELD_CLERK_A_SCORE = "clerk_a_score";
|
||||
public static final String FIELD_CLERK_B_SCORE = "clerk_b_score";
|
||||
@@ -29,4 +37,32 @@ public final class PkRedisKeyConstants {
|
||||
public static String contributionDedupKey(String sourceCode, String referenceId) {
|
||||
return DEDUP_KEY_PREFIX + sourceCode + ":" + referenceId;
|
||||
}
|
||||
|
||||
public static String upcomingKey(String tenantId) {
|
||||
return UPCOMING_PREFIX + tenantId;
|
||||
}
|
||||
|
||||
public static String startScheduleKey(String tenantId) {
|
||||
return START_SCHEDULE_PREFIX + tenantId;
|
||||
}
|
||||
|
||||
public static String startLockKey(String tenantId) {
|
||||
return START_LOCK_PREFIX + tenantId;
|
||||
}
|
||||
|
||||
public static String finishScheduleKey(String tenantId) {
|
||||
return FINISH_SCHEDULE_PREFIX + tenantId;
|
||||
}
|
||||
|
||||
public static String finishRetryKey(String tenantId) {
|
||||
return FINISH_RETRY_PREFIX + tenantId;
|
||||
}
|
||||
|
||||
public static String finishFailedKey(String tenantId) {
|
||||
return FINISH_FAILED_PREFIX + tenantId;
|
||||
}
|
||||
|
||||
public static String finishLockKey(String tenantId) {
|
||||
return FINISH_LOCK_PREFIX + tenantId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.starry.admin.modules.pk.reminder.constants;
|
||||
|
||||
public final class PkReminderConstants {
|
||||
|
||||
public static final long SCAN_INTERVAL_MILLIS = 300000L;
|
||||
public static final long UPCOMING_WINDOW_MINUTES = 60L;
|
||||
|
||||
private PkReminderConstants() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.starry.admin.modules.pk.reminder.task;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.starry.admin.modules.clerk.mapper.PlayClerkPkMapper;
|
||||
import com.starry.admin.modules.clerk.module.entity.ClerkPkEnum;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import com.starry.admin.modules.pk.redis.PkRedisKeyConstants;
|
||||
import com.starry.admin.modules.pk.reminder.constants.PkReminderConstants;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ClerkPkUpcomingReminderJob {
|
||||
|
||||
private static final double SCORE_MIN = Double.NEGATIVE_INFINITY;
|
||||
|
||||
private final PlayClerkPkMapper clerkPkMapper;
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
public ClerkPkUpcomingReminderJob(PlayClerkPkMapper clerkPkMapper, StringRedisTemplate stringRedisTemplate) {
|
||||
this.clerkPkMapper = clerkPkMapper;
|
||||
this.stringRedisTemplate = stringRedisTemplate;
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = PkReminderConstants.SCAN_INTERVAL_MILLIS)
|
||||
public void refreshUpcomingPkReminders() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime windowEnd = now.plusMinutes(PkReminderConstants.UPCOMING_WINDOW_MINUTES);
|
||||
Date begin = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());
|
||||
Date end = Date.from(windowEnd.atZone(ZoneId.systemDefault()).toInstant());
|
||||
|
||||
List<PlayClerkPkEntity> upcoming = clerkPkMapper.selectUpcomingByStatus(
|
||||
ClerkPkEnum.TO_BE_STARTED.name(),
|
||||
begin,
|
||||
end);
|
||||
if (upcoming.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<String, List<PlayClerkPkEntity>> byTenant = upcoming.stream()
|
||||
.filter(pk -> StrUtil.isNotBlank(pk.getTenantId()))
|
||||
.collect(Collectors.groupingBy(PlayClerkPkEntity::getTenantId));
|
||||
long nowEpochSeconds = now.atZone(ZoneId.systemDefault()).toEpochSecond();
|
||||
for (Map.Entry<String, List<PlayClerkPkEntity>> entry : byTenant.entrySet()) {
|
||||
String key = PkRedisKeyConstants.upcomingKey(entry.getKey());
|
||||
stringRedisTemplate.opsForZSet().removeRangeByScore(key, SCORE_MIN, nowEpochSeconds - 1);
|
||||
for (PlayClerkPkEntity pk : entry.getValue()) {
|
||||
if (pk.getPkBeginTime() == null || pk.getId() == null) {
|
||||
continue;
|
||||
}
|
||||
long score = pk.getPkBeginTime().toInstant().getEpochSecond();
|
||||
stringRedisTemplate.opsForZSet().add(key, pk.getId(), score);
|
||||
}
|
||||
stringRedisTemplate.expire(key, PkRedisKeyConstants.UPCOMING_REMINDER_TTL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.starry.admin.modules.pk.scheduler.constants;
|
||||
|
||||
public final class PkSchedulerConstants {
|
||||
|
||||
public static final long START_SCAN_INTERVAL_MILLIS = 1000L;
|
||||
public static final long FINISH_SCAN_INTERVAL_MILLIS = 1000L;
|
||||
public static final long FALLBACK_SCAN_INTERVAL_MILLIS = 300000L;
|
||||
|
||||
public static final long START_LOCK_TTL_MILLIS = 5000L;
|
||||
public static final long FINISH_LOCK_TTL_MILLIS = 5000L;
|
||||
|
||||
public static final int FINISH_RETRY_MAX_ATTEMPTS = 3;
|
||||
public static final int[] FINISH_RETRY_BACKOFF_SECONDS = {5, 10, 20};
|
||||
|
||||
public static final int START_RETRY_DELAY_SECONDS = 5;
|
||||
|
||||
private PkSchedulerConstants() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.starry.admin.modules.pk.scheduler.task;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.modules.pk.enums.PkLifecycleErrorCode;
|
||||
import com.starry.admin.modules.pk.redis.PkRedisKeyConstants;
|
||||
import com.starry.admin.modules.pk.scheduler.constants.PkSchedulerConstants;
|
||||
import com.starry.admin.modules.pk.service.ClerkPkLifecycleService;
|
||||
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
|
||||
import com.starry.admin.modules.system.service.ISysTenantService;
|
||||
import com.starry.admin.utils.TenantScope;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PkFinishSchedulerJob {
|
||||
|
||||
private static final double SCORE_MIN = Double.NEGATIVE_INFINITY;
|
||||
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
private final ClerkPkLifecycleService clerkPkLifecycleService;
|
||||
private final ISysTenantService sysTenantService;
|
||||
|
||||
public PkFinishSchedulerJob(StringRedisTemplate stringRedisTemplate,
|
||||
ClerkPkLifecycleService clerkPkLifecycleService,
|
||||
ISysTenantService sysTenantService) {
|
||||
this.stringRedisTemplate = stringRedisTemplate;
|
||||
this.clerkPkLifecycleService = clerkPkLifecycleService;
|
||||
this.sysTenantService = sysTenantService;
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = PkSchedulerConstants.FINISH_SCAN_INTERVAL_MILLIS)
|
||||
public void scanFinishSchedule() {
|
||||
List<SysTenantEntity> tenantEntities = sysTenantService.listAll();
|
||||
if (tenantEntities.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
long nowEpochSeconds = Instant.now().getEpochSecond();
|
||||
for (SysTenantEntity tenantEntity : tenantEntities) {
|
||||
String tenantId = tenantEntity.getTenantId();
|
||||
if (StrUtil.isBlank(tenantId)) {
|
||||
log.warn("PK结算调度跳过空租户, errorCode={}", PkLifecycleErrorCode.TENANT_MISSING.name());
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
handleTenant(tenantId, nowEpochSeconds);
|
||||
} catch (Exception ex) {
|
||||
log.error("PK结算调度失败, tenantId={}", tenantId, ex);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTenant(String tenantId, long nowEpochSeconds) {
|
||||
String lockKey = PkRedisKeyConstants.finishLockKey(tenantId);
|
||||
String lockValue = IdUtils.getUuid();
|
||||
Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(
|
||||
lockKey,
|
||||
lockValue,
|
||||
PkSchedulerConstants.FINISH_LOCK_TTL_MILLIS,
|
||||
TimeUnit.MILLISECONDS);
|
||||
if (!Boolean.TRUE.equals(locked)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
try (TenantScope scope = TenantScope.use(tenantId)) {
|
||||
String scheduleKey = PkRedisKeyConstants.finishScheduleKey(tenantId);
|
||||
Set<String> duePkIds = stringRedisTemplate.opsForZSet()
|
||||
.rangeByScore(scheduleKey, SCORE_MIN, nowEpochSeconds);
|
||||
if (duePkIds == null || duePkIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (String pkId : duePkIds) {
|
||||
processFinish(scheduleKey, tenantId, pkId, nowEpochSeconds);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
releaseLock(lockKey, lockValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void processFinish(String scheduleKey, String tenantId, String pkId, long nowEpochSeconds) {
|
||||
if (StrUtil.isBlank(pkId)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
clerkPkLifecycleService.finishPk(pkId);
|
||||
clearRetry(tenantId, pkId);
|
||||
removeFailed(tenantId, pkId);
|
||||
stringRedisTemplate.opsForZSet().remove(scheduleKey, pkId);
|
||||
} catch (Exception ex) {
|
||||
handleRetry(scheduleKey, tenantId, pkId, nowEpochSeconds, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRetry(String scheduleKey, String tenantId, String pkId, long nowEpochSeconds, Exception ex) {
|
||||
String retryKey = PkRedisKeyConstants.finishRetryKey(tenantId);
|
||||
Long attempt = stringRedisTemplate.opsForHash().increment(retryKey, pkId, 1L);
|
||||
int retryCount = attempt == null ? 1 : attempt.intValue();
|
||||
if (retryCount >= PkSchedulerConstants.FINISH_RETRY_MAX_ATTEMPTS) {
|
||||
String failedKey = PkRedisKeyConstants.finishFailedKey(tenantId);
|
||||
stringRedisTemplate.opsForZSet().add(failedKey, pkId, nowEpochSeconds);
|
||||
stringRedisTemplate.opsForZSet().remove(scheduleKey, pkId);
|
||||
clearRetry(tenantId, pkId);
|
||||
log.error("PK 自动结算失败超过重试上限, pkId={}, retryCount={}", pkId, retryCount, ex);
|
||||
return;
|
||||
}
|
||||
long backoffSeconds = resolveBackoffSeconds(retryCount);
|
||||
long nextScore = nowEpochSeconds + backoffSeconds;
|
||||
stringRedisTemplate.opsForZSet().add(scheduleKey, pkId, nextScore);
|
||||
log.warn("PK 自动结算失败, pkId={}, retryCount={}, nextScore={}", pkId, retryCount, nextScore, ex);
|
||||
}
|
||||
|
||||
private long resolveBackoffSeconds(int retryCount) {
|
||||
int[] backoffs = PkSchedulerConstants.FINISH_RETRY_BACKOFF_SECONDS;
|
||||
int index = Math.min(retryCount, backoffs.length) - 1;
|
||||
return backoffs[index];
|
||||
}
|
||||
|
||||
private void clearRetry(String tenantId, String pkId) {
|
||||
String retryKey = PkRedisKeyConstants.finishRetryKey(tenantId);
|
||||
stringRedisTemplate.opsForHash().delete(retryKey, pkId);
|
||||
}
|
||||
|
||||
private void removeFailed(String tenantId, String pkId) {
|
||||
String failedKey = PkRedisKeyConstants.finishFailedKey(tenantId);
|
||||
stringRedisTemplate.opsForZSet().remove(failedKey, pkId);
|
||||
}
|
||||
|
||||
private void releaseLock(String lockKey, String lockValue) {
|
||||
String currentValue = stringRedisTemplate.opsForValue().get(lockKey);
|
||||
if (lockValue.equals(currentValue)) {
|
||||
stringRedisTemplate.delete(lockKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.starry.admin.modules.pk.scheduler.task;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.modules.clerk.module.entity.ClerkPkEnum;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkPkService;
|
||||
import com.starry.admin.modules.pk.enums.PkLifecycleErrorCode;
|
||||
import com.starry.admin.modules.pk.redis.PkRedisKeyConstants;
|
||||
import com.starry.admin.modules.pk.scheduler.constants.PkSchedulerConstants;
|
||||
import com.starry.admin.modules.pk.service.ClerkPkLifecycleService;
|
||||
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
|
||||
import com.starry.admin.modules.system.service.ISysTenantService;
|
||||
import com.starry.admin.utils.TenantScope;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PkStartSchedulerJob {
|
||||
|
||||
private static final double SCORE_MIN = Double.NEGATIVE_INFINITY;
|
||||
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
private final ClerkPkLifecycleService clerkPkLifecycleService;
|
||||
private final IPlayClerkPkService clerkPkService;
|
||||
private final ISysTenantService sysTenantService;
|
||||
|
||||
public PkStartSchedulerJob(StringRedisTemplate stringRedisTemplate,
|
||||
ClerkPkLifecycleService clerkPkLifecycleService,
|
||||
IPlayClerkPkService clerkPkService,
|
||||
ISysTenantService sysTenantService) {
|
||||
this.stringRedisTemplate = stringRedisTemplate;
|
||||
this.clerkPkLifecycleService = clerkPkLifecycleService;
|
||||
this.clerkPkService = clerkPkService;
|
||||
this.sysTenantService = sysTenantService;
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = PkSchedulerConstants.START_SCAN_INTERVAL_MILLIS)
|
||||
public void scanStartSchedule() {
|
||||
List<SysTenantEntity> tenantEntities = sysTenantService.listAll();
|
||||
if (tenantEntities.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
long nowEpochSeconds = Instant.now().getEpochSecond();
|
||||
for (SysTenantEntity tenantEntity : tenantEntities) {
|
||||
String tenantId = tenantEntity.getTenantId();
|
||||
if (StrUtil.isBlank(tenantId)) {
|
||||
log.warn("PK开始调度跳过空租户, errorCode={}", PkLifecycleErrorCode.TENANT_MISSING.name());
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
handleTenant(tenantId, nowEpochSeconds);
|
||||
} catch (Exception ex) {
|
||||
log.error("PK开始调度失败, tenantId={}", tenantId, ex);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTenant(String tenantId, long nowEpochSeconds) {
|
||||
String lockKey = PkRedisKeyConstants.startLockKey(tenantId);
|
||||
String lockValue = IdUtils.getUuid();
|
||||
Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(
|
||||
lockKey,
|
||||
lockValue,
|
||||
PkSchedulerConstants.START_LOCK_TTL_MILLIS,
|
||||
TimeUnit.MILLISECONDS);
|
||||
if (!Boolean.TRUE.equals(locked)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
try (TenantScope scope = TenantScope.use(tenantId)) {
|
||||
String scheduleKey = PkRedisKeyConstants.startScheduleKey(tenantId);
|
||||
Set<String> duePkIds = stringRedisTemplate.opsForZSet()
|
||||
.rangeByScore(scheduleKey, SCORE_MIN, nowEpochSeconds);
|
||||
if (duePkIds == null || duePkIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (String pkId : duePkIds) {
|
||||
processStart(scheduleKey, pkId, nowEpochSeconds);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
releaseLock(lockKey, lockValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void processStart(String scheduleKey, String pkId, long nowEpochSeconds) {
|
||||
if (StrUtil.isBlank(pkId)) {
|
||||
return;
|
||||
}
|
||||
PlayClerkPkEntity pk = clerkPkService.selectPlayClerkPkById(pkId);
|
||||
if (pk == null) {
|
||||
stringRedisTemplate.opsForZSet().remove(scheduleKey, pkId);
|
||||
return;
|
||||
}
|
||||
if (!ClerkPkEnum.TO_BE_STARTED.name().equals(pk.getStatus())) {
|
||||
stringRedisTemplate.opsForZSet().remove(scheduleKey, pkId);
|
||||
return;
|
||||
}
|
||||
if (pk.getPkBeginTime() == null) {
|
||||
stringRedisTemplate.opsForZSet().remove(scheduleKey, pkId);
|
||||
return;
|
||||
}
|
||||
long beginEpochSeconds = pk.getPkBeginTime().toInstant().getEpochSecond();
|
||||
if (beginEpochSeconds > nowEpochSeconds) {
|
||||
stringRedisTemplate.opsForZSet().add(scheduleKey, pkId, beginEpochSeconds);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
clerkPkLifecycleService.startPk(pkId);
|
||||
} catch (Exception ex) {
|
||||
long retryAt = nowEpochSeconds + PkSchedulerConstants.START_RETRY_DELAY_SECONDS;
|
||||
stringRedisTemplate.opsForZSet().add(scheduleKey, pkId, retryAt);
|
||||
log.warn("PK 自动开始失败, pkId={}, retryAt={}", pkId, retryAt, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseLock(String lockKey, String lockValue) {
|
||||
String currentValue = stringRedisTemplate.opsForValue().get(lockKey);
|
||||
if (lockValue.equals(currentValue)) {
|
||||
stringRedisTemplate.delete(lockKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.starry.admin.modules.pk.service;
|
||||
|
||||
import com.starry.admin.modules.pk.dto.PlayClerkPkForceStartRequest;
|
||||
|
||||
/**
|
||||
* 店员 PK 生命周期管理服务。
|
||||
*/
|
||||
@@ -23,4 +25,12 @@ public interface ClerkPkLifecycleService {
|
||||
* 扫描当前需要状态流转的 PK。
|
||||
*/
|
||||
void scanAndUpdate();
|
||||
|
||||
/**
|
||||
* 强制开始PK(用于测试,无需排期)。
|
||||
*
|
||||
* @param request 强制开始请求
|
||||
* @return PK ID
|
||||
*/
|
||||
String forceStart(PlayClerkPkForceStartRequest request);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.starry.admin.modules.pk.service;
|
||||
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import com.starry.admin.modules.pk.dto.WxPkClerkHistoryPageDto;
|
||||
import com.starry.admin.modules.pk.dto.WxPkContributorDto;
|
||||
import com.starry.admin.modules.pk.dto.WxPkHistoryDto;
|
||||
import java.util.List;
|
||||
|
||||
public interface IPkDetailService {
|
||||
|
||||
List<WxPkContributorDto> getContributors(PlayClerkPkEntity pk);
|
||||
|
||||
List<WxPkHistoryDto> getHistory(PlayClerkPkEntity pk);
|
||||
|
||||
WxPkClerkHistoryPageDto getClerkHistory(String clerkId, int pageNum, int pageSize);
|
||||
}
|
||||
@@ -1,16 +1,27 @@
|
||||
package com.starry.admin.modules.pk.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.modules.clerk.module.entity.ClerkPkEnum;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkPkService;
|
||||
import com.starry.admin.modules.pk.dto.PlayClerkPkForceStartRequest;
|
||||
import com.starry.admin.modules.pk.enums.PkLifecycleErrorCode;
|
||||
import com.starry.admin.modules.pk.redis.PkRedisKeyConstants;
|
||||
import com.starry.admin.modules.pk.scheduler.constants.PkSchedulerConstants;
|
||||
import com.starry.admin.modules.pk.service.ClerkPkLifecycleService;
|
||||
import com.starry.admin.modules.pk.setting.constants.PkSettingValidationConstants;
|
||||
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
|
||||
import com.starry.admin.modules.system.service.ISysTenantService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.admin.utils.TenantScope;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -29,6 +40,9 @@ public class ClerkPkLifecycleServiceImpl implements ClerkPkLifecycleService {
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Resource
|
||||
private ISysTenantService sysTenantService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void startPk(String pkId) {
|
||||
@@ -37,6 +51,7 @@ public class ClerkPkLifecycleServiceImpl implements ClerkPkLifecycleService {
|
||||
throw new CustomException("PK不存在");
|
||||
}
|
||||
if (!ClerkPkEnum.TO_BE_STARTED.name().equals(pk.getStatus())) {
|
||||
removeStartSchedule(pk);
|
||||
return;
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
@@ -46,6 +61,8 @@ public class ClerkPkLifecycleServiceImpl implements ClerkPkLifecycleService {
|
||||
}
|
||||
pk.setStatus(ClerkPkEnum.IN_PROGRESS.name());
|
||||
clerkPkService.updateById(pk);
|
||||
removeStartSchedule(pk);
|
||||
scheduleFinish(pk);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,6 +73,7 @@ public class ClerkPkLifecycleServiceImpl implements ClerkPkLifecycleService {
|
||||
throw new CustomException("PK不存在");
|
||||
}
|
||||
if (ClerkPkEnum.FINISHED.name().equals(pk.getStatus())) {
|
||||
removeFinishSchedule(pk);
|
||||
return;
|
||||
}
|
||||
String scoreKey = PkRedisKeyConstants.scoreKey(pkId);
|
||||
@@ -80,6 +98,7 @@ public class ClerkPkLifecycleServiceImpl implements ClerkPkLifecycleService {
|
||||
pk.setWinnerClerkId(null);
|
||||
}
|
||||
clerkPkService.updateById(pk);
|
||||
removeFinishSchedule(pk);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -113,9 +132,90 @@ public class ClerkPkLifecycleServiceImpl implements ClerkPkLifecycleService {
|
||||
});
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 30000)
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String forceStart(PlayClerkPkForceStartRequest request) {
|
||||
validateForceStartRequest(request);
|
||||
String tenantId = SecurityUtils.getTenantId();
|
||||
if (StrUtil.isBlank(tenantId)) {
|
||||
throw new CustomException(PkLifecycleErrorCode.TENANT_MISSING.getMessage());
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime endTime = now.plusMinutes(request.getDurationMinutes());
|
||||
Date beginDate = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());
|
||||
Date endDate = Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant());
|
||||
if (hasClerkConflict(request.getClerkAId(), request.getClerkBId(), beginDate, endDate)) {
|
||||
throw new CustomException(PkLifecycleErrorCode.CLERK_CONFLICT.getMessage());
|
||||
}
|
||||
PlayClerkPkEntity pk = new PlayClerkPkEntity();
|
||||
pk.setId(IdUtils.getUuid());
|
||||
pk.setTenantId(tenantId);
|
||||
pk.setClerkA(request.getClerkAId());
|
||||
pk.setClerkB(request.getClerkBId());
|
||||
pk.setPkBeginTime(beginDate);
|
||||
pk.setPkEndTime(endDate);
|
||||
pk.setStatus(ClerkPkEnum.IN_PROGRESS.name());
|
||||
pk.setSettled(0);
|
||||
pk.setCreatedBy(SecurityUtils.getUserId());
|
||||
clerkPkService.save(pk);
|
||||
scheduleFinish(pk);
|
||||
return pk.getId();
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = PkSchedulerConstants.FALLBACK_SCAN_INTERVAL_MILLIS)
|
||||
public void scheduledScan() {
|
||||
scanAndUpdate();
|
||||
scanAndUpdateForAllTenants();
|
||||
}
|
||||
|
||||
private void scanAndUpdateForAllTenants() {
|
||||
List<SysTenantEntity> tenantEntities = sysTenantService.listAll();
|
||||
if (tenantEntities.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (SysTenantEntity tenantEntity : tenantEntities) {
|
||||
String tenantId = tenantEntity.getTenantId();
|
||||
if (StrUtil.isBlank(tenantId)) {
|
||||
log.warn("PK扫描跳过空租户, errorCode={}", PkLifecycleErrorCode.TENANT_MISSING.name());
|
||||
continue;
|
||||
}
|
||||
try (TenantScope scope = TenantScope.use(tenantId)) {
|
||||
scanAndUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleFinish(PlayClerkPkEntity pk) {
|
||||
if (pk == null || pk.getPkEndTime() == null) {
|
||||
throw new CustomException(PkLifecycleErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
String tenantId = pk.getTenantId();
|
||||
if (StrUtil.isBlank(tenantId)) {
|
||||
throw new CustomException(PkLifecycleErrorCode.TENANT_MISSING.getMessage());
|
||||
}
|
||||
long endEpochSeconds = pk.getPkEndTime().toInstant().getEpochSecond();
|
||||
String scheduleKey = PkRedisKeyConstants.finishScheduleKey(tenantId);
|
||||
stringRedisTemplate.opsForZSet().add(scheduleKey, pk.getId(), endEpochSeconds);
|
||||
}
|
||||
|
||||
private void removeStartSchedule(PlayClerkPkEntity pk) {
|
||||
if (pk == null || StrUtil.isBlank(pk.getTenantId())) {
|
||||
return;
|
||||
}
|
||||
String scheduleKey = PkRedisKeyConstants.startScheduleKey(pk.getTenantId());
|
||||
stringRedisTemplate.opsForZSet().remove(scheduleKey, pk.getId());
|
||||
}
|
||||
|
||||
private void removeFinishSchedule(PlayClerkPkEntity pk) {
|
||||
if (pk == null || StrUtil.isBlank(pk.getTenantId())) {
|
||||
return;
|
||||
}
|
||||
String tenantId = pk.getTenantId();
|
||||
String scheduleKey = PkRedisKeyConstants.finishScheduleKey(tenantId);
|
||||
stringRedisTemplate.opsForZSet().remove(scheduleKey, pk.getId());
|
||||
String retryKey = PkRedisKeyConstants.finishRetryKey(tenantId);
|
||||
stringRedisTemplate.opsForHash().delete(retryKey, pk.getId());
|
||||
String failedKey = PkRedisKeyConstants.finishFailedKey(tenantId);
|
||||
stringRedisTemplate.opsForZSet().remove(failedKey, pk.getId());
|
||||
}
|
||||
|
||||
private static BigDecimal parseDecimal(Object value) {
|
||||
@@ -139,4 +239,35 @@ public class ClerkPkLifecycleServiceImpl implements ClerkPkLifecycleService {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void validateForceStartRequest(PlayClerkPkForceStartRequest request) {
|
||||
if (request == null
|
||||
|| StrUtil.isBlank(request.getClerkAId())
|
||||
|| StrUtil.isBlank(request.getClerkBId())
|
||||
|| request.getDurationMinutes() == null) {
|
||||
throw new CustomException(PkLifecycleErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
if (request.getClerkAId().equals(request.getClerkBId())) {
|
||||
throw new CustomException(PkLifecycleErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
if (request.getDurationMinutes() < PkSettingValidationConstants.MIN_DURATION_MINUTES) {
|
||||
throw new CustomException(PkLifecycleErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasClerkConflict(String clerkAId, String clerkBId, Date beginTime, Date endTime) {
|
||||
return clerkPkService.count(Wrappers.<PlayClerkPkEntity>lambdaQuery()
|
||||
.in(PlayClerkPkEntity::getStatus,
|
||||
ClerkPkEnum.TO_BE_STARTED.name(),
|
||||
ClerkPkEnum.IN_PROGRESS.name())
|
||||
.and(time -> time.le(PlayClerkPkEntity::getPkBeginTime, endTime)
|
||||
.ge(PlayClerkPkEntity::getPkEndTime, beginTime))
|
||||
.and(clerk -> clerk.eq(PlayClerkPkEntity::getClerkA, clerkAId)
|
||||
.or()
|
||||
.eq(PlayClerkPkEntity::getClerkB, clerkAId)
|
||||
.or()
|
||||
.eq(PlayClerkPkEntity::getClerkA, clerkBId)
|
||||
.or()
|
||||
.eq(PlayClerkPkEntity::getClerkB, clerkBId))) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
package com.starry.admin.modules.pk.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.modules.clerk.mapper.PlayClerkPkMapper;
|
||||
import com.starry.admin.modules.clerk.module.entity.ClerkPkEnum;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
import com.starry.admin.modules.order.mapper.PlayOrderInfoMapper;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||
import com.starry.admin.modules.pk.constants.PkWxQueryConstants;
|
||||
import com.starry.admin.modules.pk.dto.WxPkClerkHistoryPageDto;
|
||||
import com.starry.admin.modules.pk.dto.WxPkClerkHistorySummaryDto;
|
||||
import com.starry.admin.modules.pk.dto.WxPkContributorDto;
|
||||
import com.starry.admin.modules.pk.dto.WxPkHistoryDto;
|
||||
import com.starry.admin.modules.pk.service.IPkDetailService;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class PkDetailServiceImpl implements IPkDetailService {
|
||||
|
||||
private static final ZoneId DEFAULT_ZONE = ZoneId.systemDefault();
|
||||
|
||||
@Resource
|
||||
private PlayOrderInfoMapper orderInfoMapper;
|
||||
|
||||
@Resource
|
||||
private PlayClerkPkMapper clerkPkMapper;
|
||||
|
||||
@Resource
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Override
|
||||
public List<WxPkContributorDto> getContributors(PlayClerkPkEntity pk) {
|
||||
validatePk(pk);
|
||||
LocalDateTime startTime = toLocalDateTime(pk.getPkBeginTime());
|
||||
LocalDateTime endTime = toLocalDateTime(pk.getPkEndTime());
|
||||
LocalDateTime now = LocalDateTime.now(DEFAULT_ZONE);
|
||||
if (endTime.isAfter(now)) {
|
||||
endTime = now;
|
||||
}
|
||||
if (endTime.isBefore(startTime)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<WxPkContributorDto> contributors = orderInfoMapper.selectPkContributors(
|
||||
pk.getTenantId(),
|
||||
pk.getClerkA(),
|
||||
pk.getClerkB(),
|
||||
startTime,
|
||||
endTime,
|
||||
OrderConstant.OrderStatus.COMPLETED.getCode(),
|
||||
PkWxQueryConstants.MIN_CONTRIBUTION_AMOUNT,
|
||||
PkWxQueryConstants.CONTRIBUTOR_LIMIT);
|
||||
if (contributors == null || contributors.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<WxPkContributorDto> normalized = new ArrayList<>();
|
||||
for (WxPkContributorDto contributor : contributors) {
|
||||
normalized.add(normalizeContributor(contributor));
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WxPkHistoryDto> getHistory(PlayClerkPkEntity pk) {
|
||||
validatePk(pk);
|
||||
List<PlayClerkPkEntity> history = clerkPkMapper.selectRecentFinishedBetweenClerks(
|
||||
pk.getTenantId(),
|
||||
pk.getClerkA(),
|
||||
pk.getClerkB(),
|
||||
ClerkPkEnum.FINISHED.name(),
|
||||
PkWxQueryConstants.HISTORY_LIMIT);
|
||||
if (history == null || history.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Map<String, String> clerkNames = loadClerkNames(pk.getClerkA(), pk.getClerkB());
|
||||
List<WxPkHistoryDto> items = new ArrayList<>();
|
||||
for (PlayClerkPkEntity item : history) {
|
||||
items.add(toHistoryDto(item, clerkNames));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxPkClerkHistoryPageDto getClerkHistory(String clerkId, int pageNum, int pageSize) {
|
||||
if (StrUtil.isBlank(clerkId)) {
|
||||
throw new CustomException("店员ID不能为空");
|
||||
}
|
||||
int safePageNum = normalizePageParam(pageNum, PkWxQueryConstants.CLERK_HISTORY_PAGE_NUM);
|
||||
int safePageSize = normalizePageParam(pageSize, PkWxQueryConstants.CLERK_HISTORY_PAGE_SIZE);
|
||||
|
||||
com.baomidou.mybatisplus.extension.plugins.pagination.Page<PlayClerkPkEntity> page =
|
||||
new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(safePageNum, safePageSize);
|
||||
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<PlayClerkPkEntity> wrapper =
|
||||
com.baomidou.mybatisplus.core.toolkit.Wrappers.lambdaQuery(PlayClerkPkEntity.class)
|
||||
.eq(PlayClerkPkEntity::getStatus, ClerkPkEnum.FINISHED.name())
|
||||
.and(w -> w.eq(PlayClerkPkEntity::getClerkA, clerkId)
|
||||
.or()
|
||||
.eq(PlayClerkPkEntity::getClerkB, clerkId))
|
||||
.orderByDesc(PlayClerkPkEntity::getPkBeginTime);
|
||||
com.baomidou.mybatisplus.extension.plugins.pagination.Page<PlayClerkPkEntity> result =
|
||||
clerkPkMapper.selectPage(page, wrapper);
|
||||
|
||||
List<WxPkHistoryDto> items = new ArrayList<>();
|
||||
if (result.getRecords() != null) {
|
||||
for (PlayClerkPkEntity item : result.getRecords()) {
|
||||
items.add(toClerkHistoryDto(item));
|
||||
}
|
||||
}
|
||||
|
||||
WxPkClerkHistoryPageDto pageDto = new WxPkClerkHistoryPageDto();
|
||||
pageDto.setItems(items);
|
||||
pageDto.setTotalCount(result.getTotal());
|
||||
pageDto.setPageNum(safePageNum);
|
||||
pageDto.setPageSize(safePageSize);
|
||||
pageDto.setSummary(buildSummary(clerkId, result.getTotal()));
|
||||
return pageDto;
|
||||
}
|
||||
|
||||
private void validatePk(PlayClerkPkEntity pk) {
|
||||
if (pk == null) {
|
||||
throw new CustomException("PK不存在");
|
||||
}
|
||||
if (StrUtil.isBlank(pk.getTenantId())) {
|
||||
throw new CustomException("租户信息缺失");
|
||||
}
|
||||
if (StrUtil.isBlank(pk.getClerkA()) || StrUtil.isBlank(pk.getClerkB())) {
|
||||
throw new CustomException("PK店员信息缺失");
|
||||
}
|
||||
if (pk.getPkBeginTime() == null || pk.getPkEndTime() == null) {
|
||||
throw new CustomException("PK时间信息缺失");
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDateTime toLocalDateTime(java.util.Date date) {
|
||||
if (date == null) {
|
||||
throw new CustomException("时间信息缺失");
|
||||
}
|
||||
return LocalDateTime.ofInstant(date.toInstant(), DEFAULT_ZONE);
|
||||
}
|
||||
|
||||
private WxPkContributorDto normalizeContributor(WxPkContributorDto source) {
|
||||
WxPkContributorDto dto = new WxPkContributorDto();
|
||||
if (source == null) {
|
||||
return dto;
|
||||
}
|
||||
if (StrUtil.isNotBlank(source.getUserId())) {
|
||||
dto.setUserId(source.getUserId());
|
||||
}
|
||||
if (StrUtil.isNotBlank(source.getNickname())) {
|
||||
dto.setNickname(source.getNickname());
|
||||
}
|
||||
if (source.getAmount() != null) {
|
||||
dto.setAmount(source.getAmount());
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
|
||||
private Map<String, String> loadClerkNames(String clerkAId, String clerkBId) {
|
||||
Map<String, String> names = new HashMap<>();
|
||||
PlayClerkUserInfoEntity clerkA = clerkUserInfoService.getById(clerkAId);
|
||||
PlayClerkUserInfoEntity clerkB = clerkUserInfoService.getById(clerkBId);
|
||||
names.put(clerkAId, clerkA == null ? "" : safeText(clerkA.getNickname()));
|
||||
names.put(clerkBId, clerkB == null ? "" : safeText(clerkB.getNickname()));
|
||||
return names;
|
||||
}
|
||||
|
||||
private WxPkHistoryDto toHistoryDto(PlayClerkPkEntity item, Map<String, String> clerkNames) {
|
||||
WxPkHistoryDto dto = new WxPkHistoryDto();
|
||||
if (item == null) {
|
||||
return dto;
|
||||
}
|
||||
dto.setClerkAName(safeText(clerkNames.get(item.getClerkA())));
|
||||
dto.setClerkBName(safeText(clerkNames.get(item.getClerkB())));
|
||||
dto.setClerkAScore(safeAmount(item.getClerkAScore()));
|
||||
dto.setClerkBScore(safeAmount(item.getClerkBScore()));
|
||||
dto.setId(safeText(item.getId()));
|
||||
dto.setClerkAId(safeText(item.getClerkA()));
|
||||
dto.setClerkBId(safeText(item.getClerkB()));
|
||||
dto.setWinnerClerkId(safeText(item.getWinnerClerkId()));
|
||||
if (item.getPkBeginTime() != null) {
|
||||
dto.setPkBeginTime(item.getPkBeginTime());
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
|
||||
private String safeText(String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
private BigDecimal safeAmount(BigDecimal value) {
|
||||
return value == null ? BigDecimal.ZERO : value;
|
||||
}
|
||||
|
||||
private int normalizePageParam(int value, int defaultValue) {
|
||||
if (value < PkWxQueryConstants.CLERK_HISTORY_MIN_PAGE) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private WxPkHistoryDto toClerkHistoryDto(PlayClerkPkEntity item) {
|
||||
WxPkHistoryDto dto = toHistoryDto(item, loadClerkNames(item.getClerkA(), item.getClerkB()));
|
||||
WxPkContributorDto topContributor = loadTopContributor(item);
|
||||
if (topContributor != null) {
|
||||
dto.setTopContributorName(safeText(topContributor.getNickname()));
|
||||
dto.setTopContributorAmount(safeAmount(topContributor.getAmount()));
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
|
||||
private WxPkContributorDto loadTopContributor(PlayClerkPkEntity pk) {
|
||||
if (pk == null || pk.getPkBeginTime() == null || pk.getPkEndTime() == null) {
|
||||
return null;
|
||||
}
|
||||
LocalDateTime startTime = toLocalDateTime(pk.getPkBeginTime());
|
||||
LocalDateTime endTime = toLocalDateTime(pk.getPkEndTime());
|
||||
List<WxPkContributorDto> contributors = orderInfoMapper.selectPkContributors(
|
||||
pk.getTenantId(),
|
||||
pk.getClerkA(),
|
||||
pk.getClerkB(),
|
||||
startTime,
|
||||
endTime,
|
||||
OrderConstant.OrderStatus.COMPLETED.getCode(),
|
||||
PkWxQueryConstants.MIN_CONTRIBUTION_AMOUNT,
|
||||
PkWxQueryConstants.TOP_CONTRIBUTOR_LIMIT);
|
||||
if (contributors == null || contributors.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return normalizeContributor(contributors.get(0));
|
||||
}
|
||||
|
||||
private WxPkClerkHistorySummaryDto buildSummary(String clerkId, long totalCount) {
|
||||
WxPkClerkHistorySummaryDto summary = new WxPkClerkHistorySummaryDto();
|
||||
summary.setTotalCount(totalCount);
|
||||
if (totalCount <= 0) {
|
||||
return summary;
|
||||
}
|
||||
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<PlayClerkPkEntity> winWrapper =
|
||||
com.baomidou.mybatisplus.core.toolkit.Wrappers.lambdaQuery(PlayClerkPkEntity.class)
|
||||
.eq(PlayClerkPkEntity::getStatus, ClerkPkEnum.FINISHED.name())
|
||||
.and(w -> w.eq(PlayClerkPkEntity::getClerkA, clerkId)
|
||||
.or()
|
||||
.eq(PlayClerkPkEntity::getClerkB, clerkId))
|
||||
.eq(PlayClerkPkEntity::getWinnerClerkId, clerkId);
|
||||
long winCount = clerkPkMapper.selectCount(winWrapper);
|
||||
summary.setWinCount(winCount);
|
||||
BigDecimal rate = BigDecimal.valueOf(winCount)
|
||||
.multiply(PkWxQueryConstants.WIN_RATE_MULTIPLIER)
|
||||
.divide(BigDecimal.valueOf(totalCount), PkWxQueryConstants.WIN_RATE_SCALE, RoundingMode.HALF_UP);
|
||||
summary.setWinRate(rate.stripTrailingZeros().toPlainString() + PkWxQueryConstants.PERCENT_SUFFIX);
|
||||
return summary;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.starry.admin.modules.pk.setting.constants;
|
||||
|
||||
public final class PkSettingApiConstants {
|
||||
|
||||
public static final String BASE_PATH = "/play/pk/settings";
|
||||
public static final String LIST_PATH = "/list";
|
||||
public static final String DETAIL_PATH = "/{id}";
|
||||
public static final String CREATE_PATH = "/create";
|
||||
public static final String BULK_CREATE_PATH = "/bulk-create";
|
||||
public static final String UPDATE_PATH = "/update/{id}";
|
||||
public static final String ENABLE_PATH = "/{id}/enable";
|
||||
public static final String DISABLE_PATH = "/{id}/disable";
|
||||
|
||||
private PkSettingApiConstants() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.starry.admin.modules.pk.setting.constants;
|
||||
|
||||
public final class PkSettingValidationConstants {
|
||||
|
||||
public static final int MIN_DURATION_MINUTES = 1;
|
||||
public static final int MAX_YEARS_AHEAD = 3;
|
||||
public static final int MIN_DAY_OF_MONTH = 1;
|
||||
public static final int MAX_DAY_OF_MONTH = 31;
|
||||
public static final int MIN_MONTH_OF_YEAR = 1;
|
||||
public static final int MAX_MONTH_OF_YEAR = 12;
|
||||
public static final int DEFAULT_PAGE_NUMBER = 1;
|
||||
public static final int DEFAULT_PAGE_SIZE = 10;
|
||||
|
||||
private PkSettingValidationConstants() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.starry.admin.modules.pk.setting.controller;
|
||||
|
||||
import com.starry.admin.modules.pk.setting.constants.PkSettingApiConstants;
|
||||
import com.starry.admin.modules.pk.setting.dto.PlayClerkPkSettingBulkCreateRequest;
|
||||
import com.starry.admin.modules.pk.setting.dto.PlayClerkPkSettingUpsertRequest;
|
||||
import com.starry.admin.modules.pk.setting.service.IPlayClerkPkSettingService;
|
||||
import com.starry.common.annotation.Log;
|
||||
import com.starry.common.enums.BusinessType;
|
||||
import com.starry.common.result.R;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import io.swagger.annotations.ApiResponse;
|
||||
import io.swagger.annotations.ApiResponses;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Api(tags = "店员PK排期设置", description = "店员PK排期设置相关接口")
|
||||
@RestController
|
||||
@RequestMapping(PkSettingApiConstants.BASE_PATH)
|
||||
public class PlayClerkPkSettingController {
|
||||
|
||||
@Resource
|
||||
private IPlayClerkPkSettingService pkSettingService;
|
||||
|
||||
/**
|
||||
* 查询PK排期设置列表
|
||||
*/
|
||||
@ApiOperation(value = "查询PK排期设置列表", notes = "分页查询PK排期设置")
|
||||
@ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
|
||||
@GetMapping(PkSettingApiConstants.LIST_PATH)
|
||||
public R list() {
|
||||
return R.ok(pkSettingService.listSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取PK排期设置详情
|
||||
*/
|
||||
@ApiOperation(value = "获取PK排期设置详情", notes = "根据ID获取PK排期设置详情")
|
||||
@ApiImplicitParam(name = "id", value = "设置ID", required = true, paramType = "path", dataType = "String")
|
||||
@ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
|
||||
@GetMapping(PkSettingApiConstants.DETAIL_PATH)
|
||||
public R getInfo(@PathVariable("id") String id) {
|
||||
return R.ok(pkSettingService.getSetting(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增PK排期设置
|
||||
*/
|
||||
@ApiOperation(value = "新增PK排期设置", notes = "创建新的PK排期设置")
|
||||
@ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
|
||||
@Log(title = "店员PK排期设置", businessType = BusinessType.INSERT)
|
||||
@PostMapping(PkSettingApiConstants.CREATE_PATH)
|
||||
public R create(
|
||||
@ApiParam(value = "PK排期设置", required = true) @RequestBody PlayClerkPkSettingUpsertRequest request) {
|
||||
String id = pkSettingService.createSetting(request);
|
||||
return R.ok(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增PK排期设置
|
||||
*/
|
||||
@ApiOperation(value = "批量新增PK排期设置", notes = "批量创建多个PK排期设置")
|
||||
@ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
|
||||
@Log(title = "店员PK排期设置", businessType = BusinessType.INSERT)
|
||||
@PostMapping(PkSettingApiConstants.BULK_CREATE_PATH)
|
||||
public R bulkCreate(
|
||||
@ApiParam(value = "PK排期设置列表", required = true)
|
||||
@RequestBody PlayClerkPkSettingBulkCreateRequest request) {
|
||||
return R.ok(pkSettingService.createSettings(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改PK排期设置
|
||||
*/
|
||||
@ApiOperation(value = "修改PK排期设置", notes = "根据ID修改PK排期设置")
|
||||
@ApiImplicitParam(name = "id", value = "设置ID", required = true, paramType = "path", dataType = "String")
|
||||
@ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
|
||||
@Log(title = "店员PK排期设置", businessType = BusinessType.UPDATE)
|
||||
@PostMapping(PkSettingApiConstants.UPDATE_PATH)
|
||||
public R update(@PathVariable("id") String id,
|
||||
@ApiParam(value = "PK排期设置", required = true) @RequestBody PlayClerkPkSettingUpsertRequest request) {
|
||||
pkSettingService.updateSetting(id, request);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用PK排期设置
|
||||
*/
|
||||
@ApiOperation(value = "启用PK排期设置", notes = "启用指定PK排期设置")
|
||||
@ApiImplicitParam(name = "id", value = "设置ID", required = true, paramType = "path", dataType = "String")
|
||||
@ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
|
||||
@Log(title = "店员PK排期设置", businessType = BusinessType.UPDATE)
|
||||
@PostMapping(PkSettingApiConstants.ENABLE_PATH)
|
||||
public R enable(@PathVariable("id") String id) {
|
||||
pkSettingService.enableSetting(id);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停用PK排期设置
|
||||
*/
|
||||
@ApiOperation(value = "停用PK排期设置", notes = "停用指定PK排期设置")
|
||||
@ApiImplicitParam(name = "id", value = "设置ID", required = true, paramType = "path", dataType = "String")
|
||||
@ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
|
||||
@Log(title = "店员PK排期设置", businessType = BusinessType.UPDATE)
|
||||
@PostMapping(PkSettingApiConstants.DISABLE_PATH)
|
||||
public R disable(@PathVariable("id") String id) {
|
||||
pkSettingService.disableSetting(id);
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.starry.admin.modules.pk.setting.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@ApiModel(value = "PlayClerkPkSettingBulkCreateRequest", description = "店员PK排期设置批量创建请求")
|
||||
@Data
|
||||
public class PlayClerkPkSettingBulkCreateRequest {
|
||||
|
||||
@ApiModelProperty(value = "批量排期设置列表", required = true)
|
||||
private List<PlayClerkPkSettingUpsertRequest> settings;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.starry.admin.modules.pk.setting.dto;
|
||||
|
||||
import com.starry.admin.modules.pk.setting.enums.PkRecurrenceType;
|
||||
import com.starry.admin.modules.pk.setting.enums.PkScheduleDayOfWeek;
|
||||
import com.starry.admin.modules.pk.setting.enums.PkSettingStatus;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import lombok.Data;
|
||||
|
||||
@ApiModel(value = "PlayClerkPkSettingUpsertRequest", description = "店员PK排期设置创建/更新请求")
|
||||
@Data
|
||||
public class PlayClerkPkSettingUpsertRequest {
|
||||
|
||||
@ApiModelProperty(value = "设置名称", required = true)
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty(value = "循环类型", required = true)
|
||||
private PkRecurrenceType recurrenceType;
|
||||
|
||||
@ApiModelProperty(value = "星期几")
|
||||
private PkScheduleDayOfWeek dayOfWeek;
|
||||
|
||||
@ApiModelProperty(value = "每月第N天")
|
||||
private Integer dayOfMonth;
|
||||
|
||||
@ApiModelProperty(value = "每年月份")
|
||||
private Integer monthOfYear;
|
||||
|
||||
@ApiModelProperty(value = "每日开始时间", required = true)
|
||||
private LocalTime startTimeOfDay;
|
||||
|
||||
@ApiModelProperty(value = "持续分钟数", required = true)
|
||||
private Integer durationMinutes;
|
||||
|
||||
@ApiModelProperty(value = "生效开始日期", required = true)
|
||||
private LocalDate effectiveStartDate;
|
||||
|
||||
@ApiModelProperty(value = "生效结束日期")
|
||||
private LocalDate effectiveEndDate;
|
||||
|
||||
@ApiModelProperty(value = "时区", required = true)
|
||||
private String timezone;
|
||||
|
||||
@ApiModelProperty(value = "店员A ID", required = true)
|
||||
private String clerkAId;
|
||||
|
||||
@ApiModelProperty(value = "店员B ID", required = true)
|
||||
private String clerkBId;
|
||||
|
||||
@ApiModelProperty(value = "状态", required = true)
|
||||
private PkSettingStatus status;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.starry.admin.modules.pk.setting.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.starry.common.domain.BaseEntity;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@ApiModel(value = "PlayClerkPkSettingEntity", description = "店员PK排期设置")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("play_clerk_pk_settings")
|
||||
public class PlayClerkPkSettingEntity extends BaseEntity<PlayClerkPkSettingEntity> {
|
||||
|
||||
@ApiModelProperty(value = "UUID", required = true)
|
||||
private String id;
|
||||
|
||||
@ApiModelProperty(value = "租户ID", required = true)
|
||||
private String tenantId;
|
||||
|
||||
@ApiModelProperty(value = "设置名称", required = true)
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty(value = "循环类型", required = true)
|
||||
private String recurrenceType;
|
||||
|
||||
@ApiModelProperty(value = "星期几")
|
||||
private String dayOfWeek;
|
||||
|
||||
@ApiModelProperty(value = "每月第N天")
|
||||
private Integer dayOfMonth;
|
||||
|
||||
@ApiModelProperty(value = "每年月份")
|
||||
private Integer monthOfYear;
|
||||
|
||||
@ApiModelProperty(value = "每日开始时间", required = true)
|
||||
private LocalTime startTimeOfDay;
|
||||
|
||||
@ApiModelProperty(value = "持续分钟数", required = true)
|
||||
private Integer durationMinutes;
|
||||
|
||||
@ApiModelProperty(value = "生效开始日期", required = true)
|
||||
private LocalDate effectiveStartDate;
|
||||
|
||||
@ApiModelProperty(value = "生效结束日期")
|
||||
private LocalDate effectiveEndDate;
|
||||
|
||||
@ApiModelProperty(value = "时区", required = true)
|
||||
private String timezone;
|
||||
|
||||
@ApiModelProperty(value = "店员A ID", required = true)
|
||||
private String clerkAId;
|
||||
|
||||
@ApiModelProperty(value = "店员B ID", required = true)
|
||||
private String clerkBId;
|
||||
|
||||
@ApiModelProperty(value = "状态", required = true)
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.starry.admin.modules.pk.setting.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
public enum PkRecurrenceType {
|
||||
ONCE("ONCE", "单次"),
|
||||
DAILY("DAILY", "每日"),
|
||||
WEEKLY("WEEKLY", "每周"),
|
||||
MONTHLY("MONTHLY", "每月"),
|
||||
YEARLY("YEARLY", "每年");
|
||||
|
||||
@Getter
|
||||
private final String value;
|
||||
@Getter
|
||||
private final String desc;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.starry.admin.modules.pk.setting.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
public enum PkScheduleDayOfWeek {
|
||||
MONDAY("MONDAY", "周一"),
|
||||
TUESDAY("TUESDAY", "周二"),
|
||||
WEDNESDAY("WEDNESDAY", "周三"),
|
||||
THURSDAY("THURSDAY", "周四"),
|
||||
FRIDAY("FRIDAY", "周五"),
|
||||
SATURDAY("SATURDAY", "周六"),
|
||||
SUNDAY("SUNDAY", "周日");
|
||||
|
||||
@Getter
|
||||
private final String value;
|
||||
@Getter
|
||||
private final String desc;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.starry.admin.modules.pk.setting.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
public enum PkSettingErrorCode {
|
||||
NOT_IMPLEMENTED("PK_SETTING_NOT_IMPLEMENTED", "PK排期设置功能尚未实现"),
|
||||
TENANT_MISSING("PK_SETTING_TENANT_MISSING", "租户信息不能为空"),
|
||||
SETTING_NOT_FOUND("PK_SETTING_NOT_FOUND", "PK排期设置不存在"),
|
||||
REQUEST_INVALID("PK_SETTING_REQUEST_INVALID", "PK排期设置参数非法"),
|
||||
RECURRENCE_INVALID("PK_SETTING_RECURRENCE_INVALID", "PK排期循环规则非法"),
|
||||
TIME_RANGE_INVALID("PK_SETTING_TIME_RANGE_INVALID", "PK排期时间范围非法"),
|
||||
CLERK_CONFLICT("PK_SETTING_CLERK_CONFLICT", "店员排期冲突");
|
||||
|
||||
@Getter
|
||||
private final String code;
|
||||
@Getter
|
||||
private final String message;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.starry.admin.modules.pk.setting.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
public enum PkSettingStatus {
|
||||
ENABLED("ENABLED", "启用"),
|
||||
DISABLED("DISABLED", "停用");
|
||||
|
||||
@Getter
|
||||
private final String value;
|
||||
@Getter
|
||||
private final String desc;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.starry.admin.modules.pk.setting.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.starry.admin.modules.pk.setting.entity.PlayClerkPkSettingEntity;
|
||||
|
||||
public interface PlayClerkPkSettingMapper extends BaseMapper<PlayClerkPkSettingEntity> {
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.starry.admin.modules.pk.setting.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.starry.admin.modules.pk.setting.dto.PlayClerkPkSettingBulkCreateRequest;
|
||||
import com.starry.admin.modules.pk.setting.dto.PlayClerkPkSettingUpsertRequest;
|
||||
import com.starry.admin.modules.pk.setting.entity.PlayClerkPkSettingEntity;
|
||||
import java.util.List;
|
||||
|
||||
public interface IPlayClerkPkSettingService extends IService<PlayClerkPkSettingEntity> {
|
||||
|
||||
String createSetting(PlayClerkPkSettingUpsertRequest request);
|
||||
|
||||
List<String> createSettings(PlayClerkPkSettingBulkCreateRequest request);
|
||||
|
||||
void updateSetting(String id, PlayClerkPkSettingUpsertRequest request);
|
||||
|
||||
void enableSetting(String id);
|
||||
|
||||
void disableSetting(String id);
|
||||
|
||||
PlayClerkPkSettingEntity getSetting(String id);
|
||||
|
||||
IPage<PlayClerkPkSettingEntity> listSettings();
|
||||
|
||||
int generateInstances(String id);
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
package com.starry.admin.modules.pk.setting.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.modules.clerk.module.entity.ClerkPkEnum;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkPkService;
|
||||
import com.starry.admin.modules.pk.redis.PkRedisKeyConstants;
|
||||
import com.starry.admin.modules.pk.setting.constants.PkSettingValidationConstants;
|
||||
import com.starry.admin.modules.pk.setting.dto.PlayClerkPkSettingBulkCreateRequest;
|
||||
import com.starry.admin.modules.pk.setting.dto.PlayClerkPkSettingUpsertRequest;
|
||||
import com.starry.admin.modules.pk.setting.entity.PlayClerkPkSettingEntity;
|
||||
import com.starry.admin.modules.pk.setting.enums.PkRecurrenceType;
|
||||
import com.starry.admin.modules.pk.setting.enums.PkScheduleDayOfWeek;
|
||||
import com.starry.admin.modules.pk.setting.enums.PkSettingErrorCode;
|
||||
import com.starry.admin.modules.pk.setting.enums.PkSettingStatus;
|
||||
import com.starry.admin.modules.pk.setting.mapper.PlayClerkPkSettingMapper;
|
||||
import com.starry.admin.modules.pk.setting.service.IPlayClerkPkSettingService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.YearMonth;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class PlayClerkPkSettingServiceImpl
|
||||
extends ServiceImpl<PlayClerkPkSettingMapper, PlayClerkPkSettingEntity>
|
||||
implements IPlayClerkPkSettingService {
|
||||
|
||||
private final IPlayClerkPkService clerkPkService;
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
private static final String PAIR_KEY_SEPARATOR = "::";
|
||||
|
||||
public PlayClerkPkSettingServiceImpl(IPlayClerkPkService clerkPkService,
|
||||
StringRedisTemplate stringRedisTemplate) {
|
||||
this.clerkPkService = clerkPkService;
|
||||
this.stringRedisTemplate = stringRedisTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String createSetting(PlayClerkPkSettingUpsertRequest request) {
|
||||
PlayClerkPkSettingEntity entity = buildEntity(request, null);
|
||||
entity.setId(IdUtils.getUuid());
|
||||
if (!save(entity)) {
|
||||
throw new CustomException(PkSettingErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
if (PkSettingStatus.ENABLED.getValue().equals(entity.getStatus())) {
|
||||
generateInstances(entity.getId());
|
||||
}
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<String> createSettings(PlayClerkPkSettingBulkCreateRequest request) {
|
||||
List<PlayClerkPkSettingUpsertRequest> settings = validateBulkRequest(request);
|
||||
List<String> createdIds = new ArrayList<>();
|
||||
for (PlayClerkPkSettingUpsertRequest settingRequest : settings) {
|
||||
PlayClerkPkSettingEntity entity = buildEntity(settingRequest, null);
|
||||
entity.setId(IdUtils.getUuid());
|
||||
if (!save(entity)) {
|
||||
throw new CustomException(PkSettingErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
if (PkSettingStatus.ENABLED.getValue().equals(entity.getStatus())) {
|
||||
generateInstances(entity.getId());
|
||||
}
|
||||
createdIds.add(entity.getId());
|
||||
}
|
||||
return createdIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateSetting(String id, PlayClerkPkSettingUpsertRequest request) {
|
||||
PlayClerkPkSettingEntity existing = getSetting(id);
|
||||
PlayClerkPkSettingEntity entity = buildEntity(request, existing);
|
||||
entity.setId(existing.getId());
|
||||
entity.setTenantId(existing.getTenantId());
|
||||
if (!updateById(entity)) {
|
||||
throw new CustomException(PkSettingErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void enableSetting(String id) {
|
||||
PlayClerkPkSettingEntity setting = getSetting(id);
|
||||
setting.setStatus(PkSettingStatus.ENABLED.getValue());
|
||||
updateById(setting);
|
||||
generateInstances(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void disableSetting(String id) {
|
||||
PlayClerkPkSettingEntity setting = getSetting(id);
|
||||
setting.setStatus(PkSettingStatus.DISABLED.getValue());
|
||||
updateById(setting);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayClerkPkSettingEntity getSetting(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
throw new CustomException(PkSettingErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
PlayClerkPkSettingEntity setting = getById(id);
|
||||
if (setting == null) {
|
||||
throw new CustomException(PkSettingErrorCode.SETTING_NOT_FOUND.getMessage());
|
||||
}
|
||||
return setting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<PlayClerkPkSettingEntity> listSettings() {
|
||||
Page<PlayClerkPkSettingEntity> page = new Page<>(
|
||||
PkSettingValidationConstants.DEFAULT_PAGE_NUMBER,
|
||||
PkSettingValidationConstants.DEFAULT_PAGE_SIZE);
|
||||
return baseMapper.selectPage(page, new LambdaQueryWrapper<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int generateInstances(String id) {
|
||||
PlayClerkPkSettingEntity setting = getSetting(id);
|
||||
if (!PkSettingStatus.ENABLED.getValue().equals(setting.getStatus())) {
|
||||
throw new CustomException(PkSettingErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
List<LocalDate> dates = generateScheduleDates(setting);
|
||||
ZoneId zoneId = ZoneId.of(setting.getTimezone());
|
||||
int createdCount = 0;
|
||||
for (LocalDate date : dates) {
|
||||
LocalDateTime startTime = LocalDateTime.of(date, setting.getStartTimeOfDay());
|
||||
LocalDateTime endTime = startTime.plusMinutes(setting.getDurationMinutes());
|
||||
Date beginTime = Date.from(startTime.atZone(zoneId).toInstant());
|
||||
Date finishTime = Date.from(endTime.atZone(zoneId).toInstant());
|
||||
|
||||
if (existsForSettingAt(setting.getId(), beginTime)) {
|
||||
continue;
|
||||
}
|
||||
if (hasClerkConflict(setting, beginTime, finishTime)) {
|
||||
throw new CustomException(PkSettingErrorCode.CLERK_CONFLICT.getMessage());
|
||||
}
|
||||
PlayClerkPkEntity pk = new PlayClerkPkEntity();
|
||||
pk.setId(IdUtils.getUuid());
|
||||
pk.setTenantId(setting.getTenantId());
|
||||
pk.setClerkA(setting.getClerkAId());
|
||||
pk.setClerkB(setting.getClerkBId());
|
||||
pk.setPkBeginTime(beginTime);
|
||||
pk.setPkEndTime(finishTime);
|
||||
pk.setStatus(ClerkPkEnum.TO_BE_STARTED.name());
|
||||
pk.setSettled(0);
|
||||
pk.setSettingId(setting.getId());
|
||||
clerkPkService.save(pk);
|
||||
scheduleStart(pk);
|
||||
createdCount++;
|
||||
}
|
||||
return createdCount;
|
||||
}
|
||||
|
||||
private PlayClerkPkSettingEntity buildEntity(PlayClerkPkSettingUpsertRequest request,
|
||||
PlayClerkPkSettingEntity existing) {
|
||||
validateRequest(request);
|
||||
PlayClerkPkSettingEntity entity = Optional.ofNullable(existing).orElseGet(PlayClerkPkSettingEntity::new);
|
||||
entity.setName(request.getName());
|
||||
entity.setRecurrenceType(request.getRecurrenceType().getValue());
|
||||
entity.setDayOfWeek(Optional.ofNullable(request.getDayOfWeek())
|
||||
.map(PkScheduleDayOfWeek::getValue)
|
||||
.orElse(null));
|
||||
entity.setDayOfMonth(request.getDayOfMonth());
|
||||
entity.setMonthOfYear(request.getMonthOfYear());
|
||||
entity.setStartTimeOfDay(request.getStartTimeOfDay());
|
||||
entity.setDurationMinutes(request.getDurationMinutes());
|
||||
entity.setEffectiveStartDate(request.getEffectiveStartDate());
|
||||
entity.setEffectiveEndDate(request.getEffectiveEndDate());
|
||||
entity.setTimezone(request.getTimezone());
|
||||
entity.setClerkAId(request.getClerkAId());
|
||||
entity.setClerkBId(request.getClerkBId());
|
||||
entity.setStatus(request.getStatus().getValue());
|
||||
if (existing == null) {
|
||||
entity.setTenantId(resolveTenantId());
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void validateRequest(PlayClerkPkSettingUpsertRequest request) {
|
||||
if (request == null) {
|
||||
throw new CustomException(PkSettingErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
if (StrUtil.isBlank(request.getName())
|
||||
|| request.getRecurrenceType() == null
|
||||
|| request.getStartTimeOfDay() == null
|
||||
|| request.getDurationMinutes() == null
|
||||
|| request.getEffectiveStartDate() == null
|
||||
|| request.getTimezone() == null
|
||||
|| StrUtil.isBlank(request.getClerkAId())
|
||||
|| StrUtil.isBlank(request.getClerkBId())
|
||||
|| request.getStatus() == null) {
|
||||
throw new CustomException(PkSettingErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
if (request.getDurationMinutes() < PkSettingValidationConstants.MIN_DURATION_MINUTES) {
|
||||
throw new CustomException(PkSettingErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
if (request.getClerkAId().equals(request.getClerkBId())) {
|
||||
throw new CustomException(PkSettingErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
ensureZoneValid(request.getTimezone());
|
||||
validateRecurrenceFields(request);
|
||||
validateEffectiveRange(request.getEffectiveStartDate(), request.getEffectiveEndDate());
|
||||
}
|
||||
|
||||
private void scheduleStart(PlayClerkPkEntity pk) {
|
||||
if (pk == null || pk.getPkBeginTime() == null || pk.getId() == null) {
|
||||
throw new CustomException(PkSettingErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
if (StrUtil.isBlank(pk.getTenantId())) {
|
||||
throw new CustomException(PkSettingErrorCode.TENANT_MISSING.getMessage());
|
||||
}
|
||||
String scheduleKey = PkRedisKeyConstants.startScheduleKey(pk.getTenantId());
|
||||
long startEpochSeconds = pk.getPkBeginTime().toInstant().getEpochSecond();
|
||||
stringRedisTemplate.opsForZSet().add(scheduleKey, pk.getId(), startEpochSeconds);
|
||||
}
|
||||
|
||||
private List<PlayClerkPkSettingUpsertRequest> validateBulkRequest(PlayClerkPkSettingBulkCreateRequest request) {
|
||||
if (request == null || request.getSettings() == null || request.getSettings().isEmpty()) {
|
||||
throw new CustomException(PkSettingErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
Set<String> pairKeys = new HashSet<>();
|
||||
for (PlayClerkPkSettingUpsertRequest setting : request.getSettings()) {
|
||||
validateRequest(setting);
|
||||
String pairKey = buildPairKey(setting.getClerkAId(), setting.getClerkBId());
|
||||
if (!pairKeys.add(pairKey)) {
|
||||
throw new CustomException(PkSettingErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
}
|
||||
return request.getSettings();
|
||||
}
|
||||
|
||||
private String buildPairKey(String clerkAId, String clerkBId) {
|
||||
if (clerkAId.compareTo(clerkBId) <= 0) {
|
||||
return clerkAId + PAIR_KEY_SEPARATOR + clerkBId;
|
||||
}
|
||||
return clerkBId + PAIR_KEY_SEPARATOR + clerkAId;
|
||||
}
|
||||
|
||||
private void ensureZoneValid(String timezone) {
|
||||
try {
|
||||
ZoneId.of(timezone);
|
||||
} catch (Exception ex) {
|
||||
throw new CustomException(PkSettingErrorCode.REQUEST_INVALID.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateRecurrenceFields(PlayClerkPkSettingUpsertRequest request) {
|
||||
PkRecurrenceType type = request.getRecurrenceType();
|
||||
if (type == PkRecurrenceType.WEEKLY && request.getDayOfWeek() == null) {
|
||||
throw new CustomException(PkSettingErrorCode.RECURRENCE_INVALID.getMessage());
|
||||
}
|
||||
if (type == PkRecurrenceType.MONTHLY && request.getDayOfMonth() == null) {
|
||||
throw new CustomException(PkSettingErrorCode.RECURRENCE_INVALID.getMessage());
|
||||
}
|
||||
if (type == PkRecurrenceType.YEARLY
|
||||
&& (request.getDayOfMonth() == null || request.getMonthOfYear() == null)) {
|
||||
throw new CustomException(PkSettingErrorCode.RECURRENCE_INVALID.getMessage());
|
||||
}
|
||||
if (request.getDayOfMonth() != null) {
|
||||
int dayOfMonth = request.getDayOfMonth();
|
||||
if (dayOfMonth < PkSettingValidationConstants.MIN_DAY_OF_MONTH
|
||||
|| dayOfMonth > PkSettingValidationConstants.MAX_DAY_OF_MONTH) {
|
||||
throw new CustomException(PkSettingErrorCode.RECURRENCE_INVALID.getMessage());
|
||||
}
|
||||
}
|
||||
if (request.getMonthOfYear() != null) {
|
||||
int monthOfYear = request.getMonthOfYear();
|
||||
if (monthOfYear < PkSettingValidationConstants.MIN_MONTH_OF_YEAR
|
||||
|| monthOfYear > PkSettingValidationConstants.MAX_MONTH_OF_YEAR) {
|
||||
throw new CustomException(PkSettingErrorCode.RECURRENCE_INVALID.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateEffectiveRange(LocalDate start, LocalDate end) {
|
||||
if (end != null && end.isBefore(start)) {
|
||||
throw new CustomException(PkSettingErrorCode.TIME_RANGE_INVALID.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private List<LocalDate> generateScheduleDates(PlayClerkPkSettingEntity setting) {
|
||||
LocalDate start = setting.getEffectiveStartDate();
|
||||
LocalDate maxEnd = start.plusYears(PkSettingValidationConstants.MAX_YEARS_AHEAD);
|
||||
LocalDate end = Optional.ofNullable(setting.getEffectiveEndDate()).orElse(maxEnd);
|
||||
if (end.isAfter(maxEnd)) {
|
||||
end = maxEnd;
|
||||
}
|
||||
PkRecurrenceType type = PkRecurrenceType.valueOf(setting.getRecurrenceType());
|
||||
List<LocalDate> dates = new ArrayList<>();
|
||||
switch (type) {
|
||||
case ONCE:
|
||||
if (!start.isAfter(end)) {
|
||||
dates.add(start);
|
||||
}
|
||||
break;
|
||||
case DAILY:
|
||||
for (LocalDate cursor = start; !cursor.isAfter(end); cursor = cursor.plusDays(1)) {
|
||||
dates.add(cursor);
|
||||
}
|
||||
break;
|
||||
case WEEKLY:
|
||||
dates.addAll(generateWeeklyDates(setting, start, end));
|
||||
break;
|
||||
case MONTHLY:
|
||||
dates.addAll(generateMonthlyDates(setting, start, end));
|
||||
break;
|
||||
case YEARLY:
|
||||
dates.addAll(generateYearlyDates(setting, start, end));
|
||||
break;
|
||||
default:
|
||||
throw new CustomException(PkSettingErrorCode.RECURRENCE_INVALID.getMessage());
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
|
||||
private List<LocalDate> generateWeeklyDates(PlayClerkPkSettingEntity setting, LocalDate start, LocalDate end) {
|
||||
List<LocalDate> dates = new ArrayList<>();
|
||||
DayOfWeek target = DayOfWeek.valueOf(setting.getDayOfWeek());
|
||||
LocalDate cursor = start;
|
||||
while (cursor.getDayOfWeek() != target) {
|
||||
cursor = cursor.plusDays(1);
|
||||
}
|
||||
for (LocalDate date = cursor; !date.isAfter(end); date = date.plusWeeks(1)) {
|
||||
dates.add(date);
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
|
||||
private List<LocalDate> generateMonthlyDates(PlayClerkPkSettingEntity setting, LocalDate start, LocalDate end) {
|
||||
List<LocalDate> dates = new ArrayList<>();
|
||||
int dayOfMonth = setting.getDayOfMonth();
|
||||
YearMonth startMonth = YearMonth.from(start);
|
||||
YearMonth endMonth = YearMonth.from(end);
|
||||
for (YearMonth cursor = startMonth; !cursor.isAfter(endMonth); cursor = cursor.plusMonths(1)) {
|
||||
if (dayOfMonth > cursor.lengthOfMonth()) {
|
||||
continue;
|
||||
}
|
||||
LocalDate date = cursor.atDay(dayOfMonth);
|
||||
if (date.isBefore(start) || date.isAfter(end)) {
|
||||
continue;
|
||||
}
|
||||
dates.add(date);
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
|
||||
private List<LocalDate> generateYearlyDates(PlayClerkPkSettingEntity setting, LocalDate start, LocalDate end) {
|
||||
List<LocalDate> dates = new ArrayList<>();
|
||||
int dayOfMonth = setting.getDayOfMonth();
|
||||
int monthOfYear = setting.getMonthOfYear();
|
||||
for (int year = start.getYear(); year <= end.getYear(); year++) {
|
||||
YearMonth yearMonth = YearMonth.of(year, monthOfYear);
|
||||
if (dayOfMonth > yearMonth.lengthOfMonth()) {
|
||||
continue;
|
||||
}
|
||||
LocalDate date = LocalDate.of(year, monthOfYear, dayOfMonth);
|
||||
if (date.isBefore(start) || date.isAfter(end)) {
|
||||
continue;
|
||||
}
|
||||
dates.add(date);
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
|
||||
private boolean existsForSettingAt(String settingId, Date beginTime) {
|
||||
LambdaQueryWrapper<PlayClerkPkEntity> wrapper = Wrappers.lambdaQuery(PlayClerkPkEntity.class)
|
||||
.eq(PlayClerkPkEntity::getSettingId, settingId)
|
||||
.eq(PlayClerkPkEntity::getPkBeginTime, beginTime);
|
||||
return clerkPkService.count(wrapper) > 0;
|
||||
}
|
||||
|
||||
private boolean hasClerkConflict(PlayClerkPkSettingEntity setting, Date beginTime, Date endTime) {
|
||||
LambdaQueryWrapper<PlayClerkPkEntity> wrapper = Wrappers.lambdaQuery(PlayClerkPkEntity.class)
|
||||
.in(PlayClerkPkEntity::getStatus,
|
||||
ClerkPkEnum.TO_BE_STARTED.name(),
|
||||
ClerkPkEnum.IN_PROGRESS.name())
|
||||
.and(time -> time.le(PlayClerkPkEntity::getPkBeginTime, endTime)
|
||||
.ge(PlayClerkPkEntity::getPkEndTime, beginTime))
|
||||
.and(clerk -> clerk.eq(PlayClerkPkEntity::getClerkA, setting.getClerkAId())
|
||||
.or()
|
||||
.eq(PlayClerkPkEntity::getClerkB, setting.getClerkAId())
|
||||
.or()
|
||||
.eq(PlayClerkPkEntity::getClerkA, setting.getClerkBId())
|
||||
.or()
|
||||
.eq(PlayClerkPkEntity::getClerkB, setting.getClerkBId()));
|
||||
return clerkPkService.count(wrapper) > 0;
|
||||
}
|
||||
|
||||
private String resolveTenantId() {
|
||||
String tenantId = SecurityUtils.getTenantId();
|
||||
if (StrUtil.isBlank(tenantId)) {
|
||||
throw new CustomException(PkSettingErrorCode.TENANT_MISSING.getMessage());
|
||||
}
|
||||
return tenantId;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiResponse;
|
||||
import io.swagger.annotations.ApiResponses;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -40,6 +41,9 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RequestMapping("/wx/commodity/")
|
||||
public class WxClerkCommodityController {
|
||||
|
||||
private static final String ROOT_PARENT_ID = "00";
|
||||
private static final String CLERK_COMMODITY_ENABLED = "1";
|
||||
|
||||
@Resource
|
||||
private IPlayCommodityInfoService playCommodityInfoService;
|
||||
|
||||
@@ -63,6 +67,12 @@ public class WxClerkCommodityController {
|
||||
if (levelId == null || levelId.isEmpty()) {
|
||||
return R.ok(tree);
|
||||
}
|
||||
if (levelInfoEntities == null) {
|
||||
throw new CustomException("商品等级信息缺失");
|
||||
}
|
||||
if (tree == null) {
|
||||
throw new CustomException("商品树缺失");
|
||||
}
|
||||
tree = formatPlayCommodityReturnVoTree(tree, levelInfoEntities, levelId);
|
||||
tree = formatPlayCommodityReturnVoTree(tree, null);
|
||||
return R.ok(tree);
|
||||
@@ -84,11 +94,23 @@ public class WxClerkCommodityController {
|
||||
throw new CustomException("请求参数异常,id不能为空");
|
||||
}
|
||||
PlayClerkUserInfoEntity clerkUserInfo = clerkUserInfoService.selectById(clerkId);
|
||||
if (clerkUserInfo == null) {
|
||||
throw new CustomException("店员不存在");
|
||||
}
|
||||
if (clerkUserInfo.getLevelId() == null || clerkUserInfo.getLevelId().isEmpty()) {
|
||||
throw new CustomException("店员等级信息缺失");
|
||||
}
|
||||
Map<String, List<PlayClerkCommodityEntity>> clerkCommodityEntities = playClerkCommodityService
|
||||
.selectCommodityTypeByUser(clerkId, "1").stream()
|
||||
.selectCommodityTypeByUser(clerkId, CLERK_COMMODITY_ENABLED).stream()
|
||||
.collect(Collectors.groupingBy(PlayClerkCommodityEntity::getCommodityId));
|
||||
List<PlayCommodityAndLevelInfoEntity> levelInfoEntities = iPlayCommodityAndLevelInfoService.selectAll();
|
||||
List<PlayCommodityReturnVo> tree = playCommodityInfoService.selectTree();
|
||||
if (levelInfoEntities == null) {
|
||||
throw new CustomException("商品等级信息缺失");
|
||||
}
|
||||
if (tree == null) {
|
||||
throw new CustomException("商品树缺失");
|
||||
}
|
||||
tree = formatPlayCommodityReturnVoTree(tree, levelInfoEntities, clerkUserInfo.getLevelId());
|
||||
tree = formatPlayCommodityReturnVoTree(tree, clerkCommodityEntities);
|
||||
return R.ok(tree);
|
||||
@@ -108,10 +130,16 @@ public class WxClerkCommodityController {
|
||||
String levelId = ThreadLocalRequestDetail.getClerkUserInfo().getLevelId();
|
||||
List<PlayCommodityAndLevelInfoEntity> levelInfoEntities = iPlayCommodityAndLevelInfoService.selectAll();
|
||||
Map<String, List<PlayClerkCommodityEntity>> clerkCommodityEntities = playClerkCommodityService
|
||||
.selectCommodityTypeByUser(ThreadLocalRequestDetail.getClerkUserInfo().getId(), "1").stream()
|
||||
.selectCommodityTypeByUser(ThreadLocalRequestDetail.getClerkUserInfo().getId(), CLERK_COMMODITY_ENABLED).stream()
|
||||
.collect(Collectors.groupingBy(PlayClerkCommodityEntity::getCommodityId));
|
||||
|
||||
List<PlayCommodityReturnVo> tree = playCommodityInfoService.selectTree();
|
||||
if (levelInfoEntities == null) {
|
||||
throw new CustomException("商品等级信息缺失");
|
||||
}
|
||||
if (tree == null) {
|
||||
throw new CustomException("商品树缺失");
|
||||
}
|
||||
tree = formatPlayCommodityReturnVoTree(tree, levelInfoEntities, levelId);
|
||||
tree = formatPlayCommodityReturnVoTree(tree, clerkCommodityEntities);
|
||||
return R.ok(tree);
|
||||
@@ -119,9 +147,21 @@ public class WxClerkCommodityController {
|
||||
|
||||
public List<PlayCommodityReturnVo> formatPlayCommodityReturnVoTree(List<PlayCommodityReturnVo> tree,
|
||||
List<PlayCommodityAndLevelInfoEntity> levelInfoEntities, String levelId) {
|
||||
if (tree == null) {
|
||||
throw new CustomException("商品树缺失");
|
||||
}
|
||||
if (levelInfoEntities == null) {
|
||||
throw new CustomException("商品等级信息缺失");
|
||||
}
|
||||
if (levelId == null || levelId.isEmpty()) {
|
||||
throw new CustomException("等级信息缺失");
|
||||
}
|
||||
Iterator<PlayCommodityReturnVo> it = tree.iterator();
|
||||
while (it.hasNext()) {
|
||||
PlayCommodityReturnVo item = it.next();
|
||||
if (item.getChild() == null) {
|
||||
item.setChild(new ArrayList<>());
|
||||
}
|
||||
// 查找当前服务项目对应的价格
|
||||
for (PlayCommodityAndLevelInfoEntity levelInfoEntity : levelInfoEntities) {
|
||||
if (item.getId().equals(levelInfoEntity.getCommodityId())
|
||||
@@ -130,7 +170,7 @@ public class WxClerkCommodityController {
|
||||
}
|
||||
}
|
||||
// 如果未设置价格,删除元素
|
||||
if (!"00".equals(item.getPId()) && item.getPrice() == null) {
|
||||
if (!ROOT_PARENT_ID.equals(item.getPId()) && item.getPrice() == null) {
|
||||
it.remove();
|
||||
}
|
||||
formatPlayCommodityReturnVoTree(item.getChild(), levelInfoEntities, levelId);
|
||||
@@ -140,12 +180,18 @@ public class WxClerkCommodityController {
|
||||
|
||||
public List<PlayCommodityReturnVo> formatPlayCommodityReturnVoTree(List<PlayCommodityReturnVo> tree,
|
||||
Map<String, List<PlayClerkCommodityEntity>> clerkCommodityEntities) {
|
||||
if (tree == null) {
|
||||
throw new CustomException("商品树缺失");
|
||||
}
|
||||
Iterator<PlayCommodityReturnVo> it = tree.iterator();
|
||||
while (it.hasNext()) {
|
||||
PlayCommodityReturnVo item = it.next();
|
||||
if ("00".equals(item.getPId()) && item.getChild().isEmpty()) {
|
||||
if (item.getChild() == null) {
|
||||
item.setChild(new ArrayList<>());
|
||||
}
|
||||
if (ROOT_PARENT_ID.equals(item.getPId()) && item.getChild().isEmpty()) {
|
||||
it.remove();
|
||||
} else if (clerkCommodityEntities != null && "00".equals(item.getPId())
|
||||
} else if (clerkCommodityEntities != null && ROOT_PARENT_ID.equals(item.getPId())
|
||||
&& !clerkCommodityEntities.containsKey(item.getId())) {
|
||||
it.remove();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
package com.starry.admin.modules.weichat.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.modules.clerk.module.entity.ClerkPkEnum;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkPkService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
import com.starry.admin.modules.pk.constants.PkWxQueryConstants;
|
||||
import com.starry.admin.modules.pk.dto.PkScoreBoardDto;
|
||||
import com.starry.admin.modules.pk.dto.WxPkClerkHistoryPageDto;
|
||||
import com.starry.admin.modules.pk.dto.WxPkDetailDto;
|
||||
import com.starry.admin.modules.pk.dto.WxPkLiveDto;
|
||||
import com.starry.admin.modules.pk.dto.WxPkUpcomingDto;
|
||||
import com.starry.admin.modules.pk.enums.PkWxState;
|
||||
import com.starry.admin.modules.pk.redis.PkRedisKeyConstants;
|
||||
import com.starry.admin.modules.pk.service.IPkDetailService;
|
||||
import com.starry.admin.modules.pk.service.IPkScoreboardService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.result.R;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
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;
|
||||
|
||||
@Api(tags = "微信PK接口", description = "微信端PK展示相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/wx/pk")
|
||||
public class WxPkController {
|
||||
|
||||
private final IPlayClerkPkService clerkPkService;
|
||||
private final IPlayClerkUserInfoService clerkUserInfoService;
|
||||
private final IPkScoreboardService pkScoreboardService;
|
||||
private final IPkDetailService pkDetailService;
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
public WxPkController(IPlayClerkPkService clerkPkService,
|
||||
IPlayClerkUserInfoService clerkUserInfoService,
|
||||
IPkScoreboardService pkScoreboardService,
|
||||
IPkDetailService pkDetailService,
|
||||
StringRedisTemplate stringRedisTemplate) {
|
||||
this.clerkPkService = clerkPkService;
|
||||
this.clerkUserInfoService = clerkUserInfoService;
|
||||
this.pkScoreboardService = pkScoreboardService;
|
||||
this.pkDetailService = pkDetailService;
|
||||
this.stringRedisTemplate = stringRedisTemplate;
|
||||
}
|
||||
|
||||
@ApiOperation(value = "店员PK实时状态", notes = "查询指定店员是否正在PK")
|
||||
@GetMapping("/clerk/live")
|
||||
public R getClerkLive(@RequestParam("clerkId") String clerkId) {
|
||||
if (StrUtil.isBlank(clerkId)) {
|
||||
throw new CustomException("店员ID不能为空");
|
||||
}
|
||||
Optional<PlayClerkPkEntity> pkOptional =
|
||||
clerkPkService.findActivePkForClerk(clerkId, LocalDateTime.now());
|
||||
if (!pkOptional.isPresent()) {
|
||||
return R.ok(WxPkLiveDto.inactive());
|
||||
}
|
||||
PlayClerkPkEntity pk = pkOptional.get();
|
||||
if (!ClerkPkEnum.IN_PROGRESS.name().equals(pk.getStatus())) {
|
||||
return R.ok(WxPkLiveDto.inactive());
|
||||
}
|
||||
PkScoreBoardDto scoreboard = pkScoreboardService.getScoreboard(pk.getId());
|
||||
return R.ok(buildLiveDto(pk, scoreboard));
|
||||
}
|
||||
|
||||
@ApiOperation(value = "即将开战PK", notes = "返回即将开始的PK信息")
|
||||
@GetMapping("/upcoming")
|
||||
public R getUpcoming() {
|
||||
String tenantId = SecurityUtils.getTenantId();
|
||||
if (StrUtil.isBlank(tenantId)) {
|
||||
throw new CustomException("租户信息缺失");
|
||||
}
|
||||
String key = PkRedisKeyConstants.upcomingKey(tenantId);
|
||||
long nowEpochSeconds = Instant.now().getEpochSecond();
|
||||
Set<String> pkIds = stringRedisTemplate.opsForZSet()
|
||||
.rangeByScore(key, nowEpochSeconds, Double.POSITIVE_INFINITY, 0, 1);
|
||||
if (pkIds == null || pkIds.isEmpty()) {
|
||||
return R.ok(WxPkUpcomingDto.inactive());
|
||||
}
|
||||
String pkId = pkIds.iterator().next();
|
||||
PlayClerkPkEntity pk = clerkPkService.selectPlayClerkPkById(pkId);
|
||||
if (pk == null) {
|
||||
return R.ok(WxPkUpcomingDto.inactive());
|
||||
}
|
||||
return R.ok(buildUpcomingDto(pk));
|
||||
}
|
||||
|
||||
@ApiOperation(value = "PK详情", notes = "查询PK详情用于详情页")
|
||||
@GetMapping("/detail")
|
||||
public R getDetail(@RequestParam("id") String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
throw new CustomException("PK ID不能为空");
|
||||
}
|
||||
PlayClerkPkEntity pk = clerkPkService.selectPlayClerkPkById(id);
|
||||
if (pk == null) {
|
||||
return R.ok(WxPkDetailDto.inactive());
|
||||
}
|
||||
PkScoreBoardDto scoreboard = pkScoreboardService.getScoreboard(pk.getId());
|
||||
return R.ok(buildDetailDto(pk, scoreboard));
|
||||
}
|
||||
|
||||
@ApiOperation(value = "店员PK历史", notes = "查询指定店员PK历史")
|
||||
@GetMapping("/clerk/history")
|
||||
public R getClerkHistory(@RequestParam("clerkId") String clerkId,
|
||||
@RequestParam(value = "pageNum", required = false) Integer pageNum,
|
||||
@RequestParam(value = "pageSize", required = false) Integer pageSize) {
|
||||
if (StrUtil.isBlank(clerkId)) {
|
||||
throw new CustomException("店员ID不能为空");
|
||||
}
|
||||
int safePageNum = pageNum == null ? PkWxQueryConstants.CLERK_HISTORY_PAGE_NUM : pageNum;
|
||||
int safePageSize = pageSize == null ? PkWxQueryConstants.CLERK_HISTORY_PAGE_SIZE : pageSize;
|
||||
WxPkClerkHistoryPageDto data = pkDetailService.getClerkHistory(clerkId, safePageNum, safePageSize);
|
||||
return R.ok(data);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "店员PK日程", notes = "查询指定店员未来PK安排")
|
||||
@GetMapping("/clerk/schedule")
|
||||
public R getClerkSchedule(@RequestParam("clerkId") String clerkId,
|
||||
@RequestParam(value = "limit", required = false) Integer limit) {
|
||||
if (StrUtil.isBlank(clerkId)) {
|
||||
throw new CustomException("店员ID不能为空");
|
||||
}
|
||||
String tenantId = SecurityUtils.getTenantId();
|
||||
if (StrUtil.isBlank(tenantId)) {
|
||||
throw new CustomException("租户信息缺失");
|
||||
}
|
||||
int safeLimit = normalizeLimit(limit);
|
||||
List<PlayClerkPkEntity> items = clerkPkService.selectUpcomingForClerk(
|
||||
tenantId,
|
||||
clerkId,
|
||||
new Date(),
|
||||
safeLimit);
|
||||
if (items == null || items.isEmpty()) {
|
||||
return R.ok(new ArrayList<>());
|
||||
}
|
||||
List<WxPkUpcomingDto> result = new ArrayList<>();
|
||||
for (PlayClerkPkEntity pk : items) {
|
||||
result.add(buildUpcomingDto(pk));
|
||||
}
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
private WxPkLiveDto buildLiveDto(PlayClerkPkEntity pk, PkScoreBoardDto scoreboard) {
|
||||
WxPkLiveDto dto = new WxPkLiveDto();
|
||||
fillBase(dto, pk);
|
||||
dto.setState(PkWxState.ACTIVE.getValue());
|
||||
dto.setClerkAScore(scoreboard.getClerkAScore());
|
||||
dto.setClerkBScore(scoreboard.getClerkBScore());
|
||||
dto.setClerkAOrderCount(scoreboard.getClerkAOrderCount());
|
||||
dto.setClerkBOrderCount(scoreboard.getClerkBOrderCount());
|
||||
dto.setRemainingSeconds(scoreboard.getRemainingSeconds());
|
||||
dto.setServerEpochSeconds(Instant.now().getEpochSecond());
|
||||
if (pk.getPkEndTime() != null) {
|
||||
dto.setPkEndEpochSeconds(pk.getPkEndTime().toInstant().getEpochSecond());
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
|
||||
private WxPkUpcomingDto buildUpcomingDto(PlayClerkPkEntity pk) {
|
||||
WxPkUpcomingDto dto = new WxPkUpcomingDto();
|
||||
fillBase(dto, pk);
|
||||
dto.setState(PkWxState.UPCOMING.getValue());
|
||||
dto.setPkBeginTime(pk.getPkBeginTime());
|
||||
return dto;
|
||||
}
|
||||
|
||||
private WxPkDetailDto buildDetailDto(PlayClerkPkEntity pk, PkScoreBoardDto scoreboard) {
|
||||
WxPkDetailDto dto = new WxPkDetailDto();
|
||||
fillBase(dto, pk);
|
||||
dto.setState(resolveState(pk.getStatus()));
|
||||
dto.setClerkAScore(scoreboard.getClerkAScore());
|
||||
dto.setClerkBScore(scoreboard.getClerkBScore());
|
||||
dto.setClerkAOrderCount(scoreboard.getClerkAOrderCount());
|
||||
dto.setClerkBOrderCount(scoreboard.getClerkBOrderCount());
|
||||
dto.setRemainingSeconds(scoreboard.getRemainingSeconds());
|
||||
dto.setPkBeginTime(pk.getPkBeginTime());
|
||||
dto.setPkEndTime(pk.getPkEndTime());
|
||||
dto.setContributors(pkDetailService.getContributors(pk));
|
||||
dto.setHistory(pkDetailService.getHistory(pk));
|
||||
return dto;
|
||||
}
|
||||
|
||||
private int normalizeLimit(Integer limit) {
|
||||
if (limit == null) {
|
||||
return PkWxQueryConstants.CLERK_SCHEDULE_DEFAULT_LIMIT;
|
||||
}
|
||||
if (limit < PkWxQueryConstants.CLERK_SCHEDULE_MIN_LIMIT) {
|
||||
return PkWxQueryConstants.CLERK_SCHEDULE_MIN_LIMIT;
|
||||
}
|
||||
if (limit > PkWxQueryConstants.CLERK_SCHEDULE_MAX_LIMIT) {
|
||||
return PkWxQueryConstants.CLERK_SCHEDULE_MAX_LIMIT;
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
private String resolveState(String status) {
|
||||
if (ClerkPkEnum.IN_PROGRESS.name().equals(status)) {
|
||||
return PkWxState.ACTIVE.getValue();
|
||||
}
|
||||
if (ClerkPkEnum.TO_BE_STARTED.name().equals(status)) {
|
||||
return PkWxState.UPCOMING.getValue();
|
||||
}
|
||||
return PkWxState.INACTIVE.getValue();
|
||||
}
|
||||
|
||||
private void fillBase(WxPkLiveDto dto, PlayClerkPkEntity pk) {
|
||||
dto.setId(pk.getId());
|
||||
dto.setClerkAId(pk.getClerkA());
|
||||
dto.setClerkBId(pk.getClerkB());
|
||||
fillClerkInfo(dto);
|
||||
}
|
||||
|
||||
private void fillBase(WxPkUpcomingDto dto, PlayClerkPkEntity pk) {
|
||||
dto.setId(pk.getId());
|
||||
dto.setClerkAId(pk.getClerkA());
|
||||
dto.setClerkBId(pk.getClerkB());
|
||||
fillClerkInfo(dto);
|
||||
}
|
||||
|
||||
private void fillBase(WxPkDetailDto dto, PlayClerkPkEntity pk) {
|
||||
dto.setId(pk.getId());
|
||||
dto.setClerkAId(pk.getClerkA());
|
||||
dto.setClerkBId(pk.getClerkB());
|
||||
fillClerkInfo(dto);
|
||||
}
|
||||
|
||||
private void fillClerkInfo(WxPkLiveDto dto) {
|
||||
fillClerkInfo(dto.getClerkAId(), dto.getClerkBId(), dto);
|
||||
}
|
||||
|
||||
private void fillClerkInfo(WxPkUpcomingDto dto) {
|
||||
fillClerkInfo(dto.getClerkAId(), dto.getClerkBId(), dto);
|
||||
}
|
||||
|
||||
private void fillClerkInfo(WxPkDetailDto dto) {
|
||||
fillClerkInfo(dto.getClerkAId(), dto.getClerkBId(), dto);
|
||||
}
|
||||
|
||||
private void fillClerkInfo(String clerkAId, String clerkBId, Object target) {
|
||||
PlayClerkUserInfoEntity clerkA = clerkUserInfoService.getById(clerkAId);
|
||||
PlayClerkUserInfoEntity clerkB = clerkUserInfoService.getById(clerkBId);
|
||||
if (target instanceof WxPkLiveDto) {
|
||||
WxPkLiveDto dto = (WxPkLiveDto) target;
|
||||
dto.setClerkAName(clerkA == null ? "" : clerkA.getNickname());
|
||||
dto.setClerkBName(clerkB == null ? "" : clerkB.getNickname());
|
||||
dto.setClerkAAvatar(clerkA == null ? "" : clerkA.getAvatar());
|
||||
dto.setClerkBAvatar(clerkB == null ? "" : clerkB.getAvatar());
|
||||
return;
|
||||
}
|
||||
if (target instanceof WxPkUpcomingDto) {
|
||||
WxPkUpcomingDto dto = (WxPkUpcomingDto) target;
|
||||
dto.setClerkAName(clerkA == null ? "" : clerkA.getNickname());
|
||||
dto.setClerkBName(clerkB == null ? "" : clerkB.getNickname());
|
||||
dto.setClerkAAvatar(clerkA == null ? "" : clerkA.getAvatar());
|
||||
dto.setClerkBAvatar(clerkB == null ? "" : clerkB.getAvatar());
|
||||
return;
|
||||
}
|
||||
if (target instanceof WxPkDetailDto) {
|
||||
WxPkDetailDto dto = (WxPkDetailDto) target;
|
||||
dto.setClerkAName(clerkA == null ? "" : clerkA.getNickname());
|
||||
dto.setClerkBName(clerkB == null ? "" : clerkB.getNickname());
|
||||
dto.setClerkAAvatar(clerkA == null ? "" : clerkA.getAvatar());
|
||||
dto.setClerkBAvatar(clerkB == null ? "" : clerkB.getAvatar());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.starry.admin.utils;
|
||||
|
||||
public final class TenantScope implements AutoCloseable {
|
||||
private final String previousTenantId;
|
||||
|
||||
private TenantScope(String tenantId) {
|
||||
this.previousTenantId = SecurityUtils.getTenantId();
|
||||
SecurityUtils.setTenantId(tenantId);
|
||||
}
|
||||
|
||||
public static TenantScope use(String tenantId) {
|
||||
return new TenantScope(tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
SecurityUtils.setTenantId(previousTenantId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
CREATE TABLE `play_clerk_pk_settings` (
|
||||
`id` varchar(32) NOT NULL COMMENT 'UUID',
|
||||
`tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
|
||||
`name` varchar(255) NOT NULL COMMENT '设置名称',
|
||||
`recurrence_type` varchar(32) NOT NULL COMMENT '循环类型',
|
||||
`day_of_week` varchar(16) DEFAULT NULL COMMENT '星期几',
|
||||
`day_of_month` int DEFAULT NULL COMMENT '每月第N天',
|
||||
`month_of_year` int DEFAULT NULL COMMENT '每年月份',
|
||||
`start_time_of_day` time NOT NULL COMMENT '每日开始时间',
|
||||
`duration_minutes` int NOT NULL COMMENT '持续分钟数',
|
||||
`effective_start_date` date NOT NULL COMMENT '生效开始日期',
|
||||
`effective_end_date` date DEFAULT NULL COMMENT '生效结束日期',
|
||||
`timezone` varchar(64) NOT NULL COMMENT '时区',
|
||||
`clerk_a_id` varchar(255) NOT NULL COMMENT '店员A ID',
|
||||
`clerk_b_id` varchar(255) NOT NULL COMMENT '店员B ID',
|
||||
`status` varchar(32) NOT NULL COMMENT '状态',
|
||||
`created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
|
||||
`created_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
|
||||
`updated_time` datetime DEFAULT NULL COMMENT '修改时间',
|
||||
`deleted` varchar(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
|
||||
`version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `key_tenant_id` (`tenant_id`) USING BTREE,
|
||||
KEY `idx_pk_settings_status` (`status`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员PK排期设置';
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE `play_clerk_pk`
|
||||
ADD COLUMN `setting_id` varchar(32) DEFAULT NULL COMMENT 'PK排期设置ID' AFTER `status`,
|
||||
ADD KEY `idx_pk_setting_id` (`setting_id`);
|
||||
288
play-admin/src/main/resources/db/migration/V22__add_pk_menu.sql
Normal file
288
play-admin/src/main/resources/db/migration/V22__add_pk_menu.sql
Normal file
@@ -0,0 +1,288 @@
|
||||
-- Add PK management menus for PC tenant.
|
||||
|
||||
SET @pk_root_id := (
|
||||
SELECT menu_id
|
||||
FROM sys_menu
|
||||
WHERE menu_name = 'PK管理' AND parent_id = 0 AND deleted = 0
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_menu (
|
||||
menu_name,
|
||||
menu_code,
|
||||
icon,
|
||||
permission,
|
||||
menu_level,
|
||||
sort,
|
||||
parent_id,
|
||||
menu_type,
|
||||
status,
|
||||
remark,
|
||||
path,
|
||||
component,
|
||||
router_query,
|
||||
is_frame,
|
||||
visible,
|
||||
updated_time,
|
||||
updated_by,
|
||||
created_time,
|
||||
created_by,
|
||||
deleted,
|
||||
version,
|
||||
perms
|
||||
)
|
||||
SELECT
|
||||
'PK管理',
|
||||
'PkManage',
|
||||
'el-icon-trophy',
|
||||
'',
|
||||
1,
|
||||
90,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
'PK管理',
|
||||
'play/pk',
|
||||
'Layout',
|
||||
'',
|
||||
0,
|
||||
1,
|
||||
NOW(),
|
||||
'migration_v22_pk_menu',
|
||||
NOW(),
|
||||
'migration_v22_pk_menu',
|
||||
0,
|
||||
1,
|
||||
''
|
||||
WHERE @pk_root_id IS NULL;
|
||||
|
||||
SET @pk_root_id := (
|
||||
SELECT menu_id
|
||||
FROM sys_menu
|
||||
WHERE menu_name = 'PK管理' AND parent_id = 0 AND deleted = 0
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
SET @pk_schedule_id := (
|
||||
SELECT menu_id
|
||||
FROM sys_menu
|
||||
WHERE menu_name = '排期管理' AND parent_id = @pk_root_id AND deleted = 0
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_menu (
|
||||
menu_name,
|
||||
menu_code,
|
||||
icon,
|
||||
permission,
|
||||
menu_level,
|
||||
sort,
|
||||
parent_id,
|
||||
menu_type,
|
||||
status,
|
||||
remark,
|
||||
path,
|
||||
component,
|
||||
router_query,
|
||||
is_frame,
|
||||
visible,
|
||||
updated_time,
|
||||
updated_by,
|
||||
created_time,
|
||||
created_by,
|
||||
deleted,
|
||||
version,
|
||||
perms
|
||||
)
|
||||
SELECT
|
||||
'排期管理',
|
||||
'PkSchedule',
|
||||
'el-icon-date',
|
||||
'play:pk:schedule:list',
|
||||
2,
|
||||
1,
|
||||
@pk_root_id,
|
||||
1,
|
||||
0,
|
||||
'排期管理',
|
||||
'schedule',
|
||||
'play/pk/schedule/index',
|
||||
'',
|
||||
0,
|
||||
1,
|
||||
NOW(),
|
||||
'migration_v22_pk_menu',
|
||||
NOW(),
|
||||
'migration_v22_pk_menu',
|
||||
0,
|
||||
1,
|
||||
''
|
||||
WHERE @pk_root_id IS NOT NULL AND @pk_schedule_id IS NULL;
|
||||
|
||||
SET @pk_live_id := (
|
||||
SELECT menu_id
|
||||
FROM sys_menu
|
||||
WHERE menu_name = '实时监控' AND parent_id = @pk_root_id AND deleted = 0
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_menu (
|
||||
menu_name,
|
||||
menu_code,
|
||||
icon,
|
||||
permission,
|
||||
menu_level,
|
||||
sort,
|
||||
parent_id,
|
||||
menu_type,
|
||||
status,
|
||||
remark,
|
||||
path,
|
||||
component,
|
||||
router_query,
|
||||
is_frame,
|
||||
visible,
|
||||
updated_time,
|
||||
updated_by,
|
||||
created_time,
|
||||
created_by,
|
||||
deleted,
|
||||
version,
|
||||
perms
|
||||
)
|
||||
SELECT
|
||||
'实时监控',
|
||||
'PkLive',
|
||||
'el-icon-video-play',
|
||||
'play:pk:live:list',
|
||||
2,
|
||||
2,
|
||||
@pk_root_id,
|
||||
1,
|
||||
0,
|
||||
'实时监控',
|
||||
'live',
|
||||
'play/pk/live/index',
|
||||
'',
|
||||
0,
|
||||
1,
|
||||
NOW(),
|
||||
'migration_v22_pk_menu',
|
||||
NOW(),
|
||||
'migration_v22_pk_menu',
|
||||
0,
|
||||
1,
|
||||
''
|
||||
WHERE @pk_root_id IS NOT NULL AND @pk_live_id IS NULL;
|
||||
|
||||
SET @pk_history_id := (
|
||||
SELECT menu_id
|
||||
FROM sys_menu
|
||||
WHERE menu_name = '战绩列表' AND parent_id = @pk_root_id AND deleted = 0
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_menu (
|
||||
menu_name,
|
||||
menu_code,
|
||||
icon,
|
||||
permission,
|
||||
menu_level,
|
||||
sort,
|
||||
parent_id,
|
||||
menu_type,
|
||||
status,
|
||||
remark,
|
||||
path,
|
||||
component,
|
||||
router_query,
|
||||
is_frame,
|
||||
visible,
|
||||
updated_time,
|
||||
updated_by,
|
||||
created_time,
|
||||
created_by,
|
||||
deleted,
|
||||
version,
|
||||
perms
|
||||
)
|
||||
SELECT
|
||||
'战绩列表',
|
||||
'PkHistory',
|
||||
'el-icon-data-analysis',
|
||||
'play:pk:history:list',
|
||||
2,
|
||||
3,
|
||||
@pk_root_id,
|
||||
1,
|
||||
0,
|
||||
'战绩列表',
|
||||
'history',
|
||||
'play/pk/history/index',
|
||||
'',
|
||||
0,
|
||||
1,
|
||||
NOW(),
|
||||
'migration_v22_pk_menu',
|
||||
NOW(),
|
||||
'migration_v22_pk_menu',
|
||||
0,
|
||||
1,
|
||||
''
|
||||
WHERE @pk_root_id IS NOT NULL AND @pk_history_id IS NULL;
|
||||
|
||||
SET @pk_settings_id := (
|
||||
SELECT menu_id
|
||||
FROM sys_menu
|
||||
WHERE menu_name = '规则设置' AND parent_id = @pk_root_id AND deleted = 0
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_menu (
|
||||
menu_name,
|
||||
menu_code,
|
||||
icon,
|
||||
permission,
|
||||
menu_level,
|
||||
sort,
|
||||
parent_id,
|
||||
menu_type,
|
||||
status,
|
||||
remark,
|
||||
path,
|
||||
component,
|
||||
router_query,
|
||||
is_frame,
|
||||
visible,
|
||||
updated_time,
|
||||
updated_by,
|
||||
created_time,
|
||||
created_by,
|
||||
deleted,
|
||||
version,
|
||||
perms
|
||||
)
|
||||
SELECT
|
||||
'规则设置',
|
||||
'PkSettings',
|
||||
'el-icon-setting',
|
||||
'play:pk:settings:list',
|
||||
2,
|
||||
4,
|
||||
@pk_root_id,
|
||||
1,
|
||||
0,
|
||||
'规则设置',
|
||||
'settings',
|
||||
'play/pk/settings/index',
|
||||
'',
|
||||
0,
|
||||
1,
|
||||
NOW(),
|
||||
'migration_v22_pk_menu',
|
||||
NOW(),
|
||||
'migration_v22_pk_menu',
|
||||
0,
|
||||
1,
|
||||
''
|
||||
WHERE @pk_root_id IS NOT NULL AND @pk_settings_id IS NULL;
|
||||
@@ -0,0 +1,10 @@
|
||||
-- Fix PK root menu path to ensure correct routing.
|
||||
|
||||
UPDATE sys_menu
|
||||
SET path = '/play/pk',
|
||||
updated_time = NOW(),
|
||||
updated_by = 'migration_v23_fix_pk_path'
|
||||
WHERE menu_name = 'PK管理'
|
||||
AND parent_id = 0
|
||||
AND deleted = 0
|
||||
AND path = 'play/pk';
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,529 @@
|
||||
package com.starry.admin.modules.pk;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.starry.admin.api.AbstractApiTest;
|
||||
import com.starry.admin.modules.clerk.module.entity.ClerkPkEnum;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkPkService;
|
||||
import com.starry.admin.modules.pk.enums.PkLifecycleErrorCode;
|
||||
import com.starry.admin.modules.pk.redis.PkRedisKeyConstants;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.result.ResultCodeEnum;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import javax.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
class PlayClerkPkApiTest extends AbstractApiTest {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final String MSG_PK_NOT_FOUND = "PK不存在";
|
||||
private static final String MSG_PK_START_NOT_REACHED = "PK开始时间尚未到达";
|
||||
private static final String CLERK_PREFIX = "pk-api-clerk-";
|
||||
private static final int MINUTES_BEFORE_START = 5;
|
||||
private static final int MINUTES_AFTER_START = 30;
|
||||
private static final int MINUTES_FUTURE_START = 15;
|
||||
private static final int MINUTES_PAST_END = 10;
|
||||
private static final int FORCE_START_DURATION_MINUTES = 30;
|
||||
private static final int SETTLED_TRUE = 1;
|
||||
private static final int SETTLED_FALSE = 0;
|
||||
private static final BigDecimal TIE_SCORE = new BigDecimal("10.00");
|
||||
private static final BigDecimal SCORE_A = new BigDecimal("12.50");
|
||||
private static final BigDecimal SCORE_B = new BigDecimal("7.25");
|
||||
private static final BigDecimal ZERO_SCORE = new BigDecimal("0.00");
|
||||
private static final int ORDER_COUNT_A = 3;
|
||||
private static final int ORDER_COUNT_B = 1;
|
||||
private static final long REMAINING_SECONDS_ZERO = 0L;
|
||||
private static final int ZERO_COUNT = 0;
|
||||
private static final int ZERO_VALUE = 0;
|
||||
|
||||
@Resource
|
||||
private IPlayClerkPkService clerkPkService;
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Test
|
||||
void forceStartShouldCreateInProgressPk() throws Exception {
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
String requestBody = buildForceStartBody(clerkAId, clerkBId, FORCE_START_DURATION_MINUTES);
|
||||
|
||||
MvcResult result = mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn();
|
||||
|
||||
String pkId = extractDataAsText(result);
|
||||
PlayClerkPkEntity persisted = loadPk(pkId);
|
||||
assertThat(persisted.getStatus()).isEqualTo(ClerkPkEnum.IN_PROGRESS.name());
|
||||
assertThat(persisted.getClerkA()).isEqualTo(clerkAId);
|
||||
assertThat(persisted.getClerkB()).isEqualTo(clerkBId);
|
||||
assertThat(persisted.getPkBeginTime()).isNotNull();
|
||||
assertThat(persisted.getPkEndTime()).isNotNull();
|
||||
assertThat(persisted.getTenantId()).isEqualTo(DEFAULT_TENANT);
|
||||
assertThat(persisted.getCreatedBy()).isNotNull();
|
||||
assertThat(persisted.getPkEndTime()).isAfter(persisted.getPkBeginTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenConflict() throws Exception {
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
createPkForClerks(clerkAId, clerkBId, now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START), ClerkPkEnum.IN_PROGRESS.name());
|
||||
|
||||
String requestBody = buildForceStartBody(clerkAId, clerkBId, FORCE_START_DURATION_MINUTES);
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.CLERK_CONFLICT.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenMissingClerkA() throws Exception {
|
||||
String requestBody = "{\"clerkAId\":\"\",\"clerkBId\":\"" + newClerkId()
|
||||
+ "\",\"durationMinutes\":" + FORCE_START_DURATION_MINUTES + "}";
|
||||
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenMissingClerkB() throws Exception {
|
||||
String requestBody = "{\"clerkAId\":\"" + newClerkId()
|
||||
+ "\",\"clerkBId\":\"\",\"durationMinutes\":" + FORCE_START_DURATION_MINUTES + "}";
|
||||
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenMissingDuration() throws Exception {
|
||||
String requestBody = "{\"clerkAId\":\"" + newClerkId()
|
||||
+ "\",\"clerkBId\":\"" + newClerkId() + "\"}";
|
||||
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenSameClerk() throws Exception {
|
||||
String clerkId = newClerkId();
|
||||
String requestBody = buildForceStartBody(clerkId, clerkId, FORCE_START_DURATION_MINUTES);
|
||||
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenDurationInvalid() throws Exception {
|
||||
String requestBody = buildForceStartBody(newClerkId(), newClerkId(), 0);
|
||||
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenConflictOnClerkAOnly() throws Exception {
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
String otherClerk = newClerkId();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
createPkForClerks(clerkAId, otherClerk, now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START), ClerkPkEnum.IN_PROGRESS.name());
|
||||
|
||||
String requestBody = buildForceStartBody(clerkAId, clerkBId, FORCE_START_DURATION_MINUTES);
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.CLERK_CONFLICT.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceStartShouldRejectWhenConflictOnClerkBOnly() throws Exception {
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
String otherClerk = newClerkId();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
createPkForClerks(otherClerk, clerkBId, now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START), ClerkPkEnum.IN_PROGRESS.name());
|
||||
|
||||
String requestBody = buildForceStartBody(clerkAId, clerkBId, FORCE_START_DURATION_MINUTES);
|
||||
mockMvc.perform(post(buildForceStartPath())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkLifecycleErrorCode.CLERK_CONFLICT.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void startPkShouldRejectWhenStartTimeNotReached() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.TO_BE_STARTED.name(),
|
||||
now.plusMinutes(MINUTES_FUTURE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
SETTLED_FALSE);
|
||||
|
||||
mockMvc.perform(post(buildStartPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(MSG_PK_START_NOT_REACHED));
|
||||
}
|
||||
|
||||
@Test
|
||||
void startPkShouldFailWhenMissing() throws Exception {
|
||||
String pkId = IdUtils.getUuid();
|
||||
mockMvc.perform(post(buildStartPath(pkId))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(MSG_PK_NOT_FOUND));
|
||||
}
|
||||
|
||||
@Test
|
||||
void startPkShouldAdvanceStatusWhenEligible() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.TO_BE_STARTED.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
SETTLED_FALSE);
|
||||
|
||||
mockMvc.perform(post(buildStartPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
PlayClerkPkEntity persisted = loadPk(pk.getId());
|
||||
assertThat(persisted.getStatus()).isEqualTo(ClerkPkEnum.IN_PROGRESS.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
void finishPkShouldFailWhenMissing() throws Exception {
|
||||
String pkId = IdUtils.getUuid();
|
||||
mockMvc.perform(post(buildFinishPath(pkId))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(MSG_PK_NOT_FOUND));
|
||||
}
|
||||
|
||||
@Test
|
||||
void finishPkShouldBeIdempotent() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.FINISHED.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
SETTLED_TRUE);
|
||||
|
||||
mockMvc.perform(post(buildFinishPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
PlayClerkPkEntity persisted = loadPk(pk.getId());
|
||||
assertThat(persisted.getStatus()).isEqualTo(ClerkPkEnum.FINISHED.name());
|
||||
assertThat(persisted.getSettled()).isEqualTo(SETTLED_TRUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void finishPkShouldPersistZeroWhenRedisEmpty() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.IN_PROGRESS.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.minusMinutes(MINUTES_PAST_END),
|
||||
SETTLED_FALSE);
|
||||
|
||||
mockMvc.perform(post(buildFinishPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
PlayClerkPkEntity persisted = loadPk(pk.getId());
|
||||
assertThat(persisted.getClerkAScore()).isEqualByComparingTo(ZERO_SCORE);
|
||||
assertThat(persisted.getClerkBScore()).isEqualByComparingTo(ZERO_SCORE);
|
||||
assertThat(persisted.getClerkAOrderCount()).isEqualTo(ZERO_COUNT);
|
||||
assertThat(persisted.getClerkBOrderCount()).isEqualTo(ZERO_COUNT);
|
||||
assertThat(persisted.getWinnerClerkId()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void finishPkShouldAssignWinnerForClerkA() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.IN_PROGRESS.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.minusMinutes(MINUTES_PAST_END),
|
||||
SETTLED_FALSE);
|
||||
|
||||
String scoreKey = PkRedisKeyConstants.scoreKey(pk.getId());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_A_SCORE, SCORE_A.toString());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_B_SCORE, SCORE_B.toString());
|
||||
|
||||
mockMvc.perform(post(buildFinishPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
PlayClerkPkEntity persisted = loadPk(pk.getId());
|
||||
assertThat(persisted.getWinnerClerkId()).isEqualTo(pk.getClerkA());
|
||||
}
|
||||
|
||||
@Test
|
||||
void finishPkShouldAssignWinnerForClerkB() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.IN_PROGRESS.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.minusMinutes(MINUTES_PAST_END),
|
||||
SETTLED_FALSE);
|
||||
|
||||
String scoreKey = PkRedisKeyConstants.scoreKey(pk.getId());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_A_SCORE, SCORE_B.toString());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_B_SCORE, SCORE_A.toString());
|
||||
|
||||
mockMvc.perform(post(buildFinishPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
PlayClerkPkEntity persisted = loadPk(pk.getId());
|
||||
assertThat(persisted.getWinnerClerkId()).isEqualTo(pk.getClerkB());
|
||||
}
|
||||
|
||||
@Test
|
||||
void finishPkShouldKeepWinnerNullOnTie() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.IN_PROGRESS.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
SETTLED_FALSE);
|
||||
|
||||
String scoreKey = PkRedisKeyConstants.scoreKey(pk.getId());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_A_SCORE, TIE_SCORE.toString());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_B_SCORE, TIE_SCORE.toString());
|
||||
|
||||
mockMvc.perform(post(buildFinishPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
PlayClerkPkEntity persisted = loadPk(pk.getId());
|
||||
assertThat(persisted.getWinnerClerkId()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void scoreboardShouldReturnRedisValues() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.IN_PROGRESS.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
SETTLED_FALSE);
|
||||
|
||||
String scoreKey = PkRedisKeyConstants.scoreKey(pk.getId());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_A_SCORE, SCORE_A.toString());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey, PkRedisKeyConstants.FIELD_CLERK_B_SCORE, SCORE_B.toString());
|
||||
stringRedisTemplate.opsForHash().put(scoreKey,
|
||||
PkRedisKeyConstants.FIELD_CLERK_A_ORDER_COUNT, String.valueOf(ORDER_COUNT_A));
|
||||
stringRedisTemplate.opsForHash().put(scoreKey,
|
||||
PkRedisKeyConstants.FIELD_CLERK_B_ORDER_COUNT, String.valueOf(ORDER_COUNT_B));
|
||||
|
||||
mockMvc.perform(get(buildScoreboardPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andExpect(jsonPath("$.data.clerkAScore").value(SCORE_A.doubleValue()))
|
||||
.andExpect(jsonPath("$.data.clerkBScore").value(SCORE_B.doubleValue()))
|
||||
.andExpect(jsonPath("$.data.clerkAOrderCount").value(ORDER_COUNT_A))
|
||||
.andExpect(jsonPath("$.data.clerkBOrderCount").value(ORDER_COUNT_B));
|
||||
}
|
||||
|
||||
@Test
|
||||
void scoreboardShouldReturnZeroWhenRedisEmpty() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.IN_PROGRESS.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
SETTLED_FALSE);
|
||||
|
||||
mockMvc.perform(get(buildScoreboardPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andExpect(jsonPath("$.data.clerkAScore").value(ZERO_VALUE))
|
||||
.andExpect(jsonPath("$.data.clerkBScore").value(ZERO_VALUE))
|
||||
.andExpect(jsonPath("$.data.clerkAOrderCount").value(ZERO_COUNT))
|
||||
.andExpect(jsonPath("$.data.clerkBOrderCount").value(ZERO_COUNT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void scoreboardShouldReturnZeroRemainingSecondsWhenEnded() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlayClerkPkEntity pk = buildPk(ClerkPkEnum.IN_PROGRESS.name(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.minusMinutes(MINUTES_PAST_END),
|
||||
SETTLED_FALSE);
|
||||
|
||||
mockMvc.perform(get(buildScoreboardPath(pk.getId()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andExpect(jsonPath("$.data.remainingSeconds").value(REMAINING_SECONDS_ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
void scoreboardShouldFailWhenMissingPk() throws Exception {
|
||||
mockMvc.perform(get(buildScoreboardPath(IdUtils.getUuid()))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(MSG_PK_NOT_FOUND));
|
||||
}
|
||||
|
||||
private PlayClerkPkEntity buildPk(String status, LocalDateTime beginTime, LocalDateTime endTime, int settled) {
|
||||
String pkId = IdUtils.getUuid();
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
PlayClerkPkEntity pk = new PlayClerkPkEntity();
|
||||
pk.setId(pkId);
|
||||
pk.setTenantId(DEFAULT_TENANT);
|
||||
pk.setClerkA(clerkAId);
|
||||
pk.setClerkB(clerkBId);
|
||||
pk.setPkBeginTime(Date.from(beginTime.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setPkEndTime(Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setStatus(status);
|
||||
pk.setSettled(settled);
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(pk);
|
||||
return pk;
|
||||
}
|
||||
|
||||
private PlayClerkPkEntity createPkForClerks(String clerkAId, String clerkBId, LocalDateTime beginTime,
|
||||
LocalDateTime endTime, String status) {
|
||||
PlayClerkPkEntity pk = new PlayClerkPkEntity();
|
||||
pk.setId(IdUtils.getUuid());
|
||||
pk.setTenantId(DEFAULT_TENANT);
|
||||
pk.setClerkA(clerkAId);
|
||||
pk.setClerkB(clerkBId);
|
||||
pk.setPkBeginTime(Date.from(beginTime.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setPkEndTime(Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setStatus(status);
|
||||
pk.setSettled(SETTLED_FALSE);
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(pk);
|
||||
return pk;
|
||||
}
|
||||
|
||||
private PlayClerkPkEntity loadPk(String pkId) {
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
PlayClerkPkEntity persisted = clerkPkService.selectPlayClerkPkById(pkId);
|
||||
assertThat(persisted).isNotNull();
|
||||
return persisted;
|
||||
}
|
||||
|
||||
private static String newClerkId() {
|
||||
return CLERK_PREFIX + IdUtils.getUuid();
|
||||
}
|
||||
|
||||
private static String buildStartPath(String pkId) {
|
||||
return "/play/pk/" + pkId + "/start";
|
||||
}
|
||||
|
||||
private static String buildFinishPath(String pkId) {
|
||||
return "/play/pk/" + pkId + "/finish";
|
||||
}
|
||||
|
||||
private static String buildScoreboardPath(String pkId) {
|
||||
return "/play/pk/" + pkId + "/scoreboard";
|
||||
}
|
||||
|
||||
private static String buildForceStartPath() {
|
||||
return "/play/pk/force-start";
|
||||
}
|
||||
|
||||
private static String buildForceStartBody(String clerkAId, String clerkBId, int durationMinutes) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("{");
|
||||
builder.append("\"clerkAId\":\"").append(clerkAId).append("\",");
|
||||
builder.append("\"clerkBId\":\"").append(clerkBId).append("\",");
|
||||
builder.append("\"durationMinutes\":").append(durationMinutes);
|
||||
builder.append("}");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String extractDataAsText(MvcResult result) throws Exception {
|
||||
JsonNode root = OBJECT_MAPPER.readTree(result.getResponse().getContentAsString());
|
||||
return root.path("data").asText();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,583 @@
|
||||
package com.starry.admin.modules.pk;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.starry.admin.api.AbstractApiTest;
|
||||
import com.starry.admin.modules.clerk.module.entity.ClerkPkEnum;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkPkService;
|
||||
import com.starry.admin.modules.pk.setting.constants.PkSettingApiConstants;
|
||||
import com.starry.admin.modules.pk.setting.enums.PkRecurrenceType;
|
||||
import com.starry.admin.modules.pk.setting.enums.PkSettingErrorCode;
|
||||
import com.starry.admin.modules.pk.setting.enums.PkSettingStatus;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.result.ResultCodeEnum;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import javax.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
class PlayClerkPkSettingApiTest extends AbstractApiTest {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final String SETTING_NAME = "PK排期测试";
|
||||
private static final String START_TIME_OF_DAY = "08:00:00";
|
||||
private static final int DURATION_MINUTES = 30;
|
||||
private static final String EFFECTIVE_START_DATE = "2025-01-01";
|
||||
private static final String EFFECTIVE_END_DATE = "2025-01-03";
|
||||
private static final String TIMEZONE = "Asia/Shanghai";
|
||||
private static final int EXPECTED_GENERATED_COUNT = 3;
|
||||
private static final int EXPECTED_ONCE_COUNT = 1;
|
||||
private static final int EXPECTED_ZERO_COUNT = 0;
|
||||
private static final int EXPECTED_BULK_COUNT = 2;
|
||||
private static final int INVALID_DAY_OF_MONTH = 32;
|
||||
private static final int INVALID_MONTH_OF_YEAR = 13;
|
||||
private static final int MONTH_OF_YEAR_JANUARY = 1;
|
||||
private static final int DAY_OF_MONTH_FIRST = 1;
|
||||
private static final int DAY_OF_MONTH_THIRTY_FIRST = 31;
|
||||
private static final int SETTLED_FALSE = 0;
|
||||
private static final String INVALID_TIMEZONE = "Invalid/Zone";
|
||||
private static final String EARLY_END_DATE = "2024-12-31";
|
||||
private static final String LONG_RANGE_START_DATE = "2025-01-01";
|
||||
private static final String LONG_RANGE_END_DATE = "2029-01-01";
|
||||
private static final String MONTHLY_SHORT_START_DATE = "2025-04-01";
|
||||
private static final String MONTHLY_SHORT_END_DATE = "2025-04-30";
|
||||
private static final String CONFLICT_START_DATE = "2025-01-01";
|
||||
private static final String CONFLICT_END_DATE = "2025-01-02";
|
||||
private static final String UPDATED_NAME = "PK排期更新";
|
||||
|
||||
@Resource
|
||||
private IPlayClerkPkService clerkPkService;
|
||||
|
||||
@Test
|
||||
void pkSettingFlowShouldWorkEndToEnd() throws Exception {
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String createRequestBody = buildRequestBody(SETTING_NAME, PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String updateRequestBody = buildRequestBody(UPDATED_NAME, PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.DISABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
MvcResult createResult = mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(createRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn();
|
||||
|
||||
String settingId = extractDataAsText(createResult);
|
||||
|
||||
String listPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.LIST_PATH;
|
||||
mockMvc.perform(get(listPath)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andExpect(jsonPath("$.data").isArray());
|
||||
|
||||
String detailPath = PkSettingApiConstants.BASE_PATH + "/"
|
||||
+ settingId;
|
||||
mockMvc.perform(get(detailPath)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andExpect(jsonPath("$.data.id").value(settingId))
|
||||
.andExpect(jsonPath("$.data.name").value(SETTING_NAME));
|
||||
|
||||
String updatePath = PkSettingApiConstants.BASE_PATH + "/update/" + settingId;
|
||||
mockMvc.perform(post(updatePath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(updateRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
mockMvc.perform(get(detailPath)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andExpect(jsonPath("$.data.name").value(UPDATED_NAME));
|
||||
|
||||
String enablePath = PkSettingApiConstants.BASE_PATH + "/" + settingId + "/enable";
|
||||
mockMvc.perform(post(enablePath)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
List<PlayClerkPkEntity> generated = clerkPkService.list(
|
||||
Wrappers.<PlayClerkPkEntity>lambdaQuery().eq(PlayClerkPkEntity::getSettingId, settingId));
|
||||
assertThat(generated).hasSize(EXPECTED_GENERATED_COUNT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectMissingWeeklyFields() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidWeeklyRequestBody = buildRequestBody("无效周排期", PkRecurrenceType.WEEKLY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidWeeklyRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.RECURRENCE_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectInvalidMonthlyDay() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidMonthlyRequestBody = buildRequestBody("无效月排期", PkRecurrenceType.MONTHLY,
|
||||
null, INVALID_DAY_OF_MONTH, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidMonthlyRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.RECURRENCE_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateShouldRejectClerkConflict() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String createRequestBody = buildRequestBody(SETTING_NAME, PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String conflictRequestBody = buildRequestBody("冲突排期", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(createRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(conflictRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.CLERK_CONFLICT.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void bulkCreateShouldCreateMultipleSettings() throws Exception {
|
||||
String bulkCreatePath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.BULK_CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkCId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkDId = "clerk-" + IdUtils.getUuid();
|
||||
String firstRequestBody = buildRequestBody("批量排期A", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String secondRequestBody = buildRequestBody("批量排期B", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkCId, clerkDId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String bulkRequestBody = buildBulkRequestBody(List.of(firstRequestBody, secondRequestBody));
|
||||
|
||||
MvcResult result = mockMvc.perform(post(bulkCreatePath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(bulkRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn();
|
||||
|
||||
JsonNode data = OBJECT_MAPPER.readTree(result.getResponse().getContentAsString()).path("data");
|
||||
assertThat(data.isArray()).isTrue();
|
||||
assertThat(data.size()).isEqualTo(EXPECTED_BULK_COUNT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bulkCreateShouldRejectDuplicatePairs() throws Exception {
|
||||
String bulkCreatePath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.BULK_CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String firstRequestBody = buildRequestBody("批量排期重复A", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String secondRequestBody = buildRequestBody("批量排期重复B", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkBId, clerkAId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String bulkRequestBody = buildBulkRequestBody(List.of(firstRequestBody, secondRequestBody));
|
||||
|
||||
mockMvc.perform(post(bulkCreatePath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(bulkRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectMissingMonthlyDay() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidMonthlyRequestBody = buildRequestBody("缺失月日", PkRecurrenceType.MONTHLY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidMonthlyRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.RECURRENCE_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectMissingYearlyFields() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidYearlyRequestBody = buildRequestBody("缺失年字段", PkRecurrenceType.YEARLY,
|
||||
null, DAY_OF_MONTH_FIRST, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidYearlyRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.RECURRENCE_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectInvalidMonthOfYear() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidYearlyRequestBody = buildRequestBody("无效月份", PkRecurrenceType.YEARLY,
|
||||
null, DAY_OF_MONTH_FIRST, INVALID_MONTH_OF_YEAR, PkSettingStatus.ENABLED, clerkAId, clerkBId,
|
||||
EFFECTIVE_START_DATE, EFFECTIVE_END_DATE, TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidYearlyRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.RECURRENCE_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectInvalidTimeRange() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidRangeRequestBody = buildRequestBody("无效时间范围", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EARLY_END_DATE, TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidRangeRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.TIME_RANGE_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectSameClerk() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidRequestBody = buildRequestBody("同店员", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkId, clerkId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSettingShouldRejectInvalidTimezone() throws Exception {
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String invalidRequestBody = buildRequestBody("无效时区", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, INVALID_TIMEZONE);
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidRequestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.REQUEST_INVALID.getMessage()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateShouldRespectThreeYearLimit() throws Exception {
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String requestBody = buildRequestBody("三年上限", PkRecurrenceType.YEARLY,
|
||||
null, DAY_OF_MONTH_FIRST, MONTH_OF_YEAR_JANUARY, PkSettingStatus.ENABLED, clerkAId, clerkBId,
|
||||
LONG_RANGE_START_DATE, LONG_RANGE_END_DATE, TIMEZONE);
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String settingId = extractDataAsText(mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn());
|
||||
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
List<PlayClerkPkEntity> generated = clerkPkService.list(
|
||||
Wrappers.<PlayClerkPkEntity>lambdaQuery().eq(PlayClerkPkEntity::getSettingId, settingId));
|
||||
assertThat(generated).isNotEmpty();
|
||||
LocalDate maxDate = generated.stream()
|
||||
.map(PlayClerkPkEntity::getPkBeginTime)
|
||||
.filter(date -> date != null)
|
||||
.map(date -> date.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate())
|
||||
.max(LocalDate::compareTo)
|
||||
.orElse(LocalDate.parse(LONG_RANGE_START_DATE));
|
||||
LocalDate maxAllowed = LocalDate.parse(LONG_RANGE_START_DATE).plusYears(3);
|
||||
assertThat(maxDate).isBefore(maxAllowed.plusDays(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void monthlyInvalidDayShouldSkipWithoutError() throws Exception {
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String requestBody = buildRequestBody("无效日期跳过", PkRecurrenceType.MONTHLY,
|
||||
null, DAY_OF_MONTH_THIRTY_FIRST, null, PkSettingStatus.ENABLED, clerkAId, clerkBId,
|
||||
MONTHLY_SHORT_START_DATE, MONTHLY_SHORT_END_DATE, TIMEZONE);
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String settingId = extractDataAsText(mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn());
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
List<PlayClerkPkEntity> generated = clerkPkService.list(
|
||||
Wrappers.<PlayClerkPkEntity>lambdaQuery().eq(PlayClerkPkEntity::getSettingId, settingId));
|
||||
assertThat(generated).hasSize(EXPECTED_ZERO_COUNT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void onceScheduleShouldGenerateSingleInstance() throws Exception {
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String requestBody = buildRequestBody("单次排期", PkRecurrenceType.ONCE,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_START_DATE, TIMEZONE);
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String settingId = extractDataAsText(mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn());
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
List<PlayClerkPkEntity> generated = clerkPkService.list(
|
||||
Wrappers.<PlayClerkPkEntity>lambdaQuery().eq(PlayClerkPkEntity::getSettingId, settingId));
|
||||
assertThat(generated).hasSize(EXPECTED_ONCE_COUNT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void disabledSettingShouldNotGenerateInstances() throws Exception {
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String requestBody = buildRequestBody("禁用生成", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.DISABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String settingId = extractDataAsText(mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn());
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
List<PlayClerkPkEntity> generated = clerkPkService.list(
|
||||
Wrappers.<PlayClerkPkEntity>lambdaQuery().eq(PlayClerkPkEntity::getSettingId, settingId));
|
||||
assertThat(generated).hasSize(EXPECTED_ZERO_COUNT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateShouldRollbackWhenConflictDetected() throws Exception {
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String requestBody = buildRequestBody("冲突回滚", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.DISABLED, clerkAId, clerkBId, CONFLICT_START_DATE,
|
||||
CONFLICT_END_DATE, TIMEZONE);
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
String settingId = extractDataAsText(mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn());
|
||||
|
||||
LocalDateTime conflictStart = LocalDateTime.of(LocalDate.parse(CONFLICT_END_DATE),
|
||||
LocalTime.parse(START_TIME_OF_DAY));
|
||||
LocalDateTime conflictEnd = conflictStart.plusMinutes(DURATION_MINUTES);
|
||||
ZoneId zoneId = ZoneId.of(TIMEZONE);
|
||||
PlayClerkPkEntity conflictPk = new PlayClerkPkEntity();
|
||||
conflictPk.setId(IdUtils.getUuid());
|
||||
conflictPk.setTenantId(DEFAULT_TENANT);
|
||||
conflictPk.setClerkA(clerkAId);
|
||||
conflictPk.setClerkB(clerkBId);
|
||||
conflictPk.setPkBeginTime(Date.from(conflictStart.atZone(zoneId).toInstant()));
|
||||
conflictPk.setPkEndTime(Date.from(conflictEnd.atZone(zoneId).toInstant()));
|
||||
conflictPk.setStatus(ClerkPkEnum.TO_BE_STARTED.name());
|
||||
conflictPk.setSettled(SETTLED_FALSE);
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(conflictPk);
|
||||
|
||||
String enablePath = PkSettingApiConstants.BASE_PATH + "/" + settingId + "/enable";
|
||||
mockMvc.perform(post(enablePath)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(PkSettingErrorCode.CLERK_CONFLICT.getMessage()));
|
||||
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
List<PlayClerkPkEntity> generated = clerkPkService.list(
|
||||
Wrappers.<PlayClerkPkEntity>lambdaQuery().eq(PlayClerkPkEntity::getSettingId, settingId));
|
||||
assertThat(generated).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void listShouldExcludeOtherTenantSettings() throws Exception {
|
||||
String tenantA = "tenant-" + IdUtils.getUuid();
|
||||
String tenantB = "tenant-" + IdUtils.getUuid();
|
||||
String clerkAId = "clerk-" + IdUtils.getUuid();
|
||||
String clerkBId = "clerk-" + IdUtils.getUuid();
|
||||
String requestBody = buildRequestBody("多租户排期", PkRecurrenceType.DAILY,
|
||||
null, null, null, PkSettingStatus.ENABLED, clerkAId, clerkBId, EFFECTIVE_START_DATE,
|
||||
EFFECTIVE_END_DATE, TIMEZONE);
|
||||
String createPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.CREATE_PATH;
|
||||
mockMvc.perform(post(createPath)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(requestBody)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, tenantB))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()));
|
||||
|
||||
String listPath = PkSettingApiConstants.BASE_PATH + PkSettingApiConstants.LIST_PATH;
|
||||
MvcResult listResult = mockMvc.perform(get(listPath)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, tenantA))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.SUCCESS.getCode()))
|
||||
.andReturn();
|
||||
JsonNode data = OBJECT_MAPPER.readTree(listResult.getResponse().getContentAsString()).path("data");
|
||||
if (data.isArray()) {
|
||||
for (JsonNode node : data) {
|
||||
String tenantId = node.path("tenantId").asText();
|
||||
assertThat(tenantId).isNotEqualTo(tenantB);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String extractDataAsText(MvcResult result) throws Exception {
|
||||
JsonNode root = OBJECT_MAPPER.readTree(result.getResponse().getContentAsString());
|
||||
return root.path("data").asText();
|
||||
}
|
||||
|
||||
private static String buildRequestBody(String name, PkRecurrenceType recurrenceType, String dayOfWeek,
|
||||
Integer dayOfMonth, Integer monthOfYear, PkSettingStatus status, String clerkAId, String clerkBId,
|
||||
String effectiveStartDate, String effectiveEndDate, String timezone) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("{");
|
||||
builder.append("\"name\":\"").append(name).append("\",");
|
||||
builder.append("\"recurrenceType\":\"").append(recurrenceType.getValue()).append("\",");
|
||||
if (dayOfWeek != null) {
|
||||
builder.append("\"dayOfWeek\":\"").append(dayOfWeek).append("\",");
|
||||
}
|
||||
if (dayOfMonth != null) {
|
||||
builder.append("\"dayOfMonth\":").append(dayOfMonth).append(",");
|
||||
}
|
||||
if (monthOfYear != null) {
|
||||
builder.append("\"monthOfYear\":").append(monthOfYear).append(",");
|
||||
}
|
||||
builder.append("\"startTimeOfDay\":\"").append(START_TIME_OF_DAY).append("\",");
|
||||
builder.append("\"durationMinutes\":").append(DURATION_MINUTES).append(",");
|
||||
builder.append("\"effectiveStartDate\":\"").append(effectiveStartDate).append("\",");
|
||||
builder.append("\"effectiveEndDate\":\"").append(effectiveEndDate).append("\",");
|
||||
builder.append("\"timezone\":\"").append(timezone).append("\",");
|
||||
builder.append("\"clerkAId\":\"").append(clerkAId).append("\",");
|
||||
builder.append("\"clerkBId\":\"").append(clerkBId).append("\",");
|
||||
builder.append("\"status\":\"").append(status.getValue()).append("\"");
|
||||
builder.append("}");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String buildBulkRequestBody(List<String> settings) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("{\"settings\":[");
|
||||
for (int i = 0; i < settings.size(); i++) {
|
||||
builder.append(settings.get(i));
|
||||
if (i < settings.size() - 1) {
|
||||
builder.append(",");
|
||||
}
|
||||
}
|
||||
builder.append("]}");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,642 @@
|
||||
package com.starry.admin.modules.pk;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.starry.admin.api.AbstractApiTest;
|
||||
import com.starry.admin.common.exception.handler.GlobalExceptionHandler;
|
||||
import com.starry.admin.modules.clerk.module.entity.ClerkPkEnum;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkPkService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
||||
import com.starry.admin.modules.pk.enums.PkWxState;
|
||||
import com.starry.admin.modules.pk.redis.PkRedisKeyConstants;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.result.ResultCodeEnum;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import javax.annotation.Resource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
class WxPkApiTest extends AbstractApiTest {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final String CLERK_PREFIX = "wx-pk-clerk-";
|
||||
private static final String CUSTOM_PREFIX = "wx-pk-custom-";
|
||||
private static final String OPENID_PREFIX = "openid-";
|
||||
private static final String CLERK_NAME_PREFIX = "店员-";
|
||||
private static final String CUSTOM_NAME_PREFIX = "贡献者-";
|
||||
private static final String PK_PREFIX = "wx-pk-";
|
||||
private static final String PARAM_CLERK_ID = "clerkId";
|
||||
private static final String PARAM_ID = "id";
|
||||
private static final String PARAM_LIMIT = "limit";
|
||||
private static final String MSG_PARAM_INVALID = GlobalExceptionHandler.PARAMETER_FORMAT_ERROR;
|
||||
private static final String PARAM_PAGE_NUM = "pageNum";
|
||||
private static final String PARAM_PAGE_SIZE = "pageSize";
|
||||
private static final int MINUTES_BEFORE_START = 5;
|
||||
private static final int MINUTES_AFTER_START = 30;
|
||||
private static final int MINUTES_FUTURE_START = 10;
|
||||
private static final int MINUTES_ORDER_OFFSET = 2;
|
||||
private static final int HISTORY_FIRST_DAY_OFFSET = 2;
|
||||
private static final int HISTORY_SECOND_DAY_OFFSET = 1;
|
||||
private static final int FUTURE_FIRST_OFFSET_MINUTES = 30;
|
||||
private static final int FUTURE_SECOND_OFFSET_MINUTES = 60;
|
||||
private static final int PAST_OFFSET_MINUTES = 10;
|
||||
private static final int FUTURE_THIRD_OFFSET_MINUTES = 90;
|
||||
private static final int DEFAULT_LIMIT = 3;
|
||||
private static final int LIMIT_ZERO = 0;
|
||||
private static final int LIMIT_NEGATIVE = -1;
|
||||
private static final int LIMIT_LARGE = 100;
|
||||
private static final int SCHEDULE_LIMIT = 2;
|
||||
private static final int HISTORY_PAGE_NUM = 1;
|
||||
private static final int HISTORY_PAGE_SIZE = 10;
|
||||
private static final long REMAINING_SECONDS_MIN = 1L;
|
||||
private static final int SETTLED_FALSE = 0;
|
||||
private static final int SETTLED_TRUE = 1;
|
||||
private static final double ZSET_SCORE_PAST = 1D;
|
||||
private static final BigDecimal AMOUNT_HIGH = new BigDecimal("120.00");
|
||||
private static final BigDecimal AMOUNT_MEDIUM = new BigDecimal("100.00");
|
||||
private static final BigDecimal AMOUNT_LOW = new BigDecimal("50.00");
|
||||
private static final int EXPECTED_HISTORY_COUNT = 2;
|
||||
|
||||
@Resource
|
||||
private IPlayClerkPkService clerkPkService;
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Resource
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Resource
|
||||
private IPlayCustomUserInfoService customUserInfoService;
|
||||
|
||||
@Resource
|
||||
private IPlayOrderInfoService orderInfoService;
|
||||
|
||||
@BeforeEach
|
||||
void clearRedis() {
|
||||
if (stringRedisTemplate.getConnectionFactory() == null) {
|
||||
return;
|
||||
}
|
||||
stringRedisTemplate.getConnectionFactory().getConnection().flushDb();
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkLiveShouldRejectWhenClerkIdMissing() throws Exception {
|
||||
mockMvc.perform(get("/wx/pk/clerk/live")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(MSG_PARAM_INVALID));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkLiveShouldReturnInactiveWhenNoActivePk() throws Exception {
|
||||
String clerkId = newClerkId();
|
||||
mockMvc.perform(get("/wx/pk/clerk/live")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.INACTIVE.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkLiveShouldReturnActiveWhenInProgress() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String pkId = newPkId();
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
PlayClerkPkEntity pk = buildPk(pkId, clerkAId, clerkBId,
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.IN_PROGRESS.name());
|
||||
clerkPkService.save(pk);
|
||||
|
||||
MvcResult result = mockMvc.perform(get("/wx/pk/clerk/live")
|
||||
.param(PARAM_CLERK_ID, clerkAId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.ACTIVE.getValue()))
|
||||
.andExpect(jsonPath("$.data.id").value(pkId))
|
||||
.andReturn();
|
||||
|
||||
JsonNode data = extractData(result);
|
||||
assertThat(data.get("remainingSeconds").asLong()).isGreaterThanOrEqualTo(REMAINING_SECONDS_MIN);
|
||||
assertThat(data.get("serverEpochSeconds").asLong()).isGreaterThanOrEqualTo(REMAINING_SECONDS_MIN);
|
||||
assertThat(data.get("pkEndEpochSeconds").asLong()).isGreaterThanOrEqualTo(REMAINING_SECONDS_MIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkLiveShouldReturnInactiveWhenToBeStarted() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
PlayClerkPkEntity pk = buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(MINUTES_FUTURE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name());
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(pk);
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/live")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.INACTIVE.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void upcomingShouldReturnInactiveWhenRedisEmpty() throws Exception {
|
||||
mockMvc.perform(get("/wx/pk/upcoming")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.INACTIVE.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void upcomingShouldReturnUpcomingWhenRedisHasPk() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String pkId = newPkId();
|
||||
PlayClerkPkEntity pk = buildPk(pkId, newClerkId(), newClerkId(),
|
||||
now.plusMinutes(MINUTES_FUTURE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name());
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(pk);
|
||||
|
||||
String key = PkRedisKeyConstants.upcomingKey(DEFAULT_TENANT);
|
||||
long score = pk.getPkBeginTime().toInstant().getEpochSecond();
|
||||
stringRedisTemplate.opsForZSet().add(key, pkId, score);
|
||||
|
||||
mockMvc.perform(get("/wx/pk/upcoming")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.UPCOMING.getValue()))
|
||||
.andExpect(jsonPath("$.data.id").value(pkId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void upcomingShouldReturnInactiveWhenPkMissing() throws Exception {
|
||||
String key = PkRedisKeyConstants.upcomingKey(DEFAULT_TENANT);
|
||||
String pkId = newPkId();
|
||||
stringRedisTemplate.opsForZSet().add(key, pkId, ZSET_SCORE_PAST);
|
||||
|
||||
mockMvc.perform(get("/wx/pk/upcoming")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.INACTIVE.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailShouldReturnInactiveWhenNotFound() throws Exception {
|
||||
mockMvc.perform(get("/wx/pk/detail")
|
||||
.param(PARAM_ID, newPkId())
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.INACTIVE.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailShouldReturnActiveWhenInProgress() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String pkId = newPkId();
|
||||
PlayClerkPkEntity pk = buildPk(pkId, newClerkId(), newClerkId(),
|
||||
now.minusMinutes(MINUTES_BEFORE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.IN_PROGRESS.name());
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(pk);
|
||||
|
||||
MvcResult result = mockMvc.perform(get("/wx/pk/detail")
|
||||
.param(PARAM_ID, pkId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.ACTIVE.getValue()))
|
||||
.andExpect(jsonPath("$.data.id").value(pkId))
|
||||
.andReturn();
|
||||
|
||||
JsonNode data = extractData(result);
|
||||
assertThat(data.get("remainingSeconds").asLong()).isGreaterThanOrEqualTo(REMAINING_SECONDS_MIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailShouldReturnUpcomingWhenToBeStarted() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String pkId = newPkId();
|
||||
PlayClerkPkEntity pk = buildPk(pkId, newClerkId(), newClerkId(),
|
||||
now.plusMinutes(MINUTES_FUTURE_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name());
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(pk);
|
||||
|
||||
mockMvc.perform(get("/wx/pk/detail")
|
||||
.param(PARAM_ID, pkId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.state").value(PkWxState.UPCOMING.getValue()))
|
||||
.andExpect(jsonPath("$.data.id").value(pkId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailShouldReturnContributorsAndHistory() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String pkId = newPkId();
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
PlayClerkPkEntity pk = buildPk(pkId, clerkAId, clerkBId,
|
||||
now.minusMinutes(MINUTES_AFTER_START),
|
||||
now.plusMinutes(MINUTES_AFTER_START),
|
||||
ClerkPkEnum.IN_PROGRESS.name());
|
||||
clerkPkService.save(pk);
|
||||
|
||||
String clerkAName = CLERK_NAME_PREFIX + clerkAId;
|
||||
String clerkBName = CLERK_NAME_PREFIX + clerkBId;
|
||||
clerkUserInfoService.save(buildClerk(clerkAId, clerkAName));
|
||||
clerkUserInfoService.save(buildClerk(clerkBId, clerkBName));
|
||||
|
||||
String customAId = newCustomId();
|
||||
String customBId = newCustomId();
|
||||
customUserInfoService.save(buildCustomUser(customAId, CUSTOM_NAME_PREFIX + "A"));
|
||||
customUserInfoService.save(buildCustomUser(customBId, CUSTOM_NAME_PREFIX + "B"));
|
||||
|
||||
orderInfoService.save(buildOrder(customAId, clerkAId, AMOUNT_MEDIUM, now.minusMinutes(MINUTES_ORDER_OFFSET)));
|
||||
orderInfoService.save(buildOrder(customBId, clerkAId, AMOUNT_LOW, now.minusMinutes(MINUTES_ORDER_OFFSET)));
|
||||
orderInfoService.save(buildOrder(customBId, clerkBId, AMOUNT_HIGH, now.minusMinutes(MINUTES_ORDER_OFFSET)));
|
||||
|
||||
clerkPkService.save(buildFinishedPk(newPkId(), clerkAId, clerkBId,
|
||||
now.minusDays(HISTORY_FIRST_DAY_OFFSET), AMOUNT_LOW, AMOUNT_HIGH));
|
||||
clerkPkService.save(buildFinishedPk(newPkId(), clerkBId, clerkAId,
|
||||
now.minusDays(HISTORY_SECOND_DAY_OFFSET), AMOUNT_HIGH, AMOUNT_LOW));
|
||||
|
||||
mockMvc.perform(get("/wx/pk/detail")
|
||||
.param(PARAM_ID, pkId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.contributors[0].userId").value(customBId))
|
||||
.andExpect(jsonPath("$.data.contributors[0].amount").value(AMOUNT_HIGH.add(AMOUNT_LOW).doubleValue()))
|
||||
.andExpect(jsonPath("$.data.history.length()").value(EXPECTED_HISTORY_COUNT))
|
||||
.andExpect(jsonPath("$.data.history[0].clerkAName").value(clerkBName));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkHistoryShouldReturnSummaryAndTopContributor() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkAId = newClerkId();
|
||||
String clerkBId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
PlayClerkPkEntity newerPk = buildFinishedPk(newPkId(), clerkAId, clerkBId,
|
||||
now.minusDays(HISTORY_SECOND_DAY_OFFSET), AMOUNT_HIGH, AMOUNT_LOW);
|
||||
newerPk.setWinnerClerkId(clerkAId);
|
||||
clerkPkService.save(newerPk);
|
||||
|
||||
PlayClerkPkEntity olderPk = buildFinishedPk(newPkId(), clerkAId, clerkBId,
|
||||
now.minusDays(HISTORY_FIRST_DAY_OFFSET), AMOUNT_LOW, AMOUNT_HIGH);
|
||||
olderPk.setWinnerClerkId(clerkBId);
|
||||
clerkPkService.save(olderPk);
|
||||
|
||||
String customHighId = newCustomId();
|
||||
String customLowId = newCustomId();
|
||||
customUserInfoService.save(buildCustomUser(customHighId, CUSTOM_NAME_PREFIX + "高"));
|
||||
customUserInfoService.save(buildCustomUser(customLowId, CUSTOM_NAME_PREFIX + "低"));
|
||||
|
||||
orderInfoService.save(buildOrder(customLowId, clerkAId, AMOUNT_LOW, now.minusDays(HISTORY_SECOND_DAY_OFFSET)
|
||||
.plusMinutes(MINUTES_ORDER_OFFSET)));
|
||||
orderInfoService.save(buildOrder(customHighId, clerkBId, AMOUNT_HIGH, now.minusDays(HISTORY_SECOND_DAY_OFFSET)
|
||||
.plusMinutes(MINUTES_ORDER_OFFSET)));
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/history")
|
||||
.param(PARAM_CLERK_ID, clerkAId)
|
||||
.param(PARAM_PAGE_NUM, String.valueOf(HISTORY_PAGE_NUM))
|
||||
.param(PARAM_PAGE_SIZE, String.valueOf(HISTORY_PAGE_SIZE))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.items.length()").value(EXPECTED_HISTORY_COUNT))
|
||||
.andExpect(jsonPath("$.data.summary.totalCount").value(EXPECTED_HISTORY_COUNT))
|
||||
.andExpect(jsonPath("$.data.summary.winCount").value(1))
|
||||
.andExpect(jsonPath("$.data.items[0].topContributorName").value(CUSTOM_NAME_PREFIX + "高"))
|
||||
.andExpect(jsonPath("$.data.items[0].winnerClerkId").value(clerkAId))
|
||||
.andExpect(jsonPath("$.data.items[0].id").isNotEmpty())
|
||||
.andExpect(jsonPath("$.data.items[0].clerkAId").value(clerkAId))
|
||||
.andExpect(jsonPath("$.data.items[0].clerkBId").value(clerkBId))
|
||||
.andExpect(jsonPath("$.data.items[0].topContributorAmount").value(AMOUNT_HIGH.doubleValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldRejectWhenClerkIdMissing() throws Exception {
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(ResultCodeEnum.FAILED.getCode()))
|
||||
.andExpect(jsonPath("$.message").value(MSG_PARAM_INVALID));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldReturnEmptyWhenNoFuturePk() throws Exception {
|
||||
String clerkId = newClerkId();
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(SCHEDULE_LIMIT))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldReturnUpcomingOrdered() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
PlayClerkPkEntity first = buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name());
|
||||
PlayClerkPkEntity second = buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name());
|
||||
clerkPkService.save(second);
|
||||
clerkPkService.save(first);
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(SCHEDULE_LIMIT))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(SCHEDULE_LIMIT))
|
||||
.andExpect(jsonPath("$.data[0].id").value(first.getId()))
|
||||
.andExpect(jsonPath("$.data[1].id").value(second.getId()))
|
||||
.andExpect(jsonPath("$.data[0].state").value(PkWxState.UPCOMING.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldApplyLimitDefaultWhenMissing() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_THIRD_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_THIRD_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(DEFAULT_LIMIT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldClampLimitWhenZeroOrNegative() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(LIMIT_ZERO))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(1));
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(LIMIT_NEGATIVE))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldClampLimitWhenTooLarge() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(LIMIT_LARGE))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldExcludePastOrNonToBeStarted() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.minusMinutes(PAST_OFFSET_MINUTES),
|
||||
now.minusMinutes(PAST_OFFSET_MINUTES - MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.IN_PROGRESS.name()));
|
||||
PlayClerkPkEntity future = buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name());
|
||||
clerkPkService.save(future);
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(SCHEDULE_LIMIT))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(1))
|
||||
.andExpect(jsonPath("$.data[0].id").value(future.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldIsolateTenants() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
clerkPkService.save(buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
|
||||
String otherTenant = "tenant-other";
|
||||
PlayClerkPkEntity other = buildPk(newPkId(), clerkId, newClerkId(),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_SECOND_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name());
|
||||
other.setTenantId(otherTenant);
|
||||
clerkPkService.save(other);
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(SCHEDULE_LIMIT))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkScheduleShouldIgnoreNonParticipant() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String clerkId = newClerkId();
|
||||
SecurityUtils.setTenantId(DEFAULT_TENANT);
|
||||
|
||||
clerkPkService.save(buildPk(newPkId(), newClerkId(), newClerkId(),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES),
|
||||
now.plusMinutes(FUTURE_FIRST_OFFSET_MINUTES + MINUTES_AFTER_START),
|
||||
ClerkPkEnum.TO_BE_STARTED.name()));
|
||||
|
||||
mockMvc.perform(get("/wx/pk/clerk/schedule")
|
||||
.param(PARAM_CLERK_ID, clerkId)
|
||||
.param(PARAM_LIMIT, String.valueOf(SCHEDULE_LIMIT))
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(0));
|
||||
}
|
||||
|
||||
private static PlayClerkPkEntity buildPk(String pkId, String clerkAId, String clerkBId,
|
||||
LocalDateTime begin, LocalDateTime end, String status) {
|
||||
PlayClerkPkEntity pk = new PlayClerkPkEntity();
|
||||
pk.setId(pkId);
|
||||
pk.setTenantId(DEFAULT_TENANT);
|
||||
pk.setClerkA(clerkAId);
|
||||
pk.setClerkB(clerkBId);
|
||||
pk.setPkBeginTime(Date.from(begin.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setPkEndTime(Date.from(end.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setStatus(status);
|
||||
pk.setSettled(SETTLED_FALSE);
|
||||
return pk;
|
||||
}
|
||||
|
||||
private static PlayClerkPkEntity buildFinishedPk(String pkId, String clerkAId, String clerkBId,
|
||||
LocalDateTime begin, BigDecimal scoreA, BigDecimal scoreB) {
|
||||
PlayClerkPkEntity pk = new PlayClerkPkEntity();
|
||||
pk.setId(pkId);
|
||||
pk.setTenantId(DEFAULT_TENANT);
|
||||
pk.setClerkA(clerkAId);
|
||||
pk.setClerkB(clerkBId);
|
||||
pk.setPkBeginTime(Date.from(begin.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setPkEndTime(Date.from(begin.plusMinutes(MINUTES_AFTER_START).atZone(ZoneId.systemDefault()).toInstant()));
|
||||
pk.setStatus(ClerkPkEnum.FINISHED.name());
|
||||
pk.setClerkAScore(scoreA);
|
||||
pk.setClerkBScore(scoreB);
|
||||
pk.setSettled(SETTLED_TRUE);
|
||||
return pk;
|
||||
}
|
||||
|
||||
private static PlayClerkUserInfoEntity buildClerk(String id, String nickname) {
|
||||
PlayClerkUserInfoEntity clerk = new PlayClerkUserInfoEntity();
|
||||
clerk.setId(id);
|
||||
clerk.setTenantId(DEFAULT_TENANT);
|
||||
clerk.setOpenid(OPENID_PREFIX + id);
|
||||
clerk.setNickname(nickname);
|
||||
clerk.setDeleted(false);
|
||||
return clerk;
|
||||
}
|
||||
|
||||
private static PlayCustomUserInfoEntity buildCustomUser(String id, String nickname) {
|
||||
PlayCustomUserInfoEntity custom = new PlayCustomUserInfoEntity();
|
||||
custom.setId(id);
|
||||
custom.setTenantId(DEFAULT_TENANT);
|
||||
custom.setNickname(nickname);
|
||||
custom.setDeleted(false);
|
||||
return custom;
|
||||
}
|
||||
|
||||
private static PlayOrderInfoEntity buildOrder(String customerId, String clerkId, BigDecimal amount,
|
||||
LocalDateTime endTime) {
|
||||
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
|
||||
order.setId(IdUtils.getUuid());
|
||||
order.setTenantId(DEFAULT_TENANT);
|
||||
order.setOrderStatus(OrderConstant.OrderStatus.COMPLETED.getCode());
|
||||
order.setOrderType(OrderConstant.OrderType.NORMAL.getCode());
|
||||
order.setPlaceType(OrderConstant.PlaceType.SPECIFIED.getCode());
|
||||
order.setPurchaserBy(customerId);
|
||||
order.setAcceptBy(clerkId);
|
||||
order.setFinalAmount(amount);
|
||||
order.setOrderEndTime(endTime);
|
||||
order.setDeleted(false);
|
||||
return order;
|
||||
}
|
||||
|
||||
private static String newClerkId() {
|
||||
return CLERK_PREFIX + IdUtils.getUuid();
|
||||
}
|
||||
|
||||
private static String newCustomId() {
|
||||
return CUSTOM_PREFIX + IdUtils.getUuid();
|
||||
}
|
||||
|
||||
private static String newPkId() {
|
||||
return PK_PREFIX + IdUtils.getUuid();
|
||||
}
|
||||
|
||||
private static JsonNode extractData(MvcResult result) throws Exception {
|
||||
JsonNode root = OBJECT_MAPPER.readTree(result.getResponse().getContentAsString());
|
||||
return root.get("data");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user