feat: implement order relation type tracking

- Add OrderRelationType enum (UNASSIGNED, LEGACY, FIRST, CONTINUED, NEUTRAL)
- Create play_clerk_customer_relation table to track first-completed history
- Add order_relation_type column to play_order_info
- Migrate existing orders to set relation types based on completion history
- Update order services to determine relation type on creation
- Update order VOs and controllers to expose relation type in API responses
- Update clerk performance calculations to account for relation types
- Update revenue calculations to distinguish between first and continued orders
- Add comprehensive API and unit tests for order relation functionality
This commit is contained in:
irving
2025-12-31 22:06:05 -05:00
parent f39b560a05
commit 911a974e51
36 changed files with 5420 additions and 179 deletions

View File

@@ -0,0 +1,197 @@
package com.starry.admin.api;
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.node.ObjectNode;
import com.starry.admin.common.apitest.ApiTestDataSeeder;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.constant.Constants;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
class LegacyOrderIntegrationTest extends WxCustomOrderApiTestSupport {
private String orderIdToCleanup;
@AfterEach
void tearDown() {
if (orderIdToCleanup != null) {
ensureTenantContext();
playOrderInfoService.removeById(orderIdToCleanup);
orderIdToCleanup = null;
}
}
@Test
void getLegacyOrderDetails_Admin_ShouldReturnLegacyType() throws Exception {
ensureTenantContext();
PlayOrderInfoEntity legacyOrder = createLegacyOrder();
mockMvc.perform(get("/order/order/" + legacyOrder.getId())
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(USER_HEADER, DEFAULT_USER)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.orderRelationType").value("LEGACY"));
}
@Test
void listByPage_Admin_ShouldReturnLegacyType() throws Exception {
ensureTenantContext();
PlayOrderInfoEntity legacyOrder = createLegacyOrder();
ObjectNode payload = objectMapper.createObjectNode();
payload.put("pageNum", 1);
payload.put("pageSize", 10);
payload.put("orderNo", legacyOrder.getOrderNo());
String response = mockMvc.perform(post("/order/order/listByPage")
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
.contentType(MediaType.APPLICATION_JSON)
.content(payload.toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andReturn().getResponse().getContentAsString();
JsonNode root = objectMapper.readTree(response);
JsonNode data = root.path("data");
JsonNode records = data.isArray() ? data : data.path("records");
String type = records.get(0).path("orderRelationType").asText();
assertThat(type).isEqualTo("LEGACY");
}
private String ensureCustomerToken() {
resetCustomerBalance();
String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);
return customerToken;
}
@Test
void queryById_WxCustom_ShouldReturnLegacyType() throws Exception {
ensureTenantContext();
PlayOrderInfoEntity legacyOrder = createLegacyOrder();
String customerToken = ensureCustomerToken();
mockMvc.perform(get("/wx/custom/order/queryById")
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
.param("id", legacyOrder.getId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.orderRelationType").value("LEGACY"));
}
@Test
void queryByPage_WxCustom_ShouldReturnLegacyType() throws Exception {
ensureTenantContext();
PlayOrderInfoEntity legacyOrder = createLegacyOrder();
String customerToken = ensureCustomerToken();
ObjectNode payload = objectMapper.createObjectNode();
payload.put("pageNum", 1);
payload.put("pageSize", 20);
String response = mockMvc.perform(post("/wx/custom/order/queryByPage")
.header(TENANT_HEADER, DEFAULT_TENANT)
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
.contentType(MediaType.APPLICATION_JSON)
.content(payload.toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andReturn().getResponse().getContentAsString();
JsonNode root = objectMapper.readTree(response);
JsonNode records = root.path("data").isArray() ? root.path("data") : root.path("data").path("records");
boolean found = false;
for (JsonNode node : records) {
if (legacyOrder.getId().equals(node.path("id").asText())) {
assertThat(node.path("orderRelationType").asText()).isEqualTo("LEGACY");
found = true;
break;
}
}
assertThat(found).withFailMessage("Legacy order not found in customer list").isTrue();
}
@Test
void calculateEstimatedRevenue_ForLegacyOrder_ShouldNotFail() throws Exception {
ensureTenantContext();
PlayOrderInfoEntity legacyOrder = createLegacyOrder();
BigDecimal revenue = playOrderInfoService.getEstimatedRevenue(
legacyOrder.getAcceptBy(),
legacyOrder.getPlaceType(),
legacyOrder.getOrderRelationType(),
legacyOrder.getOrderMoney()
);
assertThat(revenue).isNotNull();
assertThat(revenue).isGreaterThanOrEqualTo(BigDecimal.ZERO);
}
@Test
void calculateEstimatedRevenue_ForUnassignedOrder_ShouldThrowException() {
ensureTenantContext();
try {
playOrderInfoService.getEstimatedRevenue(
ApiTestDataSeeder.DEFAULT_CLERK_ID,
"0", // Specified
OrderConstant.OrderRelationType.UNASSIGNED,
new BigDecimal("100.00")
);
assertThat(true).withFailMessage("Should have thrown exception for UNASSIGNED relation type").isFalse();
} catch (Exception e) {
assertThat(e).isInstanceOf(RuntimeException.class);
assertThat(e.getMessage()).contains("未分配订单不可计算预计收益");
}
}
private PlayOrderInfoEntity createLegacyOrder() {
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
String id = "legacy-" + IdUtils.getUuid();
order.setId(id);
order.setOrderNo("LEGACY" + IdUtils.getUuid().substring(0, 6));
order.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
order.setOrderStatus("3"); // Completed
order.setOrderType("2"); // Normal
order.setPlaceType("0"); // Specified
order.setOrderRelationType(OrderConstant.OrderRelationType.LEGACY);
order.setOrderMoney(new BigDecimal("100.00"));
order.setFinalAmount(new BigDecimal("100.00"));
order.setAcceptBy(ApiTestDataSeeder.DEFAULT_CLERK_ID);
order.setPurchaserBy(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
order.setPurchaserTime(LocalDateTime.now().minusDays(100));
order.setCreatedTime(new Date());
order.setUpdatedTime(new Date());
order.setDeleted(false);
order.setPayMethod("2");
order.setUseCoupon("0");
order.setBackendEntry("0");
order.setSex("2");
order.setCommodityType("1");
order.setCommodityId(ApiTestDataSeeder.DEFAULT_COMMODITY_ID);
order.setCommodityName("Legacy Service");
order.setCommodityPrice(new BigDecimal("100.00"));
order.setCommodityNumber("1");
order.setRefundType("0");
order.setRefundAmount(BigDecimal.ZERO);
playOrderInfoService.save(order);
orderIdToCleanup = id;
return order;
}
}

View File

@@ -42,6 +42,32 @@ 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");
private static final int ID_SUFFIX_LENGTH = 6;
private static final String FIRST_FLAG = OrderConstant.YesNoFlag.YES.getCode();
private static final String CONTINUED_FLAG = OrderConstant.YesNoFlag.NO.getCode();
private static final String RELATION_FIRST = OrderConstant.OrderRelationType.FIRST.getCode();
private static final String RELATION_CONTINUED = OrderConstant.OrderRelationType.CONTINUED.getCode();
private static final String RELATION_UNASSIGNED = OrderConstant.OrderRelationType.UNASSIGNED.getCode();
private static final String RELATION_NEUTRAL = OrderConstant.OrderRelationType.NEUTRAL.getCode();
private static final String RELATION_LEGACY = OrderConstant.OrderRelationType.LEGACY.getCode();
private static final String FIELD_ORDER_RELATION_TYPE = "orderRelationType";
private static final String FIELD_FIRST_ORDER = "firstOrder";
private static final String PREFIX_FIRST_RELATION = "FR";
private static final String PREFIX_CONTINUED_RELATION = "CR";
private static final String PREFIX_UNASSIGNED_RELATION = "UR";
private static final String PREFIX_NEUTRAL_RELATION = "NR";
private static final String PREFIX_RANDOM_RELATION = "RR";
private static final String PREFIX_FIRST_FILTER_YES = "FY";
private static final String PREFIX_FIRST_FILTER_NO = "FN";
private static final String PREFIX_BACKWARD_COMPAT = "BC";
private static final String TOKEN_LEGACY_CLIENT = "legacy-client";
private static final String TOKEN_LEGACY_CONTINUED = "legacy-continued";
private static final long HOURS_OFFSET_ONE = 1L;
private static final long HOURS_OFFSET_TWO = 2L;
private static final long HOURS_OFFSET_THREE = 3L;
private static final long HOURS_OFFSET_FOUR = 4L;
private static final long HOURS_OFFSET_FIVE = 5L;
private static final long MINUTES_OFFSET_TEN = 10L;
@Autowired
private IPlayOrderInfoService orderInfoService;
@@ -87,20 +113,20 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
order.setPayMethod("2");
order.setUseCoupon("1");
order.setBackendEntry("1");
order.setFirstOrder("0");
order.setOrderRelationType(OrderConstant.OrderRelationType.CONTINUED);
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 -> {
PlayOrderInfoEntity noiseOrder = 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.setOrderRelationType(OrderConstant.OrderRelationType.FIRST);
order.setGroupId(marker + "-grp");
order.setSex("1");
order.setAcceptBy(null);
@@ -149,9 +175,13 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
// firstOrder filter
ObjectNode firstOrderPayload = queryWithMarker(marker);
firstOrderPayload.put("firstOrder", matching.getFirstOrder());
firstOrderPayload.put("firstOrder", OrderConstant.YesNoFlag.NO.getCode());
assertFilterMatches(firstOrderPayload, matching.getId());
ObjectNode firstOrderYesPayload = queryWithMarker(marker);
firstOrderYesPayload.put("firstOrder", OrderConstant.YesNoFlag.YES.getCode());
assertFilterMatches(firstOrderYesPayload, noiseOrder.getId());
// groupId filter
ObjectNode groupPayload = queryWithMarker(marker);
groupPayload.put("groupId", matching.getGroupId());
@@ -192,7 +222,7 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
combinedPayload.put("payMethod", matching.getPayMethod());
combinedPayload.put("useCoupon", matching.getUseCoupon());
combinedPayload.put("backendEntry", matching.getBackendEntry());
combinedPayload.put("firstOrder", matching.getFirstOrder());
combinedPayload.put("firstOrder", OrderConstant.YesNoFlag.NO.getCode());
combinedPayload.put("groupId", matching.getGroupId());
combinedPayload.put("sex", matching.getSex());
combinedPayload.set("purchaserTime", range(
@@ -207,6 +237,191 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
assertFilterMatches(combinedPayload, matching.getId());
}
/**
* 验证首单关系在列表中映射为 firstOrder=1。
*/
@Test
void listByPage_mapsFirstOrderFlagForFirstRelationType() throws Exception {
ensureTenantContext();
String marker = (PREFIX_FIRST_RELATION + IdUtils.getUuid().replace("-", "").substring(0, ID_SUFFIX_LENGTH)).toUpperCase();
LocalDateTime reference = LocalDateTime.now().minusHours(HOURS_OFFSET_FOUR).withNano(0);
PlayOrderInfoEntity order = persistOrder(marker, "first", reference, entity -> {
entity.setOrderRelationType(OrderConstant.OrderRelationType.FIRST);
entity.setPlaceType(OrderConstant.PlaceType.SPECIFIED.getCode());
});
JsonNode record = fetchSingleRecord(queryWithMarker(marker), order.getId());
assertRelationFields(record, RELATION_FIRST, FIRST_FLAG);
}
/**
* 验证续单关系在列表中映射为 firstOrder=0。
*/
@Test
void listByPage_mapsFirstOrderFlagForContinuedRelationType() throws Exception {
ensureTenantContext();
String marker = (PREFIX_CONTINUED_RELATION + IdUtils.getUuid().replace("-", "").substring(0, ID_SUFFIX_LENGTH)).toUpperCase();
LocalDateTime reference = LocalDateTime.now().minusHours(HOURS_OFFSET_THREE).withNano(0);
PlayOrderInfoEntity order = persistOrder(marker, "continued", reference, entity -> {
entity.setOrderRelationType(OrderConstant.OrderRelationType.CONTINUED);
entity.setPlaceType(OrderConstant.PlaceType.SPECIFIED.getCode());
});
JsonNode record = fetchSingleRecord(queryWithMarker(marker), order.getId());
assertRelationFields(record, RELATION_CONTINUED, CONTINUED_FLAG);
}
/**
* 验证未分配订单在列表中映射为 firstOrder=1。
*/
@Test
void listByPage_mapsFirstOrderFlagForUnassignedRelationType() throws Exception {
ensureTenantContext();
String marker = (PREFIX_UNASSIGNED_RELATION + IdUtils.getUuid().replace("-", "").substring(0, ID_SUFFIX_LENGTH)).toUpperCase();
LocalDateTime reference = LocalDateTime.now().minusHours(HOURS_OFFSET_TWO).withNano(0);
PlayOrderInfoEntity order = persistOrder(marker, "unassigned", reference, entity -> {
entity.setOrderRelationType(OrderConstant.OrderRelationType.UNASSIGNED);
entity.setPlaceType(OrderConstant.PlaceType.SPECIFIED.getCode());
entity.setAcceptBy(null);
});
JsonNode record = fetchSingleRecord(queryWithMarker(marker), order.getId());
assertRelationFields(record, RELATION_UNASSIGNED, FIRST_FLAG);
}
/**
* 验证历史存量关系在列表中映射为首单。
*/
@Test
void listByPage_mapsFirstOrderFlagForLegacyRelationType() throws Exception {
ensureTenantContext();
String marker = (RELATION_LEGACY + IdUtils.getUuid().replace("-", "").substring(0, ID_SUFFIX_LENGTH)).toUpperCase();
LocalDateTime reference = LocalDateTime.now().minusHours(HOURS_OFFSET_TWO).withNano(0);
PlayOrderInfoEntity order = persistOrder(marker, "legacy", reference, entity -> {
entity.setOrderRelationType(OrderConstant.OrderRelationType.LEGACY);
entity.setPlaceType(OrderConstant.PlaceType.SPECIFIED.getCode());
});
JsonNode record = fetchSingleRecord(queryWithMarker(marker), order.getId());
assertRelationFields(record, RELATION_LEGACY, FIRST_FLAG);
}
/**
* 验证中性关系在列表展示中被视为首单。
*/
@Test
void listByPage_normalizesNeutralRelationTypeToFirst() throws Exception {
ensureTenantContext();
String marker = (PREFIX_NEUTRAL_RELATION + IdUtils.getUuid().replace("-", "").substring(0, ID_SUFFIX_LENGTH)).toUpperCase();
LocalDateTime reference = LocalDateTime.now().minusHours(2).withNano(0);
PlayOrderInfoEntity order = persistOrder(marker, "neutral", reference, entity -> {
entity.setOrderRelationType(OrderConstant.OrderRelationType.NEUTRAL);
entity.setPlaceType(OrderConstant.PlaceType.SPECIFIED.getCode());
});
JsonNode record = fetchSingleRecord(queryWithMarker(marker), order.getId());
assertRelationFields(record, RELATION_FIRST, FIRST_FLAG);
}
/**
* 验证随机单强制展示为首单关系。
*/
@Test
void listByPage_normalizesRandomRelationTypeToFirst() throws Exception {
ensureTenantContext();
String marker = (PREFIX_RANDOM_RELATION + IdUtils.getUuid().replace("-", "").substring(0, ID_SUFFIX_LENGTH)).toUpperCase();
LocalDateTime reference = LocalDateTime.now().minusHours(HOURS_OFFSET_ONE).withNano(0);
PlayOrderInfoEntity order = persistOrder(marker, "random", reference, entity -> {
entity.setOrderRelationType(OrderConstant.OrderRelationType.CONTINUED);
entity.setPlaceType(OrderConstant.PlaceType.RANDOM.getCode());
});
JsonNode record = fetchSingleRecord(queryWithMarker(marker), order.getId());
assertRelationFields(record, RELATION_FIRST, FIRST_FLAG);
}
/**
* 验证首单筛选仅返回首单关系订单。
*/
@Test
void listByPage_filtersFirstOrderYesMatchesFirstRelationType() throws Exception {
ensureTenantContext();
String marker = (PREFIX_FIRST_FILTER_YES + IdUtils.getUuid().replace("-", "").substring(0, ID_SUFFIX_LENGTH)).toUpperCase();
LocalDateTime reference = LocalDateTime.now().minusHours(HOURS_OFFSET_FIVE).withNano(0);
PlayOrderInfoEntity firstOrder = persistOrder(marker, "first", reference, entity -> {
entity.setOrderRelationType(OrderConstant.OrderRelationType.FIRST);
});
persistOrder(marker, "continued", reference.plusMinutes(MINUTES_OFFSET_TEN), entity -> {
entity.setOrderRelationType(OrderConstant.OrderRelationType.CONTINUED);
});
ObjectNode payload = queryWithMarker(marker);
payload.put("firstOrder", FIRST_FLAG);
assertFilterMatches(payload, firstOrder.getId());
}
/**
* 验证续单筛选仅返回续单关系订单。
*/
@Test
void listByPage_filtersFirstOrderNoMatchesContinuedRelationType() throws Exception {
ensureTenantContext();
String marker = (PREFIX_FIRST_FILTER_NO + IdUtils.getUuid().replace("-", "").substring(0, ID_SUFFIX_LENGTH)).toUpperCase();
LocalDateTime reference = LocalDateTime.now().minusHours(HOURS_OFFSET_FIVE).withNano(0);
persistOrder(marker, "first", reference, entity -> entity.setOrderRelationType(OrderConstant.OrderRelationType.FIRST));
PlayOrderInfoEntity continuedOrder = persistOrder(marker, "continued", reference.plusMinutes(MINUTES_OFFSET_TEN), entity -> {
entity.setOrderRelationType(OrderConstant.OrderRelationType.CONTINUED);
});
ObjectNode payload = queryWithMarker(marker);
payload.put("firstOrder", CONTINUED_FLAG);
assertFilterMatches(payload, continuedOrder.getId());
}
/**
* 验证未传首单筛选时,列表仍返回首单字段(向后兼容)。
*/
@Test
void listByPage_includesFirstOrderFieldWhenFilterNotProvided() throws Exception {
ensureTenantContext();
String marker = (PREFIX_BACKWARD_COMPAT + IdUtils.getUuid().replace("-", "").substring(0, ID_SUFFIX_LENGTH)).toUpperCase();
LocalDateTime reference = LocalDateTime.now().minusHours(HOURS_OFFSET_TWO).withNano(0);
PlayOrderInfoEntity order = persistOrder(marker, TOKEN_LEGACY_CLIENT, reference, entity -> {
entity.setOrderRelationType(OrderConstant.OrderRelationType.FIRST);
entity.setPlaceType(OrderConstant.PlaceType.SPECIFIED.getCode());
});
JsonNode record = fetchSingleRecord(queryWithMarker(marker), order.getId());
assertRelationFields(record, RELATION_FIRST, FIRST_FLAG);
}
/**
* 验证续单关系在旧字段中仍保持兼容输出。
*/
@Test
void listByPage_includesFirstOrderFieldForContinuedRelation() throws Exception {
ensureTenantContext();
String marker = (PREFIX_BACKWARD_COMPAT + IdUtils.getUuid().replace("-", "").substring(0, ID_SUFFIX_LENGTH)).toUpperCase();
LocalDateTime reference = LocalDateTime.now().minusHours(HOURS_OFFSET_THREE).withNano(0);
PlayOrderInfoEntity order = persistOrder(marker, TOKEN_LEGACY_CONTINUED, reference, entity -> {
entity.setOrderRelationType(OrderConstant.OrderRelationType.CONTINUED);
entity.setPlaceType(OrderConstant.PlaceType.SPECIFIED.getCode());
});
JsonNode record = fetchSingleRecord(queryWithMarker(marker), order.getId());
assertRelationFields(record, RELATION_CONTINUED, CONTINUED_FLAG);
}
@Test
void listByPage_keywordFiltersByOrderNoOrClerkName() throws Exception {
ensureTenantContext();
@@ -736,7 +951,7 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
order.setOrderType("2");
order.setPlaceType("1");
order.setRewardType("0");
order.setFirstOrder("0");
order.setOrderRelationType(OrderConstant.OrderRelationType.FIRST);
order.setRefundType("0");
order.setRefundAmount(BigDecimal.ZERO);
order.setRefundReason(null);
@@ -846,6 +1061,25 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
.isEqualTo(expectedOrderId);
}
private JsonNode fetchSingleRecord(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();
for (JsonNode record : records) {
if (expectedOrderId.equals(record.path("id").asText())) {
return record;
}
}
throw new AssertionError("未找到订单 " + expectedOrderId);
}
private void assertRelationFields(JsonNode record, String relationCode, String firstOrderFlag) {
assertThat(record.path(FIELD_ORDER_RELATION_TYPE).asText()).isEqualTo(relationCode);
assertThat(record.path(FIELD_FIRST_ORDER).asText()).isEqualTo(firstOrderFlag);
}
private RecordsResponse executeList(ObjectNode payload) throws Exception {
MvcResult result = mockMvc.perform(post("/order/order/listByPage")
.contentType(MediaType.APPLICATION_JSON)

View File

@@ -362,7 +362,7 @@ class WxCustomOrderQueryApiTest extends WxCustomOrderApiTestSupport {
order.setOrderNo("FOCUS-" + IdUtils.getUuid().substring(0, 4));
order.setUseCoupon("1");
order.setBackendEntry("1");
order.setFirstOrder("1");
order.setOrderRelationType(OrderConstant.OrderRelationType.FIRST);
order.setGroupId("group-focus");
order.setSex("1");
order.setPurchaserTime(base.plusMinutes(5));
@@ -374,7 +374,7 @@ class WxCustomOrderQueryApiTest extends WxCustomOrderApiTestSupport {
order.setPlaceType(OrderConstant.PlaceType.RANDOM.getCode());
order.setUseCoupon("0");
order.setBackendEntry("0");
order.setFirstOrder("0");
order.setOrderRelationType(OrderConstant.OrderRelationType.CONTINUED);
order.setGroupId("group-noise");
order.setSex("2");
});
@@ -384,7 +384,7 @@ class WxCustomOrderQueryApiTest extends WxCustomOrderApiTestSupport {
payload.put("orderNo", target.getOrderNo().substring(0, 6));
payload.put("useCoupon", "1");
payload.put("backendEntry", "1");
payload.put("firstOrder", "1");
payload.put("firstOrder", OrderConstant.YesNoFlag.YES.getCode());
payload.put("groupId", "group-focus");
payload.put("sex", "1");
payload.set("purchaserTime", range(target.getPurchaserTime().minusMinutes(1), target.getPurchaserTime().plusMinutes(1)));
@@ -455,7 +455,7 @@ class WxCustomOrderQueryApiTest extends WxCustomOrderApiTestSupport {
order.setOrderType(OrderConstant.OrderType.NORMAL.getCode());
order.setPlaceType(OrderConstant.PlaceType.SPECIFIED.getCode());
order.setRewardType("0");
order.setFirstOrder("0");
order.setOrderRelationType(OrderConstant.OrderRelationType.FIRST);
order.setRefundType("0");
order.setRefundAmount(BigDecimal.ZERO);
order.setOrderMoney(new BigDecimal("99.00"));

File diff suppressed because it is too large Load Diff

View File

@@ -49,7 +49,7 @@ class OrderCreationContextTest {
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.SPECIFIED)
.rewardType(OrderConstant.RewardType.BALANCE)
.isFirstOrder(true)
.orderRelationType(OrderConstant.OrderRelationType.UNASSIGNED)
.commodityInfo(commodityInfo)
.paymentInfo(paymentInfo)
.purchaserBy("customer_001")
@@ -64,8 +64,7 @@ class OrderCreationContextTest {
assertEquals(OrderConstant.OrderStatus.PENDING, request.getOrderStatus());
assertEquals(OrderConstant.OrderType.NORMAL, request.getOrderType());
assertEquals(OrderConstant.PlaceType.SPECIFIED, request.getPlaceType());
assertTrue(request.isFirstOrder());
assertEquals("1", request.getFirstOrderString());
assertEquals(OrderConstant.OrderRelationType.UNASSIGNED, request.getOrderRelationType());
// 验证商品信息
assertNotNull(request.getCommodityInfo());
@@ -88,7 +87,7 @@ class OrderCreationContextTest {
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.SPECIFIED)
.isFirstOrder(false)
.orderRelationType(OrderConstant.OrderRelationType.UNASSIGNED)
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
.purchaserBy("customer")
@@ -105,7 +104,7 @@ class OrderCreationContextTest {
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.RANDOM)
.isFirstOrder(false)
.orderRelationType(OrderConstant.OrderRelationType.FIRST)
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
.purchaserBy("customer")
@@ -127,7 +126,7 @@ class OrderCreationContextTest {
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.REWARD)
.isFirstOrder(false)
.orderRelationType(OrderConstant.OrderRelationType.UNASSIGNED)
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
.purchaserBy("customer")
@@ -139,37 +138,35 @@ class OrderCreationContextTest {
}
@Test
@DisplayName("测试首单标识转换")
void testFirstOrderStringConversion() {
// 测试首单
@DisplayName("测试订单关系类型设置")
void testRelationTypeAssignment() {
OrderCreationContext firstOrder = OrderCreationContext.builder()
.orderId("order_001")
.orderNo("ORD001")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.SPECIFIED)
.isFirstOrder(true)
.orderRelationType(OrderConstant.OrderRelationType.FIRST)
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
.purchaserBy("customer")
.build();
assertEquals("1", firstOrder.getFirstOrderString());
assertEquals(OrderConstant.OrderRelationType.FIRST, firstOrder.getOrderRelationType());
// 测试非首单
OrderCreationContext notFirstOrder = OrderCreationContext.builder()
OrderCreationContext continuedOrder = OrderCreationContext.builder()
.orderId("order_002")
.orderNo("ORD002")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.SPECIFIED)
.isFirstOrder(false)
.orderRelationType(OrderConstant.OrderRelationType.CONTINUED)
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
.purchaserBy("customer")
.build();
assertEquals("0", notFirstOrder.getFirstOrderString());
assertEquals(OrderConstant.OrderRelationType.CONTINUED, continuedOrder.getOrderRelationType());
}
@Test
@@ -182,7 +179,7 @@ class OrderCreationContextTest {
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.RANDOM)
.isFirstOrder(false)
.orderRelationType(OrderConstant.OrderRelationType.FIRST)
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
.purchaserBy("customer")
@@ -200,7 +197,7 @@ class OrderCreationContextTest {
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.RANDOM)
.isFirstOrder(false)
.orderRelationType(OrderConstant.OrderRelationType.FIRST)
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
.purchaserBy("customer")

View File

@@ -17,6 +17,7 @@ import com.starry.admin.common.exception.CustomException;
import com.starry.admin.common.exception.ServiceException;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.admin.modules.order.mapper.PlayClerkCustomerRelationMapper;
import com.starry.admin.modules.order.mapper.PlayOrderInfoMapper;
import com.starry.admin.modules.order.mapper.PlayOrderLogInfoMapper;
import com.starry.admin.modules.order.module.constant.OrderConstant;
@@ -46,6 +47,7 @@ import com.starry.admin.modules.order.module.dto.OrderRefundContext;
import com.starry.admin.modules.order.module.dto.OrderRevocationContext;
import com.starry.admin.modules.order.module.dto.PaymentInfo;
import com.starry.admin.modules.order.module.dto.RandomOrderRequirements;
import com.starry.admin.modules.order.module.entity.PlayClerkCustomerRelationEntity;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.module.event.OrderRevocationEvent;
import com.starry.admin.modules.order.module.vo.ClerkEstimatedRevenueVo;
@@ -60,6 +62,7 @@ import com.starry.admin.modules.shop.service.IPlayCouponInfoService;
import com.starry.admin.modules.weichat.service.NotificationSender;
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
import com.starry.admin.modules.withdraw.service.IEarningsService;
import com.starry.admin.utils.SecurityUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
@@ -110,6 +113,9 @@ class OrderLifecycleServiceImplTest {
@Mock
private PlayOrderLogInfoMapper orderLogInfoMapper;
@Mock
private PlayClerkCustomerRelationMapper clerkCustomerRelationMapper;
@Mock
private IPlayBalanceDetailsInfoService playBalanceDetailsInfoService;
@@ -118,6 +124,12 @@ class OrderLifecycleServiceImplTest {
@BeforeEach
void initStrategies() {
SecurityUtils.setTenantId("tenant-test");
PlayClerkCustomerRelationEntity relation = new PlayClerkCustomerRelationEntity();
relation.setHasCompleted(YesNoFlag.NO.getCode());
relation.setDeleted(Boolean.FALSE);
lenient().when(clerkCustomerRelationMapper.selectForUpdate(anyString(), anyString(), anyString()))
.thenReturn(relation);
lifecycleService.initPlacementStrategies();
}
@@ -302,7 +314,7 @@ class OrderLifecycleServiceImplTest {
.orderType(OrderType.NORMAL)
.placeType(PlaceType.OTHER)
.rewardType(RewardType.NOT_APPLICABLE)
.isFirstOrder(false)
.orderRelationType(OrderConstant.OrderRelationType.UNASSIGNED)
.commodityInfo(CommodityInfo.builder()
.commodityId("commodity")
.commodityType(CommodityType.SERVICE)
@@ -332,7 +344,7 @@ class OrderLifecycleServiceImplTest {
.orderType(OrderType.NORMAL)
.placeType(PlaceType.SPECIFIED)
.rewardType(RewardType.NOT_APPLICABLE)
.isFirstOrder(true)
.orderRelationType(OrderConstant.OrderRelationType.UNASSIGNED)
.commodityInfo(CommodityInfo.builder()
.commodityId("commodity-01")
.commodityType(CommodityType.SERVICE)
@@ -370,10 +382,10 @@ class OrderLifecycleServiceImplTest {
revenueVo.setRevenueAmount(BigDecimal.valueOf(89.5));
revenueVo.setRevenueRatio(50);
when(orderInfoMapper.selectCount(any())).thenReturn(0L);
when(orderInfoMapper.insert(any())).thenReturn(1);
when(clerkRevenueCalculator.calculateEstimatedRevenue(
anyString(), anyList(), anyString(), anyString(), any())).thenReturn(revenueVo);
anyString(), anyList(), anyString(), any(OrderConstant.OrderRelationType.class), any()))
.thenReturn(revenueVo);
doNothing().when(customUserInfoService).saveOrderInfo(any());
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), anyString());
@@ -403,10 +415,10 @@ class OrderLifecycleServiceImplTest {
request.getAcceptBy(),
request.getPaymentInfo().getCouponIds(),
request.getPlaceType().getCode(),
YesNoFlag.YES.getCode(),
OrderConstant.OrderRelationType.FIRST,
request.getPaymentInfo().getOrderMoney());
assertEquals(YesNoFlag.YES.getCode(), created.getFirstOrder());
assertEquals(OrderConstant.OrderRelationType.FIRST, created.getOrderRelationType());
assertEquals(revenueVo.getRevenueAmount(), created.getEstimatedRevenue());
assertEquals(revenueVo.getRevenueRatio(), created.getEstimatedRevenueRatio());
assertEquals(PayMethod.WECHAT.getCode(), created.getPayMethod());
@@ -1411,7 +1423,8 @@ class OrderLifecycleServiceImplTest {
revenueVo.setRevenueAmount(BigDecimal.ZERO);
revenueVo.setRevenueRatio(0);
lenient().when(clerkRevenueCalculator.calculateEstimatedRevenue(
anyString(), anyList(), anyString(), anyString(), any())).thenReturn(revenueVo);
anyString(), anyList(), anyString(), any(OrderConstant.OrderRelationType.class), any()))
.thenReturn(revenueVo);
}
private PaymentInfo payment(BigDecimal gross, BigDecimal net, BigDecimal discount, List<String> couponIds) {
@@ -1432,7 +1445,9 @@ class OrderLifecycleServiceImplTest {
.orderType(OrderType.NORMAL)
.placeType(placeType)
.rewardType(rewardType)
.isFirstOrder(true)
.orderRelationType(placeType == PlaceType.RANDOM
? OrderConstant.OrderRelationType.FIRST
: OrderConstant.OrderRelationType.UNASSIGNED)
.commodityInfo(CommodityInfo.builder()
.commodityId("commodity-" + placeType.getCode())
.commodityType(CommodityType.SERVICE)

View File

@@ -36,6 +36,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -70,6 +71,11 @@ class PlayClerkPerformanceServiceImplTest {
@InjectMocks
private PlayClerkPerformanceServiceImpl service;
@BeforeEach
void setupDefaultMocks() {
lenient().when(playOrderInfoService.list(any(Wrapper.class))).thenReturn(Collections.emptyList());
}
@Test
@DisplayName("queryOverview should aggregate metrics and sort clerks by GMV")
void queryOverviewAggregatesAndSorts() {
@@ -499,7 +505,7 @@ class PlayClerkPerformanceServiceImplTest {
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
order.setAcceptBy(clerkId);
order.setPurchaserBy(purchaser);
order.setFirstOrder(firstOrder);
order.setOrderRelationType(resolveRelationType(firstOrder, placeType));
order.setPlaceType(placeType);
order.setRefundType(refundType);
order.setFinalAmount(finalAmount);
@@ -511,6 +517,16 @@ class PlayClerkPerformanceServiceImplTest {
return order;
}
private OrderConstant.OrderRelationType resolveRelationType(String firstOrder, String placeType) {
if (OrderConstant.PlaceType.RANDOM.getCode().equals(placeType)) {
return OrderConstant.OrderRelationType.FIRST;
}
if (OrderConstant.YesNoFlag.NO.getCode().equals(firstOrder)) {
return OrderConstant.OrderRelationType.CONTINUED;
}
return OrderConstant.OrderRelationType.FIRST;
}
private void setAuthentication() {
LoginUser loginUser = new LoginUser();
UsernamePasswordAuthenticationToken authentication =