优化
This commit is contained in:
+2
-2
@@ -29,7 +29,7 @@ public interface CustomerTransitStatisticsMapper {
|
||||
JOIN fl_ocean_booking b ON b.id = c.ocean_booking_id
|
||||
WHERE c.actual_sailing_at IS NOT NULL
|
||||
AND c.actual_warehouse_arrival_at IS NULL
|
||||
AND b.status NOT IN ('CANCELLED', 'FAILED')
|
||||
AND b.status <> 'CANCELLED'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM fl_loading_plan p
|
||||
JOIN fl_loading_plan_item i ON i.loading_plan_id = p.id
|
||||
@@ -43,7 +43,7 @@ public interface CustomerTransitStatisticsMapper {
|
||||
JOIN fl_loading_plan_item i ON i.loading_plan_id = p.id AND i.deleted = 0
|
||||
WHERE c.actual_sailing_at IS NOT NULL
|
||||
AND c.actual_warehouse_arrival_at IS NULL
|
||||
AND b.status NOT IN ('CANCELLED', 'FAILED')
|
||||
AND b.status <> 'CANCELLED'
|
||||
AND i.account_id IS NOT NULL AND i.account_id > 0
|
||||
) x
|
||||
WHERE x.customer_id = #{customerId}
|
||||
|
||||
+1
-16
@@ -36,23 +36,11 @@ public class PlatformOceanBookingController {
|
||||
public ApiResponse<Detail> detail(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long oceanBookingId) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.detail(oceanBookingId)); }
|
||||
|
||||
@PostMapping @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:create')")
|
||||
@BusinessAudit(event="OCEAN_BOOKING_CREATE", resourceType="OCEAN_BOOKING", description="创建海运订舱单", captureBefore=false)
|
||||
@BusinessAudit(event="OCEAN_BOOKING_CREATE", resourceType="OCEAN_BOOKING", description="登记已确认海运订舱", captureBefore=false)
|
||||
public ApiResponse<Detail> create(@AuthenticationPrincipal AuthenticatedUser user, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody CreateRequest request) { validateOptionalCustomer(user, request.data().customerId()); return ApiResponse.success(service.create(user.userId(), user.username(), key, request)); }
|
||||
@PutMapping("/{oceanBookingId}") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:edit')")
|
||||
@BusinessAudit(event="OCEAN_BOOKING_UPDATE", resourceType="OCEAN_BOOKING", resourceId="#oceanBookingId", description="编辑海运订舱草稿")
|
||||
public ApiResponse<Detail> updateDraft(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long oceanBookingId, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody UpdateDraftRequest request) { validateOptionalCustomer(user, request.data().customerId()); return ApiResponse.success(service.updateDraft(oceanBookingId, user.userId(), user.username(), key, request)); }
|
||||
@PostMapping("/{oceanBookingId}/submit") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:submit')")
|
||||
@BusinessAudit(event="OCEAN_BOOKING_SUBMIT", resourceType="OCEAN_BOOKING", resourceId="#oceanBookingId", description="提交海运订舱")
|
||||
public ApiResponse<Detail> submit(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long oceanBookingId, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody ActionRequest request) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.submit(oceanBookingId, user.userId(), user.username(), key, request)); }
|
||||
@PostMapping("/{oceanBookingId}/confirm") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:confirm')")
|
||||
@BusinessAudit(event="OCEAN_BOOKING_CONFIRM", resourceType="OCEAN_BOOKING", resourceId="#oceanBookingId", description="确认海运订舱")
|
||||
public ApiResponse<Detail> confirm(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long oceanBookingId, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody ActionRequest request) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.confirm(oceanBookingId, user.userId(), user.username(), key, request)); }
|
||||
@PostMapping("/{oceanBookingId}/release") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:release')")
|
||||
@BusinessAudit(event="OCEAN_BOOKING_RELEASE", resourceType="OCEAN_BOOKING", resourceId="#oceanBookingId", description="确认放舱")
|
||||
public ApiResponse<Detail> release(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long oceanBookingId, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody ReleaseRequest request) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.release(oceanBookingId, user.userId(), user.username(), key, request)); }
|
||||
@PostMapping("/{oceanBookingId}/fail") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:confirm')")
|
||||
@BusinessAudit(event="OCEAN_BOOKING_FAIL", resourceType="OCEAN_BOOKING", resourceId="#oceanBookingId", description="标记海运订舱失败")
|
||||
public ApiResponse<Detail> fail(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long oceanBookingId, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody ActionRequest request) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.fail(oceanBookingId, user.userId(), user.username(), key, request)); }
|
||||
@PostMapping("/{oceanBookingId}/cancel") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:cancel')")
|
||||
@BusinessAudit(event="OCEAN_BOOKING_CANCEL", resourceType="OCEAN_BOOKING", resourceId="#oceanBookingId", description="取消海运订舱")
|
||||
public ApiResponse<Detail> cancel(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long oceanBookingId, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody ActionRequest request) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.cancel(oceanBookingId, user.userId(), user.username(), key, request)); }
|
||||
@@ -62,8 +50,5 @@ public class PlatformOceanBookingController {
|
||||
@PostMapping("/{oceanBookingId}/change-confirm") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:change')")
|
||||
@BusinessAudit(event="OCEAN_BOOKING_CHANGE_CONFIRM", resourceType="OCEAN_BOOKING", resourceId="#oceanBookingId", description="确认变更船期")
|
||||
public ApiResponse<Detail> confirmChange(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long oceanBookingId, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody ActionRequest request) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.confirmChange(oceanBookingId, user.userId(), user.username(), key, request)); }
|
||||
@PostMapping("/{oceanBookingId}/change-fail") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:change')")
|
||||
@BusinessAudit(event="OCEAN_BOOKING_CHANGE_FAIL", resourceType="OCEAN_BOOKING", resourceId="#oceanBookingId", description="标记变更船期失败")
|
||||
public ApiResponse<Detail> failChange(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long oceanBookingId, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody ActionRequest request) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.failChange(oceanBookingId, user.userId(), user.username(), key, request)); }
|
||||
private static void validateOptionalCustomer(AuthenticatedUser user, Long customerId) { FirstlegScopeGuard.platform(user); if (customerId != null) FirstlegScopeGuard.platform(user, customerId); }
|
||||
}
|
||||
|
||||
@@ -2,10 +2,8 @@ package com.baiyue.firstleg.oceanbooking.domain;
|
||||
|
||||
public enum OceanBookingStatus {
|
||||
DRAFT,
|
||||
SUBMITTED,
|
||||
CONFIRMED,
|
||||
RELEASED,
|
||||
FAILED,
|
||||
CHANGE_PENDING,
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
@@ -44,13 +44,13 @@ public final class OceanBookingDtos {
|
||||
@NotBlank @Size(max = 64) String channelCode,
|
||||
@NotNull @Positive Long warehouseId,
|
||||
@NotBlank @Size(max = 128) String warehouseName,
|
||||
@Size(max = 128) String billOfLadingNo,
|
||||
@NotBlank @Size(max = 128) String billOfLadingNo,
|
||||
@Positive Long shippingCompanyId,
|
||||
@Size(max = 128) String shippingCompanyName,
|
||||
@Positive Long bookingAgentId,
|
||||
@Size(max = 128) String bookingAgentName,
|
||||
@Size(max = 128) String vesselName,
|
||||
@Size(max = 64) String voyageNo,
|
||||
@NotBlank @Size(max = 128) String vesselName,
|
||||
@NotBlank @Size(max = 64) String voyageNo,
|
||||
@Size(max = 32) String departurePortCode,
|
||||
@Size(max = 128) String departurePortName,
|
||||
@Size(max = 32) String destinationPortCode,
|
||||
@@ -58,16 +58,14 @@ public final class OceanBookingDtos {
|
||||
@Size(max = 128) String placeOfDelivery,
|
||||
@NotBlank @Size(max = 32) String serviceTerm,
|
||||
@NotBlank @Size(max = 32) String freightTerm,
|
||||
LocalDateTime estimatedSailingAt,
|
||||
LocalDateTime estimatedArrivalAt,
|
||||
@NotNull LocalDateTime estimatedSailingAt,
|
||||
@NotNull LocalDateTime estimatedArrivalAt,
|
||||
@Size(max = 1000) String remark,
|
||||
@Valid @NotEmpty @Size(max = 3) List<PartyRequest> parties,
|
||||
@Valid @NotEmpty @Size(max = 20) List<ContainerRequirementRequest> containerRequirements) {}
|
||||
|
||||
public record CreateRequest(@NotNull @Min(0) Integer expectedVersion,
|
||||
@Valid @NotNull DraftData data) {}
|
||||
public record UpdateDraftRequest(@NotNull @Min(0) Integer expectedVersion,
|
||||
@Valid @NotNull DraftData data) {}
|
||||
public record ActionRequest(@NotNull @Min(0) Integer expectedVersion,
|
||||
@Size(max = 1000) String reason) {}
|
||||
public record ReleaseRequest(@NotNull @Min(0) Integer expectedVersion,
|
||||
|
||||
@@ -45,7 +45,6 @@ public class OceanBookingEntity {
|
||||
private LocalDateTime pendingEstimatedSailingAt;
|
||||
private LocalDateTime pendingEstimatedArrivalAt;
|
||||
private String changeReason;
|
||||
private String failureReason;
|
||||
private String cancellationReason;
|
||||
private String remark;
|
||||
private Long createdBy;
|
||||
|
||||
+2
-20
@@ -30,31 +30,13 @@ public interface OceanBookingMapper extends BaseMapper<OceanBookingEntity> {
|
||||
Page<OceanBookingEntity> selectPage(Page<OceanBookingEntity> page, @Param("query") Query query);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_ocean_booking SET customer_id=#{value.customerId},customer_name=#{value.customerName},
|
||||
channel_code=#{value.channelCode},channel_name=#{value.channelName},warehouse_id=#{value.warehouseId},warehouse_name=#{value.warehouseName},
|
||||
bill_of_lading_no=#{value.billOfLadingNo},shipping_company_id=#{value.shippingCompanyId},
|
||||
shipping_company_name=#{value.shippingCompanyName},booking_agent_id=#{value.bookingAgentId},
|
||||
booking_agent_name=#{value.bookingAgentName},vessel_name=#{value.vesselName},voyage_no=#{value.voyageNo},
|
||||
departure_port_code=#{value.departurePortCode},departure_port_name=#{value.departurePortName},
|
||||
destination_port_code=#{value.destinationPortCode},destination_port_name=#{value.destinationPortName},
|
||||
place_of_delivery=#{value.placeOfDelivery},service_term=#{value.serviceTerm},freight_term=#{value.freightTerm},
|
||||
estimated_sailing_at=#{value.estimatedSailingAt},estimated_arrival_at=#{value.estimatedArrivalAt},
|
||||
remark=#{value.remark},updated_by=#{operatorId},updated_by_name=#{operatorName},
|
||||
updated_at=CURRENT_TIMESTAMP(3),version=version+1
|
||||
WHERE id=#{value.id} AND status='DRAFT' AND version=#{expectedVersion}
|
||||
""")
|
||||
int updateDraft(@Param("value") OceanBookingEntity value, @Param("expectedVersion") int expectedVersion,
|
||||
@Param("operatorId") long operatorId, @Param("operatorName") String operatorName);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_ocean_booking SET status=#{toStatus},failure_reason=#{failureReason},
|
||||
UPDATE fl_ocean_booking SET status=#{toStatus},
|
||||
cancellation_reason=#{cancellationReason},updated_by=#{operatorId},updated_by_name=#{operatorName},
|
||||
updated_at=CURRENT_TIMESTAMP(3),version=version+1
|
||||
WHERE id=#{oceanBookingId} AND status=#{fromStatus} AND version=#{expectedVersion}
|
||||
""")
|
||||
int transition(@Param("oceanBookingId") long oceanBookingId, @Param("fromStatus") String fromStatus,
|
||||
@Param("toStatus") String toStatus, @Param("failureReason") String failureReason,
|
||||
@Param("cancellationReason") String cancellationReason,
|
||||
@Param("toStatus") String toStatus, @Param("cancellationReason") String cancellationReason,
|
||||
@Param("expectedVersion") int expectedVersion, @Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
|
||||
+2
-5
@@ -28,12 +28,9 @@ public class OceanBookingRepository {
|
||||
}
|
||||
public OceanBookingEntity find(long id) { return bookings.selectById(id); }
|
||||
public void insert(OceanBookingEntity value) { bookings.insert(value); }
|
||||
public int updateDraft(OceanBookingEntity value, int version, long operatorId, String operatorName) {
|
||||
return bookings.updateDraft(value, version, operatorId, operatorName);
|
||||
}
|
||||
public int transition(long id, String from, String to, String failureReason, String cancellationReason,
|
||||
public int transition(long id, String from, String to, String cancellationReason,
|
||||
int version, long operatorId, String operatorName) {
|
||||
return bookings.transition(id, from, to, failureReason, cancellationReason, version, operatorId, operatorName);
|
||||
return bookings.transition(id, from, to, cancellationReason, version, operatorId, operatorName);
|
||||
}
|
||||
public int release(long id, ReleaseRequest request, long operatorId, String operatorName) {
|
||||
return bookings.release(id, request.billOfLadingNo().trim(), request.vesselName().trim(),
|
||||
|
||||
+29
-100
@@ -67,6 +67,7 @@ public class OceanBookingService {
|
||||
public Detail create(long operatorId, String operatorName, String key, CreateRequest request) {
|
||||
if (request.expectedVersion() != 0) throw concurrent();
|
||||
validateDraftStructure(request.data());
|
||||
validateRange(request.data().estimatedSailingAt(), request.data().estimatedArrivalAt());
|
||||
String action = "CREATE";
|
||||
String hash = hash(action, null, request);
|
||||
Detail replay = claimOrReplay(null, operatorId, action, key, hash);
|
||||
@@ -75,6 +76,8 @@ public class OceanBookingService {
|
||||
OceanBookingEntity value = new OceanBookingEntity();
|
||||
value.setId(IdWorker.getId());
|
||||
value.setBookingNo("OB" + value.getId());
|
||||
// Child-row triggers still require DRAFT while the aggregate is assembled.
|
||||
// The status transition is in the same transaction, so callers only observe CONFIRMED.
|
||||
value.setStatus(OceanBookingStatus.DRAFT.name());
|
||||
copy(value, request.data(), channel.channelName());
|
||||
value.setCreatedBy(operatorId); value.setCreatedByName(operatorName);
|
||||
@@ -82,46 +85,16 @@ public class OceanBookingService {
|
||||
repository.insert(value);
|
||||
repository.replaceParties(value.getId(), parties(value.getId(), request.data().parties()));
|
||||
repository.replaceRequirements(value.getId(), requirements(value.getId(), request.data().containerRequirements()));
|
||||
addHistory(value.getId(), null, OceanBookingStatus.DRAFT.name(), action, null, null, value, operatorId, operatorName);
|
||||
return complete(value.getId(), operatorId, action, key, detail(value.getId()));
|
||||
if (repository.transition(value.getId(), OceanBookingStatus.DRAFT.name(),
|
||||
OceanBookingStatus.CONFIRMED.name(), null, 0, operatorId, operatorName) != 1) {
|
||||
throw concurrent();
|
||||
}
|
||||
OceanBookingEntity confirmed = required(value.getId());
|
||||
addHistory(value.getId(), OceanBookingStatus.DRAFT.name(), OceanBookingStatus.CONFIRMED.name(), action,
|
||||
null, value, confirmed, operatorId, operatorName);
|
||||
return complete(value.getId(), operatorId, action, key, detail(confirmed));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Detail updateDraft(long oceanBookingId, long operatorId, String operatorName, String key,
|
||||
UpdateDraftRequest request) {
|
||||
validateDraftStructure(request.data());
|
||||
String action = "UPDATE_DRAFT";
|
||||
Detail replay = claimOrReplay(oceanBookingId, operatorId, action, key, hash(action, oceanBookingId, request));
|
||||
if (replay != null) return replay;
|
||||
OceanBookingEntity before = requiredVersion(oceanBookingId, request.expectedVersion());
|
||||
requireStatus(before, OceanBookingStatus.DRAFT);
|
||||
ChannelOption channel = requireActiveChannel(request.data().warehouseId(), request.data().channelCode());
|
||||
OceanBookingEntity value = new OceanBookingEntity(); value.setId(oceanBookingId); copy(value, request.data(), channel.channelName());
|
||||
if (repository.updateDraft(value, request.expectedVersion(), operatorId, operatorName) != 1) throw concurrent();
|
||||
repository.replaceParties(oceanBookingId, parties(oceanBookingId, request.data().parties()));
|
||||
repository.replaceRequirements(oceanBookingId, requirements(oceanBookingId, request.data().containerRequirements()));
|
||||
return complete(oceanBookingId, operatorId, action, key, detail(oceanBookingId));
|
||||
}
|
||||
|
||||
@Transactional public Detail submit(long id, long operatorId, String name, String key, ActionRequest request) {
|
||||
return transition(id, operatorId, name, key, "SUBMIT", OceanBookingStatus.DRAFT,
|
||||
OceanBookingStatus.SUBMITTED, request, false);
|
||||
}
|
||||
@Transactional public Detail confirm(long id, long operatorId, String name, String key, ActionRequest request) {
|
||||
String action = "CONFIRM";
|
||||
Detail replay = claimOrReplay(id, operatorId, action, key, hash(action, id, request));
|
||||
if (replay != null) return replay;
|
||||
OceanBookingEntity before = requiredVersion(id, request.expectedVersion());
|
||||
requireStatus(before, OceanBookingStatus.SUBMITTED);
|
||||
requireActiveChannel(before.getWarehouseId(), before.getChannelCode());
|
||||
validateConfirmation(before, repository.requirements(id));
|
||||
if (repository.transition(id, OceanBookingStatus.SUBMITTED.name(), OceanBookingStatus.CONFIRMED.name(),
|
||||
null, null, request.expectedVersion(), operatorId, name) != 1) throw concurrent();
|
||||
OceanBookingEntity after = required(id);
|
||||
addHistory(id, before.getStatus(), after.getStatus(), action, request.reason(), before, after,
|
||||
operatorId, name);
|
||||
return complete(id, operatorId, action, key, detail(after));
|
||||
}
|
||||
@Transactional public Detail release(long id, long operatorId, String name, String key, ReleaseRequest request) {
|
||||
validateRange(request.estimatedSailingAt(), request.estimatedArrivalAt());
|
||||
String action = "RELEASE";
|
||||
@@ -129,6 +102,7 @@ public class OceanBookingService {
|
||||
if (replay != null) return replay;
|
||||
OceanBookingEntity before = requiredVersion(id, request.expectedVersion());
|
||||
requireStatus(before, OceanBookingStatus.CONFIRMED);
|
||||
validateRegisteredTransport(before, request);
|
||||
if (repository.release(id, request, operatorId, name) != 1) throw concurrent();
|
||||
repository.syncReleasedTransport(id, request, operatorId, name);
|
||||
repository.allocations(id).forEach(allocation ->
|
||||
@@ -139,28 +113,6 @@ public class OceanBookingService {
|
||||
return complete(id, operatorId, action, key, detail(after));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Detail fail(long id, long operatorId, String name, String key, ActionRequest request) {
|
||||
if (!StringUtils.hasText(request.reason())) throw badRequest("失败原因不能为空");
|
||||
String action = "FAIL";
|
||||
Detail replay = claimOrReplay(id, operatorId, action, key, hash(action, id, request));
|
||||
if (replay != null) return replay;
|
||||
OceanBookingEntity before = requiredVersion(id, request.expectedVersion());
|
||||
requireStatus(before, OceanBookingStatus.SUBMITTED);
|
||||
if (repository.transition(id, OceanBookingStatus.SUBMITTED.name(), OceanBookingStatus.FAILED.name(), trim(request.reason()), null,
|
||||
request.expectedVersion(), operatorId, name) != 1) throw concurrent();
|
||||
OceanBookingEntity after = required(id);
|
||||
addHistory(id, OceanBookingStatus.SUBMITTED.name(), after.getStatus(), action, request.reason(), before, after, operatorId, name);
|
||||
return complete(id, operatorId, action, key, detail(after));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Detail failChange(long id, long operatorId, String name, String key, ActionRequest request) {
|
||||
if (!StringUtils.hasText(request.reason())) throw badRequest("改船失败原因不能为空");
|
||||
return transition(id, operatorId, name, key, "FAIL_CHANGE", OceanBookingStatus.CHANGE_PENDING,
|
||||
OceanBookingStatus.FAILED, request, true);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Detail cancel(long id, long operatorId, String name, String key, ActionRequest request) {
|
||||
if (!StringUtils.hasText(request.reason())) throw badRequest("取消原因不能为空");
|
||||
@@ -172,7 +124,7 @@ public class OceanBookingService {
|
||||
if (from == OceanBookingStatus.RELEASED || from == OceanBookingStatus.CANCELLED) {
|
||||
throw conflict("订舱已越过不可逆节点或已经取消");
|
||||
}
|
||||
if (repository.transition(id, from.name(), OceanBookingStatus.CANCELLED.name(), null,
|
||||
if (repository.transition(id, from.name(), OceanBookingStatus.CANCELLED.name(),
|
||||
trim(request.reason()), request.expectedVersion(), operatorId, name) != 1) throw concurrent();
|
||||
OceanBookingEntity after = required(id);
|
||||
addHistory(id, from.name(), after.getStatus(), action, request.reason(), before, after, operatorId, name);
|
||||
@@ -211,20 +163,6 @@ public class OceanBookingService {
|
||||
return complete(id, operatorId, action, key, detail(after));
|
||||
}
|
||||
|
||||
private Detail transition(long id, long operatorId, String name, String key, String action,
|
||||
OceanBookingStatus from, OceanBookingStatus to, ActionRequest request,
|
||||
boolean failure) {
|
||||
Detail replay = claimOrReplay(id, operatorId, action, key, hash(action, id, request));
|
||||
if (replay != null) return replay;
|
||||
OceanBookingEntity before = requiredVersion(id, request.expectedVersion());
|
||||
requireStatus(before, from);
|
||||
if (from == OceanBookingStatus.DRAFT && to == OceanBookingStatus.SUBMITTED) validateSubmission(id);
|
||||
if (repository.transition(id, from.name(), to.name(), failure ? trim(request.reason()) : null, null,
|
||||
request.expectedVersion(), operatorId, name) != 1) throw concurrent();
|
||||
OceanBookingEntity after = required(id);
|
||||
addHistory(id, from.name(), to.name(), action, request.reason(), before, after, operatorId, name);
|
||||
return complete(id, operatorId, action, key, detail(after));
|
||||
}
|
||||
|
||||
private Detail claimOrReplay(Long id, long operatorId, String action, String key, String hash) {
|
||||
validateKey(key);
|
||||
@@ -266,10 +204,10 @@ public class OceanBookingService {
|
||||
if (!StringUtils.hasText(data.serviceTerm()) || !StringUtils.hasText(data.freightTerm())) {
|
||||
throw badRequest("Service Term和Freight Term不能为空");
|
||||
}
|
||||
if (data.estimatedSailingAt() != null || data.estimatedArrivalAt() != null
|
||||
|| StringUtils.hasText(data.billOfLadingNo()) || StringUtils.hasText(data.vesselName())
|
||||
|| StringUtils.hasText(data.voyageNo())) {
|
||||
throw badRequest("真实运输数据请在确认放舱时录入");
|
||||
if (!StringUtils.hasText(data.billOfLadingNo()) || !StringUtils.hasText(data.vesselName())
|
||||
|| !StringUtils.hasText(data.voyageNo()) || data.estimatedSailingAt() == null
|
||||
|| data.estimatedArrivalAt() == null) {
|
||||
throw badRequest("提单号、船名、航次、ETD和ETA不能为空");
|
||||
}
|
||||
Set<String> types = new HashSet<>();
|
||||
for (PartyRequest party : data.parties()) {
|
||||
@@ -278,14 +216,16 @@ public class OceanBookingService {
|
||||
}
|
||||
if (!types.equals(PARTY_TYPES)) throw badRequest("订舱方必须各包含一条SHIPPER、CONSIGNEE和NOTIFY_PARTY");
|
||||
}
|
||||
private void validateSubmission(long id) {
|
||||
Set<String> types = repository.parties(id).stream().map(OceanBookingPartyEntity::getPartyType)
|
||||
.collect(java.util.stream.Collectors.toSet());
|
||||
if (!types.equals(PARTY_TYPES) || repository.requirements(id).isEmpty()) {
|
||||
throw conflict("订舱方或箱型需求不完整,不能提交");
|
||||
private static void validateRange(LocalDateTime from, LocalDateTime to) { if (from != null && to != null && to.isBefore(from)) throw badRequest("预计到港时间不能早于预计开船时间"); }
|
||||
private static void validateRegisteredTransport(OceanBookingEntity booking, ReleaseRequest request) {
|
||||
if (!java.util.Objects.equals(trim(booking.getBillOfLadingNo()), trim(request.billOfLadingNo()))
|
||||
|| !java.util.Objects.equals(trim(booking.getVesselName()), trim(request.vesselName()))
|
||||
|| !java.util.Objects.equals(trim(booking.getVoyageNo()), trim(request.voyageNo()))
|
||||
|| !java.util.Objects.equals(booking.getEstimatedSailingAt(), request.estimatedSailingAt())
|
||||
|| !java.util.Objects.equals(booking.getEstimatedArrivalAt(), request.estimatedArrivalAt())) {
|
||||
throw conflict("放舱运输字段必须与已登记的提单、船名、航次、ETD和ETA一致");
|
||||
}
|
||||
}
|
||||
private static void validateRange(LocalDateTime from, LocalDateTime to) { if (from != null && to != null && to.isBefore(from)) throw badRequest("预计到港时间不能早于预计开船时间"); }
|
||||
private ChannelOption requireActiveChannel(Long originWarehouseId, String channelCode) {
|
||||
if (originWarehouseId == null || originWarehouseId <= 0) throw badRequest("请选择有效起运仓");
|
||||
String normalized = normalizeChannel(channelCode);
|
||||
@@ -312,26 +252,15 @@ public class OceanBookingService {
|
||||
v.setCustomerId(d.customerId()); v.setCustomerName(trim(d.customerName()));
|
||||
v.setChannelCode(d.channelCode().trim()); v.setChannelName(resolvedChannelName);
|
||||
v.setWarehouseId(d.warehouseId()); v.setWarehouseName(d.warehouseName().trim());
|
||||
v.setBillOfLadingNo(null);
|
||||
v.setBillOfLadingNo(d.billOfLadingNo().trim());
|
||||
v.setShippingCompanyId(d.shippingCompanyId()); v.setShippingCompanyName(trim(d.shippingCompanyName()));
|
||||
v.setBookingAgentId(d.bookingAgentId()); v.setBookingAgentName(trim(d.bookingAgentName()));
|
||||
v.setVesselName(null); v.setVoyageNo(null);
|
||||
v.setVesselName(d.vesselName().trim()); v.setVoyageNo(d.voyageNo().trim());
|
||||
v.setDeparturePortCode(trim(d.departurePortCode())); v.setDeparturePortName(trim(d.departurePortName()));
|
||||
v.setDestinationPortCode(trim(d.destinationPortCode())); v.setDestinationPortName(trim(d.destinationPortName()));
|
||||
v.setPlaceOfDelivery(trim(d.placeOfDelivery())); v.setServiceTerm(trim(d.serviceTerm())); v.setFreightTerm(trim(d.freightTerm()));
|
||||
v.setEstimatedSailingAt(null); v.setEstimatedArrivalAt(null); v.setRemark(trim(d.remark()));
|
||||
}
|
||||
|
||||
private static void validateConfirmation(OceanBookingEntity booking,
|
||||
List<OceanBookingContainerRequirementEntity> requirements) {
|
||||
if (!StringUtils.hasText(booking.getChannelCode()) || !StringUtils.hasText(booking.getChannelName())
|
||||
|| booking.getWarehouseId() == null || !StringUtils.hasText(booking.getWarehouseName())) {
|
||||
throw conflict("订舱缺少渠道或国内中转仓快照,不能确认");
|
||||
}
|
||||
if (requirements.isEmpty() || requirements.stream().anyMatch(requirement ->
|
||||
requirement.getCapacityCbm() == null || requirement.getMaxWeightKg() == null)) {
|
||||
throw conflict("箱型需求缺少容量或最大承重快照,不能确认");
|
||||
}
|
||||
v.setEstimatedSailingAt(d.estimatedSailingAt()); v.setEstimatedArrivalAt(d.estimatedArrivalAt());
|
||||
v.setRemark(trim(d.remark()));
|
||||
}
|
||||
|
||||
private static List<OceanBookingPartyEntity> parties(long id, List<PartyRequest> requests) { return requests.stream().map(r -> { OceanBookingPartyEntity v = new OceanBookingPartyEntity(); v.setId(IdWorker.getId()); v.setOceanBookingId(id); v.setPartyType(r.partyType().trim().toUpperCase()); v.setPartyName(r.partyName().trim()); v.setAddress(trim(r.address())); v.setContactName(trim(r.contactName())); v.setContactPhone(trim(r.contactPhone())); return v; }).toList(); }
|
||||
|
||||
Reference in New Issue
Block a user