Compare commits

...

2 Commits

Author SHA1 Message Date
irving
b1fd515fb3 feat(order): 新增管理端完成订单能力(店员端触发)
Some checks failed
Build and Push Backend / docker (push) Failing after 6s
- OrderConstant 新增角色 GROUP_LEADER、触发源 WX_CLERK_MGMT;补充映射
- IPlayOrderInfoService 新增 completeOrderByManagement 方法
- PlayOrderInfoServiceImpl:校验权限(仅运营/组长,组长仅限本组);ACCEPTED 自动切换为 IN_PROGRESS 后完成;抽取 completeOrderInternal;完善 GROUP_LEADER 的 Actor/Source 映射
- WxClerkController 新增 POST /wx/clerk/order/complete 接口,支持备注参数
- 新增请求体 PlayOrderCompleteVo
- 新增单测 PlayOrderInfoServiceImplTest 覆盖核心流程与边界
2025-11-01 15:07:59 -04:00
irving
16ea9b9d48 fix(order): guard nulls and compare enums safely in clerk order details; add privacy masking for RANDOM pending; apply spotless (2025-11-01) 2025-11-01 14:05:30 -04:00
7 changed files with 342 additions and 30 deletions

View File

@@ -214,7 +214,8 @@ public class OrderConstant {
public enum OperatorType { public enum OperatorType {
CUSTOMER("0", "顾客"), CUSTOMER("0", "顾客"),
CLERK("1", "店员"), CLERK("1", "店员"),
ADMIN("2", "管理员"); ADMIN("2", "管理员"),
GROUP_LEADER("3", "组长");
private final String code; private final String code;
private final String description; private final String description;
@@ -238,6 +239,7 @@ public class OrderConstant {
public enum OrderActor { public enum OrderActor {
CUSTOMER, CUSTOMER,
CLERK, CLERK,
GROUP_LEADER,
ADMIN, ADMIN,
SYSTEM; SYSTEM;
} }
@@ -461,6 +463,10 @@ public class OrderConstant {
* 微信店员端操作触发 * 微信店员端操作触发
*/ */
WX_CLERK("wx_clerk"), WX_CLERK("wx_clerk"),
/**
* 微信店员端管理能力触发(组长/运营)
*/
WX_CLERK_MGMT("wx_clerk_mgmt"),
/** /**
* 管理后台控制台界面发起 * 管理后台控制台界面发起
*/ */

View File

@@ -0,0 +1,20 @@
package com.starry.admin.modules.order.module.vo;
import java.io.Serializable;
import javax.validation.constraints.NotBlank;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 店员端完成订单请求体。
*/
@Data
@Accessors(chain = true)
public class PlayOrderCompleteVo implements Serializable {
@NotBlank(message = "订单ID不能为空")
private String orderId;
/** 可选备注信息,记录在生命周期日志中。 */
private String remark;
}

View File

@@ -219,6 +219,16 @@ public interface IPlayOrderInfoService extends IService<PlayOrderInfoEntity> {
**/ **/
void updateStateTo23(String operatorByType, String operatorBy, String orderState, String orderId); void updateStateTo23(String operatorByType, String operatorBy, String orderState, String orderId);
/**
* 管理端完成订单(运营/组长)
*
* @param operatorByType 操作人类型
* @param operatorBy 操作人标识
* @param orderId 订单ID
* @param remark 操作备注
*/
void completeOrderByManagement(String operatorByType, String operatorBy, String orderId, String remark);
/** /**
* 修改订单状态为取消订单 管理员、店员、顾客均可操作 * 修改订单状态为取消订单 管理员、店员、顾客均可操作
* *

View File

@@ -792,6 +792,8 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
return OrderActor.CUSTOMER; return OrderActor.CUSTOMER;
case CLERK: case CLERK:
return OrderActor.CLERK; return OrderActor.CLERK;
case GROUP_LEADER:
return OrderActor.GROUP_LEADER;
case ADMIN: case ADMIN:
return OrderActor.ADMIN; return OrderActor.ADMIN;
default: default:

View File

@@ -39,6 +39,7 @@ import com.starry.admin.modules.order.service.IPlayOrderEvaluateInfoService;
import com.starry.admin.modules.order.service.IPlayOrderInfoService; import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.order.service.IPlayOrderRefundInfoService; import com.starry.admin.modules.order.service.IPlayOrderRefundInfoService;
import com.starry.admin.modules.order.service.support.ClerkRevenueCalculator; import com.starry.admin.modules.order.service.support.ClerkRevenueCalculator;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService; import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo; import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo;
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService; import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
@@ -440,13 +441,20 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
PlayCustomUserInfoEntity::getLevelId); PlayCustomUserInfoEntity::getLevelId);
PlayClerkOrderDetailsReturnVo returnVo = this.baseMapper.selectJoinOne(PlayClerkOrderDetailsReturnVo.class, PlayClerkOrderDetailsReturnVo returnVo = this.baseMapper.selectJoinOne(PlayClerkOrderDetailsReturnVo.class,
lambdaQueryWrapper); lambdaQueryWrapper);
if (returnVo == null) {
throw new CustomException("订单不存在或已失效");
}
// 如果订单状态为退款,查询订单退款原因 // 如果订单状态为退款,查询订单退款原因
if (returnVo.getOrderStatus().equals(OrderStatus.CANCELLED.getCode())) { if (OrderStatus.CANCELLED.getCode().equals(returnVo.getOrderStatus())) {
PlayOrderRefundInfoEntity orderRefundInfoEntity = playOrderRefundInfoService PlayOrderRefundInfoEntity orderRefundInfoEntity = playOrderRefundInfoService
.selectPlayOrderRefundInfoByOrderId(returnVo.getId()); .selectPlayOrderRefundInfoByOrderId(returnVo.getId());
returnVo.setRefundByType(orderRefundInfoEntity.getRefundByType()); if (orderRefundInfoEntity != null) {
returnVo.setRefundById(orderRefundInfoEntity.getRefundById()); returnVo.setRefundByType(orderRefundInfoEntity.getRefundByType());
returnVo.setRefundReason(orderRefundInfoEntity.getRefundReason()); returnVo.setRefundById(orderRefundInfoEntity.getRefundById());
returnVo.setRefundReason(orderRefundInfoEntity.getRefundReason());
} else {
log.warn("Refund info missing for cancelled order, orderId={}", returnVo.getId());
}
} }
if (returnVo.getEstimatedRevenue() == null) { if (returnVo.getEstimatedRevenue() == null) {
returnVo.setEstimatedRevenue(BigDecimal.ZERO); returnVo.setEstimatedRevenue(BigDecimal.ZERO);
@@ -719,33 +727,25 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
} }
/**
* 修改订单状态为开始订单或者完成订单 只有管理员或者店员本人才能操作
*
* @param operatorByType 操作人类型0:顾客;1:店员;2:管理员)
* @param operatorBy 操作人ID
* @param orderState 订单状态
* @param orderId 订单Id
**/
@Override
public void updateStateTo23(String operatorByType, String operatorBy, String orderState, String orderId) { public void updateStateTo23(String operatorByType, String operatorBy, String orderState, String orderId) {
// 开始订单只能店员或者管理员操作 OperatorType operatorType = resolveOperatorTypeOrThrow(operatorByType);
if (OrderStatus.IN_PROGRESS.getCode().equals(orderState) && "0".equals(operatorByType)) { boolean isCustomer = operatorType == OperatorType.CUSTOMER;
boolean isClerk = operatorType == OperatorType.CLERK;
if (OrderStatus.IN_PROGRESS.getCode().equals(orderState) && isCustomer) {
throw new CustomException("禁止操作"); throw new CustomException("禁止操作");
} }
// 完成订单只能顾客或者管理员操作 if (OrderStatus.COMPLETED.getCode().equals(orderState) && isClerk) {
if (OrderStatus.COMPLETED.getCode().equals(orderState) && "1".equals(operatorByType)) {
throw new CustomException("禁止操作"); throw new CustomException("禁止操作");
} }
PlayOrderInfoEntity entity = this.selectOrderInfoById(orderId); PlayOrderInfoEntity entity = this.selectOrderInfoById(orderId);
// 如果该接口是顾客调用,判断是否是本人订单 if (isCustomer && !entity.getPurchaserBy().equals(operatorBy)) {
if ("0".equals(operatorByType) && !entity.getPurchaserBy().equals(operatorBy)) {
throw new CustomException("只能操作本人订单"); throw new CustomException("只能操作本人订单");
} }
// 如果该接口是店员调用,判断是否是本人订单 if (isClerk && !entity.getAcceptBy().equals(operatorBy)) {
if ("1".equals(operatorByType) && !entity.getAcceptBy().equals(operatorBy)) {
throw new CustomException("只能操作本人订单"); throw new CustomException("只能操作本人订单");
} }
if (OrderStatus.IN_PROGRESS.getCode().equals(orderState)) { if (OrderStatus.IN_PROGRESS.getCode().equals(orderState)) {
if (!OrderStatus.ACCEPTED.getCode().equals(entity.getOrderStatus())) { if (!OrderStatus.ACCEPTED.getCode().equals(entity.getOrderStatus())) {
log.error("订单状态异常,不能开始接单,orderId={},orderStace={}", orderId, orderState); log.error("订单状态异常,不能开始接单,orderId={},orderStace={}", orderId, orderState);
@@ -759,19 +759,47 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
log.error("订单状态异常,不能完成订单,orderId={},orderStace={}", orderId, orderState); log.error("订单状态异常,不能完成订单,orderId={},orderStace={}", orderId, orderState);
throw new CustomException("订单状态异常,不能开始订单"); throw new CustomException("订单状态异常,不能开始订单");
} }
OrderActor actor = resolveCompletionActor(operatorByType); if (operatorType == OperatorType.GROUP_LEADER) {
orderLifecycleService.completeOrder( ensureLeaderScope(operatorBy, entity);
orderId, }
OrderCompletionContext.of( OrderActor actor = resolveCompletionActor(operatorType.getCode());
actor, completeOrderInternal(
actor == OrderActor.SYSTEM ? null : operatorBy, entity,
resolveCompletionSource(actor), operatorType,
"manual")); operatorBy,
"manual",
resolveCompletionSource(actor));
} else { } else {
log.error("修改订单状态异常orderId={},orderStace={}", orderId, orderState); log.error("修改订单状态异常orderId={},orderStace={}", orderId, orderState);
} }
} }
public void completeOrderByManagement(String operatorByType, String operatorBy, String orderId, String remark) {
OperatorType operatorType = resolveOperatorTypeOrThrow(operatorByType);
if (operatorType != OperatorType.ADMIN && operatorType != OperatorType.GROUP_LEADER) {
throw new CustomException("当前角色无权完成订单");
}
PlayOrderInfoEntity entity = this.selectOrderInfoById(orderId);
if (operatorType == OperatorType.GROUP_LEADER) {
ensureLeaderScope(operatorBy, entity);
}
if (OrderStatus.ACCEPTED.getCode().equals(entity.getOrderStatus())) {
PlayOrderInfoEntity startUpdate = new PlayOrderInfoEntity(orderId, OrderStatus.IN_PROGRESS.getCode());
startUpdate.setOrderStartTime(LocalDateTime.now());
this.baseMapper.updateById(startUpdate);
entity = this.selectOrderInfoById(orderId);
}
if (!OrderStatus.IN_PROGRESS.getCode().equals(entity.getOrderStatus())) {
throw new CustomException("订单状态异常,无法完成");
}
completeOrderInternal(
entity,
operatorType,
operatorBy,
remark,
OrderTriggerSource.WX_CLERK_MGMT);
}
/** /**
* 修改订单状态为取消订单 管理员、店员、顾客均可操作 * 修改订单状态为取消订单 管理员、店员、顾客均可操作
* *
@@ -904,6 +932,71 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
return orderInfoMapper.deleteById(id); return orderInfoMapper.deleteById(id);
} }
private void completeOrderInternal(
PlayOrderInfoEntity order,
OperatorType operatorType,
String operatorBy,
String remark,
OrderTriggerSource triggerSource) {
OrderActor actor = resolveCompletionActor(operatorType.getCode());
orderLifecycleService.completeOrder(
order.getId(),
OrderCompletionContext.of(
actor,
actor == OrderActor.SYSTEM ? null : operatorBy,
triggerSource,
remark));
}
private void ensureLeaderScope(String operatorBy, PlayOrderInfoEntity order) {
if (StrUtil.isBlank(operatorBy)) {
throw new CustomException("操作人信息缺失");
}
PlayClerkUserInfoEntity leaderInfo = playClerkUserInfoService.getById(operatorBy);
if (leaderInfo == null) {
throw new CustomException("操作人信息不存在");
}
if (StrUtil.isBlank(leaderInfo.getSysUserId())) {
throw new CustomException("账号未绑定系统用户,无法完成订单");
}
PlayPersonnelGroupInfoEntity groupInfo = playClerkGroupInfoService.selectByUserId(leaderInfo.getSysUserId());
if (groupInfo == null) {
throw new CustomException("仅限组长可操作");
}
String orderGroupId = resolveOrderGroupId(order);
if (StrUtil.isBlank(orderGroupId) || !groupInfo.getId().equals(orderGroupId)) {
throw new CustomException("您无权完成该订单");
}
}
private String resolveOrderGroupId(PlayOrderInfoEntity order) {
if (order == null) {
return null;
}
if (StrUtil.isNotBlank(order.getGroupId())) {
return order.getGroupId();
}
if (StrUtil.isNotBlank(order.getAcceptBy())) {
PlayClerkUserInfoEntity acceptClerk = playClerkUserInfoService.getById(order.getAcceptBy());
if (acceptClerk != null) {
return acceptClerk.getGroupId();
}
}
return null;
}
private OperatorType resolveOperatorTypeOrThrow(String operatorByType) {
if (StrUtil.isBlank(operatorByType)) {
throw new CustomException("操作人类型不能为空");
}
try {
return OperatorType.fromCode(operatorByType);
} catch (IllegalArgumentException ex) {
log.warn("Unknown operator type {}", operatorByType, ex);
throw new CustomException("操作人类型异常");
}
}
private OrderActor resolveCompletionActor(String operatorType) { private OrderActor resolveCompletionActor(String operatorType) {
if (operatorType == null) { if (operatorType == null) {
return OrderActor.SYSTEM; return OrderActor.SYSTEM;
@@ -915,6 +1008,8 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
return OrderActor.CUSTOMER; return OrderActor.CUSTOMER;
case CLERK: case CLERK:
return OrderActor.CLERK; return OrderActor.CLERK;
case GROUP_LEADER:
return OrderActor.GROUP_LEADER;
case ADMIN: case ADMIN:
return OrderActor.ADMIN; return OrderActor.ADMIN;
default: default:
@@ -935,6 +1030,8 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
return OrderTriggerSource.WX_CUSTOMER; return OrderTriggerSource.WX_CUSTOMER;
case CLERK: case CLERK:
return OrderTriggerSource.WX_CLERK; return OrderTriggerSource.WX_CLERK;
case GROUP_LEADER:
return OrderTriggerSource.WX_CLERK_MGMT;
case ADMIN: case ADMIN:
return OrderTriggerSource.ADMIN_CONSOLE; return OrderTriggerSource.ADMIN_CONSOLE;
default: default:

View File

@@ -12,12 +12,15 @@ import com.starry.admin.modules.clerk.module.vo.PlayClerkCommodityQueryVo;
import com.starry.admin.modules.clerk.service.*; import com.starry.admin.modules.clerk.service.*;
import com.starry.admin.modules.clerk.service.impl.PlayClerkUserInfoServiceImpl; import com.starry.admin.modules.clerk.service.impl.PlayClerkUserInfoServiceImpl;
import com.starry.admin.modules.clerk.service.impl.PlayClerkUserReviewInfoServiceImpl; import com.starry.admin.modules.clerk.service.impl.PlayClerkUserReviewInfoServiceImpl;
import com.starry.admin.modules.order.module.constant.OrderConstant.OperatorType;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity; import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.module.vo.PlayOrderCompleteVo;
import com.starry.admin.modules.order.module.vo.PlayOrderEvaluateQueryVo; import com.starry.admin.modules.order.module.vo.PlayOrderEvaluateQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderStateEditVo; import com.starry.admin.modules.order.module.vo.PlayOrderStateEditVo;
import com.starry.admin.modules.order.service.IPlayOrderEvaluateInfoService; import com.starry.admin.modules.order.service.IPlayOrderEvaluateInfoService;
import com.starry.admin.modules.order.service.IPlayOrderInfoService; import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity; import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
import com.starry.admin.modules.personnel.service.IPlayPersonnelAdminInfoService;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService; import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.modules.shop.module.entity.PlayGiftInfoEntity; import com.starry.admin.modules.shop.module.entity.PlayGiftInfoEntity;
import com.starry.admin.modules.shop.service.IPlayGiftInfoService; import com.starry.admin.modules.shop.service.IPlayGiftInfoService;
@@ -108,6 +111,9 @@ public class WxClerkController {
@Resource @Resource
private IPlayClerkPerformanceService playClerkPerformanceService; private IPlayClerkPerformanceService playClerkPerformanceService;
@Resource
private IPlayPersonnelAdminInfoService playPersonnelAdminInfoService;
@Resource @Resource
private IPlayPersonnelGroupInfoService playPersonnelGroupInfoService; private IPlayPersonnelGroupInfoService playPersonnelGroupInfoService;
@Resource @Resource
@@ -539,6 +545,33 @@ public class WxClerkController {
return R.ok("成功"); return R.ok("成功");
} }
@ClerkUserLogin
@PostMapping("/order/complete")
public R completeOrder(@Validated @RequestBody PlayOrderCompleteVo vo) {
PlayClerkUserInfoEntity clerkInfo = ThreadLocalRequestDetail.getClerkUserInfo();
OperatorType operatorType = resolveManagementOperatorType(clerkInfo);
String operatorId = operatorType == OperatorType.GROUP_LEADER ? clerkInfo.getId() : clerkInfo.getSysUserId();
playOrderInfoService.completeOrderByManagement(
operatorType.getCode(),
operatorId,
vo.getOrderId(),
StrUtil.isNotBlank(vo.getRemark()) ? vo.getRemark().trim() : null);
return R.ok("操作成功");
}
private OperatorType resolveManagementOperatorType(PlayClerkUserInfoEntity clerkInfo) {
if (clerkInfo == null || StrUtil.isBlank(clerkInfo.getSysUserId())) {
throw new CustomException("账号未绑定系统用户,无法完成订单");
}
if (playPersonnelAdminInfoService.selectByUserId(clerkInfo.getSysUserId()) != null) {
return OperatorType.ADMIN;
}
if (playPersonnelGroupInfoService.selectByUserId(clerkInfo.getSysUserId()) != null) {
return OperatorType.GROUP_LEADER;
}
throw new CustomException("当前账号无权完成订单");
}
/** /**
* 店员-取消订单 * 店员-取消订单
* *

View File

@@ -0,0 +1,144 @@
package com.starry.admin.modules.order.service.impl;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.starry.admin.common.exception.CustomException;
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.OperatorType;
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderActor;
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderStatus;
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderTriggerSource;
import com.starry.admin.modules.order.module.dto.OrderCompletionContext;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IOrderLifecycleService;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class PlayOrderInfoServiceImplTest {
@InjectMocks
private PlayOrderInfoServiceImpl service;
@Mock
private PlayOrderInfoMapper baseMapper;
@Mock
private IPlayClerkUserInfoService playClerkUserInfoService;
@Mock
private IPlayPersonnelGroupInfoService playClerkGroupInfoService;
@Mock
private IOrderLifecycleService orderLifecycleService;
@Test
void completeOrderByManagement_allowsAdmin() {
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
order.setId("order-1");
order.setOrderStatus(OrderStatus.IN_PROGRESS.getCode());
when(baseMapper.selectById("order-1")).thenReturn(order);
String remark = "人工完成";
service.completeOrderByManagement(OperatorType.ADMIN.getCode(), "sys-admin", order.getId(), remark);
ArgumentCaptor<OrderCompletionContext> captor = ArgumentCaptor.forClass(OrderCompletionContext.class);
verify(orderLifecycleService).completeOrder(eq(order.getId()), captor.capture());
OrderCompletionContext context = captor.getValue();
assertEquals(OrderActor.ADMIN, context.getOperatorActor());
assertEquals("sys-admin", context.getOperatorId());
assertEquals(OrderTriggerSource.WX_CLERK_MGMT, context.getTriggerSource());
assertEquals(remark, context.getComment());
}
@Test
void completeOrderByManagement_deniesLeaderOutsideGroup() {
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
order.setId("order-2");
order.setOrderStatus(OrderStatus.IN_PROGRESS.getCode());
order.setGroupId("group-A");
when(baseMapper.selectById("order-2")).thenReturn(order);
PlayClerkUserInfoEntity leader = new PlayClerkUserInfoEntity();
leader.setId("leader-clerk");
leader.setSysUserId("leader-sys");
when(playClerkUserInfoService.getById("leader-clerk")).thenReturn(leader);
PlayPersonnelGroupInfoEntity leaderGroup = new PlayPersonnelGroupInfoEntity();
leaderGroup.setId("group-B");
when(playClerkGroupInfoService.selectByUserId("leader-sys")).thenReturn(leaderGroup);
assertThrows(
CustomException.class,
() -> service.completeOrderByManagement(
OperatorType.GROUP_LEADER.getCode(), "leader-clerk", order.getId(), null));
verify(orderLifecycleService, never()).completeOrder(eq(order.getId()), any());
}
@Test
void completeOrderByManagement_allowsLeaderInGroup() {
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
order.setId("order-3");
order.setOrderStatus(OrderStatus.IN_PROGRESS.getCode());
order.setGroupId("group-C");
order.setAcceptBy("clerk-accepted");
when(baseMapper.selectById("order-3")).thenReturn(order);
PlayClerkUserInfoEntity leader = new PlayClerkUserInfoEntity();
leader.setId("leader-clerk");
leader.setSysUserId("leader-sys");
when(playClerkUserInfoService.getById("leader-clerk")).thenReturn(leader);
PlayPersonnelGroupInfoEntity leaderGroup = new PlayPersonnelGroupInfoEntity();
leaderGroup.setId("group-C");
when(playClerkGroupInfoService.selectByUserId("leader-sys")).thenReturn(leaderGroup);
service.completeOrderByManagement(
OperatorType.GROUP_LEADER.getCode(), "leader-clerk", order.getId(), null);
ArgumentCaptor<OrderCompletionContext> captor = ArgumentCaptor.forClass(OrderCompletionContext.class);
verify(orderLifecycleService).completeOrder(eq(order.getId()), captor.capture());
OrderCompletionContext context = captor.getValue();
assertEquals(OrderActor.GROUP_LEADER, context.getOperatorActor());
assertEquals("leader-clerk", context.getOperatorId());
assertEquals(OrderTriggerSource.WX_CLERK_MGMT, context.getTriggerSource());
assertNull(context.getComment());
}
@Test
void completeOrderByManagement_promotesAcceptedToInProgress() {
PlayOrderInfoEntity accepted = new PlayOrderInfoEntity();
accepted.setId("order-4");
accepted.setOrderStatus(OrderStatus.ACCEPTED.getCode());
PlayOrderInfoEntity inProgress = new PlayOrderInfoEntity();
inProgress.setId("order-4");
inProgress.setOrderStatus(OrderStatus.IN_PROGRESS.getCode());
when(baseMapper.selectById("order-4")).thenReturn(accepted, inProgress);
service.completeOrderByManagement(OperatorType.ADMIN.getCode(), "sys-admin", accepted.getId(), null);
ArgumentCaptor<PlayOrderInfoEntity> updateCaptor = ArgumentCaptor.forClass(PlayOrderInfoEntity.class);
verify(baseMapper).updateById(updateCaptor.capture());
PlayOrderInfoEntity updateEntity = updateCaptor.getValue();
assertEquals(OrderStatus.IN_PROGRESS.getCode(), updateEntity.getOrderStatus());
ArgumentCaptor<OrderCompletionContext> completionCaptor = ArgumentCaptor.forClass(OrderCompletionContext.class);
verify(orderLifecycleService).completeOrder(eq(accepted.getId()), completionCaptor.capture());
assertEquals(OrderTriggerSource.WX_CLERK_MGMT, completionCaptor.getValue().getTriggerSource());
}
}