|
|
@@ -0,0 +1,193 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
|
|
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
|
|
+import com.etotem.cfc.entity.Product;
|
|
|
+import com.etotem.cfc.entity.ProductOrder;
|
|
|
+import com.etotem.cfc.entity.ReferralTree;
|
|
|
+import com.etotem.cfc.mapper.ProductMapper;
|
|
|
+import com.etotem.cfc.mapper.ProductOrderMapper;
|
|
|
+import com.etotem.cfc.mapper.ReferralTreeMapper;
|
|
|
+import org.apache.ibatis.builder.MapperBuilderAssistant;
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.mockito.InjectMocks;
|
|
|
+import org.mockito.Mock;
|
|
|
+import org.mockito.MockitoAnnotations;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 团队页「关联订单 / 关联金额(元)」按商城商品-SKU 维度统计单元测试。
|
|
|
+ *
|
|
|
+ * <p>口径与 {@code getTeamConsumption} 一致:referral_tree 下级成员 + product_orders
|
|
|
+ * fulfillStatus=completed;按 (productId, skuId) 分组,订单号去重,金额分→元。</p>
|
|
|
+ */
|
|
|
+class CommissionServiceTeamProductPerformanceTest {
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private ReferralTreeMapper referralTreeMapper;
|
|
|
+ @Mock
|
|
|
+ private ProductOrderMapper productOrderMapper;
|
|
|
+ @Mock
|
|
|
+ private ProductMapper productMapper;
|
|
|
+
|
|
|
+ @InjectMocks
|
|
|
+ private CommissionService commissionService;
|
|
|
+
|
|
|
+ @BeforeEach
|
|
|
+ void setUp() {
|
|
|
+ MockitoAnnotations.openMocks(this);
|
|
|
+ TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), ReferralTree.class);
|
|
|
+ TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), ProductOrder.class);
|
|
|
+ TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), Product.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ private ReferralTree treeNode(Long memberId, int level) {
|
|
|
+ ReferralTree node = new ReferralTree();
|
|
|
+ node.setParentId(1L);
|
|
|
+ node.setMemberId(memberId);
|
|
|
+ node.setLevel(level);
|
|
|
+ return node;
|
|
|
+ }
|
|
|
+
|
|
|
+ private ProductOrder order(String orderNo, Long productId, Long skuId, Integer totalAmount, Long buyerId) {
|
|
|
+ ProductOrder o = new ProductOrder();
|
|
|
+ o.setOrderNo(orderNo);
|
|
|
+ o.setProductId(productId);
|
|
|
+ o.setSkuId(skuId);
|
|
|
+ o.setTotalAmount(totalAmount);
|
|
|
+ o.setBuyerId(buyerId);
|
|
|
+ o.setFulfillStatus("completed");
|
|
|
+ return o;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Product product(Long id, String name) {
|
|
|
+ Product p = new Product();
|
|
|
+ p.setId(id);
|
|
|
+ p.setName(name);
|
|
|
+ return p;
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private Map<String, Object> invokeWith(Long userId, Integer maxLevel, List<ReferralTree> tree,
|
|
|
+ List<ProductOrder> orders, List<Product> products) {
|
|
|
+ when(referralTreeMapper.selectList(any())).thenReturn(tree);
|
|
|
+ if (!tree.isEmpty()) {
|
|
|
+ when(productOrderMapper.selectList(any())).thenReturn(orders);
|
|
|
+ }
|
|
|
+ when(productMapper.selectBatchIds(any())).thenReturn(products);
|
|
|
+ return commissionService.getTeamProductPerformance(userId, maxLevel);
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private List<Map<String, Object>> itemsOf(Map<String, Object> result) {
|
|
|
+ Object items = result.get("items");
|
|
|
+ assertNotNull(items, "items 必须存在");
|
|
|
+ return (List<Map<String, Object>>) items;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void assertYuan(String expected, Object actual) {
|
|
|
+ assertTrue(actual instanceof BigDecimal, "金额应为 BigDecimal,实际:" + actual);
|
|
|
+ assertEquals(0, new BigDecimal(expected).compareTo((BigDecimal) actual),
|
|
|
+ "金额应为 " + expected + " 元,实际:" + actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void 按下级已完成订单按商品与SKU分组且金额换算为元() {
|
|
|
+ List<ReferralTree> tree = Arrays.asList(treeNode(101L, 1), treeNode(102L, 2));
|
|
|
+ List<ProductOrder> orders = Arrays.asList(
|
|
|
+ // 同一订单 A 含两个 SKU:各自计 1 单,团队级只算 1 单
|
|
|
+ order("A", 12L, 5L, 149500, 101L),
|
|
|
+ order("A", 12L, 6L, 50000, 101L),
|
|
|
+ order("B", 12L, 5L, 10000, 102L)
|
|
|
+ );
|
|
|
+
|
|
|
+ Map<String, Object> result = invokeWith(1L, 3, tree, orders,
|
|
|
+ Collections.singletonList(product(12L, "肠道菌群测评")));
|
|
|
+
|
|
|
+ assertEquals(2, result.get("totalOrderCount"), "团队级订单数应按 orderNo 去重(A/B 两单)");
|
|
|
+ assertYuan("2095.00", result.get("totalAmountYuan"));
|
|
|
+ assertEquals(3, result.get("maxLevel"));
|
|
|
+
|
|
|
+ List<Map<String, Object>> items = itemsOf(result);
|
|
|
+ assertEquals(2, items.size(), "应按 (productId, skuId) 分成两组");
|
|
|
+
|
|
|
+ Map<String, Object> sku5 = items.get(0);
|
|
|
+ assertEquals(12L, sku5.get("productId"));
|
|
|
+ assertEquals("肠道菌群测评", sku5.get("productName"));
|
|
|
+ assertEquals(5L, ((Number) sku5.get("skuId")).longValue());
|
|
|
+ assertEquals(2, sku5.get("orderCount"), "SKU 5 关联订单 A/B 两单");
|
|
|
+ assertYuan("1595.00", sku5.get("amountYuan"));
|
|
|
+
|
|
|
+ Map<String, Object> sku6 = items.get(1);
|
|
|
+ assertEquals(6L, ((Number) sku6.get("skuId")).longValue());
|
|
|
+ assertEquals(1, sku6.get("orderCount"));
|
|
|
+ assertYuan("500.00", sku6.get("amountYuan"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void skuId为空时归入0且商品名回退订单快照() {
|
|
|
+ List<ReferralTree> tree = Collections.singletonList(treeNode(101L, 1));
|
|
|
+ ProductOrder o = order("C", 88L, null, 9900, 101L);
|
|
|
+ o.setProductName("快照商品名");
|
|
|
+
|
|
|
+ Map<String, Object> result = invokeWith(1L, 3, tree, Collections.singletonList(o),
|
|
|
+ new ArrayList<Product>());
|
|
|
+
|
|
|
+ List<Map<String, Object>> items = itemsOf(result);
|
|
|
+ assertEquals(1, items.size());
|
|
|
+ assertEquals(0L, ((Number) items.get(0).get("skuId")).longValue(), "skuId 为 null 时应返回 0");
|
|
|
+ assertEquals("快照商品名", items.get(0).get("productName"), "商品表取不到时应回退订单行快照名");
|
|
|
+ assertYuan("99.00", items.get(0).get("amountYuan"));
|
|
|
+ assertEquals(1, result.get("totalOrderCount"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void 空团队返回空列表且总额为0() {
|
|
|
+ Map<String, Object> result = invokeWith(1L, 3, new ArrayList<ReferralTree>(),
|
|
|
+ new ArrayList<ProductOrder>(), new ArrayList<Product>());
|
|
|
+
|
|
|
+ assertTrue(itemsOf(result).isEmpty());
|
|
|
+ assertEquals(0, result.get("totalOrderCount"));
|
|
|
+ assertYuan("0.00", result.get("totalAmountYuan"));
|
|
|
+ assertEquals(3, result.get("maxLevel"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void 有下级但无已完成订单返回空列表() {
|
|
|
+ List<ReferralTree> tree = Collections.singletonList(treeNode(101L, 1));
|
|
|
+
|
|
|
+ Map<String, Object> result = invokeWith(1L, 2, tree,
|
|
|
+ new ArrayList<ProductOrder>(), new ArrayList<Product>());
|
|
|
+
|
|
|
+ assertTrue(itemsOf(result).isEmpty());
|
|
|
+ assertEquals(0, result.get("totalOrderCount"));
|
|
|
+ assertYuan("0.00", result.get("totalAmountYuan"));
|
|
|
+ assertEquals(2, result.get("maxLevel"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void maxLevel为空或超限时归一化() {
|
|
|
+ List<ReferralTree> tree = Collections.singletonList(treeNode(101L, 1));
|
|
|
+
|
|
|
+ Map<String, Object> defaulted = invokeWith(1L, null, tree,
|
|
|
+ new ArrayList<ProductOrder>(), new ArrayList<Product>());
|
|
|
+ assertEquals(3, defaulted.get("maxLevel"), "maxLevel 缺省应为 3");
|
|
|
+
|
|
|
+ Map<String, Object> capped = invokeWith(1L, 99, tree,
|
|
|
+ new ArrayList<ProductOrder>(), new ArrayList<Product>());
|
|
|
+ assertEquals(10, capped.get("maxLevel"), "maxLevel 超过 10 应截断为 10");
|
|
|
+ }
|
|
|
+}
|