fix(firstleg): allow optional ocean booking customer

This commit is contained in:
2026-09-09 11:21:37 +08:00
parent 81b729929c
commit f37e9ce555
6 changed files with 144 additions and 30 deletions
@@ -34,6 +34,7 @@ import com.baiyue.firstleg.oceanbooking.entity.OceanBookingOperationEntity;
@Service
public class OceanBookingDocumentService {
public static final String BUSINESS_TYPE = "OCEAN_BOOKING_DOCUMENT";
private static final long FILE_TENANT_ID = 0L;
private static final Set<String> DOCUMENT_TYPES = Set.of("BOOKING_CONFIRMATION", "BILL_OF_LADING");
@Resource private OceanBookingRepository repository;
@@ -54,7 +55,7 @@ public class OceanBookingDocumentService {
OceanBookingEntity booking = lockDraft(oceanBookingId, expectedVersion);
OceanBookingFileClient.Metadata metadata;
try {
var response = files.upload(internalToken, operatorId, booking.getCustomerId(), BUSINESS_TYPE,
var response = files.upload(internalToken, operatorId, FILE_TENANT_ID, BUSINESS_TYPE,
Long.toString(oceanBookingId), file);
if (response == null || !response.successful() || response.data() == null) {
throw bad("订舱文件上传失败");
@@ -115,7 +116,7 @@ public class OceanBookingDocumentService {
}
metadata(booking, document.getFileId());
try {
ResponseEntity<byte[]> response = files.download(internalToken, booking.getCustomerId(), BUSINESS_TYPE,
ResponseEntity<byte[]> response = files.download(internalToken, FILE_TENANT_ID, BUSINESS_TYPE,
Long.toString(oceanBookingId), document.getFileId());
byte[] content = response.getBody();
if (!response.getStatusCode().is2xxSuccessful() || content == null || content.length == 0) {
@@ -132,9 +133,6 @@ public class OceanBookingDocumentService {
}
public void requireActiveValidDocument(OceanBookingEntity booking) {
if (booking.getCustomerId() == null || booking.getCustomerId() <= 0) {
throw conflict("订舱缺少客户归属,无法校验文件");
}
boolean valid = false;
for (OceanBookingDocumentEntity document : repository.activeDocuments(booking.getId())) {
if (!DOCUMENT_TYPES.contains(document.getDocumentType())) continue;
@@ -178,9 +176,6 @@ public class OceanBookingDocumentService {
if (booking == null) throw new BizException(ResultCode.NOT_FOUND, "订舱单不存在");
if (!Integer.valueOf(expectedVersion).equals(booking.getVersion())) throw concurrent();
if (!"DRAFT".equals(booking.getStatus())) throw conflict("已放舱订舱文件永久只读");
if (booking.getCustomerId() == null || booking.getCustomerId() <= 0) {
throw bad("请先保存订舱客户后再上传文件");
}
return booking;
}
@@ -192,7 +187,7 @@ public class OceanBookingDocumentService {
private OceanBookingFileClient.Metadata metadata(OceanBookingEntity booking, long fileId) {
try {
var response = files.metadata(internalToken, booking.getCustomerId(), BUSINESS_TYPE,
var response = files.metadata(internalToken, FILE_TENANT_ID, BUSINESS_TYPE,
Long.toString(booking.getId()), fileId);
if (response == null || !response.successful() || response.data() == null) {
throw new BizException(ResultCode.NOT_FOUND, "订舱文件不存在或归属不匹配");
@@ -207,7 +202,7 @@ public class OceanBookingDocumentService {
}
private static void validateMetadata(OceanBookingEntity booking, OceanBookingFileClient.Metadata metadata) {
if (metadata.fileId() == null || !Objects.equals(metadata.tenantId(), booking.getCustomerId())
if (metadata.fileId() == null || !Objects.equals(metadata.tenantId(), FILE_TENANT_ID)
|| !BUSINESS_TYPE.equals(metadata.businessType())
|| !Long.toString(booking.getId()).equals(metadata.businessId())) {
throw new BizException(ResultCode.VALIDATION_FAILED, "订舱文件归属不匹配");
@@ -50,6 +50,7 @@ public class OceanBookingService {
@Resource private WarehousePartnerClient partners;
@Resource private FirstlegDocumentNumberGenerator documentNumbers;
@Resource private OceanBookingDocumentService documents;
@Resource private com.baiyue.firstleg.service.IamCustomerSnapshotService customers;
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
@Value("${baiyue.warehouse.public-tenant-id:0}") private long publicWarehouseTenantId;
@@ -200,7 +201,7 @@ public class OceanBookingService {
private void validateDraftStructure(DraftData data) {
boolean hasCustomerId = data.customerId() != null;
boolean hasCustomerName = StringUtils.hasText(data.customerName());
if (hasCustomerId != hasCustomerName) throw badRequest("客户ID和客户名称必须同时填写或同时为空");
if (!hasCustomerId && hasCustomerName) throw badRequest("客户名称不能脱离客户ID提交");
if (data.parties() != null) {
Set<String> types = new HashSet<>();
for (PartyRequest party : data.parties()) {
@@ -221,8 +222,7 @@ public class OceanBookingService {
private static void validateRange(LocalDateTime from, LocalDateTime to) { if (from != null && to != null && to.isBefore(from)) throw badRequest("预计到港时间不能早于预计开船时间"); }
private static void validateRelease(OceanBookingEntity booking, List<OceanBookingPartyEntity> parties,
List<OceanBookingContainerRequirementEntity> requirements) {
if (booking.getCustomerId() == null || !StringUtils.hasText(booking.getCustomerName())
|| !StringUtils.hasText(booking.getChannelCode()) || booking.getWarehouseId() == null
if (!StringUtils.hasText(booking.getChannelCode()) || booking.getWarehouseId() == null
|| !StringUtils.hasText(booking.getBillOfLadingNo()) || !StringUtils.hasText(booking.getVesselName())
|| !StringUtils.hasText(booking.getVoyageNo()) || !StringUtils.hasText(booking.getServiceTerm())
|| !StringUtils.hasText(booking.getFreightTerm()) || booking.getEstimatedSailingAt() == null
@@ -258,8 +258,9 @@ public class OceanBookingService {
}
private record ResolvedDraft(String warehouseName, String channelName, String shippingCompanyName,
String carrierCode, String bookingAgentName) {}
String carrierCode, String bookingAgentName, String customerName) {}
private ResolvedDraft resolveDraft(DraftData d) {
String customerName = resolveCustomer(d.customerId(), d.customerName());
String warehouseName = null; String channelName = null;
if (d.warehouseId() != null && StringUtils.hasText(d.channelCode())) {
WarehouseMasterDataClient.Warehouse warehouse = requireDomesticTransitWarehouse(d.warehouseId());
@@ -269,10 +270,10 @@ public class OceanBookingService {
var carrier = resolveCarrier(d.shippingCompanyId(), d.shippingCompanyName(), d.carrierCode());
var agent = resolveBookingAgent(d.bookingAgentId(), d.bookingAgentName(), carrier == null ? null : carrier.carrierCode());
return new ResolvedDraft(warehouseName, channelName, carrier == null ? null : carrier.partnerName(),
carrier == null ? null : carrier.carrierCode(), agent == null ? null : agent.name());
carrier == null ? null : carrier.carrierCode(), agent == null ? null : agent.name(), customerName);
}
private static void copy(OceanBookingEntity v, DraftData d, ResolvedDraft resolved) {
v.setCustomerId(d.customerId()); v.setCustomerName(trim(d.customerName()));
v.setCustomerId(d.customerId()); v.setCustomerName(resolved.customerName());
v.setChannelCode(trim(d.channelCode())); v.setChannelName(resolved.channelName());
v.setWarehouseId(d.warehouseId()); v.setWarehouseName(resolved.warehouseName());
v.setBillOfLadingNo(trim(d.billOfLadingNo()));
@@ -288,10 +289,20 @@ public class OceanBookingService {
}
private void validateMasterData(OceanBookingEntity booking) {
resolveCustomer(booking.getCustomerId(), booking.getCustomerName());
resolveCarrier(booking.getShippingCompanyId(), booking.getShippingCompanyName(), booking.getCarrierCode());
resolveBookingAgent(booking.getBookingAgentId(), booking.getBookingAgentName(), booking.getCarrierCode());
}
private String resolveCustomer(Long id, String name) {
if (id == null) {
if (StringUtils.hasText(name)) throw badRequest("客户名称不能脱离客户ID提交");
return null;
}
if (id <= 0) throw badRequest("请选择有效客户");
return customers.active(id, name).tenantName();
}
private WarehousePartnerClient.Snapshot resolveCarrier(Long id, String suppliedName, String suppliedCode) {
if (id == null) {
if (StringUtils.hasText(suppliedName) || StringUtils.hasText(suppliedCode)) throw badRequest("船公司名称和编码不能脱离shippingCompanyId提交");