Compare commits

...

3 Commits

Author SHA1 Message Date
irving
4fdcf6ddbd fix test
Some checks failed
Build and Push Backend / docker (push) Failing after 6s
2025-11-08 20:31:30 -05:00
irving
7d07e32271 feat: enrich withdrawal audit info 2025-11-08 20:09:07 -05:00
irving
438aef7af7 fix: ignore null level prices when updating commodity 2025-11-08 20:06:15 -05:00
5 changed files with 206 additions and 3 deletions

View File

@@ -109,9 +109,13 @@ public class PlayCommodityInfoController {
if (!jsonObject.containsKey(playClerkLevelInfoEntity.getId())) { if (!jsonObject.containsKey(playClerkLevelInfoEntity.getId())) {
throw new CustomException("请求参数错误"); throw new CustomException("请求参数错误");
} }
String rawPrice = jsonObject.getString(playClerkLevelInfoEntity.getId());
if (rawPrice == null || rawPrice.trim().isEmpty()) {
continue;
}
double price = 0.0; double price = 0.0;
try { try {
price = Double.parseDouble(jsonObject.getString(playClerkLevelInfoEntity.getId())); price = Double.parseDouble(rawPrice);
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw new CustomException("请求参数错误,价格格式为空"); throw new CustomException("请求参数错误,价格格式为空");
} }

View File

@@ -4,6 +4,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.admin.common.exception.CustomException; 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.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity; import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderInfoService; import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.withdraw.entity.EarningsBackfillLogEntity; import com.starry.admin.modules.withdraw.entity.EarningsBackfillLogEntity;
@@ -36,6 +40,7 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.Resource; import javax.annotation.Resource;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -59,6 +64,10 @@ public class AdminWithdrawalController {
private IEarningsBackfillLogService backfillLogService; private IEarningsBackfillLogService backfillLogService;
@Resource @Resource
private IPlayOrderInfoService orderInfoService; private IPlayOrderInfoService orderInfoService;
@Resource
private IPlayClerkUserInfoService clerkUserInfoService;
@Resource
private IPlayCustomUserInfoService customUserInfoService;
@ApiOperation("分页查询提现请求") @ApiOperation("分页查询提现请求")
@PostMapping("/requests/listByPage") @PostMapping("/requests/listByPage")
@@ -112,7 +121,39 @@ public class AdminWithdrawalController {
.in(PlayOrderInfoEntity::getId, orderIds) .in(PlayOrderInfoEntity::getId, orderIds)
.list() .list()
.stream() .stream()
.collect(Collectors.toMap(PlayOrderInfoEntity::getId, it -> it)); .collect(Collectors.toMap(PlayOrderInfoEntity::getId, Function.identity()));
Map<String, PlayClerkUserInfoEntity> clerkMap = Collections.emptyMap();
Map<String, PlayCustomUserInfoEntity> customerMap = Collections.emptyMap();
if (!orderMap.isEmpty()) {
List<String> clerkIds = orderMap.values().stream()
.map(PlayOrderInfoEntity::getAcceptBy)
.filter(clerkIdValue -> clerkIdValue != null && !clerkIdValue.isEmpty())
.distinct()
.collect(Collectors.toList());
if (!clerkIds.isEmpty()) {
clerkMap = clerkUserInfoService.lambdaQuery()
.eq(PlayClerkUserInfoEntity::getTenantId, tenantId)
.in(PlayClerkUserInfoEntity::getId, clerkIds)
.list()
.stream()
.collect(Collectors.toMap(PlayClerkUserInfoEntity::getId, Function.identity()));
}
List<String> customerIds = orderMap.values().stream()
.map(PlayOrderInfoEntity::getPurchaserBy)
.filter(customerIdValue -> customerIdValue != null && !customerIdValue.isEmpty())
.distinct()
.collect(Collectors.toList());
if (!customerIds.isEmpty()) {
customerMap = customUserInfoService.lambdaQuery()
.eq(PlayCustomUserInfoEntity::getTenantId, tenantId)
.in(PlayCustomUserInfoEntity::getId, customerIds)
.list()
.stream()
.collect(Collectors.toMap(PlayCustomUserInfoEntity::getId, Function.identity()));
}
}
List<ClerkEarningLineVo> vos = new ArrayList<>(lines.size()); List<ClerkEarningLineVo> vos = new ArrayList<>(lines.size());
for (EarningsLineEntity line : lines) { for (EarningsLineEntity line : lines) {
@@ -131,6 +172,22 @@ public class AdminWithdrawalController {
vo.setOrderNo(order.getOrderNo()); vo.setOrderNo(order.getOrderNo());
vo.setOrderStatus(order.getOrderStatus()); vo.setOrderStatus(order.getOrderStatus());
vo.setOrderEndTime(toLocalDateTime(order.getOrderEndTime())); vo.setOrderEndTime(toLocalDateTime(order.getOrderEndTime()));
String clerkId = order.getAcceptBy();
if (clerkId != null && !clerkId.isEmpty()) {
vo.setOrderClerkId(clerkId);
PlayClerkUserInfoEntity clerk = clerkMap.get(clerkId);
if (clerk != null) {
vo.setOrderClerkNickname(clerk.getNickname());
}
}
String customerId = order.getPurchaserBy();
if (customerId != null && !customerId.isEmpty()) {
vo.setOrderCustomerId(customerId);
PlayCustomUserInfoEntity customer = customerMap.get(customerId);
if (customer != null) {
vo.setOrderCustomerNickname(customer.getNickname());
}
}
} }
} }
vos.add(vo); vos.add(vo);

View File

@@ -27,6 +27,11 @@ public class ClerkEarningLineVo {
private String orderNo; private String orderNo;
private String orderStatus; private String orderStatus;
private String orderCustomerId;
private String orderCustomerNickname;
private String orderClerkId;
private String orderClerkNickname;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime orderEndTime; private LocalDateTime orderEndTime;

View File

@@ -8,6 +8,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.starry.admin.common.apitest.ApiTestDataSeeder; import com.starry.admin.common.apitest.ApiTestDataSeeder;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity; import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderInfoService; import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity; import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
@@ -18,6 +22,7 @@ import com.starry.admin.modules.withdraw.service.IWithdrawalService;
import com.starry.admin.utils.SecurityUtils; import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.IdUtils; import com.starry.common.utils.IdUtils;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@@ -40,6 +45,10 @@ class AdminWithdrawalControllerApiTest extends AbstractApiTest {
private IWithdrawalService withdrawalService; private IWithdrawalService withdrawalService;
@Autowired @Autowired
private IPlayOrderInfoService orderInfoService; private IPlayOrderInfoService orderInfoService;
@Autowired
private IPlayClerkUserInfoService clerkUserInfoService;
@Autowired
private IPlayCustomUserInfoService customUserInfoService;
private final ObjectMapper objectMapper = new ObjectMapper(); private final ObjectMapper objectMapper = new ObjectMapper();
private final List<String> earningsToCleanup = new ArrayList<>(); private final List<String> earningsToCleanup = new ArrayList<>();
@@ -65,6 +74,14 @@ class AdminWithdrawalControllerApiTest extends AbstractApiTest {
@Test @Test
void auditReturnsEarningLinesWithOrderDetails() throws Exception { void auditReturnsEarningLinesWithOrderDetails() throws Exception {
ensureTenantContext(); ensureTenantContext();
ensureProfileFixtures();
PlayClerkUserInfoEntity expectedClerk = clerkUserInfoService.getById(ApiTestDataSeeder.DEFAULT_CLERK_ID);
PlayCustomUserInfoEntity expectedCustomer = customUserInfoService.getById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
Assertions.assertThat(expectedClerk).as("default clerk fixture missing").isNotNull();
Assertions.assertThat(expectedCustomer).as("default customer fixture missing").isNotNull();
String expectedClerkNickname = expectedClerk.getNickname();
String expectedCustomerNickname = expectedCustomer.getNickname();
PlayOrderInfoEntity order = seedOrder(LocalDateTime.now().minusHours(2)); PlayOrderInfoEntity order = seedOrder(LocalDateTime.now().minusHours(2));
WithdrawalRequestEntity withdrawal = seedWithdrawal(ApiTestDataSeeder.DEFAULT_TENANT_ID, new BigDecimal("88.60")); WithdrawalRequestEntity withdrawal = seedWithdrawal(ApiTestDataSeeder.DEFAULT_TENANT_ID, new BigDecimal("88.60"));
@@ -83,6 +100,7 @@ class AdminWithdrawalControllerApiTest extends AbstractApiTest {
.andReturn(); .andReturn();
JsonNode data = objectMapper.readTree(result.getResponse().getContentAsString()).get("data"); JsonNode data = objectMapper.readTree(result.getResponse().getContentAsString()).get("data");
boolean foundOrder = false; boolean foundOrder = false;
boolean foundMissing = false; boolean foundMissing = false;
for (JsonNode node : data) { for (JsonNode node : data) {
@@ -90,6 +108,10 @@ class AdminWithdrawalControllerApiTest extends AbstractApiTest {
if (order.getOrderNo().equals(orderNo)) { if (order.getOrderNo().equals(orderNo)) {
Assertions.assertThat(node.path("orderStatus").asText()).isEqualTo(order.getOrderStatus()); Assertions.assertThat(node.path("orderStatus").asText()).isEqualTo(order.getOrderStatus());
Assertions.assertThat(node.path("earningType").asText()).isEqualTo(EarningsType.ORDER.name()); Assertions.assertThat(node.path("earningType").asText()).isEqualTo(EarningsType.ORDER.name());
Assertions.assertThat(node.path("orderClerkId").asText()).isEqualTo(ApiTestDataSeeder.DEFAULT_CLERK_ID);
Assertions.assertThat(normalizeUtf8(node.path("orderClerkNickname").asText())).isEqualTo(expectedClerkNickname);
Assertions.assertThat(node.path("orderCustomerId").asText()).isEqualTo(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
Assertions.assertThat(normalizeUtf8(node.path("orderCustomerNickname").asText())).isEqualTo(expectedCustomerNickname);
foundOrder = true; foundOrder = true;
} }
if (node.path("orderNo").isNull()) { if (node.path("orderNo").isNull()) {
@@ -151,7 +173,11 @@ class AdminWithdrawalControllerApiTest extends AbstractApiTest {
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data[0].orderNo").value(nullValue())) .andExpect(jsonPath("$.data[0].orderNo").value(nullValue()))
.andExpect(jsonPath("$.data[0].orderId").value(orphanOrderId)); .andExpect(jsonPath("$.data[0].orderId").value(orphanOrderId))
.andExpect(jsonPath("$.data[0].orderClerkId").value(nullValue()))
.andExpect(jsonPath("$.data[0].orderClerkNickname").value(nullValue()))
.andExpect(jsonPath("$.data[0].orderCustomerId").value(nullValue()))
.andExpect(jsonPath("$.data[0].orderCustomerNickname").value(nullValue()));
} }
@Test @Test
@@ -282,6 +308,72 @@ class AdminWithdrawalControllerApiTest extends AbstractApiTest {
return id; return id;
} }
private void ensureProfileFixtures() {
ensureTenantContext();
ensureClerkFixture();
ensureCustomerFixture();
}
private void ensureClerkFixture() {
PlayClerkUserInfoEntity clerk = clerkUserInfoService.getById(ApiTestDataSeeder.DEFAULT_CLERK_ID);
if (clerk != null) {
return;
}
PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
entity.setId(ApiTestDataSeeder.DEFAULT_CLERK_ID);
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
entity.setSysUserId(ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID);
entity.setOpenid(ApiTestDataSeeder.DEFAULT_CLERK_OPEN_ID);
entity.setNickname("小测官");
entity.setGroupId(ApiTestDataSeeder.DEFAULT_GROUP_ID);
entity.setLevelId(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID);
entity.setFixingLevel("1");
entity.setSex("2");
entity.setPhone("13900000001");
entity.setWeiChatCode("apitest-clerk");
entity.setAvatar("https://example.com/avatar.png");
entity.setAccountBalance(BigDecimal.ZERO);
entity.setOnboardingState("1");
entity.setListingState("1");
entity.setDisplayState("1");
entity.setOnlineState("1");
entity.setRandomOrderState("1");
entity.setClerkState("1");
entity.setEntryTime(LocalDateTime.now());
entity.setToken("apitest-clerk-token");
entity.setDeleted(false);
clerkUserInfoService.save(entity);
}
private void ensureCustomerFixture() {
PlayCustomUserInfoEntity customer = customUserInfoService.getById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
if (customer != null) {
return;
}
PlayCustomUserInfoEntity entity = new PlayCustomUserInfoEntity();
entity.setId(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
entity.setOpenid("openid-customer-apitest");
entity.setUnionid("unionid-customer-apitest");
entity.setNickname("测试顾客");
entity.setSex(1);
entity.setPhone("13700000002");
entity.setWeiChatCode("apitest-customer");
entity.setAccountBalance(new BigDecimal("200.00"));
entity.setAccumulatedRechargeAmount(new BigDecimal("200.00"));
entity.setAccumulatedConsumptionAmount(BigDecimal.ZERO);
entity.setAccountState("1");
entity.setSubscribeState("1");
entity.setPurchaseState("1");
entity.setMobilePhoneState("1");
Date now = new Date();
entity.setRegistrationTime(now);
entity.setLastLoginTime(now);
entity.setToken("apitest-customer-token");
entity.setDeleted(false);
customUserInfoService.save(entity);
}
private void ensureTenantContext() { private void ensureTenantContext() {
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
} }
@@ -289,4 +381,16 @@ class AdminWithdrawalControllerApiTest extends AbstractApiTest {
private Date toDate(LocalDateTime value) { private Date toDate(LocalDateTime value) {
return Date.from(value.atZone(ZoneId.systemDefault()).toInstant()); return Date.from(value.atZone(ZoneId.systemDefault()).toInstant());
} }
private String normalizeUtf8(String value) {
if (value == null) {
return null;
}
boolean convertible = value.chars().allMatch(ch -> ch <= 0xFF);
if (!convertible) {
return value;
}
byte[] bytes = value.getBytes(StandardCharsets.ISO_8859_1);
return new String(bytes, StandardCharsets.UTF_8);
}
} }

View File

@@ -221,6 +221,39 @@ class PlayCommodityInfoApiTest extends WxCustomOrderApiTestSupport {
assertThat(pricing.getPrice()).isEqualByComparingTo(new BigDecimal("188.50")); assertThat(pricing.getPrice()).isEqualByComparingTo(new BigDecimal("188.50"));
} }
@Test
// 测试用例调用价格更新接口时若某个等级价格传入null接口应忽略该列并保持原价确保支持分步维护价格。
void updateInfoSkipsNullLevelPrices() throws Exception {
ensureTenantContext();
PlayCommodityAndLevelInfoEntity before = commodityAndLevelInfoService.lambdaQuery()
.eq(PlayCommodityAndLevelInfoEntity::getCommodityId, ApiTestDataSeeder.DEFAULT_COMMODITY_ID)
.eq(PlayCommodityAndLevelInfoEntity::getLevelId, ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID)
.one();
assertThat(before).as("种子数据应具备默认等级价格").isNotNull();
ObjectNode payload = objectMapper.createObjectNode();
payload.put("id", ApiTestDataSeeder.DEFAULT_COMMODITY_ID);
payload.putNull(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID);
mockMvc.perform(post("/shop/commodity/updateInfo")
.header(USER_HEADER, DEFAULT_USER)
.header(TENANT_HEADER, DEFAULT_TENANT)
.contentType(MediaType.APPLICATION_JSON)
.content(payload.toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200));
ensureTenantContext();
PlayCommodityAndLevelInfoEntity after = commodityAndLevelInfoService.lambdaQuery()
.eq(PlayCommodityAndLevelInfoEntity::getCommodityId, ApiTestDataSeeder.DEFAULT_COMMODITY_ID)
.eq(PlayCommodityAndLevelInfoEntity::getLevelId, ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID)
.one();
assertThat(after).isNotNull();
assertThat(after.getPrice()).as("空价格不应覆盖原值").isEqualByComparingTo(before.getPrice());
}
@Test @Test
// 测试用例:使用商品修改接口把自动结算等待时长从不限时(-1调整为10分钟验证更新后查询返回新的配置。 // 测试用例:使用商品修改接口把自动结算等待时长从不限时(-1调整为10分钟验证更新后查询返回新的配置。
void updateEndpointSwitchesAutomaticSettlement() throws Exception { void updateEndpointSwitchesAutomaticSettlement() throws Exception {