From 0260498bd371efd8176484e0443a4fa5b1c34598 Mon Sep 17 00:00:00 2001 From: hy001 Date: Mon, 25 May 2026 14:32:15 +0800 Subject: [PATCH] =?UTF-8?q?CP=E5=85=B3=E7=B3=BB=E7=94=B3=E8=AF=B7=20?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/command/user/SendCpApplyCmdExe.java | 3 + .../command/user/query/CpApplyIdQryExe.java | 29 +++-- .../user/query/CpApplyStatusQryExe.java | 20 +++- .../common/cp/CpRelationC2cNoticeUtils.java | 9 ++ .../app/listener/user/CpApplyListener.java | 5 +- .../user/query/CpApplyIdQryExeTest.java | 43 ++++++++ .../user/query/CpApplyStatusQryExeTest.java | 18 +++ .../listener/user/CpApplyListenerTest.java | 35 +++++- .../rds/service/user/user/CpApplyService.java | 12 +- .../user/user/impl/CpApplyServiceImpl.java | 56 ++++++++-- ...mManualSalaryPaymentClientServiceImpl.java | 19 ++-- ...ualSalaryPaymentClientServiceImplTest.java | 103 ++++++++++++++++++ 12 files changed, 321 insertions(+), 31 deletions(-) create mode 100644 rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/command/user/query/CpApplyIdQryExeTest.java create mode 100644 rc-service/rc-service-other/other-inner-endpoint/src/test/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImplTest.java diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/SendCpApplyCmdExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/SendCpApplyCmdExe.java index 18cb6dc9..9a022bea 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/SendCpApplyCmdExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/SendCpApplyCmdExe.java @@ -108,6 +108,9 @@ public class SendCpApplyCmdExe { ResponseAssert.isFalse(UserRelationErrorCode.UNAVAILABLE_OPS_YOURSELF, Objects.equals(sendUserId, acceptUserId)); + cpApplyService.expireOverdueWaitApplies(sendUserId); + cpApplyService.expireOverdueWaitApplies(acceptUserId); + boolean eqRegion = userRegionGateway.checkEqRegion(sendUserId, acceptUserId); ResponseAssert.isTrue(UserRelationErrorCode.UNAVAILABLE_NOT_REGION, eqRegion); diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/query/CpApplyIdQryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/query/CpApplyIdQryExe.java index a0e19a98..bf20ba0a 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/query/CpApplyIdQryExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/query/CpApplyIdQryExe.java @@ -3,7 +3,8 @@ package com.red.circle.other.app.command.user.query; import com.red.circle.other.app.dto.cmd.user.relation.cp.CpApplyIdQueryCmd; import com.red.circle.other.infra.database.rds.service.user.user.CpApplyService; import com.red.circle.other.infra.enums.user.user.CpRelationshipType; -import java.util.Optional; +import java.util.Objects; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -16,13 +17,27 @@ import org.springframework.stereotype.Component; @RequiredArgsConstructor public class CpApplyIdQryExe { - private final CpApplyService cpApplyService; - + private final CpApplyService cpApplyService; + public String execute(CpApplyIdQueryCmd cmd) { - return Optional.ofNullable(cpApplyService.getId(cmd.requiredReqUserId(), cmd.getUserId(), - CpRelationshipType.normalize(cmd.getRelationType()))) + Long currentUserId = cmd.requiredReqUserId(); + Long targetUserId = cmd.getUserId(); + String relationType = CpRelationshipType.normalize(cmd.getRelationType()); + cpApplyService.expireOverdueWaitApplies(currentUserId); + cpApplyService.expireOverdueWaitApplies(targetUserId); + + Long applyId = cpApplyService.getId(targetUserId, currentUserId, relationType); + if (isEmptyApplyId(applyId)) { + applyId = cpApplyService.getId(currentUserId, targetUserId, relationType); + } + return Optional.ofNullable(applyId) + .filter(id -> id > 0) .map(String::valueOf) .orElse(null); } - -} + + private boolean isEmptyApplyId(Long applyId) { + return Objects.isNull(applyId) || applyId <= 0; + } + +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/query/CpApplyStatusQryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/query/CpApplyStatusQryExe.java index 6be69d5e..3eea2de8 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/query/CpApplyStatusQryExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/query/CpApplyStatusQryExe.java @@ -3,6 +3,8 @@ package com.red.circle.other.app.command.user.query; import com.red.circle.other.app.dto.cmd.user.relation.cp.CpApplyStatusQueryCmd; import com.red.circle.other.infra.database.rds.entity.user.user.CpApply; import com.red.circle.other.infra.database.rds.service.user.user.CpApplyService; +import com.red.circle.other.infra.enums.user.user.CpApplyStatus; +import java.time.LocalDateTime; import java.util.Objects; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -14,10 +16,26 @@ import org.springframework.stereotype.Component; @RequiredArgsConstructor public class CpApplyStatusQryExe { + private static final long APPLY_WAIT_EXPIRE_HOURS = 24L; + private final CpApplyService cpApplyService; public String execute(CpApplyStatusQueryCmd cmd) { CpApply cpApply = cpApplyService.getById(cmd.getApplyId()); - return Objects.nonNull(cpApply) ? cpApply.getStatus() : null; + if (Objects.isNull(cpApply)) { + return null; + } + if (isOverdueWait(cpApply)) { + cpApplyService.expireWaitById(cpApply.getId()); + return CpApplyStatus.EXPIRE.name(); + } + return cpApply.getStatus(); + } + + private boolean isOverdueWait(CpApply cpApply) { + return Objects.equals(cpApply.getStatus(), CpApplyStatus.WAIT.name()) + && Objects.nonNull(cpApply.getCreateTime()) + && cpApply.getCreateTime().toLocalDateTime() + .isBefore(LocalDateTime.now().minusHours(APPLY_WAIT_EXPIRE_HOURS)); } } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/cp/CpRelationC2cNoticeUtils.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/cp/CpRelationC2cNoticeUtils.java index 75ec60e7..0f95321a 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/cp/CpRelationC2cNoticeUtils.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/cp/CpRelationC2cNoticeUtils.java @@ -9,6 +9,7 @@ import com.red.circle.tool.core.json.JacksonUtils; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; +import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; public final class CpRelationC2cNoticeUtils { @@ -36,9 +37,17 @@ public final class CpRelationC2cNoticeUtils { content.put("applyType", isReconcile ? "RECONCILE" : "APPLY"); content.put("relationType", relationType.name()); putAcceptProfile(content, acceptProfile); + long createTimeSeconds = System.currentTimeMillis() / 1000; + long expireTimeSeconds = createTimeSeconds + TimeUnit.DAYS.toSeconds(1); + content.put("createTime", createTimeSeconds); + content.put("sendTime", createTimeSeconds); + content.put("dismissEndTime", expireTimeSeconds); Map payload = basePayload(applyId, relationType.name(), title, ACTION_INVITE_PENDING, "PENDING", content); + payload.put("createTime", createTimeSeconds); + payload.put("sendTime", createTimeSeconds); + payload.put("dismissEndTime", expireTimeSeconds); putSenderProfile(payload, senderProfile); putAcceptProfile(payload, acceptProfile); return buildMessage(fromUserId, toUserId, payload); diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/user/CpApplyListener.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/user/CpApplyListener.java index eba27308..f9478b13 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/user/CpApplyListener.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/user/CpApplyListener.java @@ -56,12 +56,15 @@ public class CpApplyListener implements MessageListener { // 等待超时 if (Objects.equals(cpApply.getStatus(), CpApplyStatus.WAIT.name())) { expire(cpApply); + } else { + log.info("[CP] skip expire cp apply, applyId={}, status={}", cpApply.getId(), + cpApply.getStatus()); } }); } private void expire(CpApply cpApply) { - boolean changed = cpApplyService.changeStatusById(cpApply.getId(), CpApplyStatus.EXPIRE); + boolean changed = cpApplyService.expireWaitById(cpApply.getId()); if (changed) { sendExpiredNotice(cpApply); } diff --git a/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/command/user/query/CpApplyIdQryExeTest.java b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/command/user/query/CpApplyIdQryExeTest.java new file mode 100644 index 00000000..f1a41416 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/command/user/query/CpApplyIdQryExeTest.java @@ -0,0 +1,43 @@ +package com.red.circle.other.app.command.user.query; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.red.circle.other.app.dto.cmd.user.relation.cp.CpApplyIdQueryCmd; +import com.red.circle.other.infra.database.rds.service.user.user.CpApplyService; +import org.junit.jupiter.api.Test; + +class CpApplyIdQryExeTest { + + @Test + void shouldReturnApplyIdSentByCurrentUser() { + CpApplyService cpApplyService = mock(CpApplyService.class); + when(cpApplyService.getId(2001L, 1001L, "BROTHER")).thenReturn(3001L); + + CpApplyIdQryExe exe = new CpApplyIdQryExe(cpApplyService); + + assertEquals("3001", exe.execute(cmd(1001L, 2001L, "BROTHER"))); + verify(cpApplyService).expireOverdueWaitApplies(1001L); + verify(cpApplyService).expireOverdueWaitApplies(2001L); + } + + @Test + void shouldFallbackToApplyIdReceivedByCurrentUser() { + CpApplyService cpApplyService = mock(CpApplyService.class); + when(cpApplyService.getId(1001L, 2001L, "CP")).thenReturn(3002L); + + CpApplyIdQryExe exe = new CpApplyIdQryExe(cpApplyService); + + assertEquals("3002", exe.execute(cmd(1001L, 2001L, "CP"))); + } + + private static CpApplyIdQueryCmd cmd(Long reqUserId, Long targetUserId, String relationType) { + CpApplyIdQueryCmd cmd = new CpApplyIdQueryCmd(); + cmd.setReqUserId(reqUserId); + cmd.setUserId(targetUserId); + cmd.setRelationType(relationType); + return cmd; + } +} diff --git a/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/command/user/query/CpApplyStatusQryExeTest.java b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/command/user/query/CpApplyStatusQryExeTest.java index 34b27365..11ea8ada 100644 --- a/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/command/user/query/CpApplyStatusQryExeTest.java +++ b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/command/user/query/CpApplyStatusQryExeTest.java @@ -3,11 +3,14 @@ package com.red.circle.other.app.command.user.query; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.red.circle.other.app.dto.cmd.user.relation.cp.CpApplyStatusQueryCmd; import com.red.circle.other.infra.database.rds.entity.user.user.CpApply; import com.red.circle.other.infra.database.rds.service.user.user.CpApplyService; +import java.sql.Timestamp; +import java.time.LocalDateTime; import org.junit.jupiter.api.Test; class CpApplyStatusQryExeTest { @@ -30,6 +33,21 @@ class CpApplyStatusQryExeTest { assertNull(exe.execute(cmd(3001L))); } + @Test + void shouldExpireOverdueWaitApplyWhenQueryingStatus() { + CpApplyService cpApplyService = mock(CpApplyService.class); + CpApply cpApply = new CpApply() + .setId(3001L) + .setStatus("WAIT"); + cpApply.setCreateTime(Timestamp.valueOf(LocalDateTime.now().minusHours(25))); + when(cpApplyService.getById(3001L)).thenReturn(cpApply); + + CpApplyStatusQryExe exe = new CpApplyStatusQryExe(cpApplyService); + + assertEquals("EXPIRE", exe.execute(cmd(3001L))); + verify(cpApplyService).expireWaitById(3001L); + } + private static CpApplyStatusQueryCmd cmd(Long applyId) { CpApplyStatusQueryCmd cmd = new CpApplyStatusQueryCmd(); cmd.setApplyId(applyId); diff --git a/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/listener/user/CpApplyListenerTest.java b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/listener/user/CpApplyListenerTest.java index 0bda6de2..52f68eca 100644 --- a/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/listener/user/CpApplyListenerTest.java +++ b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/listener/user/CpApplyListenerTest.java @@ -6,6 +6,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; @@ -45,7 +47,7 @@ class CpApplyListenerTest { .setStatus(CpApplyStatus.WAIT.name()); when(cpApplyService.getById(3001L)).thenReturn(apply); - when(cpApplyService.changeStatusById(3001L, CpApplyStatus.EXPIRE)).thenReturn(true); + when(cpApplyService.expireWaitById(3001L)).thenReturn(true); when(userProfileGateway.getByUserId(1001L)).thenReturn(profile(1001L)); when(userProfileGateway.getByUserId(2001L)).thenReturn(profile(2001L)); when(imMessageClient.sendCustomMessage(any(CustomC2cMsgBodyCmd.class))) @@ -68,12 +70,41 @@ class CpApplyListenerTest { ArgumentCaptor captor = ArgumentCaptor.forClass( CustomC2cMsgBodyCmd.class); InOrder inOrder = inOrder(cpApplyService, imMessageClient); - inOrder.verify(cpApplyService).changeStatusById(3001L, CpApplyStatus.EXPIRE); + inOrder.verify(cpApplyService).expireWaitById(3001L); inOrder.verify(imMessageClient).sendCustomMessage(captor.capture()); List messages = captor.getAllValues(); assertExpiredMessage(messages.get(0), "1001", "2001"); } + @Test + void nonWaitApplyShouldNotExpireAgain() { + CpApplyService cpApplyService = mock(CpApplyService.class); + ImMessageClient imMessageClient = mock(ImMessageClient.class); + UserProfileGateway userProfileGateway = mock(UserProfileGateway.class); + MessageEventProcess messageEventProcess = mock(MessageEventProcess.class); + CpApply apply = new CpApply() + .setId(3001L) + .setStatus(CpApplyStatus.EXPIRE.name()); + + when(cpApplyService.getById(3001L)).thenReturn(apply); + when(messageEventProcess.consume(any(MessageEventProcessDescribe.class), + eq(CpApplyEvent.class), any())).thenAnswer(invocation -> { + @SuppressWarnings("unchecked") + Consumer consumer = invocation.getArgument(2, Consumer.class); + consumer.accept(new CpApplyEvent() + .setId(3001L) + .setSysOrigin(SysOriginPlatformEnum.LIKEI)); + return Action.SUCCESS; + }); + + CpApplyListener listener = new CpApplyListener(cpApplyService, imMessageClient, + userProfileGateway, messageEventProcess); + + assertEquals(Action.SUCCESS, listener.consume(new ConsumerMessage())); + verify(cpApplyService, never()).expireWaitById(3001L); + verify(imMessageClient, never()).sendCustomMessage(any(CustomC2cMsgBodyCmd.class)); + } + @SuppressWarnings("unchecked") private static void assertExpiredMessage(CustomC2cMsgBodyCmd message, String fromAccount, String toAccount) { diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/CpApplyService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/CpApplyService.java index 332648cc..bfdfd331 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/CpApplyService.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/CpApplyService.java @@ -76,7 +76,17 @@ public interface CpApplyService extends BaseService { * @param status 状态 * @return true 成功,false 失败 */ - boolean changeStatusById(Long id, CpApplyStatus status); + boolean changeStatusById(Long id, CpApplyStatus status); + + /** + * 只把等待中的申请置为超时. + */ + boolean expireWaitById(Long id); + + /** + * 清理用户相关超过 24 小时仍处于等待状态的申请. + */ + void expireOverdueWaitApplies(Long userId); /** * 拒绝所有等待状态在用户. diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/impl/CpApplyServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/impl/CpApplyServiceImpl.java index eed281c6..9a9aadae 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/impl/CpApplyServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/impl/CpApplyServiceImpl.java @@ -10,9 +10,10 @@ import com.red.circle.other.infra.database.rds.entity.user.user.CpApply; import com.red.circle.other.infra.database.rds.service.user.user.CpApplyService; import com.red.circle.other.infra.enums.user.user.CpApplyStatus; import com.red.circle.other.infra.enums.user.user.CpRelationshipType; -import com.red.circle.other.inner.model.cmd.user.CpApplyQryCmd; -import java.time.LocalDateTime; -import java.util.List; +import com.red.circle.other.inner.model.cmd.user.CpApplyQryCmd; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.List; import java.util.Objects; import java.util.Optional; import org.springframework.stereotype.Service; @@ -26,9 +27,11 @@ import org.springframework.stereotype.Service; * @since 2021-07-13 */ @Service -public class CpApplyServiceImpl extends BaseServiceImpl implements - CpApplyService { - +public class CpApplyServiceImpl extends BaseServiceImpl implements + CpApplyService { + + private static final long APPLY_WAIT_EXPIRE_HOURS = 24L; + @Override public void dismiss(Long requestUserId) { dismiss(requestUserId, CpRelationshipType.CP.name()); @@ -62,10 +65,12 @@ public class CpApplyServiceImpl extends BaseServiceImpl imp .eq(CpApply::getRelationType, CpRelationshipType.normalize(relationType)) .eq(CpApply::getAcceptApplyUserId, acceptApplyUserId) .eq(CpApply::getSendApplyUserId, sendApplyUserId) - .last(PageConstant.LIMIT_ONE) - .getOne() - ).map(CpApply::getId).orElse(null); - } + .gt(CpApply::getCreateTime, waitExpireBefore()) + .orderByDesc(CpApply::getCreateTime) + .last(PageConstant.LIMIT_ONE) + .getOne() + ).map(CpApply::getId).orElse(null); + } @Override public boolean exists(Long acceptApplyUserId, Long sendApplyUserId) { @@ -89,6 +94,7 @@ public class CpApplyServiceImpl extends BaseServiceImpl imp .eq(CpApply::getSendApplyUserId, sendApplyUserId) .eq(CpApply::getRelationType, CpRelationshipType.normalize(relationType)) .eq(CpApply::getStatus, CpApplyStatus.WAIT.name()) + .gt(CpApply::getCreateTime, waitExpireBefore()) .getOne()) .map(cpApply -> Objects.nonNull(cpApply.getId())) .orElse(Boolean.FALSE); @@ -98,9 +104,39 @@ public class CpApplyServiceImpl extends BaseServiceImpl imp public boolean changeStatusById(Long id, CpApplyStatus status) { return update() .set(CpApply::getStatus, status.name()) + .set(CpApply::getUpdateTime, LocalDateTime.now()) .eq(CpApply::getId, id) .execute(); } + + @Override + public boolean expireWaitById(Long id) { + return update() + .set(CpApply::getStatus, CpApplyStatus.EXPIRE.name()) + .set(CpApply::getUpdateTime, LocalDateTime.now()) + .eq(CpApply::getId, id) + .eq(CpApply::getStatus, CpApplyStatus.WAIT.name()) + .execute(); + } + + @Override + public void expireOverdueWaitApplies(Long userId) { + if (Objects.isNull(userId)) { + return; + } + update() + .set(CpApply::getStatus, CpApplyStatus.EXPIRE.name()) + .set(CpApply::getUpdateTime, LocalDateTime.now()) + .eq(CpApply::getStatus, CpApplyStatus.WAIT.name()) + .lt(CpApply::getCreateTime, waitExpireBefore()) + .and(where -> where.eq(CpApply::getSendApplyUserId, userId) + .or().eq(CpApply::getAcceptApplyUserId, userId)) + .execute(); + } + + private Timestamp waitExpireBefore() { + return Timestamp.valueOf(LocalDateTime.now().minusHours(APPLY_WAIT_EXPIRE_HOURS)); + } @Override public void refuseStatusWait(Long acceptApplyUserId) { diff --git a/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImpl.java b/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImpl.java index d9cdcace..f756861d 100644 --- a/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImpl.java +++ b/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImpl.java @@ -134,7 +134,8 @@ public class TeamManualSalaryPaymentClientServiceImpl implements billBelong, row.getSysOrigin()); for (PolicyPayment policyPayment : row.getPayments()) { - if (Boolean.TRUE.equals(teamSalaryPaymentService.checkRepeatPayment(payment.getId(), + if (policyPayment.getSalary().compareTo(BigDecimal.ZERO) <= 0 + && Boolean.TRUE.equals(teamSalaryPaymentService.checkRepeatPayment(payment.getId(), row.getUserId(), row.getRegion(), policyPayment.getLevel(), policyPayment.getAnchor()))) { continue; @@ -501,32 +502,32 @@ public class TeamManualSalaryPaymentClientServiceImpl implements .setScale(2, RoundingMode.DOWN); BigDecimal agentMissing = agentExpected.subtract(paymentSummary.getAgentIssuedSalary()) .setScale(2, RoundingMode.DOWN); - boolean memberSettled = isRoleSettled(cyclePayments, userId, region, billTarget.getLevel(), - Boolean.TRUE, memberExpected); - boolean agentSettled = isRoleSettled(cyclePayments, userId, region, billTarget.getLevel(), - Boolean.FALSE, agentExpected); + boolean memberHasSettlementRecord = hasPaidPolicy(cyclePayments, userId, region, + billTarget.getLevel(), Boolean.TRUE); + boolean agentHasSettlementRecord = hasPaidPolicy(cyclePayments, userId, region, + billTarget.getLevel(), Boolean.FALSE); List payments = new ArrayList<>(); if (remainingPayable.compareTo(BigDecimal.ZERO) <= 0) { - if (!memberSettled && memberExpected.compareTo(BigDecimal.ZERO) > 0) { + if (!memberHasSettlementRecord && memberExpected.compareTo(BigDecimal.ZERO) > 0) { payments.add(new PolicyPayment(policy, billTarget.getLevel(), BigDecimal.ZERO.setScale(2, RoundingMode.DOWN), Boolean.TRUE)); } - if (!agentSettled && agentExpected.compareTo(BigDecimal.ZERO) > 0) { + if (!agentHasSettlementRecord && agentExpected.compareTo(BigDecimal.ZERO) > 0) { payments.add(new PolicyPayment(policy, billTarget.getLevel(), BigDecimal.ZERO.setScale(2, RoundingMode.DOWN), Boolean.FALSE)); } return payments; } - if (!memberSettled && memberMissing.compareTo(BigDecimal.ZERO) > 0) { + if (memberMissing.compareTo(BigDecimal.ZERO) > 0) { BigDecimal issueSalary = memberMissing.min(remainingPayable).setScale(2, RoundingMode.DOWN); if (issueSalary.compareTo(BigDecimal.ZERO) > 0) { payments.add(new PolicyPayment(policy, billTarget.getLevel(), issueSalary, Boolean.TRUE)); remainingPayable = remainingPayable.subtract(issueSalary).setScale(2, RoundingMode.DOWN); } } - if (!agentSettled && agentMissing.compareTo(BigDecimal.ZERO) > 0 + if (agentMissing.compareTo(BigDecimal.ZERO) > 0 && remainingPayable.compareTo(BigDecimal.ZERO) > 0) { BigDecimal issueSalary = agentMissing.min(remainingPayable).setScale(2, RoundingMode.DOWN); if (issueSalary.compareTo(BigDecimal.ZERO) > 0) { diff --git a/rc-service/rc-service-other/other-inner-endpoint/src/test/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImplTest.java b/rc-service/rc-service-other/other-inner-endpoint/src/test/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImplTest.java new file mode 100644 index 00000000..9656bf8b --- /dev/null +++ b/rc-service/rc-service-other/other-inner-endpoint/src/test/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImplTest.java @@ -0,0 +1,103 @@ +package com.red.circle.other.app.inner.service.team.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.red.circle.other.infra.database.mongo.dto.team.TeamBillMemberTarget; +import com.red.circle.other.infra.database.mongo.dto.team.TeamSalaryPaymentDetails; +import com.red.circle.other.infra.database.mongo.entity.team.team.TeamSalaryPayment; +import java.lang.reflect.Constructor; +import java.math.BigDecimal; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +class TeamManualSalaryPaymentClientServiceImplTest { + + @Test + void buildSettlementPaymentsShouldTopUpWhenCurrentLevelRecordExistsButSalaryIsInsufficient() + throws Exception { + TeamManualSalaryPaymentClientServiceImpl service = + new TeamManualSalaryPaymentClientServiceImpl( + null, null, null, null, null, null, null, null); + Long userId = 2054591808909930498L; + String region = "2046067717605224449"; + + TeamBillMemberTarget billTarget = new TeamBillMemberTarget() + .setLevel(6) + .setMemberSalary(new BigDecimal("35.00")) + .setOwnSalary(new BigDecimal("8.75")); + TeamSalaryPayment currentPayment = new TeamSalaryPayment() + .setSalarySendDetails(List.of(new TeamSalaryPaymentDetails() + .setTeamMemberId(userId) + .setTeamRegionId(region) + .setPolicyLevel(6) + .setAnchor(Boolean.TRUE) + .setSalary(new BigDecimal("3.00")) + .setRecycled(Boolean.FALSE))); + + Object paymentSummary = paymentSummary( + new BigDecimal("31.50"), + new BigDecimal("22.00"), + new BigDecimal("9.50")); + + List payments = ReflectionTestUtils.invokeMethod(service, "buildSettlementPayments", + null, billTarget, List.of(currentPayment), userId, region, paymentSummary, + new BigDecimal("12.25"), false); + + assertEquals(1, payments.size()); + Object payment = payments.get(0); + assertEquals(6, ReflectionTestUtils.getField(payment, "level")); + assertEquals(Boolean.TRUE, ReflectionTestUtils.getField(payment, "anchor")); + assertEquals(new BigDecimal("12.25"), ReflectionTestUtils.getField(payment, "salary")); + } + + @Test + void buildSettlementPaymentsShouldSettleZeroWhenPayableSalaryIsNegative() throws Exception { + TeamManualSalaryPaymentClientServiceImpl service = + new TeamManualSalaryPaymentClientServiceImpl( + null, null, null, null, null, null, null, null); + Long userId = 1008071L; + String region = "2046067717605224449"; + + TeamBillMemberTarget billTarget = new TeamBillMemberTarget() + .setLevel(6) + .setMemberSalary(new BigDecimal("35.00")) + .setOwnSalary(new BigDecimal("8.75")); + Object paymentSummary = paymentSummary( + new BigDecimal("45.00"), + new BigDecimal("36.00"), + new BigDecimal("9.00")); + + List payments = ReflectionTestUtils.invokeMethod(service, "buildSettlementPayments", + null, billTarget, List.of(), userId, region, paymentSummary, + new BigDecimal("-1.25"), false); + + assertEquals(2, payments.size()); + assertEquals(BigDecimal.ZERO.setScale(2), ReflectionTestUtils.getField(payments.get(0), + "salary")); + assertEquals(BigDecimal.ZERO.setScale(2), ReflectionTestUtils.getField(payments.get(1), + "salary")); + } + + private Object paymentSummary(BigDecimal totalIssuedSalary, BigDecimal memberIssuedSalary, + BigDecimal agentIssuedSalary) throws Exception { + Class summaryClass = Class.forName( + "com.red.circle.other.app.inner.service.team.impl." + + "TeamManualSalaryPaymentClientServiceImpl$PaymentSummary"); + Constructor constructor = summaryClass.getDeclaredConstructor( + BigDecimal.class, + BigDecimal.class, + BigDecimal.class, + BigDecimal.class, + BigDecimal.class, + BigDecimal.class); + constructor.setAccessible(true); + return constructor.newInstance( + totalIssuedSalary, + memberIssuedSalary, + agentIssuedSalary, + BigDecimal.ZERO.setScale(2), + BigDecimal.ZERO.setScale(2), + BigDecimal.ZERO.setScale(2)); + } +}