diff --git a/firstleg/src/main/java/com/baiyue/firstleg/entity/ContainerBookingEntity.java b/firstleg/src/main/java/com/baiyue/firstleg/entity/ContainerBookingEntity.java index 1901cacff..fd19f6105 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/entity/ContainerBookingEntity.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/entity/ContainerBookingEntity.java @@ -1,6 +1,7 @@ package com.baiyue.firstleg.entity; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; @@ -75,6 +76,9 @@ public class ContainerBookingEntity { @TableField(exist = false) private Long planId; @TableField(exist = false) private String planNo; @TableField(exist = false) private String planStatus; + /** Identity of the port schedule selected for the current transport read model. */ + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @TableField(exist = false) private Long currentPortScheduleId; /** Actual completion timestamp projected from the associated loading plan. */ @JsonIgnore @TableField(exist = false) private LocalDateTime completedLoadingAt; @TableField(exist = false) private Boolean exceptionActive; diff --git a/firstleg/src/main/java/com/baiyue/firstleg/repository/ContainerRepository.java b/firstleg/src/main/java/com/baiyue/firstleg/repository/ContainerRepository.java index 4d9266c9c..b4b318752 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/repository/ContainerRepository.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/repository/ContainerRepository.java @@ -71,9 +71,12 @@ public class ContainerRepository { } private void resolveCurrentPortSchedule(ContainerBookingEntity value) { - if (value == null || value.getCustomerId() == null || value.getOceanBookingId() == null) return; + if (value == null) return; + value.setCurrentPortScheduleId(null); + if (value.getCustomerId() == null || value.getOceanBookingId() == null) return; PortScheduleEntity schedule = schedules.latestForDispatch(value.getCustomerId(), value.getOceanBookingId(), value.getId()); if (schedule == null) return; + value.setCurrentPortScheduleId(schedule.getId()); value.setVesselName(schedule.getVesselName()); value.setVoyageNo(schedule.getVoyageNo()); value.setEstimatedSailingAt(schedule.getEtdAt()); diff --git a/firstleg/src/main/resources/openapi/firstleg-v1.yaml b/firstleg/src/main/resources/openapi/firstleg-v1.yaml index d70822b29..faa977f34 100644 --- a/firstleg/src/main/resources/openapi/firstleg-v1.yaml +++ b/firstleg/src/main/resources/openapi/firstleg-v1.yaml @@ -2260,6 +2260,7 @@ components: required: [id, bookingNo, cabinetNo, sourceType, recordStatus, warehouseId, warehouseName, channelGroupCode, containerType, capacityCbm, maxWeightKg, planStatus, exceptionActive, destinationClearanceDocumentStatus, customsDeclarationDocumentStatus, deliveryOrderDocumentStatus, version] properties: id: {type: string, pattern: '^[1-9][0-9]*$'} + currentPortScheduleId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, readOnly: true, description: 当前运输读模型由 latestForDispatch 选中的船期记录 ID;无匹配时为空,仅用于定位关联船期,不作为业务编号展示} planId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: 关联智能装柜计划ID;未创建计划时为空} planNo: {type: string, nullable: true, pattern: '^LP-[0-9]{6}-[0-9]{4}$', example: LP-260818-0001, description: 预装与确认装柜清单共用的稳定编号;未创建计划时为空} bookingNo: {type: string, pattern: '^CB-[0-9]{6}-[0-9]{4}$', example: CB-260818-0001, description: 平台内部货柜登记号} diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/ContainerRepositoryLoadingTimeTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/ContainerRepositoryLoadingTimeTest.java index 2cd56dd02..f80e3b4f3 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/ContainerRepositoryLoadingTimeTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/ContainerRepositoryLoadingTimeTest.java @@ -4,6 +4,8 @@ import com.baiyue.firstleg.entity.ContainerBookingEntity; import com.baiyue.firstleg.mapper.ContainerBookingMapper; import com.baiyue.firstleg.portschedule.entity.PortScheduleEntity; import com.baiyue.firstleg.portschedule.mapper.PortScheduleMapper; +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -32,6 +34,7 @@ class ContainerRepositoryLoadingTimeTest { ContainerBookingEntity result = repository.find(7L); assertThat(result.getLoadingAt()).isEqualTo(completedAt); + assertThat(result.getCurrentPortScheduleId()).isNull(); } @Test @@ -58,6 +61,7 @@ class ContainerRepositoryLoadingTimeTest { booking.setVesselName("OLD-VESSEL"); booking.setVoyageNo("OLD-V01"); PortScheduleEntity schedule = new PortScheduleEntity(); + schedule.setId(11L); schedule.setVesselName("NEW-VESSEL"); schedule.setVoyageNo("NEW-V01"); schedule.setEtdAt(LocalDateTime.of(2026, 10, 7, 8, 0)); @@ -68,9 +72,43 @@ class ContainerRepositoryLoadingTimeTest { ContainerBookingEntity result = repository.find(8L); assertThat(result.getContainerNo()).isEqualTo("TSTU0000001"); + assertThat(result.getCurrentPortScheduleId()).isEqualTo(11L); assertThat(result.getVesselName()).isEqualTo("NEW-VESSEL"); assertThat(result.getVoyageNo()).isEqualTo("NEW-V01"); assertThat(result.getEstimatedSailingAt()).isEqualTo(schedule.getEtdAt()); assertThat(result.getEstimatedPortArrivalAt()).isEqualTo(schedule.getEtaAt()); } + + @Test + void leavesCurrentPortScheduleNullWhenNoScheduleMatches() { + ContainerBookingEntity booking = new ContainerBookingEntity(); + booking.setId(8L); + booking.setCustomerId(9L); + booking.setOceanBookingId(10L); + booking.setCurrentPortScheduleId(11L); + booking.setVesselName("BOOKING-VESSEL"); + booking.setVoyageNo("BOOKING-V01"); + when(mapper.selectBooking(8L)).thenReturn(booking); + when(schedules.latestForDispatch(9L, 10L, 8L)).thenReturn(null); + + ContainerBookingEntity result = repository.find(8L); + + assertThat(result.getCurrentPortScheduleId()).isNull(); + assertThat(result.getVesselName()).isEqualTo("BOOKING-VESSEL"); + assertThat(result.getVoyageNo()).isEqualTo("BOOKING-V01"); + } + + @Test + void currentPortScheduleIsNonPersistentAndSerializedReadOnly() throws Exception { + var field = ContainerBookingEntity.class.getDeclaredField("currentPortScheduleId"); + assertThat(field.getAnnotation(TableField.class).exist()).isFalse(); + ObjectMapper json = new ObjectMapper(); + ContainerBookingEntity booking = new ContainerBookingEntity(); + booking.setCurrentPortScheduleId(11L); + + assertThat(json.readTree(json.writeValueAsString(booking)).get("currentPortScheduleId").asLong()) + .isEqualTo(11L); + assertThat(json.readValue("{\"currentPortScheduleId\":11}", ContainerBookingEntity.class) + .getCurrentPortScheduleId()).isNull(); + } }