feat: earnings deduction batch + test auth hardening
This commit is contained in:
@@ -0,0 +1,913 @@
|
||||
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.header;
|
||||
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.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.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
||||
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
||||
import com.starry.admin.modules.withdraw.enums.EarningsSourceType;
|
||||
import com.starry.admin.modules.withdraw.enums.EarningsType;
|
||||
import com.starry.admin.modules.withdraw.service.IEarningsAdjustmentService;
|
||||
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
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.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
/**
|
||||
* End-to-end contract tests for admin batch deductions (bonus/punishment across clerks).
|
||||
*
|
||||
* <p>These tests are expected to FAIL until the batch deduction system is implemented end-to-end.</p>
|
||||
*/
|
||||
class AdminEarningsDeductionBatchControllerApiTest extends AbstractApiTest {
|
||||
|
||||
private static final String IDEMPOTENCY_HEADER = "Idempotency-Key";
|
||||
private static final String PERMISSIONS_HEADER = "X-Test-Permissions";
|
||||
private static final String PERMISSIONS_CREATE_READ = "withdraw:deduction:create,withdraw:deduction:read";
|
||||
|
||||
private static final String BASE_URL = "/admin/earnings/deductions";
|
||||
|
||||
private static final DateTimeFormatter DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Autowired
|
||||
private IPlayOrderInfoService orderInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Autowired
|
||||
private IEarningsService earningsService;
|
||||
|
||||
@Autowired
|
||||
private IEarningsAdjustmentService adjustmentService;
|
||||
|
||||
private final List<String> ordersToCleanup = new ArrayList<>();
|
||||
private final List<String> earningsToCleanup = new ArrayList<>();
|
||||
private final List<String> batchIdsToCleanup = new ArrayList<>();
|
||||
private final List<String> clerkIdsToCleanup = new ArrayList<>();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
ordersToCleanup.clear();
|
||||
earningsToCleanup.clear();
|
||||
batchIdsToCleanup.clear();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
cleanupBatches(batchIdsToCleanup);
|
||||
if (!earningsToCleanup.isEmpty()) {
|
||||
earningsService.removeByIds(earningsToCleanup);
|
||||
}
|
||||
if (!ordersToCleanup.isEmpty()) {
|
||||
orderInfoService.removeByIds(ordersToCleanup);
|
||||
}
|
||||
if (!clerkIdsToCleanup.isEmpty()) {
|
||||
clerkUserInfoService.removeByIds(clerkIdsToCleanup);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void previewRequiresRequiredFieldsReturns400() throws Exception {
|
||||
mockMvc.perform(post(BASE_URL + "/preview")
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{}"))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
void previewPercentageUsesOnlyOrderLinesPositiveAmountsAndOrderEndTimeWindow() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now().withNano(0);
|
||||
LocalDateTime begin = now.minusDays(7);
|
||||
LocalDateTime end = now.plusSeconds(1);
|
||||
String clerkId = ensureTestClerkInDefaultTenant();
|
||||
|
||||
// In window: order1 + order2
|
||||
String order1 = seedOrder(clerkId, now.minusDays(1), new BigDecimal("100.00"));
|
||||
seedOrderEarningLine(clerkId, order1, new BigDecimal("100.00"), "withdrawn", EarningsType.ORDER);
|
||||
seedOrderEarningLine(clerkId, order1, new BigDecimal("-30.00"), "available", EarningsType.ADJUSTMENT);
|
||||
|
||||
String order2 = seedOrder(clerkId, now.minusDays(2), new BigDecimal("50.00"));
|
||||
seedOrderEarningLine(clerkId, order2, new BigDecimal("50.00"), "available", EarningsType.ORDER);
|
||||
|
||||
// Out of window: order3 should not contribute
|
||||
String order3 = seedOrder(clerkId, now.minusDays(30), new BigDecimal("999.00"));
|
||||
seedOrderEarningLine(clerkId, order3, new BigDecimal("999.00"), "withdrawn", EarningsType.ORDER);
|
||||
|
||||
// Adjustment line (order_id=null) should not contribute even if positive
|
||||
seedAdjustmentEarningLine(clerkId, new BigDecimal("1000.00"), "available");
|
||||
|
||||
String payload = "{" +
|
||||
"\"clerkIds\":[\"" + clerkId + "\"]," +
|
||||
"\"beginTime\":\"" + DATE_TIME.format(begin) + "\"," +
|
||||
"\"endTime\":\"" + DATE_TIME.format(end) + "\"," +
|
||||
"\"ruleType\":\"PERCENTAGE\"," +
|
||||
"\"percentage\":\"10.00\"," +
|
||||
"\"operation\":\"BONUS\"," +
|
||||
"\"reasonDescription\":\"week bonus\"" +
|
||||
"}";
|
||||
|
||||
MvcResult result = mockMvc.perform(post(BASE_URL + "/preview")
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.items").isArray())
|
||||
.andReturn();
|
||||
|
||||
JsonNode root = objectMapper.readTree(result.getResponse().getContentAsString());
|
||||
JsonNode item = root.path("data").path("items").get(0);
|
||||
assertThat(item.path("clerkId").asText()).isEqualTo(clerkId);
|
||||
|
||||
BigDecimal baseAmount = new BigDecimal(item.path("baseAmount").asText("0"));
|
||||
BigDecimal applyAmount = new BigDecimal(item.path("applyAmount").asText("0"));
|
||||
assertThat(baseAmount).isEqualByComparingTo("150.00"); // 100 + 50 (negative & non-order excluded)
|
||||
assertThat(applyAmount).isEqualByComparingTo("15.00"); // 10% bonus
|
||||
}
|
||||
|
||||
@Test
|
||||
void previewWindowIsInclusiveOnBoundaries() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now().withNano(0);
|
||||
LocalDateTime begin = now.minusDays(7);
|
||||
LocalDateTime end = now.minusDays(1);
|
||||
String clerkId = ensureTestClerkInDefaultTenant();
|
||||
|
||||
String orderBegin = seedOrder(clerkId, begin, new BigDecimal("20.00"));
|
||||
seedOrderEarningLine(clerkId, orderBegin, new BigDecimal("20.00"), "withdrawn", EarningsType.ORDER);
|
||||
|
||||
String orderEnd = seedOrder(clerkId, end, new BigDecimal("30.00"));
|
||||
seedOrderEarningLine(clerkId, orderEnd, new BigDecimal("30.00"), "withdrawn", EarningsType.ORDER);
|
||||
|
||||
String payload = "{" +
|
||||
"\"clerkIds\":[\"" + clerkId + "\"]," +
|
||||
"\"beginTime\":\"" + DATE_TIME.format(begin) + "\"," +
|
||||
"\"endTime\":\"" + DATE_TIME.format(end) + "\"," +
|
||||
"\"ruleType\":\"PERCENTAGE\"," +
|
||||
"\"percentage\":\"10.00\"," +
|
||||
"\"operation\":\"BONUS\"," +
|
||||
"\"reasonDescription\":\"boundary\"" +
|
||||
"}";
|
||||
|
||||
MvcResult result = mockMvc.perform(post(BASE_URL + "/preview")
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
JsonNode root = objectMapper.readTree(result.getResponse().getContentAsString());
|
||||
JsonNode item = root.path("data").path("items").get(0);
|
||||
BigDecimal baseAmount = new BigDecimal(item.path("baseAmount").asText("0"));
|
||||
assertThat(baseAmount).isEqualByComparingTo("50.00"); // 20 + 30 (both boundary-included)
|
||||
}
|
||||
|
||||
@Test
|
||||
void previewRejectsCrossTenantClerkScopeReturns403() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now().withNano(0);
|
||||
LocalDateTime begin = now.minusDays(7);
|
||||
LocalDateTime end = now.plusSeconds(1);
|
||||
|
||||
String order1 = seedOrder(ApiTestDataSeeder.DEFAULT_CLERK_ID, now.minusDays(1), new BigDecimal("100.00"));
|
||||
seedOrderEarningLine(ApiTestDataSeeder.DEFAULT_CLERK_ID, order1, new BigDecimal("100.00"), "withdrawn", EarningsType.ORDER);
|
||||
|
||||
String payload = "{" +
|
||||
"\"clerkIds\":[\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"]," +
|
||||
"\"beginTime\":\"" + DATE_TIME.format(begin) + "\"," +
|
||||
"\"endTime\":\"" + DATE_TIME.format(end) + "\"," +
|
||||
"\"ruleType\":\"PERCENTAGE\"," +
|
||||
"\"percentage\":\"10.00\"," +
|
||||
"\"operation\":\"BONUS\"," +
|
||||
"\"reasonDescription\":\"tenant isolation\"" +
|
||||
"}";
|
||||
|
||||
mockMvc.perform(post(BASE_URL + "/preview")
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, "tenant-other")
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isForbidden());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createReturns202AndProvidesPollingHandle() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now().withNano(0);
|
||||
LocalDateTime begin = now.minusDays(7);
|
||||
LocalDateTime end = now.plusSeconds(1);
|
||||
|
||||
String order1 = seedOrder(ApiTestDataSeeder.DEFAULT_CLERK_ID, now.minusDays(1), new BigDecimal("100.00"));
|
||||
seedOrderEarningLine(ApiTestDataSeeder.DEFAULT_CLERK_ID, order1, new BigDecimal("100.00"), "available", EarningsType.ORDER);
|
||||
|
||||
String key = UUID.randomUUID().toString();
|
||||
String payload = "{" +
|
||||
"\"clerkIds\":[\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"]," +
|
||||
"\"beginTime\":\"" + DATE_TIME.format(begin) + "\"," +
|
||||
"\"endTime\":\"" + DATE_TIME.format(end) + "\"," +
|
||||
"\"ruleType\":\"PERCENTAGE\"," +
|
||||
"\"percentage\":\"10.00\"," +
|
||||
"\"operation\":\"PUNISHMENT\"," +
|
||||
"\"reasonDescription\":\"week penalty\"" +
|
||||
"}";
|
||||
|
||||
MvcResult result = mockMvc.perform(post(BASE_URL)
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.header(IDEMPOTENCY_HEADER, key)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isAccepted())
|
||||
.andExpect(header().string("Location", BASE_URL + "/idempotency/" + key))
|
||||
.andExpect(jsonPath("$.code").value(202))
|
||||
.andExpect(jsonPath("$.data.batchId").isNotEmpty())
|
||||
.andExpect(jsonPath("$.data.idempotencyKey").value(key))
|
||||
.andExpect(jsonPath("$.data.status").value("PROCESSING"))
|
||||
.andReturn();
|
||||
|
||||
String batchId = extractBatchId(result);
|
||||
batchIdsToCleanup.add(batchId);
|
||||
|
||||
awaitApplied(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createIsIdempotentWithSameKeyAndSameBody() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now().withNano(0);
|
||||
LocalDateTime begin = now.minusDays(7);
|
||||
LocalDateTime end = now.plusSeconds(1);
|
||||
|
||||
String order1 = seedOrder(ApiTestDataSeeder.DEFAULT_CLERK_ID, now.minusDays(1), new BigDecimal("100.00"));
|
||||
seedOrderEarningLine(ApiTestDataSeeder.DEFAULT_CLERK_ID, order1, new BigDecimal("100.00"), "available", EarningsType.ORDER);
|
||||
|
||||
String key = UUID.randomUUID().toString();
|
||||
String payload = "{" +
|
||||
"\"clerkIds\":[\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"]," +
|
||||
"\"beginTime\":\"" + DATE_TIME.format(begin) + "\"," +
|
||||
"\"endTime\":\"" + DATE_TIME.format(end) + "\"," +
|
||||
"\"ruleType\":\"FIXED\"," +
|
||||
"\"amount\":\"50.00\"," +
|
||||
"\"operation\":\"BONUS\"," +
|
||||
"\"reasonDescription\":\"fixed bonus\"" +
|
||||
"}";
|
||||
|
||||
MvcResult first = mockMvc.perform(post(BASE_URL)
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.header(IDEMPOTENCY_HEADER, key)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isAccepted())
|
||||
.andReturn();
|
||||
String batchA = extractBatchId(first);
|
||||
|
||||
MvcResult second = mockMvc.perform(post(BASE_URL)
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.header(IDEMPOTENCY_HEADER, key)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isAccepted())
|
||||
.andReturn();
|
||||
String batchB = extractBatchId(second);
|
||||
|
||||
assertThat(batchB).isEqualTo(batchA);
|
||||
batchIdsToCleanup.add(batchA);
|
||||
awaitApplied(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createConcurrentRequestsSameKeyOnlyOneBatchCreated() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now().withNano(0);
|
||||
LocalDateTime begin = now.minusDays(7);
|
||||
LocalDateTime end = now.plusSeconds(1);
|
||||
|
||||
String order1 = seedOrder(ApiTestDataSeeder.DEFAULT_CLERK_ID, now.minusDays(1), new BigDecimal("100.00"));
|
||||
seedOrderEarningLine(ApiTestDataSeeder.DEFAULT_CLERK_ID, order1, new BigDecimal("100.00"), "available", EarningsType.ORDER);
|
||||
|
||||
String key = UUID.randomUUID().toString();
|
||||
String payload = "{" +
|
||||
"\"clerkIds\":[\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"]," +
|
||||
"\"beginTime\":\"" + DATE_TIME.format(begin) + "\"," +
|
||||
"\"endTime\":\"" + DATE_TIME.format(end) + "\"," +
|
||||
"\"ruleType\":\"FIXED\"," +
|
||||
"\"amount\":\"10.00\"," +
|
||||
"\"operation\":\"BONUS\"," +
|
||||
"\"reasonDescription\":\"concurrent\"" +
|
||||
"}";
|
||||
|
||||
ExecutorService pool = Executors.newFixedThreadPool(2);
|
||||
try {
|
||||
Callable<String> call = () -> {
|
||||
MvcResult result = mockMvc.perform(post(BASE_URL)
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.header(IDEMPOTENCY_HEADER, key)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isAccepted())
|
||||
.andReturn();
|
||||
return extractBatchId(result);
|
||||
};
|
||||
List<Future<String>> futures = new ArrayList<>();
|
||||
futures.add(pool.submit(call));
|
||||
futures.add(pool.submit(call));
|
||||
|
||||
String a = futures.get(0).get();
|
||||
String b = futures.get(1).get();
|
||||
assertThat(a).isNotBlank();
|
||||
assertThat(b).isEqualTo(a);
|
||||
batchIdsToCleanup.add(a);
|
||||
awaitApplied(key);
|
||||
} finally {
|
||||
pool.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSameKeyDifferentBodyReturns409() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now().withNano(0);
|
||||
LocalDateTime begin = now.minusDays(7);
|
||||
LocalDateTime end = now.plusSeconds(1);
|
||||
|
||||
String order1 = seedOrder(ApiTestDataSeeder.DEFAULT_CLERK_ID, now.minusDays(1), new BigDecimal("100.00"));
|
||||
seedOrderEarningLine(ApiTestDataSeeder.DEFAULT_CLERK_ID, order1, new BigDecimal("100.00"), "available", EarningsType.ORDER);
|
||||
|
||||
String key = UUID.randomUUID().toString();
|
||||
String payloadA = "{" +
|
||||
"\"clerkIds\":[\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"]," +
|
||||
"\"beginTime\":\"" + DATE_TIME.format(begin) + "\"," +
|
||||
"\"endTime\":\"" + DATE_TIME.format(end) + "\"," +
|
||||
"\"ruleType\":\"FIXED\"," +
|
||||
"\"amount\":\"50.00\"," +
|
||||
"\"operation\":\"BONUS\"," +
|
||||
"\"reasonDescription\":\"fixed bonus\"" +
|
||||
"}";
|
||||
String payloadB = payloadA.replace("\"50.00\"", "\"60.00\"");
|
||||
|
||||
MvcResult first = mockMvc.perform(post(BASE_URL)
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.header(IDEMPOTENCY_HEADER, key)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payloadA))
|
||||
.andExpect(status().isAccepted())
|
||||
.andReturn();
|
||||
String batchId = extractBatchId(first);
|
||||
batchIdsToCleanup.add(batchId);
|
||||
|
||||
mockMvc.perform(post(BASE_URL)
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.header(IDEMPOTENCY_HEADER, key)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payloadB))
|
||||
.andExpect(status().isConflict());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pollMissingIdempotencyKeyReturns404() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/idempotency/" + UUID.randomUUID())
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ))
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
void idempotencyKeyIsTenantScoped() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now().withNano(0);
|
||||
LocalDateTime begin = now.minusDays(7);
|
||||
LocalDateTime end = now.plusSeconds(1);
|
||||
|
||||
String order1 = seedOrder(ApiTestDataSeeder.DEFAULT_CLERK_ID, now.minusDays(1), new BigDecimal("100.00"));
|
||||
seedOrderEarningLine(ApiTestDataSeeder.DEFAULT_CLERK_ID, order1, new BigDecimal("100.00"), "available", EarningsType.ORDER);
|
||||
|
||||
String key = UUID.randomUUID().toString();
|
||||
String payload = "{" +
|
||||
"\"clerkIds\":[\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"]," +
|
||||
"\"beginTime\":\"" + DATE_TIME.format(begin) + "\"," +
|
||||
"\"endTime\":\"" + DATE_TIME.format(end) + "\"," +
|
||||
"\"ruleType\":\"FIXED\"," +
|
||||
"\"amount\":\"10.00\"," +
|
||||
"\"operation\":\"PUNISHMENT\"," +
|
||||
"\"reasonDescription\":\"tenant scope\"" +
|
||||
"}";
|
||||
|
||||
MvcResult create = mockMvc.perform(post(BASE_URL)
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.header(IDEMPOTENCY_HEADER, key)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isAccepted())
|
||||
.andReturn();
|
||||
String batchId = extractBatchId(create);
|
||||
batchIdsToCleanup.add(batchId);
|
||||
awaitApplied(key);
|
||||
|
||||
mockMvc.perform(get(BASE_URL + "/idempotency/" + key)
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, "tenant-other")
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ))
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
void itemsAfterAppliedHaveAdjustmentIdAndNoDuplicateEarningsLines() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now().withNano(0);
|
||||
LocalDateTime begin = now.minusDays(7);
|
||||
LocalDateTime end = now.plusSeconds(1);
|
||||
|
||||
String order1 = seedOrder(ApiTestDataSeeder.DEFAULT_CLERK_ID, now.minusDays(1), new BigDecimal("100.00"));
|
||||
seedOrderEarningLine(ApiTestDataSeeder.DEFAULT_CLERK_ID, order1, new BigDecimal("100.00"), "available", EarningsType.ORDER);
|
||||
|
||||
String key = UUID.randomUUID().toString();
|
||||
String payload = "{" +
|
||||
"\"clerkIds\":[\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"]," +
|
||||
"\"beginTime\":\"" + DATE_TIME.format(begin) + "\"," +
|
||||
"\"endTime\":\"" + DATE_TIME.format(end) + "\"," +
|
||||
"\"ruleType\":\"FIXED\"," +
|
||||
"\"amount\":\"50.00\"," +
|
||||
"\"operation\":\"PUNISHMENT\"," +
|
||||
"\"reasonDescription\":\"fixed penalty\"" +
|
||||
"}";
|
||||
|
||||
MvcResult create = mockMvc.perform(post(BASE_URL)
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.header(IDEMPOTENCY_HEADER, key)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isAccepted())
|
||||
.andReturn();
|
||||
String batchId = extractBatchId(create);
|
||||
batchIdsToCleanup.add(batchId);
|
||||
|
||||
awaitApplied(key);
|
||||
|
||||
MvcResult items = mockMvc.perform(get(BASE_URL + "/" + batchId + "/items")
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.param("pageNum", "1")
|
||||
.param("pageSize", "20"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").isArray())
|
||||
.andExpect(jsonPath("$.data[0].adjustmentId").isNotEmpty())
|
||||
.andReturn();
|
||||
|
||||
JsonNode root = objectMapper.readTree(items.getResponse().getContentAsString());
|
||||
JsonNode first = root.path("data").get(0);
|
||||
String adjustmentId = first.path("adjustmentId").asText();
|
||||
assertThat(adjustmentId).isNotBlank();
|
||||
|
||||
long count = earningsService.lambdaQuery()
|
||||
.eq(EarningsLineEntity::getTenantId, ApiTestDataSeeder.DEFAULT_TENANT_ID)
|
||||
.eq(EarningsLineEntity::getClerkId, ApiTestDataSeeder.DEFAULT_CLERK_ID)
|
||||
.eq(EarningsLineEntity::getSourceType, EarningsSourceType.ADJUSTMENT)
|
||||
.eq(EarningsLineEntity::getSourceId, adjustmentId)
|
||||
.count();
|
||||
assertThat(count).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void itemsPaginationWorks() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now().withNano(0);
|
||||
LocalDateTime begin = now.minusDays(7);
|
||||
LocalDateTime end = now.plusSeconds(1);
|
||||
|
||||
String secondClerkId = ensureTestClerkInDefaultTenant();
|
||||
|
||||
String order1 = seedOrder(ApiTestDataSeeder.DEFAULT_CLERK_ID, now.minusDays(1), new BigDecimal("100.00"));
|
||||
seedOrderEarningLine(ApiTestDataSeeder.DEFAULT_CLERK_ID, order1, new BigDecimal("100.00"), "available", EarningsType.ORDER);
|
||||
|
||||
String order2 = seedOrder(secondClerkId, now.minusDays(1), new BigDecimal("80.00"));
|
||||
seedOrderEarningLine(secondClerkId, order2, new BigDecimal("80.00"), "available", EarningsType.ORDER);
|
||||
|
||||
String key = UUID.randomUUID().toString();
|
||||
String payload = "{" +
|
||||
"\"clerkIds\":[\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\",\"" + secondClerkId + "\"]," +
|
||||
"\"beginTime\":\"" + DATE_TIME.format(begin) + "\"," +
|
||||
"\"endTime\":\"" + DATE_TIME.format(end) + "\"," +
|
||||
"\"ruleType\":\"FIXED\"," +
|
||||
"\"amount\":\"10.00\"," +
|
||||
"\"operation\":\"BONUS\"," +
|
||||
"\"reasonDescription\":\"pagination\"" +
|
||||
"}";
|
||||
|
||||
MvcResult create = mockMvc.perform(post(BASE_URL)
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.header(IDEMPOTENCY_HEADER, key)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isAccepted())
|
||||
.andReturn();
|
||||
String batchId = extractBatchId(create);
|
||||
batchIdsToCleanup.add(batchId);
|
||||
|
||||
awaitApplied(key);
|
||||
|
||||
mockMvc.perform(get(BASE_URL + "/" + batchId + "/items")
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.param("pageNum", "1")
|
||||
.param("pageSize", "1"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").isArray())
|
||||
.andExpect(jsonPath("$.data.length()").value(1))
|
||||
.andExpect(jsonPath("$.total").value(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void logsContainCreatedAndFinalEvents() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now().withNano(0);
|
||||
LocalDateTime begin = now.minusDays(7);
|
||||
LocalDateTime end = now.plusSeconds(1);
|
||||
|
||||
String order1 = seedOrder(ApiTestDataSeeder.DEFAULT_CLERK_ID, now.minusDays(1), new BigDecimal("100.00"));
|
||||
seedOrderEarningLine(ApiTestDataSeeder.DEFAULT_CLERK_ID, order1, new BigDecimal("100.00"), "available", EarningsType.ORDER);
|
||||
|
||||
String key = UUID.randomUUID().toString();
|
||||
String payload = "{" +
|
||||
"\"clerkIds\":[\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"]," +
|
||||
"\"beginTime\":\"" + DATE_TIME.format(begin) + "\"," +
|
||||
"\"endTime\":\"" + DATE_TIME.format(end) + "\"," +
|
||||
"\"ruleType\":\"FIXED\"," +
|
||||
"\"amount\":\"10.00\"," +
|
||||
"\"operation\":\"BONUS\"," +
|
||||
"\"reasonDescription\":\"audit log\"" +
|
||||
"}";
|
||||
|
||||
MvcResult create = mockMvc.perform(post(BASE_URL)
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.header(IDEMPOTENCY_HEADER, key)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isAccepted())
|
||||
.andReturn();
|
||||
String batchId = extractBatchId(create);
|
||||
batchIdsToCleanup.add(batchId);
|
||||
|
||||
awaitApplied(key);
|
||||
|
||||
mockMvc.perform(get(BASE_URL + "/" + batchId + "/logs")
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").isArray())
|
||||
.andExpect(jsonPath("$.data[?(@.eventType=='CREATED')]").exists())
|
||||
.andExpect(jsonPath("$.data[?(@.eventType=='APPLY_STARTED')]").exists())
|
||||
.andExpect(jsonPath("$.data[?(@.eventType=='BATCH_APPLIED')]").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
void itemsAndLogsAreTenantScopedToBatchTenant() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now().withNano(0);
|
||||
LocalDateTime begin = now.minusDays(7);
|
||||
LocalDateTime end = now.plusSeconds(1);
|
||||
|
||||
String order1 = seedOrder(ApiTestDataSeeder.DEFAULT_CLERK_ID, now.minusDays(1), new BigDecimal("100.00"));
|
||||
seedOrderEarningLine(ApiTestDataSeeder.DEFAULT_CLERK_ID, order1, new BigDecimal("100.00"), "available", EarningsType.ORDER);
|
||||
|
||||
String key = UUID.randomUUID().toString();
|
||||
String payload = "{" +
|
||||
"\"clerkIds\":[\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"]," +
|
||||
"\"beginTime\":\"" + DATE_TIME.format(begin) + "\"," +
|
||||
"\"endTime\":\"" + DATE_TIME.format(end) + "\"," +
|
||||
"\"ruleType\":\"FIXED\"," +
|
||||
"\"amount\":\"10.00\"," +
|
||||
"\"operation\":\"BONUS\"," +
|
||||
"\"reasonDescription\":\"tenant scope items\"" +
|
||||
"}";
|
||||
|
||||
MvcResult create = mockMvc.perform(post(BASE_URL)
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.header(IDEMPOTENCY_HEADER, key)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isAccepted())
|
||||
.andReturn();
|
||||
String batchId = extractBatchId(create);
|
||||
batchIdsToCleanup.add(batchId);
|
||||
awaitApplied(key);
|
||||
|
||||
mockMvc.perform(get(BASE_URL + "/" + batchId + "/items")
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, "tenant-other")
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.param("pageNum", "1")
|
||||
.param("pageSize", "10"))
|
||||
.andExpect(status().isNotFound());
|
||||
|
||||
mockMvc.perform(get(BASE_URL + "/" + batchId + "/logs")
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, "tenant-other")
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ))
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
void logsRecordOperatorInCreatedBy() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now().withNano(0);
|
||||
LocalDateTime begin = now.minusDays(7);
|
||||
LocalDateTime end = now.plusSeconds(1);
|
||||
|
||||
String order1 = seedOrder(ApiTestDataSeeder.DEFAULT_CLERK_ID, now.minusDays(1), new BigDecimal("100.00"));
|
||||
seedOrderEarningLine(ApiTestDataSeeder.DEFAULT_CLERK_ID, order1, new BigDecimal("100.00"), "available", EarningsType.ORDER);
|
||||
|
||||
String key = UUID.randomUUID().toString();
|
||||
String payload = "{" +
|
||||
"\"clerkIds\":[\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"]," +
|
||||
"\"beginTime\":\"" + DATE_TIME.format(begin) + "\"," +
|
||||
"\"endTime\":\"" + DATE_TIME.format(end) + "\"," +
|
||||
"\"ruleType\":\"FIXED\"," +
|
||||
"\"amount\":\"10.00\"," +
|
||||
"\"operation\":\"BONUS\"," +
|
||||
"\"reasonDescription\":\"operator audit\"" +
|
||||
"}";
|
||||
|
||||
MvcResult create = mockMvc.perform(post(BASE_URL)
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ)
|
||||
.header(IDEMPOTENCY_HEADER, key)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isAccepted())
|
||||
.andReturn();
|
||||
String batchId = extractBatchId(create);
|
||||
batchIdsToCleanup.add(batchId);
|
||||
awaitApplied(key);
|
||||
|
||||
MvcResult logs = mockMvc.perform(get(BASE_URL + "/" + batchId + "/logs")
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
JsonNode root = objectMapper.readTree(logs.getResponse().getContentAsString());
|
||||
JsonNode data = root.path("data");
|
||||
boolean hasOperator = false;
|
||||
for (JsonNode node : data) {
|
||||
String createdBy = node.path("createdBy").asText();
|
||||
if (ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID.equals(createdBy)) {
|
||||
hasOperator = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertThat(hasOperator).isTrue();
|
||||
}
|
||||
|
||||
private String extractBatchId(MvcResult result) throws Exception {
|
||||
JsonNode root = objectMapper.readTree(result.getResponse().getContentAsString());
|
||||
return root.path("data").path("batchId").asText();
|
||||
}
|
||||
|
||||
private String awaitApplied(String idempotencyKey) throws Exception {
|
||||
for (int i = 0; i < 120; i++) {
|
||||
MvcResult poll = mockMvc.perform(get(BASE_URL + "/idempotency/" + idempotencyKey)
|
||||
.header(USER_HEADER, ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(PERMISSIONS_HEADER, PERMISSIONS_CREATE_READ))
|
||||
.andReturn();
|
||||
if (poll.getResponse().getStatus() == 200) {
|
||||
JsonNode root = objectMapper.readTree(poll.getResponse().getContentAsString());
|
||||
String status = root.path("data").path("status").asText();
|
||||
if ("APPLIED".equals(status)) {
|
||||
return root.path("data").path("batchId").asText();
|
||||
}
|
||||
if ("FAILED".equals(status)) {
|
||||
throw new AssertionError("batch failed unexpectedly: key=" + idempotencyKey);
|
||||
}
|
||||
}
|
||||
Thread.sleep(50);
|
||||
}
|
||||
throw new AssertionError("batch not applied within timeout: key=" + idempotencyKey);
|
||||
}
|
||||
|
||||
private String seedOrder(String clerkId, LocalDateTime endTime, BigDecimal estimatedRevenue) {
|
||||
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
|
||||
String id = "order-deduct-" + IdUtils.getUuid();
|
||||
order.setId(id);
|
||||
order.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
order.setOrderNo("DED-" + System.currentTimeMillis());
|
||||
order.setOrderStatus("3");
|
||||
order.setOrderType("2");
|
||||
order.setPlaceType("0");
|
||||
order.setRewardType("0");
|
||||
order.setAcceptBy(clerkId);
|
||||
order.setPurchaserBy(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
||||
order.setCommodityId(ApiTestDataSeeder.DEFAULT_COMMODITY_ID);
|
||||
order.setOrderMoney(new BigDecimal("120.50"));
|
||||
order.setFinalAmount(order.getOrderMoney());
|
||||
order.setEstimatedRevenue(estimatedRevenue);
|
||||
order.setOrderSettlementState("1");
|
||||
order.setOrderEndTime(endTime);
|
||||
order.setOrderSettlementTime(endTime);
|
||||
Date nowDate = toDate(LocalDateTime.now());
|
||||
order.setCreatedBy(ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID);
|
||||
order.setCreatedTime(nowDate);
|
||||
order.setUpdatedBy(ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID);
|
||||
order.setUpdatedTime(nowDate);
|
||||
order.setDeleted(false);
|
||||
orderInfoService.save(order);
|
||||
ordersToCleanup.add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
private void seedOrderEarningLine(String clerkId, String orderId, BigDecimal amount, String status, EarningsType earningType) {
|
||||
EarningsLineEntity entity = new EarningsLineEntity();
|
||||
String id = "earn-deduct-" + IdUtils.getUuid();
|
||||
entity.setId(id);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setClerkId(clerkId);
|
||||
entity.setOrderId(orderId);
|
||||
entity.setSourceType(EarningsSourceType.ORDER);
|
||||
entity.setSourceId(orderId);
|
||||
entity.setAmount(amount);
|
||||
entity.setEarningType(earningType);
|
||||
entity.setStatus(status);
|
||||
entity.setUnlockTime(LocalDateTime.now().minusHours(1));
|
||||
Date nowDate = toDate(LocalDateTime.now());
|
||||
entity.setCreatedBy(ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID);
|
||||
entity.setCreatedTime(nowDate);
|
||||
entity.setUpdatedBy(ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID);
|
||||
entity.setUpdatedTime(nowDate);
|
||||
entity.setDeleted(false);
|
||||
earningsService.save(entity);
|
||||
earningsToCleanup.add(id);
|
||||
}
|
||||
|
||||
private void seedAdjustmentEarningLine(String clerkId, BigDecimal amount, String status) {
|
||||
EarningsLineEntity entity = new EarningsLineEntity();
|
||||
String id = "earn-deduct-adj-" + IdUtils.getUuid();
|
||||
entity.setId(id);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setClerkId(clerkId);
|
||||
entity.setOrderId(null);
|
||||
entity.setSourceType(EarningsSourceType.ADJUSTMENT);
|
||||
entity.setSourceId("adj-seed-" + IdUtils.getUuid());
|
||||
entity.setAmount(amount);
|
||||
entity.setEarningType(EarningsType.ADJUSTMENT);
|
||||
entity.setStatus(status);
|
||||
entity.setUnlockTime(LocalDateTime.now().minusHours(1));
|
||||
Date nowDate = toDate(LocalDateTime.now());
|
||||
entity.setCreatedBy(ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID);
|
||||
entity.setCreatedTime(nowDate);
|
||||
entity.setUpdatedBy(ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID);
|
||||
entity.setUpdatedTime(nowDate);
|
||||
entity.setDeleted(false);
|
||||
earningsService.save(entity);
|
||||
earningsToCleanup.add(id);
|
||||
}
|
||||
|
||||
private Date toDate(LocalDateTime time) {
|
||||
return Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
|
||||
}
|
||||
|
||||
private void cleanupBatches(List<String> batchIds) {
|
||||
if (batchIds == null || batchIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!tableExists("play_earnings_deduction_batch")) {
|
||||
return;
|
||||
}
|
||||
for (String batchId : batchIds) {
|
||||
cleanupBatch(batchId);
|
||||
}
|
||||
batchIds.clear();
|
||||
}
|
||||
|
||||
private void cleanupBatch(String batchId) {
|
||||
if (batchId == null || batchId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> adjustmentIds = new ArrayList<>();
|
||||
if (tableExists("play_earnings_deduction_item")) {
|
||||
adjustmentIds = jdbcTemplate.queryForList(
|
||||
"select adjustment_id from play_earnings_deduction_item where batch_id = ? and adjustment_id is not null",
|
||||
String.class,
|
||||
batchId);
|
||||
}
|
||||
|
||||
if (!adjustmentIds.isEmpty()) {
|
||||
earningsService.lambdaUpdate()
|
||||
.eq(EarningsLineEntity::getTenantId, ApiTestDataSeeder.DEFAULT_TENANT_ID)
|
||||
.eq(EarningsLineEntity::getSourceType, EarningsSourceType.ADJUSTMENT)
|
||||
.in(EarningsLineEntity::getSourceId, adjustmentIds)
|
||||
.remove();
|
||||
adjustmentService.removeByIds(adjustmentIds);
|
||||
}
|
||||
|
||||
if (tableExists("play_earnings_deduction_batch_log")) {
|
||||
jdbcTemplate.update("delete from play_earnings_deduction_batch_log where batch_id = ?", batchId);
|
||||
}
|
||||
if (tableExists("play_earnings_deduction_item")) {
|
||||
jdbcTemplate.update("delete from play_earnings_deduction_item where batch_id = ?", batchId);
|
||||
}
|
||||
jdbcTemplate.update("delete from play_earnings_deduction_batch where id = ?", batchId);
|
||||
}
|
||||
|
||||
private boolean tableExists(String table) {
|
||||
try {
|
||||
Integer count = jdbcTemplate.queryForObject(
|
||||
"select count(*) from information_schema.tables where lower(table_name)=lower(?)",
|
||||
Integer.class,
|
||||
table);
|
||||
return count != null && count > 0;
|
||||
} catch (Exception ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String ensureTestClerkInDefaultTenant() {
|
||||
String clerkId = "clerk-deduct-" + IdUtils.getUuid();
|
||||
PlayClerkUserInfoEntity clerk = new PlayClerkUserInfoEntity();
|
||||
clerk.setId(clerkId);
|
||||
clerk.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
clerk.setSysUserId("sysuser-" + IdUtils.getUuid());
|
||||
clerk.setOpenid("openid-" + clerkId);
|
||||
clerk.setNickname("Batch Clerk");
|
||||
clerk.setGroupId(ApiTestDataSeeder.DEFAULT_GROUP_ID);
|
||||
clerk.setLevelId(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID);
|
||||
clerk.setFixingLevel("1");
|
||||
clerk.setSex("2");
|
||||
clerk.setPhone("139" + String.valueOf(System.nanoTime()).substring(0, 8));
|
||||
clerk.setWeiChatCode("wechat-" + IdUtils.getUuid());
|
||||
clerk.setAvatar("https://example.com/avatar.png");
|
||||
clerk.setAccountBalance(BigDecimal.ZERO);
|
||||
clerk.setOnboardingState("1");
|
||||
clerk.setListingState("1");
|
||||
clerk.setDisplayState("1");
|
||||
clerk.setOnlineState("1");
|
||||
clerk.setRandomOrderState("1");
|
||||
clerk.setClerkState("1");
|
||||
clerk.setEntryTime(LocalDateTime.now());
|
||||
clerk.setToken("token-" + IdUtils.getUuid());
|
||||
clerkUserInfoService.save(clerk);
|
||||
clerkIdsToCleanup.add(clerkId);
|
||||
return clerkId;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user