From ec5c1782c6f62702250b59459cf1119057761f83 Mon Sep 17 00:00:00 2001 From: irving Date: Wed, 31 Dec 2025 23:02:43 -0500 Subject: [PATCH] fix: allow manager cancellation of random orders --- .../impl/PlayOrderInfoServiceImpl.java | 29 ++- .../admin/api/WxCustomRandomOrderApiTest.java | 241 ++++++++++++++++++ 2 files changed, 268 insertions(+), 2 deletions(-) diff --git a/play-admin/src/main/java/com/starry/admin/modules/order/service/impl/PlayOrderInfoServiceImpl.java b/play-admin/src/main/java/com/starry/admin/modules/order/service/impl/PlayOrderInfoServiceImpl.java index bfccdac..0a1290d 100644 --- a/play-admin/src/main/java/com/starry/admin/modules/order/service/impl/PlayOrderInfoServiceImpl.java +++ b/play-admin/src/main/java/com/starry/admin/modules/order/service/impl/PlayOrderInfoServiceImpl.java @@ -44,7 +44,9 @@ import com.starry.admin.modules.order.service.IPlayOrderInfoService; import com.starry.admin.modules.order.service.IPlayOrderRefundInfoService; import com.starry.admin.modules.order.service.support.ClerkRevenueCalculator; import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity; +import com.starry.admin.modules.personnel.module.entity.PlayPersonnelAdminInfoEntity; import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService; +import com.starry.admin.modules.personnel.service.IPlayPersonnelAdminInfoService; import com.starry.admin.modules.shop.module.constant.CouponUseState; import com.starry.admin.modules.shop.service.IPlayCouponDetailsService; import com.starry.admin.modules.weichat.entity.order.*; @@ -98,6 +100,9 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl images) { if (!"2".equals(operatorByType)) { - throw new CustomException("禁止操作"); + if (!("1".equals(operatorByType) && isClerkManagementOperator(operatorBy))) { + throw new CustomException("禁止操作"); + } } PlayOrderInfoEntity orderInfo = this.selectOrderInfoById(orderId); if (!OrderStatus.ACCEPTED.getCode().equals(orderInfo.getOrderStatus()) diff --git a/play-admin/src/test/java/com/starry/admin/api/WxCustomRandomOrderApiTest.java b/play-admin/src/test/java/com/starry/admin/api/WxCustomRandomOrderApiTest.java index 9ba865f..dcc84d9 100644 --- a/play-admin/src/test/java/com/starry/admin/api/WxCustomRandomOrderApiTest.java +++ b/play-admin/src/test/java/com/starry/admin/api/WxCustomRandomOrderApiTest.java @@ -15,8 +15,11 @@ import com.fasterxml.jackson.databind.JsonNode; import com.starry.admin.common.apitest.ApiTestDataSeeder; import com.starry.admin.common.task.OverdueOrderHandlerTask; import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity; +import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity; import com.starry.admin.modules.order.module.constant.OrderConstant; import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity; +import com.starry.admin.modules.personnel.module.entity.PlayPersonnelAdminInfoEntity; +import com.starry.admin.modules.personnel.service.IPlayPersonnelAdminInfoService; import com.starry.admin.modules.shop.module.constant.CouponUseState; import com.starry.admin.modules.shop.module.entity.PlayCouponDetailsEntity; import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo; @@ -39,6 +42,19 @@ import org.springframework.test.web.servlet.MvcResult; class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { + private static final String DEFAULT_WECHAT_CODE = "apitest-customer-wx"; + private static final String CANCEL_REASON = "API random cancel by role"; + private static final int CODE_OK = 200; + private static final int CODE_FAIL = 500; + private static final int SINGLE_QUANTITY = 1; + private static final String CLERK_STATE_ACTIVE = "1"; + private static final String CLERK_ONLINE_ENABLED = "1"; + private static final String CLERK_LISTING_ENABLED = "1"; + private static final String CLERK_DISPLAY_ENABLED = "1"; + private static final String CLERK_RANDOM_ORDER_ENABLED = "1"; + private static final String CLERK_FIXING_LEVEL = "1"; + private static final String CLERK_ONBOARDING_STATE = "1"; + @MockBean private NotificationSender notificationSender; @@ -51,6 +67,9 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { @org.springframework.beans.factory.annotation.Autowired private com.starry.admin.modules.order.service.IPlayOrderInfoService orderInfoService; + @org.springframework.beans.factory.annotation.Autowired + private IPlayPersonnelAdminInfoService playPersonnelAdminInfoService; + @Test void randomOrderFailsWhenBalanceInsufficient() throws Exception { SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); @@ -243,6 +262,228 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { } } + @Test + void randomOrderCancellationPermissions_pendingOrder() throws Exception { + SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); + String nonManagerClerkId = "clerk-apitest-nonmgr-" + IdUtils.getUuid().substring(0, 6); + String adminClerkId = "clerk-apitest-admin-" + IdUtils.getUuid().substring(0, 6); + String adminSysUserId = "apitest-admin-user-" + IdUtils.getUuid().substring(0, 6); + String adminSysUserCode = "apitest-admin-code-" + IdUtils.getUuid().substring(0, 6); + String customerToken = null; + String leaderToken = null; + String nonManagerToken = null; + String adminToken = null; + try { + resetCustomerBalance(); + customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID); + customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken); + + leaderToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID); + clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, leaderToken); + nonManagerToken = createClerk(nonManagerClerkId, null, null); + adminToken = createClerk(adminClerkId, adminSysUserId, null); + createAdminInfo(adminSysUserId, adminSysUserCode); + + String pendingOrderId = createRandomOrder("API random pending " + IdUtils.getUuid(), customerToken); + assertCancelAsClerk(pendingOrderId, nonManagerToken, CODE_FAIL); + + String leaderOrderId = createRandomOrder("API random pending leader " + IdUtils.getUuid(), customerToken); + assertCancelAsClerk(leaderOrderId, leaderToken, CODE_OK); + assertOrderStatus(leaderOrderId, OrderConstant.OrderStatus.CANCELLED.getCode()); + + String adminOrderId = createRandomOrder("API random pending admin " + IdUtils.getUuid(), customerToken); + assertCancelAsClerk(adminOrderId, adminToken, CODE_OK); + assertOrderStatus(adminOrderId, OrderConstant.OrderStatus.CANCELLED.getCode()); + } finally { + if (nonManagerClerkId != null) { + removeClerk(nonManagerClerkId); + } + if (adminClerkId != null) { + removeClerk(adminClerkId); + } + removeAdminInfo(adminSysUserId); + CustomSecurityContextHolder.remove(); + } + } + + @Test + void randomOrderCancellationPermissions_acceptedOrder() throws Exception { + SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); + String nonManagerClerkId = "clerk-apitest-nonmgr-" + IdUtils.getUuid().substring(0, 6); + String otherClerkId = "clerk-apitest-other-" + IdUtils.getUuid().substring(0, 6); + String adminClerkId = "clerk-apitest-admin-" + IdUtils.getUuid().substring(0, 6); + String adminSysUserId = "apitest-admin-user-" + IdUtils.getUuid().substring(0, 6); + String adminSysUserCode = "apitest-admin-code-" + IdUtils.getUuid().substring(0, 6); + String customerToken = null; + String leaderToken = null; + String nonManagerToken = null; + String otherToken = null; + String adminToken = null; + try { + resetCustomerBalance(); + customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID); + customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken); + + leaderToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID); + clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, leaderToken); + nonManagerToken = createClerk(nonManagerClerkId, null, null); + otherToken = createClerk(otherClerkId, null, null); + adminToken = createClerk(adminClerkId, adminSysUserId, null); + createAdminInfo(adminSysUserId, adminSysUserCode); + + String acceptedOrderId = createRandomOrder("API random accepted " + IdUtils.getUuid(), customerToken); + acceptOrder(acceptedOrderId, nonManagerToken); + + assertCancelAsClerk(acceptedOrderId, otherToken, CODE_FAIL); + assertCancelAsClerk(acceptedOrderId, nonManagerToken, CODE_OK); + assertOrderStatus(acceptedOrderId, OrderConstant.OrderStatus.CANCELLED.getCode()); + + String leaderOrderId = createRandomOrder("API random accepted leader " + IdUtils.getUuid(), customerToken); + acceptOrder(leaderOrderId, nonManagerToken); + assertCancelAsClerk(leaderOrderId, leaderToken, CODE_OK); + assertOrderStatus(leaderOrderId, OrderConstant.OrderStatus.CANCELLED.getCode()); + + String adminOrderId = createRandomOrder("API random accepted admin " + IdUtils.getUuid(), customerToken); + acceptOrder(adminOrderId, nonManagerToken); + assertCancelAsClerk(adminOrderId, adminToken, CODE_OK); + assertOrderStatus(adminOrderId, OrderConstant.OrderStatus.CANCELLED.getCode()); + } finally { + removeClerk(nonManagerClerkId); + removeClerk(otherClerkId); + removeClerk(adminClerkId); + removeAdminInfo(adminSysUserId); + CustomSecurityContextHolder.remove(); + } + } + + private String createRandomOrder(String remark, String customerToken) throws Exception { + String payload = "{" + + "\"sex\":\"" + OrderConstant.Gender.FEMALE.getCode() + "\"," + + "\"levelId\":\"" + ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID + "\"," + + "\"commodityId\":\"" + ApiTestDataSeeder.DEFAULT_COMMODITY_ID + "\"," + + "\"commodityQuantity\":" + SINGLE_QUANTITY + "," + + "\"weiChatCode\":\"" + DEFAULT_WECHAT_CODE + "\"," + + "\"excludeHistory\":\"" + OrderConstant.EXCLUDE_HISTORY_NO + "\"," + + "\"couponIds\":[]," + + "\"remark\":\"" + remark + "\"" + + "}"; + + mockMvc.perform(post("/wx/custom/order/random") + .header(USER_HEADER, DEFAULT_USER) + .header(TENANT_HEADER, DEFAULT_TENANT) + .header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken) + .contentType(MediaType.APPLICATION_JSON) + .content(payload)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(CODE_OK)); + + ensureTenantContext(); + PlayOrderInfoEntity order = playOrderInfoService.lambdaQuery() + .eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID) + .eq(PlayOrderInfoEntity::getRemark, remark) + .orderByDesc(PlayOrderInfoEntity::getCreatedTime) + .last("limit 1") + .one(); + Assertions.assertThat(order).isNotNull(); + return order.getId(); + } + + private void acceptOrder(String orderId, String clerkTokenValue) throws Exception { + mockMvc.perform(get("/wx/clerk/order/accept") + .param("id", orderId) + .header(USER_HEADER, DEFAULT_USER) + .header(TENANT_HEADER, DEFAULT_TENANT) + .header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkTokenValue)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(CODE_OK)); + } + + private void assertCancelAsClerk(String orderId, String clerkTokenValue, int expectedCode) throws Exception { + String payload = "{" + + "\"orderId\":\"" + orderId + "\"," + + "\"refundReason\":\"" + CANCEL_REASON + "\"," + + "\"images\":[]" + + "}"; + mockMvc.perform(post("/wx/clerk/order/cancellation") + .header(USER_HEADER, DEFAULT_USER) + .header(TENANT_HEADER, DEFAULT_TENANT) + .header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkTokenValue) + .contentType(MediaType.APPLICATION_JSON) + .content(payload)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(expectedCode)); + } + + private void assertOrderStatus(String orderId, String expectedStatus) { + ensureTenantContext(); + PlayOrderInfoEntity order = playOrderInfoService.selectOrderInfoById(orderId); + Assertions.assertThat(order).isNotNull(); + Assertions.assertThat(order.getOrderStatus()).isEqualTo(expectedStatus); + } + + private String createClerk(String clerkId, String sysUserId, String groupId) { + ensureTenantContext(); + PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity(); + entity.setId(clerkId); + entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); + entity.setSysUserId(sysUserId); + entity.setOpenid("openid-" + clerkId); + entity.setNickname("测试店员-" + clerkId); + entity.setGroupId(groupId); + entity.setLevelId(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID); + entity.setFixingLevel(CLERK_FIXING_LEVEL); + entity.setSex(OrderConstant.Gender.FEMALE.getCode()); + entity.setPhone("1390000" + clerkId.substring(0, 4)); + entity.setWeiChatCode("apitest-clerk-" + clerkId); + entity.setAvatar("https://example.com/avatar.png"); + entity.setAccountBalance(BigDecimal.ZERO); + entity.setOnboardingState(CLERK_ONBOARDING_STATE); + entity.setListingState(CLERK_LISTING_ENABLED); + entity.setDisplayState(CLERK_DISPLAY_ENABLED); + entity.setOnlineState(CLERK_ONLINE_ENABLED); + entity.setRandomOrderState(CLERK_RANDOM_ORDER_ENABLED); + entity.setClerkState(CLERK_STATE_ACTIVE); + entity.setEntryTime(LocalDateTime.now()); + clerkUserInfoService.save(entity); + String token = wxTokenService.createWxUserToken(clerkId); + clerkUserInfoService.updateTokenById(clerkId, token); + return token; + } + + private void removeClerk(String clerkId) { + if (clerkId == null) { + return; + } + ensureTenantContext(); + clerkUserInfoService.removeById(clerkId); + } + + private void createAdminInfo(String sysUserId, String sysUserCode) { + if (sysUserId == null) { + return; + } + ensureTenantContext(); + PlayPersonnelAdminInfoEntity entity = new PlayPersonnelAdminInfoEntity(); + entity.setId("admin-info-" + sysUserId); + entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); + entity.setSysUserId(sysUserId); + entity.setSysUserCode(sysUserCode); + entity.setAdminName("API测试管理员"); + entity.setLeaderName("API测试负责人"); + entity.setAddTime(LocalDateTime.now()); + playPersonnelAdminInfoService.save(entity); + } + + private void removeAdminInfo(String sysUserId) { + if (sysUserId == null) { + return; + } + ensureTenantContext(); + playPersonnelAdminInfoService.lambdaUpdate() + .eq(PlayPersonnelAdminInfoEntity::getSysUserId, sysUserId) + .remove(); + } + @Test // 测试用例:订单已接单后由管理员强制取消,也应释放所使用的优惠券 void randomOrderForceCancelReleasesCoupon() throws Exception {