From 51ec9dd85bd6ca27a6881336ca4ee041d49340e8 Mon Sep 17 00:00:00 2001 From: irving Date: Fri, 7 Nov 2025 23:42:15 -0500 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=90=8E=E5=8F=B0=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E7=AD=9B=E9=80=89=E5=8F=8A=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/module/vo/PlayOrderInfoQueryVo.java | 6 + .../impl/PlayOrderInfoServiceImpl.java | 47 +++ .../api/PlayOrderInfoControllerApiTest.java | 323 ++++++++++++++++++ 3 files changed, 376 insertions(+) create mode 100644 play-admin/src/test/java/com/starry/admin/api/PlayOrderInfoControllerApiTest.java diff --git a/play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderInfoQueryVo.java b/play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderInfoQueryVo.java index 2df3e80..f8d4a05 100644 --- a/play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderInfoQueryVo.java +++ b/play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderInfoQueryVo.java @@ -54,6 +54,12 @@ public class PlayOrderInfoQueryVo extends BasePageEntity { @ApiModelProperty(value = "是否首单", example = "1", notes = "0:不是,1:是") private String firstOrder; + /** + * 随机单要求-店员性别(0:未知;1:男;2:女) + */ + @ApiModelProperty(value = "店员性别", example = "2", notes = "0:未知;1:男;2:女") + private String sex; + /** * 是否使用优惠券[0:未使用,1:已使用] */ 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 89d308f..1a00204 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 @@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.github.yulichang.wrapper.MPJLambdaWrapper; @@ -421,6 +422,25 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), vo.getClerkNickName()); @@ -728,6 +748,33 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl wrapper, List range, + SFunction column) { + if (CollectionUtil.isEmpty(range)) { + return; + } + String start = normalizeRangeValue(range, 0, false); + String end = normalizeRangeValue(range, 1, true); + if (StrUtil.isNotBlank(start) && StrUtil.isNotBlank(end)) { + wrapper.between(column, start, end); + } else if (StrUtil.isNotBlank(start)) { + wrapper.ge(column, start); + } else if (StrUtil.isNotBlank(end)) { + wrapper.le(column, end); + } + } + + private String normalizeRangeValue(List range, int index, boolean isEnd) { + if (range.size() <= index) { + return null; + } + String raw = range.get(index); + if (StrUtil.isBlank(raw)) { + return null; + } + return isEnd ? DateRangeUtils.normalizeEndOptional(raw) : DateRangeUtils.normalizeStartOptional(raw); + } + public void updateStateTo23(String operatorByType, String operatorBy, String orderState, String orderId) { OperatorType operatorType = resolveOperatorTypeOrThrow(operatorByType); boolean isCustomer = operatorType == OperatorType.CUSTOMER; diff --git a/play-admin/src/test/java/com/starry/admin/api/PlayOrderInfoControllerApiTest.java b/play-admin/src/test/java/com/starry/admin/api/PlayOrderInfoControllerApiTest.java new file mode 100644 index 0000000..0f7c715 --- /dev/null +++ b/play-admin/src/test/java/com/starry/admin/api/PlayOrderInfoControllerApiTest.java @@ -0,0 +1,323 @@ +package com.starry.admin.api; + +import static org.assertj.core.api.Assertions.assertThat; +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.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.starry.admin.common.apitest.ApiTestDataSeeder; +import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity; +import com.starry.admin.modules.order.service.IPlayOrderInfoService; +import com.starry.admin.utils.SecurityUtils; +import com.starry.common.context.CustomSecurityContextHolder; +import com.starry.common.utils.IdUtils; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.function.Consumer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; + +class PlayOrderInfoControllerApiTest extends AbstractApiTest { + + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final BigDecimal DEFAULT_AMOUNT = new BigDecimal("188.00"); + + @Autowired + private IPlayOrderInfoService orderInfoService; + + private final ObjectMapper objectMapper = new ObjectMapper(); + private final List orderIdsToCleanup = new ArrayList<>(); + + @AfterEach + void tearDown() { + ensureTenantContext(); + if (!orderIdsToCleanup.isEmpty()) { + orderInfoService.removeByIds(orderIdsToCleanup); + orderIdsToCleanup.clear(); + } + CustomSecurityContextHolder.remove(); + } + + @Test + void listByPage_honorsAllSupportedFilters() throws Exception { + ensureTenantContext(); + String marker = ("FT" + IdUtils.getUuid().replace("-", "").substring(0, 6)).toUpperCase(); + LocalDateTime reference = LocalDateTime.now().minusHours(6).withNano(0); + + PlayOrderInfoEntity matching = persistOrder(marker, "match", reference, order -> { + order.setOrderStatus("3"); + order.setPlaceType("2"); + order.setPayMethod("2"); + order.setUseCoupon("1"); + order.setBackendEntry("1"); + order.setFirstOrder("0"); + order.setGroupId(ApiTestDataSeeder.DEFAULT_GROUP_ID); + order.setSex("2"); + order.setAcceptBy(ApiTestDataSeeder.DEFAULT_CLERK_ID); + order.setPurchaserBy(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID); + }); + + persistOrder(marker, "noise", reference.minusDays(3), order -> { + order.setOrderStatus("0"); + order.setPlaceType("0"); + order.setPayMethod("0"); + order.setUseCoupon("0"); + order.setBackendEntry("0"); + order.setFirstOrder("1"); + order.setGroupId(marker + "-grp"); + order.setSex("1"); + order.setAcceptBy(null); + order.setPurchaserBy("customer-" + marker); + }); + + // orderNo filter (exact) + ObjectNode orderNoPayload = baseQuery(); + orderNoPayload.put("orderNo", matching.getOrderNo()); + assertFilterMatches(orderNoPayload, matching.getId()); + + // acceptBy filter + ObjectNode acceptPayload = queryWithMarker(marker); + acceptPayload.put("acceptBy", matching.getAcceptBy()); + assertFilterMatches(acceptPayload, matching.getId()); + + // purchaserBy filter + ObjectNode purchaserPayload = queryWithMarker(marker); + purchaserPayload.put("purchaserBy", matching.getPurchaserBy()); + assertFilterMatches(purchaserPayload, matching.getId()); + + // orderStatus filter + ObjectNode statusPayload = queryWithMarker(marker); + statusPayload.put("orderStatus", matching.getOrderStatus()); + assertFilterMatches(statusPayload, matching.getId()); + + // placeType filter + ObjectNode placePayload = queryWithMarker(marker); + placePayload.put("placeType", matching.getPlaceType()); + assertFilterMatches(placePayload, matching.getId()); + + // payMethod filter + ObjectNode payPayload = queryWithMarker(marker); + payPayload.put("payMethod", matching.getPayMethod()); + assertFilterMatches(payPayload, matching.getId()); + + // useCoupon filter + ObjectNode couponPayload = queryWithMarker(marker); + couponPayload.put("useCoupon", matching.getUseCoupon()); + assertFilterMatches(couponPayload, matching.getId()); + + // backendEntry filter + ObjectNode backendPayload = queryWithMarker(marker); + backendPayload.put("backendEntry", matching.getBackendEntry()); + assertFilterMatches(backendPayload, matching.getId()); + + // firstOrder filter + ObjectNode firstOrderPayload = queryWithMarker(marker); + firstOrderPayload.put("firstOrder", matching.getFirstOrder()); + assertFilterMatches(firstOrderPayload, matching.getId()); + + // groupId filter + ObjectNode groupPayload = queryWithMarker(marker); + groupPayload.put("groupId", matching.getGroupId()); + assertFilterMatches(groupPayload, matching.getId()); + + // sex filter + ObjectNode sexPayload = queryWithMarker(marker); + sexPayload.put("sex", matching.getSex()); + assertFilterMatches(sexPayload, matching.getId()); + + // purchaserTime range filter + ObjectNode purchaserTimePayload = queryWithMarker(marker); + purchaserTimePayload.set("purchaserTime", range( + reference.minusMinutes(30), + reference.plusMinutes(30))); + assertFilterMatches(purchaserTimePayload, matching.getId()); + + // acceptTime range filter + ObjectNode acceptTimePayload = queryWithMarker(marker); + acceptTimePayload.set("acceptTime", range( + matching.getAcceptTime().minusMinutes(15), + matching.getAcceptTime().plusMinutes(15))); + assertFilterMatches(acceptTimePayload, matching.getId()); + + // endOrderTime range filter + ObjectNode endTimePayload = queryWithMarker(marker); + endTimePayload.set("endOrderTime", range( + matching.getOrderEndTime().minusMinutes(15), + matching.getOrderEndTime().plusMinutes(15))); + assertFilterMatches(endTimePayload, matching.getId()); + + // Combined filters to verify logical AND behaviour + ObjectNode combinedPayload = queryWithMarker(marker); + combinedPayload.put("acceptBy", matching.getAcceptBy()); + combinedPayload.put("purchaserBy", matching.getPurchaserBy()); + combinedPayload.put("orderStatus", matching.getOrderStatus()); + combinedPayload.put("placeType", matching.getPlaceType()); + combinedPayload.put("payMethod", matching.getPayMethod()); + combinedPayload.put("useCoupon", matching.getUseCoupon()); + combinedPayload.put("backendEntry", matching.getBackendEntry()); + combinedPayload.put("firstOrder", matching.getFirstOrder()); + combinedPayload.put("groupId", matching.getGroupId()); + combinedPayload.put("sex", matching.getSex()); + combinedPayload.set("purchaserTime", range( + reference.minusMinutes(5), + reference.plusMinutes(5))); + combinedPayload.set("acceptTime", range( + matching.getAcceptTime().minusMinutes(5), + matching.getAcceptTime().plusMinutes(5))); + combinedPayload.set("endOrderTime", range( + matching.getOrderEndTime().minusMinutes(5), + matching.getOrderEndTime().plusMinutes(5))); + assertFilterMatches(combinedPayload, matching.getId()); + } + + private PlayOrderInfoEntity persistOrder( + String marker, + String token, + LocalDateTime purchaserTime, + Consumer customizer) { + PlayOrderInfoEntity order = buildBaselineOrder(marker, token, purchaserTime); + customizer.accept(order); + assertThat(orderInfoService.save(order)) + .withFailMessage("Failed to persist order %s", order.getOrderNo()) + .isTrue(); + orderIdsToCleanup.add(order.getId()); + return order; + } + + private PlayOrderInfoEntity buildBaselineOrder(String marker, String token, LocalDateTime purchaserTime) { + PlayOrderInfoEntity order = new PlayOrderInfoEntity(); + order.setId("order-" + token + "-" + IdUtils.getUuid().substring(0, 8)); + String tokenFragment = token.length() >= 2 ? token.substring(0, 2) : token; + order.setOrderNo(marker + tokenFragment.toUpperCase() + IdUtils.getUuid().substring(0, 4)); + order.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); + order.setOrderType("2"); + order.setPlaceType("1"); + order.setRewardType("0"); + order.setFirstOrder("0"); + order.setRefundType("0"); + order.setRefundAmount(BigDecimal.ZERO); + order.setRefundReason(null); + order.setOrderMoney(DEFAULT_AMOUNT); + order.setDiscountAmount(BigDecimal.ZERO); + order.setFinalAmount(DEFAULT_AMOUNT); + order.setEstimatedRevenue(new BigDecimal("88.00")); + order.setEstimatedRevenueRatio(50); + order.setLabels(Collections.singletonList("label-" + marker)); + order.setUseCoupon("1"); + order.setCouponIds(Collections.singletonList("coupon-" + marker)); + order.setBackendEntry("1"); + order.setPaymentSource("balance"); + order.setPayMethod("2"); + order.setPayState("2"); + order.setWeiChatCode("wx-" + marker); + order.setPurchaserBy(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID); + order.setPurchaserTime(purchaserTime); + order.setAcceptBy(ApiTestDataSeeder.DEFAULT_CLERK_ID); + order.setAcceptTime(purchaserTime.plusMinutes(20)); + order.setGroupId(ApiTestDataSeeder.DEFAULT_GROUP_ID); + order.setOrderStartTime(purchaserTime.plusMinutes(30)); + order.setOrderEndTime(purchaserTime.plusHours(2)); + order.setOrderSettlementState("0"); + order.setOrdersExpiredState("0"); + order.setSex("2"); + order.setCommodityId(ApiTestDataSeeder.DEFAULT_COMMODITY_ID); + order.setCommodityType("1"); + order.setCommodityPrice(DEFAULT_AMOUNT); + order.setCommodityName("API Filter Commodity"); + order.setServiceDuration("60min"); + order.setCommodityNumber("1"); + order.setLevelId(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID); + order.setExcludeHistory("0"); + order.setRemark("marker-" + marker); + order.setBackendRemark("backend-" + marker); + order.setProfitSharingAmount(BigDecimal.ZERO); + order.setDeleted(Boolean.FALSE); + order.setCreatedBy("apitest"); + order.setUpdatedBy("apitest"); + order.setCreatedTime(toDate(purchaserTime)); + order.setUpdatedTime(toDate(purchaserTime.plusMinutes(45))); + return order; + } + + private ObjectNode baseQuery() { + ObjectNode node = objectMapper.createObjectNode(); + node.put("pageNum", 1); + node.put("pageSize", 20); + return node; + } + + private ObjectNode queryWithMarker(String marker) { + ObjectNode node = baseQuery(); + node.put("orderNo", marker); + return node; + } + + private ArrayNode range(LocalDateTime start, LocalDateTime end) { + ArrayNode array = objectMapper.createArrayNode(); + array.add(DATE_TIME_FORMATTER.format(start)); + array.add(DATE_TIME_FORMATTER.format(end)); + return array; + } + + private void assertFilterMatches(ObjectNode payload, String expectedOrderId) throws Exception { + RecordsResponse response = executeList(payload); + JsonNode records = response.records; + assertThat(records.isArray()) + .withFailMessage("Records payload is not an array for body=%s | response=%s", payload, response.rawResponse) + .isTrue(); + assertThat(records.size()) + .withFailMessage("Unexpected record count for body=%s | response=%s", payload, response.rawResponse) + .isEqualTo(1); + assertThat(records.get(0).path("id").asText()) + .withFailMessage("Unexpected order id for body=%s | response=%s", payload, response.rawResponse) + .isEqualTo(expectedOrderId); + } + + private RecordsResponse executeList(ObjectNode payload) throws Exception { + MvcResult result = mockMvc.perform(post("/order/order/listByPage") + .contentType(MediaType.APPLICATION_JSON) + .header(TENANT_HEADER, DEFAULT_TENANT) + .header(USER_HEADER, DEFAULT_USER) + .content(payload.toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andReturn(); + + String responseBody = result.getResponse().getContentAsString(); + JsonNode root = objectMapper.readTree(responseBody); + JsonNode data = root.path("data"); + JsonNode records = data.isArray() ? data : data.path("records"); + return new RecordsResponse(records, responseBody); + } + + private Date toDate(LocalDateTime time) { + return Date.from(time.atZone(ZoneId.systemDefault()).toInstant()); + } + + private void ensureTenantContext() { + SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); + } + + private static class RecordsResponse { + private final JsonNode records; + private final String rawResponse; + + private RecordsResponse(JsonNode records, String rawResponse) { + this.records = records; + this.rawResponse = rawResponse; + } + } +}