优化
This commit is contained in:
+12
@@ -2,6 +2,7 @@ package com.baiyue.lastleg.calendar;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import org.apache.ibatis.mapping.Environment;
|
||||
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
@@ -28,12 +29,23 @@ class WarehouseCalendarMapperTest {
|
||||
}
|
||||
var config=new MybatisConfiguration(); config.setEnvironment(new Environment("test",new JdbcTransactionFactory(),ds));
|
||||
config.addMapper(WarehouseCalendarMapper.class);
|
||||
config.addMapper(LastlegOrderMapper.class);
|
||||
try(var session=new MybatisSqlSessionFactoryBuilder().build(config).openSession()) {
|
||||
var rows=session.getMapper(WarehouseCalendarMapper.class).calendar(LocalDateTime.parse("2026-11-01T00:00:00"),LocalDateTime.parse("2026-12-01T00:00:00"),List.of("1"));
|
||||
assertEquals(3,rows.size());
|
||||
assertEquals("1",rows.get(0).sourceContainerBookingId()); assertNull(rows.get(0).plannedAt());
|
||||
assertEquals(2,rows.get(1).version()); assertEquals("2026-11-10T09:00",rows.get(1).plannedAt().toString());
|
||||
assertEquals(1,rows.get(2).planCount()); assertNotNull(rows.get(2).plannedAt());
|
||||
// 清关提柜与日历必须读取相同的人工/冻结事实,默认日期由前端统一推算。
|
||||
var customs=session.getMapper(LastlegOrderMapper.class).findContainerArrivalTimes(List.of("1","2","3"));
|
||||
assertEquals(3,customs.size());
|
||||
for(var calendar:rows) {
|
||||
var arrival=customs.stream().filter(row -> row.sourceContainerBookingId().equals(calendar.sourceContainerBookingId())).findFirst().orElseThrow();
|
||||
assertEquals(calendar.plannedAt(),arrival.estimatedWarehouseArrivalAt());
|
||||
assertEquals(calendar.actualAt(),arrival.actualWarehouseArrivalAt());
|
||||
assertEquals(calendar.version(),arrival.version());
|
||||
assertEquals(calendar.planCount(),arrival.planCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -45,6 +45,15 @@ class HttpDispatchEvidenceGatewayTest {
|
||||
var pdf=new HttpDispatchEvidenceGateway.Metadata(3L,0L,"OVERSEAS_DISPATCH_DRIVER_PHOTO_EVIDENCE","9","driver.pdf","application/pdf",128L);
|
||||
assertThatThrownBy(()->HttpDispatchEvidenceGateway.verified(new HttpDispatchEvidenceGateway.Envelope("200",pdf),9,DispatchEvidenceGateway.Kind.DRIVER_PHOTO,3L)).isInstanceOf(ResponseStatusException.class);
|
||||
}
|
||||
/** 异常图片独立归属本车,审核照片及PDF均不能用作异常图片。 */
|
||||
@Test void exceptionImagesRejectOtherKindsCarsAndPdf() {
|
||||
var photo=new HttpDispatchEvidenceGateway.Metadata(3L,0L,"OVERSEAS_DISPATCH_EXCEPTION_EVIDENCE","9","exception.png","image/png",128L);
|
||||
assertThat(HttpDispatchEvidenceGateway.verified(new HttpDispatchEvidenceGateway.Envelope("200",photo),9,DispatchEvidenceGateway.Kind.EXCEPTION,3L).fileId()).isEqualTo(3L);
|
||||
assertThatThrownBy(()->HttpDispatchEvidenceGateway.verified(new HttpDispatchEvidenceGateway.Envelope("200",photo),10,DispatchEvidenceGateway.Kind.EXCEPTION,3L)).isInstanceOf(ResponseStatusException.class);
|
||||
assertThatThrownBy(()->HttpDispatchEvidenceGateway.verified(new HttpDispatchEvidenceGateway.Envelope("200",photo),9,DispatchEvidenceGateway.Kind.DRIVER_PHOTO,3L)).isInstanceOf(ResponseStatusException.class);
|
||||
var pdf=new HttpDispatchEvidenceGateway.Metadata(3L,0L,"OVERSEAS_DISPATCH_EXCEPTION_EVIDENCE","9","exception.pdf","application/pdf",128L);
|
||||
assertThatThrownBy(()->HttpDispatchEvidenceGateway.verified(new HttpDispatchEvidenceGateway.Envelope("200",pdf),9,DispatchEvidenceGateway.Kind.EXCEPTION,3L)).isInstanceOf(ResponseStatusException.class);
|
||||
}
|
||||
/** 客户、旧主档或缺失媒体类型均拒绝,不因为文件本身存在而放行。 */
|
||||
@Test void rejectsForeignTenantMasterScopeAndMalformedMetadata() {
|
||||
for(var value:java.util.List.of(
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package com.baiyue.lastleg.dispatch.service;
|
||||
|
||||
import com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway;
|
||||
import com.baiyue.lastleg.dispatch.port.DispatchCommands;
|
||||
import com.baiyue.lastleg.dispatch.port.DispatchContracts;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import java.util.List;
|
||||
import static com.baiyue.lastleg.dispatch.port.DispatchTypes.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/** 通过真实Mapper和事务验证异常登记,不把登记事实解释成签收或退货。 */
|
||||
class DispatchExceptionPersistenceTest {
|
||||
/** 登记使用排车独立维护权限。 */
|
||||
@BeforeEach void authorize() { SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("operator-test","","platform:overseas-warehouse:dispatch:edit")); }
|
||||
/** 清除线程身份,避免影响其他权限回归。 */
|
||||
@AfterEach void clear() { SecurityContextHolder.clearContext(); }
|
||||
|
||||
/** 每种原型类型均可保存,原键重试不追加,最终完成后仍恢复原结果。 */
|
||||
@Test void recordsAllTypesAndReplaysWithoutChangingDeliveryFacts() throws Exception {
|
||||
var f=new DispatchAggregatePersistenceTest.Fixture();var no=create(f);long version=0;
|
||||
var before=f.db.queryForMap("SELECT stage,shipped_package_count,departed_at,review_state FROM ll_dispatch_order");
|
||||
for(var type:ExceptionType.values()) {
|
||||
var body=new DispatchCommands.MarkException(version,"exception-"+type,type,ReturnDirection.OVERSEAS_WAREHOUSE,"真实异常",List.of());
|
||||
var saved=f.tx.execute(t->f.service.markException(f.actor,no,body));
|
||||
var replay=f.tx.execute(t->f.service.markException(f.actor,no,body));
|
||||
assertThat(saved.stage()).isEqualTo(Stage.DELIVERING);assertThat(replay.replayed()).isTrue();
|
||||
assertThat(replay.recordVersion()).isEqualTo(saved.recordVersion());version=saved.recordVersion();
|
||||
}
|
||||
var records=f.service.detail(f.actor,no).dispatchInfo().exceptions();assertThat(records).hasSize(3);
|
||||
assertThat(records).allSatisfy(r->{assertThat(r.description()).isEqualTo("真实异常");assertThat(r.occurredAt()).isNotNull();});
|
||||
assertThat(f.db.queryForMap("SELECT stage,shipped_package_count,departed_at,review_state FROM ll_dispatch_order")).isEqualTo(before);
|
||||
assertThat(f.db.queryForObject("SELECT COUNT(*) FROM ll_inventory_ledger",Long.class)).isZero();
|
||||
assertThat(f.db.queryForObject("SELECT COUNT(*) FROM ll_dispatch_history WHERE action='MARK_EXCEPTION'",Long.class)).isEqualTo(3);
|
||||
var first=new DispatchCommands.MarkException(0L,"exception-REFUSED",ExceptionType.REFUSED,ReturnDirection.OVERSEAS_WAREHOUSE,"真实异常",List.of());
|
||||
f.db.update("UPDATE ll_dispatch_order SET stage='DELIVERED'");
|
||||
assertThat(f.tx.execute(t->f.service.markException(f.actor,no,first)).replayed()).isTrue();
|
||||
assertThatThrownBy(()->f.tx.execute(t->f.service.markException(f.actor,no,new DispatchCommands.MarkException(0L,"exception-REFUSED",ExceptionType.REFUSED,null,"改了内容",List.of())))).isInstanceOf(RuntimeException.class);
|
||||
assertThat(f.service.detail(f.actor,no).dispatchInfo().exceptions()).hasSize(3);
|
||||
}
|
||||
|
||||
/** 非派送阶段、过期版本、只读身份、PDA及运输任务不能登记。 */
|
||||
@Test void rejectsWrongStageVersionPermissionAndTask() throws Exception {
|
||||
var f=new DispatchAggregatePersistenceTest.Fixture();var no=create(f);var body=request(0,"blocked",List.of());
|
||||
for(var stage:List.of("BEFORE_LOADING","LOADING","AFTER_LOADING","DELIVERED")) {
|
||||
f.db.update("UPDATE ll_dispatch_order SET stage=?",stage);
|
||||
assertThatThrownBy(()->f.tx.execute(t->f.service.markException(f.actor,no,body))).hasMessageContaining("仅派送中");
|
||||
}
|
||||
f.db.update("UPDATE ll_dispatch_order SET stage='DELIVERING'");
|
||||
assertThatThrownBy(()->f.tx.execute(t->f.service.markException(f.actor,no,request(9,"stale",List.of())))).hasMessageContaining("版本");
|
||||
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("operator-test","","platform:overseas-warehouse:dispatch:list"));
|
||||
assertThatThrownBy(()->f.tx.execute(t->f.service.markException(f.actor,no,body))).hasMessageContaining("权限");
|
||||
authorize();f.db.update("UPDATE ll_dispatch_order SET task_type='LOCAL_TRUCK'");
|
||||
assertThatThrownBy(()->f.tx.execute(t->f.service.markException(f.actor,no,body))).hasMessageContaining("仅派送中");
|
||||
f.db.update("UPDATE ll_dispatch_order SET task_type='DELIVERY',status='CANCELLED'");
|
||||
assertThatThrownBy(()->f.tx.execute(t->f.service.markException(f.actor,no,body))).hasMessageContaining("仅派送中");
|
||||
assertThatThrownBy(()->f.tx.execute(t->f.service.markException(new DispatchContracts.Actor("pda",ActorMode.PDA,"9"),no,body))).hasMessageContaining("仅平台");
|
||||
assertThat(f.db.queryForObject("SELECT record_version FROM ll_dispatch_order",Long.class)).isZero();
|
||||
}
|
||||
|
||||
/** 图片严格核对当前车和类别;附件失败或缺类型不保存任何业务记录。 */
|
||||
@Test void verifiesImageOwnershipAndKeepsFailedRequestUnchanged() throws Exception {
|
||||
var f=new DispatchAggregatePersistenceTest.Fixture();var no=create(f);var id=f.repository.find(no).getId();
|
||||
var files=mock(DispatchEvidenceGateway.class);ReflectionTestUtils.setField(f.service,"evidence",files);
|
||||
when(files.metadata(id,DispatchEvidenceGateway.Kind.EXCEPTION,91L)).thenThrow(new IllegalArgumentException("文件不属于当前车"));
|
||||
assertThatThrownBy(()->f.tx.execute(t->f.service.markException(f.actor,no,request(0,"files",List.of(91L))))).hasMessageContaining("不属于");
|
||||
assertThat(f.service.detail(f.actor,no).dispatchInfo().exceptions()).isEmpty();
|
||||
var missingType=new DispatchCommands.MarkException(0L,"no-type",null,null,null,List.of());
|
||||
assertThatThrownBy(()->f.tx.execute(t->f.service.markException(f.actor,no,missingType))).hasMessageContaining("请选择异常类型");
|
||||
assertThatThrownBy(()->f.tx.execute(t->f.service.markException(f.actor,no,request(0,"duplicates",List.of(91L,91L))))).hasMessageContaining("图片引用");
|
||||
doReturn(new DispatchEvidenceGateway.File(91L,"现场.png","image/png",10L)).when(files).metadata(id,DispatchEvidenceGateway.Kind.EXCEPTION,91L);
|
||||
f.tx.execute(t->f.service.markException(f.actor,no,request(0,"files",List.of(91L))));
|
||||
assertThat(f.service.detail(f.actor,no).dispatchInfo().exceptions().get(0).imageFileIds()).containsExactly(91L);
|
||||
verify(files,times(2)).metadata(id,DispatchEvidenceGateway.Kind.EXCEPTION,91L);
|
||||
}
|
||||
|
||||
/** 创建真实排车后仅以SQL模拟已发车前置,不声称此用例执行装车流程。 */
|
||||
private String create(DispatchAggregatePersistenceTest.Fixture f) {
|
||||
var created=f.tx.execute(t->f.service.create(f.actor,f.configured("create",List.of())));
|
||||
f.db.update("UPDATE ll_dispatch_order SET stage='DELIVERING',shipped_package_count=3,departed_at=CURRENT_TIMESTAMP");return created.dispatchNo();
|
||||
}
|
||||
/** 构造类型必填、其余选填的最小登记请求。 */
|
||||
private DispatchCommands.MarkException request(long version,String key,List<Long> images) { return new DispatchCommands.MarkException(version,key,ExceptionType.VEHICLE_EXCEPTION,null,null,images); }
|
||||
}
|
||||
+1
-1
@@ -148,7 +148,7 @@ class LastlegOrderServiceTest {
|
||||
when(orders.countContainerPlans("BOOKING-1")).thenReturn(0L);
|
||||
when(orders.updateContainerExpectedWarehouseArrival("BOOKING-1", estimatedAt, 0)).thenReturn(2);
|
||||
when(orders.findContainerArrivalTimes(List.of("BOOKING-1"))).thenReturn(List.of(
|
||||
new ContainerArrivalTimeView("BOOKING-1", estimatedAt, null, 1)));
|
||||
new ContainerArrivalTimeView("BOOKING-1", estimatedAt, null, 1, 0)));
|
||||
|
||||
ContainerArrivalTimeView result = service.changeExpectedWarehouseArrival("BOOKING-1", "arrival-1",
|
||||
new LastlegOrderRequests.ChangeExpectedWarehouseArrival(estimatedAt, 0, null), "admin");
|
||||
|
||||
Reference in New Issue
Block a user