fix(deduction): make apply idempotent
Some checks failed
Build and Push Backend / docker (push) Has been cancelled
Some checks failed
Build and Push Backend / docker (push) Has been cancelled
This commit is contained in:
@@ -228,6 +228,10 @@ public class EarningsDeductionBatchServiceImpl
|
|||||||
if (item == null || !StringUtils.hasText(item.getClerkId())) {
|
if (item == null || !StringUtils.hasText(item.getClerkId())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (StringUtils.hasText(item.getAdjustmentId())) {
|
||||||
|
adjustmentService.triggerApplyAsync(item.getAdjustmentId());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
BigDecimal amount = item.getApplyAmount() == null ? BigDecimal.ZERO : item.getApplyAmount();
|
BigDecimal amount = item.getApplyAmount() == null ? BigDecimal.ZERO : item.getApplyAmount();
|
||||||
if (amount.compareTo(BigDecimal.ZERO) == 0) {
|
if (amount.compareTo(BigDecimal.ZERO) == 0) {
|
||||||
markItemFailed(item.getId(), "applyAmount=0");
|
markItemFailed(item.getId(), "applyAmount=0");
|
||||||
@@ -241,7 +245,7 @@ public class EarningsDeductionBatchServiceImpl
|
|||||||
EarningsAdjustmentReasonType.MANUAL,
|
EarningsAdjustmentReasonType.MANUAL,
|
||||||
batch.getReasonDescription(),
|
batch.getReasonDescription(),
|
||||||
idempotencyKey,
|
idempotencyKey,
|
||||||
LocalDateTime.now());
|
null);
|
||||||
|
|
||||||
if (!StringUtils.hasText(item.getAdjustmentId())) {
|
if (!StringUtils.hasText(item.getAdjustmentId())) {
|
||||||
EarningsDeductionItemEntity patch = new EarningsDeductionItemEntity();
|
EarningsDeductionItemEntity patch = new EarningsDeductionItemEntity();
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package com.starry.admin.modules.withdraw.service.impl;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import com.starry.admin.api.AbstractApiTest;
|
||||||
|
import com.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||||
|
import com.starry.admin.modules.withdraw.entity.EarningsDeductionBatchEntity;
|
||||||
|
import com.starry.admin.modules.withdraw.entity.EarningsDeductionBatchLogEntity;
|
||||||
|
import com.starry.admin.modules.withdraw.entity.EarningsDeductionItemEntity;
|
||||||
|
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
||||||
|
import com.starry.admin.modules.withdraw.enums.EarningsDeductionOperationType;
|
||||||
|
import com.starry.admin.modules.withdraw.enums.EarningsDeductionRuleType;
|
||||||
|
import com.starry.admin.modules.withdraw.enums.EarningsSourceType;
|
||||||
|
import com.starry.admin.modules.withdraw.mapper.EarningsDeductionBatchLogMapper;
|
||||||
|
import com.starry.admin.modules.withdraw.mapper.EarningsDeductionItemMapper;
|
||||||
|
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.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
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;
|
||||||
|
|
||||||
|
class EarningsDeductionBatchServiceImplIdempotencyIntegrationTest extends AbstractApiTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EarningsDeductionBatchServiceImpl batchService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EarningsDeductionItemMapper itemMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EarningsDeductionBatchLogMapper logMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IEarningsAdjustmentService adjustmentService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IEarningsService earningsService;
|
||||||
|
|
||||||
|
private String batchId;
|
||||||
|
private String clerkId;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||||
|
clerkId = IdUtils.getUuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||||
|
if (batchId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EarningsDeductionItemEntity> items = itemMapper.selectList(Wrappers.lambdaQuery(EarningsDeductionItemEntity.class)
|
||||||
|
.eq(EarningsDeductionItemEntity::getTenantId, ApiTestDataSeeder.DEFAULT_TENANT_ID)
|
||||||
|
.eq(EarningsDeductionItemEntity::getBatchId, batchId)
|
||||||
|
.eq(EarningsDeductionItemEntity::getDeleted, false));
|
||||||
|
|
||||||
|
List<String> adjustmentIds = items.stream()
|
||||||
|
.map(EarningsDeductionItemEntity::getAdjustmentId)
|
||||||
|
.filter(id -> id != null && !id.isBlank())
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (!adjustmentIds.isEmpty()) {
|
||||||
|
earningsService.lambdaUpdate()
|
||||||
|
.eq(EarningsLineEntity::getTenantId, ApiTestDataSeeder.DEFAULT_TENANT_ID)
|
||||||
|
.eq(EarningsLineEntity::getClerkId, clerkId)
|
||||||
|
.eq(EarningsLineEntity::getSourceType, EarningsSourceType.ADJUSTMENT)
|
||||||
|
.in(EarningsLineEntity::getSourceId, adjustmentIds)
|
||||||
|
.remove();
|
||||||
|
adjustmentService.removeByIds(adjustmentIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
itemMapper.delete(Wrappers.lambdaQuery(EarningsDeductionItemEntity.class)
|
||||||
|
.eq(EarningsDeductionItemEntity::getTenantId, ApiTestDataSeeder.DEFAULT_TENANT_ID)
|
||||||
|
.eq(EarningsDeductionItemEntity::getBatchId, batchId));
|
||||||
|
|
||||||
|
logMapper.delete(Wrappers.lambdaQuery(EarningsDeductionBatchLogEntity.class)
|
||||||
|
.eq(EarningsDeductionBatchLogEntity::getTenantId, ApiTestDataSeeder.DEFAULT_TENANT_ID)
|
||||||
|
.eq(EarningsDeductionBatchLogEntity::getBatchId, batchId));
|
||||||
|
|
||||||
|
batchService.removeById(batchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void applyOnce_canBeTriggeredTwiceWithoutBreakingIdempotency() {
|
||||||
|
String tenantId = ApiTestDataSeeder.DEFAULT_TENANT_ID;
|
||||||
|
LocalDateTime begin = LocalDateTime.now().minusDays(1);
|
||||||
|
LocalDateTime end = LocalDateTime.now();
|
||||||
|
String idempotencyKey = "e2e-deduct-idem-" + UUID.randomUUID();
|
||||||
|
|
||||||
|
EarningsDeductionBatchEntity batch = batchService.createOrGetProcessing(
|
||||||
|
tenantId,
|
||||||
|
List.of(clerkId),
|
||||||
|
begin,
|
||||||
|
end,
|
||||||
|
EarningsDeductionRuleType.FIXED,
|
||||||
|
new BigDecimal("1.00"),
|
||||||
|
EarningsDeductionOperationType.PUNISHMENT,
|
||||||
|
"idempotency test",
|
||||||
|
idempotencyKey);
|
||||||
|
assertNotNull(batch);
|
||||||
|
batchId = batch.getId();
|
||||||
|
|
||||||
|
batchService.applyOnce(batchId);
|
||||||
|
|
||||||
|
EarningsDeductionItemEntity item = itemMapper.selectOne(Wrappers.lambdaQuery(EarningsDeductionItemEntity.class)
|
||||||
|
.eq(EarningsDeductionItemEntity::getTenantId, tenantId)
|
||||||
|
.eq(EarningsDeductionItemEntity::getBatchId, batchId)
|
||||||
|
.eq(EarningsDeductionItemEntity::getClerkId, clerkId)
|
||||||
|
.last("limit 1"));
|
||||||
|
assertNotNull(item);
|
||||||
|
assertTrue(item.getAdjustmentId() != null && !item.getAdjustmentId().isBlank());
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> batchService.applyOnce(batchId));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user