feat(firstleg): expose batch carrier actual facts

This commit is contained in:
2026-09-08 19:09:08 +08:00
parent 75e67fa980
commit b0c81e337d
10 changed files with 182 additions and 0 deletions
@@ -5,6 +5,7 @@ import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.PageResult; import com.baiyue.common.core.api.PageResult;
import com.baiyue.common.security.AuthenticatedUser; import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.CarrierDescriptor; import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.CarrierDescriptor;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ContainerActualFacts;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.CreateRequest; import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.CreateRequest;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.Detail; import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.Detail;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ManualResultRequest; import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ManualResultRequest;
@@ -19,6 +20,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -28,6 +30,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.List; import java.util.List;
@@ -47,6 +50,17 @@ public class PlatformCarrierScheduleQueryController {
return ApiResponse.success(service.carriers()); return ApiResponse.success(service.carriers());
} }
@Operation(summary = "按货柜批量查询实际船节点摘要",
description = "最多200柜;仅返回实际节点时间,不返回或推断预计时间;没有船节点任务的货柜不返回行。")
@GetMapping("/container-actual-facts")
@PreAuthorize("hasAuthority('platform:firstleg:port-schedule-query:list')")
public ApiResponse<List<ContainerActualFacts>> containerActualFacts(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam("containerBookingIds") @Size(min = 1, max = 200) List<@Positive Long> containerBookingIds) {
FirstlegScopeGuard.platform(user);
return ApiResponse.success(service.containerActualFacts(containerBookingIds));
}
@GetMapping @GetMapping
@PreAuthorize("hasAuthority('platform:firstleg:port-schedule-query:list')") @PreAuthorize("hasAuthority('platform:firstleg:port-schedule-query:list')")
public ApiResponse<PageResult<QueryView>> page(@AuthenticationPrincipal AuthenticatedUser user, public ApiResponse<PageResult<QueryView>> page(@AuthenticationPrincipal AuthenticatedUser user,
@@ -80,6 +80,17 @@ public final class CarrierScheduleQueryDtos {
public record QueryView(Task query, boolean expired) {} public record QueryView(Task query, boolean expired) {}
/** Actual-only summary used by container workbenches; estimated facts are intentionally excluded. */
public record ContainerActualFacts(Long containerBookingId, Long queryId, Integer queryVersion,
String queryStatus, String latestActualNode,
LocalDateTime latestActualAt,
LocalDateTime departedWarehouseAt,
LocalDateTime departedOriginPortAt,
LocalDateTime arrivedDestinationPortAt,
LocalDateTime pickupReadyAt,
LocalDateTime pickedUpAt,
LocalDateTime emptyReturnedAt) {}
public record NodeFact(Long id, String providerCode, String providerEventId, public record NodeFact(Long id, String providerCode, String providerEventId,
String providerNodeCode, String standardNode, String providerNodeCode, String standardNode,
int nodeSequence, LocalDateTime observedAt, LocalDateTime estimatedAt, int nodeSequence, LocalDateTime observedAt, LocalDateTime estimatedAt,
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.portschedule.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.Query; import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.Query;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ContainerActualFacts;
import com.baiyue.firstleg.portschedule.entity.CarrierScheduleQueryEntity; import com.baiyue.firstleg.portschedule.entity.CarrierScheduleQueryEntity;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@@ -46,6 +47,33 @@ public interface CarrierScheduleQueryMapper extends BaseMapper<CarrierScheduleQu
@Param("oceanBookingId") long oceanBookingId, @Param("oceanBookingId") long oceanBookingId,
@Param("containerBookingId") long containerBookingId); @Param("containerBookingId") long containerBookingId);
@Select("""
<script>
SELECT q.container_booking_id AS containerBookingId,q.id AS queryId,q.version AS queryVersion,
q.status AS queryStatus,q.latest_standard_node AS latestActualNode,
q.latest_node_observed_at AS latestActualAt,
MAX(CASE WHEN f.standard_node='DEPARTED_WAREHOUSE' THEN f.actual_at END) AS departedWarehouseAt,
MAX(CASE WHEN f.standard_node='DEPARTED_ORIGIN_PORT' THEN f.actual_at END) AS departedOriginPortAt,
MAX(CASE WHEN f.standard_node='ARRIVED_DESTINATION_PORT' THEN f.actual_at END) AS arrivedDestinationPortAt,
MAX(CASE WHEN f.standard_node='PICKUP_READY' THEN f.actual_at END) AS pickupReadyAt,
MAX(CASE WHEN f.standard_node='PICKED_UP' THEN f.actual_at END) AS pickedUpAt,
MAX(CASE WHEN f.standard_node='EMPTY_RETURNED' THEN f.actual_at END) AS emptyReturnedAt
FROM fl_carrier_schedule_query q
JOIN (
SELECT container_booking_id,MAX(id) AS query_id
FROM fl_carrier_schedule_query
WHERE container_booking_id IN
<foreach collection="containerBookingIds" item="id" open="(" separator="," close=")">#{id}</foreach>
GROUP BY container_booking_id
) latest ON latest.query_id=q.id
LEFT JOIN fl_carrier_schedule_node_fact f ON f.query_id=q.id AND f.actual_at IS NOT NULL
GROUP BY q.container_booking_id,q.id,q.version,q.status,q.latest_standard_node,q.latest_node_observed_at
ORDER BY q.container_booking_id
</script>
""")
List<ContainerActualFacts> selectContainerActualFacts(
@Param("containerBookingIds") List<Long> containerBookingIds);
@Select(""" @Select("""
SELECT * FROM fl_carrier_schedule_query SELECT * FROM fl_carrier_schedule_query
WHERE status IN ('READY','MANUAL_REQUIRED') WHERE status IN ('READY','MANUAL_REQUIRED')
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baiyue.common.core.api.PageResult; import com.baiyue.common.core.api.PageResult;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.Query; import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.Query;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ContainerActualFacts;
import com.baiyue.firstleg.portschedule.entity.CarrierScheduleQueryAttemptEntity; import com.baiyue.firstleg.portschedule.entity.CarrierScheduleQueryAttemptEntity;
import com.baiyue.firstleg.portschedule.entity.CarrierScheduleQueryEntity; import com.baiyue.firstleg.portschedule.entity.CarrierScheduleQueryEntity;
import com.baiyue.firstleg.portschedule.entity.CarrierScheduleQueryHistoryEntity; import com.baiyue.firstleg.portschedule.entity.CarrierScheduleQueryHistoryEntity;
@@ -40,6 +41,9 @@ public class CarrierScheduleQueryRepository {
public CarrierScheduleQueryEntity findByBusinessScope(long customerId, long oceanBookingId, long containerBookingId) { public CarrierScheduleQueryEntity findByBusinessScope(long customerId, long oceanBookingId, long containerBookingId) {
return queries.findByBusinessScope(customerId, oceanBookingId, containerBookingId); return queries.findByBusinessScope(customerId, oceanBookingId, containerBookingId);
} }
public List<ContainerActualFacts> containerActualFacts(List<Long> containerBookingIds) {
return queries.selectContainerActualFacts(containerBookingIds);
}
public List<CarrierScheduleQueryEntity> due(LocalDateTime now, int limit) { return queries.selectDue(now, limit); } public List<CarrierScheduleQueryEntity> due(LocalDateTime now, int limit) { return queries.selectDue(now, limit); }
public boolean claimLease(long id, String owner, LocalDateTime now, LocalDateTime leaseUntil) { public boolean claimLease(long id, String owner, LocalDateTime now, LocalDateTime leaseUntil) {
return queries.claimLease(id, owner, now, leaseUntil) == 1; return queries.claimLease(id, owner, now, leaseUntil) == 1;
@@ -7,6 +7,7 @@ import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.event.ContainerNumbers; import com.baiyue.common.event.ContainerNumbers;
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingEntity; import com.baiyue.firstleg.oceanbooking.entity.OceanBookingEntity;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.CarrierDescriptor; import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.CarrierDescriptor;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ContainerActualFacts;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.CreateRequest; import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.CreateRequest;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.Detail; import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.Detail;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ManualResultRequest; import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ManualResultRequest;
@@ -48,6 +49,7 @@ import java.time.Clock;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.HexFormat; import java.util.HexFormat;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
@@ -76,6 +78,18 @@ public class CarrierScheduleQueryService {
source.page(), source.size(), source.total(), source.pages()); source.page(), source.size(), source.total(), source.pages());
} }
public List<ContainerActualFacts> containerActualFacts(List<Long> containerBookingIds) {
if (containerBookingIds == null || containerBookingIds.isEmpty() || containerBookingIds.size() > 200) {
throw badRequest("containerBookingIds数量必须在1到200之间");
}
Set<Long> unique = new HashSet<>();
for (Long id : containerBookingIds) {
if (id == null || id <= 0) throw badRequest("containerBookingIds必须全部为正数");
if (!unique.add(id)) throw badRequest("containerBookingIds不能包含重复值");
}
return repository.containerActualFacts(containerBookingIds);
}
public Detail detail(long id) { return detail(required(id)); } public Detail detail(long id) { return detail(required(id)); }
public List<History> history(long id) { public List<History> history(long id) {
@@ -10,6 +10,25 @@ paths:
x-permission: platform:firstleg:port-schedule-query:list x-permission: platform:firstleg:port-schedule-query:list
responses: responses:
'200': {description: 'MATSON, SML, ZIM, COSCO, EMC and HEDE descriptors'} '200': {description: 'MATSON, SML, ZIM, COSCO, EMC and HEDE descriptors'}
/api/platform/v1/carrier-schedule-queries/container-actual-facts:
get:
summary: Batch query actual carrier-node facts for container workbenches
description: Returns at most one row for each container that has a carrier-schedule query. Missing rows mean that no query exists. Estimated facts are never returned or inferred.
x-permission: platform:firstleg:port-schedule-query:list
parameters:
- name: containerBookingIds
in: query
required: true
schema:
type: array
minItems: 1
maxItems: 200
uniqueItems: true
items: {type: integer, format: int64, minimum: 1}
responses:
'200':
description: Actual-only summaries ordered by containerBookingId
content: {application/json: {schema: {$ref: '#/components/schemas/ContainerActualFactsListEnvelope'}}}
/api/platform/v1/carrier-schedule-queries: /api/platform/v1/carrier-schedule-queries:
get: get:
summary: Query carrier schedule tasks summary: Query carrier schedule tasks
@@ -175,6 +194,30 @@ components:
properties: properties:
query: {$ref: '#/components/schemas/Task'} query: {$ref: '#/components/schemas/Task'}
expired: {type: boolean} expired: {type: boolean}
ContainerActualFacts:
type: object
required: [containerBookingId, queryId, queryVersion, queryStatus]
description: Actual facts only. All node times are null until an immutable fact with actualAt exists; estimatedAt is intentionally absent.
properties:
containerBookingId: {type: integer, format: int64}
queryId: {type: integer, format: int64}
queryVersion: {type: integer, minimum: 0}
queryStatus: {$ref: '#/components/schemas/QueryStatus'}
latestActualNode: {allOf: [{$ref: '#/components/schemas/StandardNode'}], nullable: true}
latestActualAt: {type: string, format: date-time, nullable: true}
departedWarehouseAt: {type: string, format: date-time, nullable: true}
departedOriginPortAt: {type: string, format: date-time, nullable: true}
arrivedDestinationPortAt: {type: string, format: date-time, nullable: true}
pickupReadyAt: {type: string, format: date-time, nullable: true}
pickedUpAt: {type: string, format: date-time, nullable: true}
emptyReturnedAt: {type: string, format: date-time, nullable: true}
ContainerActualFactsListEnvelope:
type: object
required: [code, data]
properties:
code: {type: string, example: '200'}
message: {type: string, nullable: true}
data: {type: array, items: {$ref: '#/components/schemas/ContainerActualFacts'}}
NodeFact: NodeFact:
type: object type: object
required: [id, providerCode, providerEventId, providerNodeCode, standardNode, nodeSequence, observedAt, rawPayloadHash, createdAt] required: [id, providerCode, providerEventId, providerNodeCode, standardNode, nodeSequence, observedAt, rawPayloadHash, createdAt]
@@ -14,6 +14,18 @@ class CarrierScheduleQueryOpenApiContractTest {
.getContentAsString(StandardCharsets.UTF_8); .getContentAsString(StandardCharsets.UTF_8);
assertThat(yaml) assertThat(yaml)
.contains("/api/platform/v1/carrier-schedule-queries:") .contains("/api/platform/v1/carrier-schedule-queries:")
.contains("/api/platform/v1/carrier-schedule-queries/container-actual-facts:")
.contains("name: containerBookingIds")
.contains("maxItems: 200")
.contains("uniqueItems: true")
.contains("ContainerActualFacts:")
.contains("departedWarehouseAt:")
.contains("departedOriginPortAt:")
.contains("arrivedDestinationPortAt:")
.contains("pickupReadyAt:")
.contains("pickedUpAt:")
.contains("emptyReturnedAt:")
.contains("Estimated facts are never returned or inferred")
.contains("/api/platform/v1/carrier-schedule-queries/{queryId}/manual-results:") .contains("/api/platform/v1/carrier-schedule-queries/{queryId}/manual-results:")
.contains("/api/platform/v1/carrier-schedule-queries/{queryId}/manual-nodes:") .contains("/api/platform/v1/carrier-schedule-queries/{queryId}/manual-nodes:")
.contains("x-permission: platform:firstleg:port-schedule-query:manual") .contains("x-permission: platform:firstleg:port-schedule-query:manual")
@@ -18,6 +18,7 @@ class PlatformCarrierScheduleQueryControllerContractTest {
.containsExactly("/api/platform/v1/carrier-schedule-queries"); .containsExactly("/api/platform/v1/carrier-schedule-queries");
Map<String, String> permissions = Map.of( Map<String, String> permissions = Map.of(
"carriers", "platform:firstleg:port-schedule-query:list", "carriers", "platform:firstleg:port-schedule-query:list",
"containerActualFacts", "platform:firstleg:port-schedule-query:list",
"page", "platform:firstleg:port-schedule-query:list", "page", "platform:firstleg:port-schedule-query:list",
"detail", "platform:firstleg:port-schedule-query:list", "detail", "platform:firstleg:port-schedule-query:list",
"history", "platform:firstleg:port-schedule-query:list", "history", "platform:firstleg:port-schedule-query:list",
@@ -9,10 +9,31 @@ import org.springframework.core.io.ClassPathResource;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class CarrierScheduleQuerySqlContractTest { class CarrierScheduleQuerySqlContractTest {
@Test
void batchesActualContainerFactsWithoutEstimatedOrPerNodeQueries() throws Exception {
String sql = String.join("\n", CarrierScheduleQueryMapper.class
.getMethod("selectContainerActualFacts", List.class)
.getAnnotation(Select.class).value());
assertThat(sql)
.contains("container_booking_id IN")
.contains("f.actual_at IS NOT NULL")
.contains("MAX(CASE WHEN f.standard_node='DEPARTED_WAREHOUSE'")
.contains("MAX(CASE WHEN f.standard_node='DEPARTED_ORIGIN_PORT'")
.contains("MAX(CASE WHEN f.standard_node='ARRIVED_DESTINATION_PORT'")
.contains("MAX(CASE WHEN f.standard_node='PICKUP_READY'")
.contains("MAX(CASE WHEN f.standard_node='PICKED_UP'")
.contains("MAX(CASE WHEN f.standard_node='EMPTY_RETURNED'")
.contains("ORDER BY q.container_booking_id")
.doesNotContain("estimated_at");
assertThat(count(sql, "fl_carrier_schedule_node_fact")).isEqualTo(1);
}
@Test @Test
void annotationSqlUsesExecutableComparisonOperators() throws Exception { void annotationSqlUsesExecutableComparisonOperators() throws Exception {
String selectDue = String.join("\n", CarrierScheduleQueryMapper.class String selectDue = String.join("\n", CarrierScheduleQueryMapper.class
@@ -149,4 +170,8 @@ class CarrierScheduleQuerySqlContractTest {
return new ClassPathResource("db/phase3-carrier-schedule-query-schema.sql") return new ClassPathResource("db/phase3-carrier-schedule-query-schema.sql")
.getContentAsString(StandardCharsets.UTF_8); .getContentAsString(StandardCharsets.UTF_8);
} }
private static int count(String source, String needle) {
return source.split(java.util.regex.Pattern.quote(needle), -1).length - 1;
}
} }
@@ -4,6 +4,7 @@ import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingContainerAllocationEntity; import com.baiyue.firstleg.oceanbooking.entity.OceanBookingContainerAllocationEntity;
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingEntity; import com.baiyue.firstleg.oceanbooking.entity.OceanBookingEntity;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.CreateRequest; import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.CreateRequest;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ContainerActualFacts;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ManualResultRequest; import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ManualResultRequest;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ManualNodeRequest; import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ManualNodeRequest;
import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ProviderObservationRequest; import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ProviderObservationRequest;
@@ -72,6 +73,35 @@ class CarrierScheduleQueryServiceTest {
when(portSchedules.containerBooking(20L)).thenReturn(container); when(portSchedules.containerBooking(20L)).thenReturn(container);
} }
@Test
void returnsBatchActualFactsFromOneRepositoryCall() {
ContainerActualFacts facts = new ContainerActualFacts(20L, 30L, 2, "READY",
"PICKUP_READY", NOW, NOW.minusDays(12), NOW.minusDays(10), NOW.minusDays(2),
NOW, null, null);
when(repository.containerActualFacts(List.of(20L, 21L))).thenReturn(List.of(facts));
assertThat(service.containerActualFacts(List.of(20L, 21L))).containsExactly(facts);
verify(repository).containerActualFacts(List.of(20L, 21L));
}
@Test
void rejectsInvalidActualFactBatchBeforeRepositoryAccess() {
assertThatThrownBy(() -> service.containerActualFacts(null))
.isInstanceOf(BizException.class).hasMessageContaining("1到200");
assertThatThrownBy(() -> service.containerActualFacts(List.of()))
.isInstanceOf(BizException.class).hasMessageContaining("1到200");
assertThatThrownBy(() -> service.containerActualFacts(java.util.Collections.nCopies(201, 1L)))
.isInstanceOf(BizException.class).hasMessageContaining("1到200");
assertThatThrownBy(() -> service.containerActualFacts(java.util.Arrays.asList(1L, null)))
.isInstanceOf(BizException.class).hasMessageContaining("正数");
assertThatThrownBy(() -> service.containerActualFacts(List.of(1L, 0L)))
.isInstanceOf(BizException.class).hasMessageContaining("正数");
assertThatThrownBy(() -> service.containerActualFacts(List.of(1L, 1L)))
.isInstanceOf(BizException.class).hasMessageContaining("重复");
verify(repository, never()).containerActualFacts(any());
}
@Test @Test
void createsTaskOnlyForBookingCustomerAndAllocatedContainer() { void createsTaskOnlyForBookingCustomerAndAllocatedContainer() {
OceanBookingEntity booking = new OceanBookingEntity(); booking.setCustomerId(10001L); OceanBookingEntity booking = new OceanBookingEntity(); booking.setCustomerId(10001L);