日志记录提交

This commit is contained in:
2026-08-05 14:20:04 +08:00
parent 264458920b
commit 06f7f9d2f2
83 changed files with 3256 additions and 27 deletions
@@ -0,0 +1,44 @@
package com.baiyue.firstleg.controller;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.prepost.PreAuthorize;
import java.lang.reflect.Method;
import java.util.Arrays;
import static org.assertj.core.api.Assertions.assertThat;
@DisplayName("费用管理接口权限契约")
class FeeManagementContractTest {
@Test
@DisplayName("通过和驳回共用审核按钮权限")
void protectsReviewActions() {
for (String methodName : new String[] {"approve", "reject"}) {
Method method = Arrays.stream(PlatformInboundFeeDetailController.class.getDeclaredMethods())
.filter(candidate -> candidate.getName().equals(methodName))
.findFirst().orElseThrow();
assertThat(method.getAnnotation(PreAuthorize.class).value())
.isEqualTo("hasAuthority('firstleg:inbound:fee-detail:review')");
}
}
@Test
@DisplayName("费用项目增删改查权限彼此独立")
void separatesCrudPermissions() {
assertPermission("list", "firstleg:fee-item:view");
assertPermission("detail", "firstleg:fee-item:view");
assertPermission("create", "firstleg:fee-item:create");
assertPermission("update", "firstleg:fee-item:edit");
assertPermission("changeStatus", "firstleg:fee-item:edit");
assertPermission("delete", "firstleg:fee-item:delete");
}
private static void assertPermission(String methodName, String permission) {
Method method = Arrays.stream(PlatformFeeItemController.class.getDeclaredMethods())
.filter(candidate -> candidate.getName().equals(methodName))
.findFirst().orElseThrow();
assertThat(method.getAnnotation(PreAuthorize.class).value())
.isEqualTo("hasAuthority('" + permission + "')");
}
}
@@ -1,8 +1,10 @@
package com.baiyue.firstleg.controller;
import com.baiyue.common.audit.BusinessAudit;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.firstleg.dto.ExceptionReleaseRequest;
import com.baiyue.firstleg.dto.MeasurementReviewRequest;
import com.baiyue.firstleg.service.AutomaticInboundCompletionService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
@@ -43,6 +45,17 @@ class InboundMeasurementReviewContractTest {
.doesNotContain("待客户确认");
}
@Test
void shouldAuditAutomaticInboundCompletion() throws Exception {
Method method = AutomaticInboundCompletionService.class.getDeclaredMethod("completeApproved",
long.class, long.class, long.class, String.class, long.class, int.class);
BusinessAudit audit = method.getAnnotation(BusinessAudit.class);
assertThat(audit.event()).isEqualTo("INBOUND_AUTO_COMPLETE");
assertThat(audit.resourceType()).isEqualTo("INBOUND");
assertThat(audit.resourceId()).isEqualTo("#inboundId");
assertThat(audit.captureBefore()).isFalse();
}
private void assertAuthority(String name, String expression, Class<?>... types) throws Exception {
Method method = PlatformInboundMeasurementReviewController.class.getDeclaredMethod(name, types);
assertThat(method.getAnnotation(PreAuthorize.class).value()).isEqualTo(expression);
@@ -0,0 +1,148 @@
package com.baiyue.firstleg.service;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.dto.FeeItemDtos;
import com.baiyue.firstleg.dto.InboundFeeDetailDtos;
import com.baiyue.firstleg.entity.FeeItemEntity;
import com.baiyue.firstleg.entity.InboundFeeDetailEntity;
import com.baiyue.firstleg.entity.InboundOrderEntity;
import com.baiyue.firstleg.repository.FeeItemRepository;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@DisplayName("费用项目和入库费用明细")
@SpringJUnitConfig({FeeItemService.class, InboundFeeDetailService.class})
class FeeManagementServiceTest {
@MockBean
private FeeItemRepository repository;
@Resource
private FeeItemService feeItems;
@Resource
private InboundFeeDetailService details;
@Test
@DisplayName("费用项目列表返回分页结果")
void pagesItems() {
PageResult<FeeItemEntity> expected = new PageResult<>(List.of(), 2, 20, 21, 2);
when(repository.listItems(10001L, "仓储成本", "INTERNATIONAL", "人工", "ACTIVE", 2, 20))
.thenReturn(expected);
assertThat(feeItems.list(10001L, "仓储成本", "INTERNATIONAL", "人工", "ACTIVE", 2, 20))
.isSameAs(expected);
}
@Test
@DisplayName("新增费用项目保存原型字段")
void createsItem() {
doAnswer(invocation -> {
((FeeItemEntity) invocation.getArgument(0)).setId(11L);
return null;
}).when(repository).insertItem(any());
FeeItemEntity created = feeItems.create(10001L, 9L, "admin", itemRequest());
assertThat(created.getId()).isEqualTo(11L);
assertThat(created.getAllocationFee()).isTrue();
assertThat(created.getWarehouseReceivable()).isTrue();
assertThat(created.getCreatedByName()).isEqualTo("admin");
}
@Test
@DisplayName("入库费用绑定项目并生成订单快照")
void bindsInboundFee() {
InboundOrderEntity inbound = inbound();
FeeItemEntity item = item(true, true);
when(repository.findInbound(10001L, 21L)).thenReturn(inbound);
when(repository.findItem(10001L, 11L)).thenReturn(item);
doAnswer(invocation -> {
((InboundFeeDetailEntity) invocation.getArgument(0)).setId(31L);
return null;
}).when(repository).insertDetail(any());
InboundFeeDetailEntity created = details.create(10001L, 21L, 9L,
detailRequest("RECEIVABLE"));
assertThat(created.getId()).isEqualTo(31L);
assertThat(created.getInboundCode()).isEqualTo("RK-20260805-001");
assertThat(created.getFeeTypeNameSnapshot()).isEqualTo("仓库租赁费");
assertThat(created.getActualGrossWeightKg()).isEqualByComparingTo("10.5000");
assertThat(created.getReviewStatus()).isEqualTo("PENDING");
}
@Test
@DisplayName("拒绝绑定未启用仓储应收的项目")
void rejectsInapplicableItem() {
when(repository.findInbound(10001L, 21L)).thenReturn(inbound());
when(repository.findItem(10001L, 11L)).thenReturn(item(false, true));
assertThatThrownBy(() -> details.create(10001L, 21L, 9L,
detailRequest("RECEIVABLE")))
.isInstanceOf(BizException.class)
.hasMessageContaining("仓储应收/应付");
}
@Test
@DisplayName("审核只更新待审核明细")
void approvesPendingDetail() {
when(repository.findDetail(10001L, 21L, 31L)).thenReturn(new InboundFeeDetailEntity());
when(repository.review(10001L, 21L, 31L, "APPROVED", 9L, "admin", null)).thenReturn(1);
details.approve(10001L, 21L, 31L, 9L, "admin");
verify(repository).review(10001L, 21L, 31L, "APPROVED", 9L, "admin", null);
}
private static FeeItemDtos.SaveRequest itemRequest() {
return new FeeItemDtos.SaveRequest("仓储成本", "INTERNATIONAL", "仓库租赁费",
false, false, true, true, true, "应付仓库租赁费");
}
private static InboundFeeDetailDtos.SaveRequest detailRequest(String direction) {
return new InboundFeeDetailDtos.SaveRequest(11L, direction, LocalDate.of(2026, 8, 5),
new BigDecimal("12.3456"), "CNY", new BigDecimal("123.46"), "联调费用");
}
private static FeeItemEntity item(boolean receivable, boolean payable) {
FeeItemEntity item = new FeeItemEntity();
item.setId(11L);
item.setCategory("仓储成本");
item.setTradeScope("INTERNATIONAL");
item.setFeeTypeName("仓库租赁费");
item.setWarehouseReceivable(receivable);
item.setWarehousePayable(payable);
item.setStatus("ACTIVE");
return item;
}
private static InboundOrderEntity inbound() {
InboundOrderEntity inbound = new InboundOrderEntity();
inbound.setId(21L);
inbound.setInboundCode("RK-20260805-001");
inbound.setChannelCode("SEA");
inbound.setChannelName("海运");
inbound.setDestinationCode("ONT8");
inbound.setDestinationName("ONT8");
inbound.setTotalPackageCount(10);
inbound.setActualGrossWeightKg(new BigDecimal("10.5000"));
inbound.setActualVolumeCbm(new BigDecimal("1.250000"));
inbound.setVolumeWeightKg(new BigDecimal("208.3333"));
inbound.setChargeableWeightKg(new BigDecimal("208.3333"));
inbound.setChargeableVolumeCbm(new BigDecimal("1.250000"));
return inbound;
}
}