Merge branch 'test' of http://8.163.30.128:10082/java-project/baiyue-firstleg into dev
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
package com.baiyue.firstleg.client;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@FeignClient(name = "iam-service", contextId = "firstlegIamCustomerSnapshotClient",
|
||||
url = "${baiyue.clients.iam.url:}", path = "/internal/iam")
|
||||
public interface IamCustomerSnapshotClient {
|
||||
@GetMapping("/finance-customers")
|
||||
ApiResponse<PageResult<Customer>> page(
|
||||
@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestParam("page") int page,
|
||||
@RequestParam("size") int size,
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
@RequestParam(value = "status", required = false) String status,
|
||||
@RequestParam(value = "customerId", required = false) Long customerId);
|
||||
|
||||
record Customer(long id, String tenantCode, String tenantName, String status,
|
||||
String contactName, String contactPhone) {}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
/** 仅用于确认证据文件属于当前客户和入库单,不在 firstleg 复制文件元数据。 */
|
||||
@FeignClient(name = "file-service", contextId = "firstlegLoadingEvidenceFileClient",
|
||||
@@ -15,5 +16,10 @@ public interface LoadingEvidenceFileClient {
|
||||
@PathVariable long tenantId, @PathVariable long inboundId,
|
||||
@PathVariable long fileId);
|
||||
|
||||
@GetMapping("/{tenantId}/{inboundId}/{fileId}/content")
|
||||
ResponseEntity<byte[]> download(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@PathVariable long tenantId, @PathVariable long inboundId,
|
||||
@PathVariable long fileId);
|
||||
|
||||
record FileMetadata(Long fileId, String fileName, String businessType, String contentType) {}
|
||||
}
|
||||
|
||||
@@ -31,16 +31,34 @@ public interface OrderCargoClient {
|
||||
@PathVariable long tenantId,
|
||||
@PathVariable long orderId);
|
||||
|
||||
@GetMapping("/{tenantId}/{orderId}/measurement-declaration")
|
||||
ApiResponse<MeasurementDeclarationSnapshot> measurementDeclaration(
|
||||
@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@PathVariable long tenantId, @PathVariable long orderId);
|
||||
|
||||
record MeasurementDeclarationSnapshot(
|
||||
String referenceId, BigDecimal originalGrossWeightKg,
|
||||
BigDecimal originalVolumeCbm, String defaultActualGoodsName) {}
|
||||
|
||||
record CargoBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||
Integer originalPackageCount, BigDecimal originalGrossWeightKg,
|
||||
BigDecimal originalVolumeCbm, String skuNo, String cargoNameEn,
|
||||
Integer unitsPerPackage, Integer totalQuantity, BigDecimal netWeightKg,
|
||||
String cargoRemark) {
|
||||
String cargoRemark, String customerRemark, String latestInternalNote) {
|
||||
public CargoBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||
Integer originalPackageCount, BigDecimal originalGrossWeightKg,
|
||||
BigDecimal originalVolumeCbm) {
|
||||
this(cargoId, orderId, itemNo, cargoName, originalPackageCount,
|
||||
originalGrossWeightKg, originalVolumeCbm, null, null, null, null, null, null);
|
||||
originalGrossWeightKg, originalVolumeCbm, null, null, null, null, null, null, null, null);
|
||||
}
|
||||
public CargoBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||
Integer originalPackageCount, BigDecimal originalGrossWeightKg,
|
||||
BigDecimal originalVolumeCbm, String skuNo, String cargoNameEn,
|
||||
Integer unitsPerPackage, Integer totalQuantity, BigDecimal netWeightKg,
|
||||
String cargoRemark) {
|
||||
this(cargoId, orderId, itemNo, cargoName, originalPackageCount, originalGrossWeightKg,
|
||||
originalVolumeCbm, skuNo, cargoNameEn, unitsPerPackage, totalQuantity, netWeightKg,
|
||||
cargoRemark, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-15
@@ -13,39 +13,44 @@ import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@FeignClient(name = "warehouse-service", contextId = "warehousePalletLocationClient",
|
||||
url = "${baiyue.clients.warehouse.url:}", path = "/internal/warehouse/v1/pallet-locations")
|
||||
url = "${baiyue.clients.warehouse.url:}", path = "/internal/warehouse/v1")
|
||||
public interface WarehousePalletLocationClient {
|
||||
@PostMapping("/reservations")
|
||||
@GetMapping("/pallets/snapshots")
|
||||
ApiResponse<List<PalletSnapshot>> palletSnapshots(
|
||||
@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestParam("warehouseId") long warehouseId);
|
||||
|
||||
@PostMapping("/pallet-locations/reservations")
|
||||
ApiResponse<BatchResult> reserve(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestBody ReserveRequest request);
|
||||
|
||||
@PostMapping("/confirm")
|
||||
@PostMapping("/pallet-locations/confirm")
|
||||
ApiResponse<BatchResult> confirm(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestBody ConfirmRequest request);
|
||||
|
||||
@PostMapping("/release")
|
||||
@PostMapping("/pallet-locations/release")
|
||||
ApiResponse<List<ActiveResource>> release(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestBody ReleaseRequest request);
|
||||
|
||||
@PostMapping("/release-selected")
|
||||
@PostMapping("/pallet-locations/release-selected")
|
||||
ApiResponse<List<SelectiveReleaseResult>> releaseSelected(
|
||||
@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestBody SelectiveReleaseRequest request);
|
||||
|
||||
@PostMapping("/transfer")
|
||||
@PostMapping("/pallet-locations/transfer")
|
||||
ApiResponse<List<TransferResult>> transfer(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestBody TransferRequest request);
|
||||
|
||||
@PostMapping("/settlements")
|
||||
@PostMapping("/pallet-locations/settlements")
|
||||
ApiResponse<SettlementResult> settle(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestBody SettlementRequest request);
|
||||
|
||||
@GetMapping("/settlements/target")
|
||||
@GetMapping("/pallet-locations/settlements/target")
|
||||
ApiResponse<Long> settlementTarget(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestParam("sourceBusinessId") long sourceBusinessId,
|
||||
@RequestParam("idempotencyKey") String idempotencyKey);
|
||||
|
||||
@GetMapping("/occupied-resources")
|
||||
@GetMapping("/pallet-locations/occupied-resources")
|
||||
ApiResponse<List<OccupiedResource>> occupiedResources(
|
||||
@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestParam("tenantId") long tenantId,
|
||||
@@ -54,18 +59,19 @@ public interface WarehousePalletLocationClient {
|
||||
|
||||
record ReserveRequest(long tenantId, long warehouseId, String sourceService, long businessId,
|
||||
String inboundCode, String idempotencyKey, long operatorId,
|
||||
BigDecimal totalVolumeCbm, BigDecimal palletCapacityCbm, String palletType,
|
||||
BigDecimal totalVolumeCbm, List<PalletReference> pallets,
|
||||
LocalDateTime expiresAt, boolean measurementCompleted) {}
|
||||
record PalletReference(long weightResultId, long palletId) {}
|
||||
record ConfirmRequest(long tenantId, long warehouseId, String sourceService, long businessId,
|
||||
long operatorId, BigDecimal totalVolumeCbm, BigDecimal palletCapacityCbm,
|
||||
int packageCount) {}
|
||||
long operatorId, BigDecimal totalVolumeCbm, int packageCount) {}
|
||||
record ReleaseRequest(long tenantId, String sourceService, long businessId, long operatorId,
|
||||
String reason) {}
|
||||
record SelectiveReleaseRequest(long tenantId, String sourceService, long businessId,
|
||||
String idempotencyKey, long operatorId, String reason,
|
||||
List<Long> locationIds) {}
|
||||
record AllocationResult(long reservationId, String reservationNo, int allocationNo,
|
||||
long palletId, String palletCode, long locationId, String locationCode,
|
||||
long palletId, String palletCode, BigDecimal palletCapacityCbm,
|
||||
long locationId, String locationCode,
|
||||
BigDecimal allocatedVolumeCbm, String state) {}
|
||||
record BatchResult(int palletCount, List<AllocationResult> allocations) {}
|
||||
record ActiveResource(long reservationId, long warehouseId, long palletId, String palletCode,
|
||||
@@ -90,6 +96,8 @@ public interface WarehousePalletLocationClient {
|
||||
List<SettlementTransferItem> remainingItems) {}
|
||||
record SettlementResult(List<SelectiveReleaseResult> released, List<TransferResult> transferred) {}
|
||||
record OccupiedResource(long reservationId, long occupancyId, int allocationNo, long warehouseId,
|
||||
long palletId, long locationId, String locationCode, boolean sharedOverflow,
|
||||
int allocatedPackageCount) {}
|
||||
long palletId, long locationId, String locationCode, boolean sharedOverflow,
|
||||
int allocatedPackageCount) {}
|
||||
record PalletSnapshot(long palletId, String palletCode, String palletType,
|
||||
BigDecimal capacityCbm, BigDecimal tareWeightKg) {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.baiyue.firstleg.client;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@FeignClient(name = "warehouse-service", contextId = "firstlegWarehouseSupplierClient",
|
||||
url = "${baiyue.clients.warehouse.url:}", path = "/internal/warehouse/v1/suppliers")
|
||||
public interface WarehouseSupplierClient {
|
||||
@GetMapping("/{supplierId}")
|
||||
ApiResponse<Snapshot> get(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@PathVariable long supplierId,
|
||||
@RequestParam(defaultValue = "true") boolean requireActive,
|
||||
@RequestParam(required = false) String category);
|
||||
|
||||
record Snapshot(long supplierId, String name, String category, String status) {}
|
||||
}
|
||||
+7
-4
@@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -27,17 +28,19 @@ public class InternalFinanceFeeSourceController {
|
||||
@GetMapping("/inbounds/{inboundId}/fees/{feeId}/source-snapshot")
|
||||
public ApiResponse<FinanceFeeSourceSnapshot> inbound(
|
||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||
@PathVariable long inboundId, @PathVariable long feeId) {
|
||||
@PathVariable long inboundId, @PathVariable long feeId,
|
||||
@RequestParam @jakarta.validation.constraints.Min(1) int sourceVersion) {
|
||||
authenticate(token);
|
||||
return ApiResponse.success(service.inbound(inboundId, feeId));
|
||||
return ApiResponse.success(service.inbound(inboundId, feeId, sourceVersion));
|
||||
}
|
||||
|
||||
@GetMapping("/containers/{containerId}/fees/{feeId}/source-snapshot")
|
||||
public ApiResponse<FinanceFeeSourceSnapshot> container(
|
||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||
@PathVariable long containerId, @PathVariable long feeId) {
|
||||
@PathVariable long containerId, @PathVariable long feeId,
|
||||
@RequestParam @jakarta.validation.constraints.Min(1) int sourceVersion) {
|
||||
authenticate(token);
|
||||
return ApiResponse.success(service.container(containerId, feeId));
|
||||
return ApiResponse.success(service.container(containerId, feeId, sourceVersion));
|
||||
}
|
||||
|
||||
private void authenticate(String token) {
|
||||
|
||||
+6
-1
@@ -9,6 +9,7 @@ import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.OrderApprovedInboundCommand;
|
||||
import com.baiyue.firstleg.dto.InboundOrderReference;
|
||||
import com.baiyue.firstleg.service.InboundOrderCreationService;
|
||||
import com.baiyue.firstleg.service.InboundCartonLabelService;
|
||||
import com.baiyue.firstleg.service.UnifiedInboundQueryService;
|
||||
import com.baiyue.firstleg.dto.UnifiedInboundDetail;
|
||||
import com.baiyue.firstleg.dto.UnifiedInboundQuery;
|
||||
@@ -35,6 +36,8 @@ public class InternalInboundOrderController {
|
||||
@Resource
|
||||
private InboundOrderCreationService service;
|
||||
@Resource
|
||||
private InboundCartonLabelService cartonLabels;
|
||||
@Resource
|
||||
private UnifiedInboundQueryService unifiedQueryService;
|
||||
@Value("${baiyue.security.internal-service-token}")
|
||||
private String internalToken;
|
||||
@@ -59,7 +62,9 @@ public class InternalInboundOrderController {
|
||||
|| command.totalPackageCount() != null && command.totalPackageCount() < 1) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "订单入库快照不完整");
|
||||
}
|
||||
return ApiResponse.success(service.createFromApprovedOrder(command));
|
||||
long inboundId = service.createFromApprovedOrder(command);
|
||||
cartonLabels.getOrCreate(command.tenantId(), inboundId);
|
||||
return ApiResponse.success(inboundId);
|
||||
}
|
||||
|
||||
@PostMapping("/order-preparing")
|
||||
|
||||
+3
-3
@@ -31,12 +31,12 @@ public class InternalPriceBookController {
|
||||
}
|
||||
|
||||
@PostMapping("/channels/validate")
|
||||
public ApiResponse<Void> validateChannel(
|
||||
public ApiResponse<PriceBookChannelValidation.Response> validateChannel(
|
||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||
@Valid @RequestBody PriceBookChannelValidation.Request request) {
|
||||
requireToken(token);
|
||||
service.requireAvailableChannel(request.tenantId(), request.originWarehouseId(), request.channelCode());
|
||||
return ApiResponse.success(null);
|
||||
return ApiResponse.success(service.requireAvailableChannel(request.tenantId(), request.originWarehouseId(),
|
||||
request.channelCode(), request.destinationType(), request.requireInboundCodePrefix()));
|
||||
}
|
||||
|
||||
private void requireToken(String token) {
|
||||
|
||||
+26
-6
@@ -6,8 +6,10 @@ import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.dto.ContainerDtos.FullUpdateRequest;
|
||||
import com.baiyue.firstleg.dto.ContainerDtos.Query;
|
||||
import com.baiyue.firstleg.dto.ContainerDtos.SaveRequest;
|
||||
import com.baiyue.firstleg.dto.ContainerDtos.TransportUpdateRequest;
|
||||
import com.baiyue.firstleg.dto.ContainerDtos.SimulationCreateRequest;
|
||||
import com.baiyue.firstleg.dto.ContainerDtos.OceanBookingLinkRequest;
|
||||
import com.baiyue.firstleg.dto.ContainerDtos.OceanBookingUnlinkRequest;
|
||||
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||
import com.baiyue.firstleg.service.ContainerService;
|
||||
import com.baiyue.firstleg.service.FirstlegScopeGuard;
|
||||
@@ -55,6 +57,18 @@ public class PlatformContainerController {
|
||||
public ApiResponse<Overview> overview(@AuthenticationPrincipal AuthenticatedUser user,@PathVariable @Positive long id) {
|
||||
FirstlegScopeGuard.platform(user); return ApiResponse.success(overview.overview(id));
|
||||
}
|
||||
@GetMapping("/{id}/pda/photos/{fileId}/download")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:container:list')")
|
||||
public ResponseEntity<byte[]> downloadPdaPhoto(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long id,
|
||||
@PathVariable @Positive long fileId) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
var value = overview.downloadPhoto(id, fileId);
|
||||
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, org.springframework.http.ContentDisposition.inline()
|
||||
.filename(value.fileName(), StandardCharsets.UTF_8).build().toString())
|
||||
.contentType(value.contentType()).contentLength(value.content().length).body(value.content());
|
||||
}
|
||||
@PostMapping("/{id}/exception") @PreAuthorize("hasAuthority('platform:firstleg:container:edit')")
|
||||
public ApiResponse<Void> markException(@AuthenticationPrincipal AuthenticatedUser user,@PathVariable @Positive long id,
|
||||
@RequestHeader("Idempotency-Key") String key,
|
||||
@@ -70,15 +84,21 @@ public class PlatformContainerController {
|
||||
@GetMapping("/{id}/documents/{documentType}") @PreAuthorize("hasAuthority('platform:firstleg:container:list')")
|
||||
public ApiResponse<BusinessDocument> logisticsDocument(@AuthenticationPrincipal AuthenticatedUser user,@PathVariable @Positive long id,@PathVariable String documentType){FirstlegScopeGuard.platform(user);return ApiResponse.success(loadingLists.containerLogisticsDocument(id,documentType));}
|
||||
@PostMapping(value="/{id}/documents/{documentType}/upload",consumes=MediaType.MULTIPART_FORM_DATA_VALUE) @PreAuthorize("hasAuthority('platform:firstleg:container:edit')")
|
||||
public ApiResponse<BusinessDocument> uploadLogisticsDocument(@AuthenticationPrincipal AuthenticatedUser user,@PathVariable @Positive long id,@PathVariable String documentType,@RequestHeader("Idempotency-Key") String key,@RequestPart("file") MultipartFile file,@RequestPart("expectedVersion") @Min(0) int version,@RequestPart(value="operationReason",required=false) String reason){FirstlegScopeGuard.platform(user);return ApiResponse.success(loadingLists.uploadContainerLogisticsDocument(id,documentType,user.userId(),user.username(),key,version,reason,file));}
|
||||
public ApiResponse<BusinessDocument> uploadLogisticsDocument(@AuthenticationPrincipal AuthenticatedUser user,@PathVariable @Positive long id,@PathVariable String documentType,@RequestHeader("Idempotency-Key") String key,@RequestPart("file") MultipartFile file,@RequestParam("expectedVersion") @Min(0) int version,@RequestParam(value="operationReason",required=false) String reason){FirstlegScopeGuard.platform(user);return ApiResponse.success(loadingLists.uploadContainerLogisticsDocument(id,documentType,user.userId(),user.username(),key,version,reason,file));}
|
||||
@GetMapping("/{id}/documents/{documentType}/{fileId}/download") @PreAuthorize("hasAuthority('platform:firstleg:container:list')")
|
||||
public ResponseEntity<byte[]> downloadDocument(@AuthenticationPrincipal AuthenticatedUser user,@PathVariable @Positive long id,@PathVariable String documentType,@PathVariable @Positive long fileId){FirstlegScopeGuard.platform(user);var d=loadingLists.downloadContainerDocument(id,documentType,fileId);return ResponseEntity.ok().cacheControl(CacheControl.noStore()).header(HttpHeaders.CONTENT_DISPOSITION,org.springframework.http.ContentDisposition.attachment().filename(d.fileName(),StandardCharsets.UTF_8).build().toString()).contentType(d.contentType()).contentLength(d.content().length).body(d.content());}
|
||||
@PostMapping @PreAuthorize("hasAuthority('platform:firstleg:container:create')")
|
||||
@BusinessAudit(event="CONTAINER_CREATE", resourceType="CONTAINER", description="新增货柜基础资料", captureBefore=false)
|
||||
public ApiResponse<ContainerBookingEntity> create(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Valid @RequestBody SaveRequest request) {
|
||||
FirstlegScopeGuard.platform(user); return ApiResponse.success(service.create(user.userId(), user.username(), request));
|
||||
@Valid @RequestBody SimulationCreateRequest request) {
|
||||
FirstlegScopeGuard.platform(user); return ApiResponse.success(service.createSimulation(user.userId(), user.username(), request));
|
||||
}
|
||||
@PostMapping("/{id}/ocean-booking-link") @PreAuthorize("hasAuthority('platform:firstleg:container:edit')")
|
||||
@BusinessAudit(event="CONTAINER_OCEAN_BOOKING_LINK", resourceType="CONTAINER", resourceId="#id", description="关联真实订舱柜")
|
||||
public ApiResponse<ContainerBookingEntity> linkOceanBooking(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long id, @Valid @RequestBody OceanBookingLinkRequest request) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.linkOceanBooking(id, user.userId(), user.username(), request)); }
|
||||
@DeleteMapping("/{id}/ocean-booking-link") @PreAuthorize("hasAuthority('platform:firstleg:container:edit')")
|
||||
@BusinessAudit(event="CONTAINER_OCEAN_BOOKING_UNLINK", resourceType="CONTAINER", resourceId="#id", description="解除真实订舱柜关联")
|
||||
public ApiResponse<ContainerBookingEntity> unlinkOceanBooking(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long id, @Valid @RequestBody OceanBookingUnlinkRequest request) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.unlinkOceanBooking(id, user.userId(), user.username(), request)); }
|
||||
@PutMapping("/{id}") @PreAuthorize("hasAuthority('platform:firstleg:container:edit')")
|
||||
@BusinessAudit(event="CONTAINER_UPDATE", resourceType="CONTAINER", resourceId="#id", description="完整编辑货柜基础资料")
|
||||
@Operation(summary="完整编辑货柜", description="仅待装柜或预装柜中时允许")
|
||||
@@ -127,8 +147,8 @@ public class PlatformContainerController {
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long id,
|
||||
@RequestHeader("Idempotency-Key") String key,
|
||||
@RequestPart("file") MultipartFile file,
|
||||
@RequestPart("expectedVersion") @Min(0) int expectedVersion,
|
||||
@RequestPart(value = "operationReason", required = false) @Size(max = 500) String operationReason) {
|
||||
@RequestParam("expectedVersion") @Min(0) int expectedVersion,
|
||||
@RequestParam(value = "operationReason", required = false) @Size(max = 500) String operationReason) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(loadingLists.uploadContainerClearance(
|
||||
id, user.userId(), user.username(), key, expectedVersion, operationReason, file));
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ public class PlatformContainerFeeDetailController {
|
||||
@PathVariable long loadingPlanId, @PathVariable long feeId,
|
||||
@Valid @RequestBody AdjustRequest request) {
|
||||
return ApiResponse.success(service.adjust(FirstlegScopeGuard.platform(user, accountId),
|
||||
loadingPlanId, feeId, request.expectedSourceVersion(), user.userId(), request.values()));
|
||||
loadingPlanId, feeId, user.userId(), request));
|
||||
}
|
||||
|
||||
@PostMapping("/{feeId}/void")
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ public class PlatformFbaPricingController {
|
||||
@PutMapping("/published")
|
||||
@PreAuthorize("hasAuthority('firstleg:fba-pricing:adjust')")
|
||||
@BusinessAudit(event = "FBA_FEE_ADJUST", resourceType = "INBOUND", resourceId = "#inboundId",
|
||||
description = "调整已发布入库费用,不回退入库状态")
|
||||
description = "调整初算或已发布入库费用,不回退待分柜状态")
|
||||
public ApiResponse<FeeSnapshotResponse> adjustPublished(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam long tenantId,
|
||||
@PathVariable long inboundId, @RequestHeader("Idempotency-Key") String idempotencyKey,
|
||||
|
||||
+3
-1
@@ -41,8 +41,10 @@ public class PlatformInboundFeeDetailController {
|
||||
public ApiResponse<List<InboundFeeDetailEntity>> list(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long accountId,
|
||||
@RequestParam(defaultValue = "false") boolean includeHistory,
|
||||
@PathVariable long inboundId) {
|
||||
return ApiResponse.success(service.list(FirstlegScopeGuard.platform(user, accountId), inboundId));
|
||||
return ApiResponse.success(service.list(
|
||||
FirstlegScopeGuard.platform(user, accountId), inboundId, includeHistory));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
|
||||
+21
-4
@@ -33,6 +33,8 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 平台后台入库测量录入、提交和 OCR 重量结果维护接口。 */
|
||||
@Tag(name = "平台入库测量", description = "维护测量草稿、箱规明细和可追溯 OCR 重量结果。")
|
||||
@RestController
|
||||
@@ -53,6 +55,19 @@ public class PlatformInboundMeasurementController {
|
||||
return ApiResponse.success(service.detail(tenantId, inboundId));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询称重可选托盘快照",
|
||||
description = "按入库单所属仓库返回未删除的托盘只读快照,不校验托盘状态或占用资格。")
|
||||
@GetMapping("/weight-pallet-options")
|
||||
@PreAuthorize("hasAnyAuthority('platform:firstleg:inbound:measure:view',"
|
||||
+ " 'platform:firstleg:inbound:weight:edit')")
|
||||
public ApiResponse<List<InboundMeasurementResponses.PalletSnapshot>> weightPalletOptions(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long tenantId,
|
||||
@PathVariable long inboundId) {
|
||||
requirePlatformTenant(user, tenantId);
|
||||
return ApiResponse.success(service.weightPalletOptions(tenantId, inboundId));
|
||||
}
|
||||
|
||||
@BusinessAudit(event = "INBOUND_MEASUREMENT_DRAFT_SAVE", resourceType = "INBOUND",
|
||||
resourceId = "#inboundId", description = "保存入库测量草稿")
|
||||
@Operation(summary = "保存测量草稿", description = "保存人工测量批次和服务端计算明细,不推进入库状态。")
|
||||
@@ -114,7 +129,7 @@ public class PlatformInboundMeasurementController {
|
||||
@RequestPart("image") MultipartFile image) {
|
||||
requirePlatformTenant(user, tenantId);
|
||||
CartonMeasurementCommand command = new CartonMeasurementCommand(
|
||||
request.boxCode(), request.length(), request.width(), request.height(), request.weight(),
|
||||
request.boxCode(), request.length(), request.width(), request.height(),
|
||||
request.machineNo(), request.measuredAt(), image, request.scannedShipmentId());
|
||||
return ApiResponse.success(cartonMeasurements.upsertForInbound(
|
||||
tenantId, user.userId(), inboundId, command));
|
||||
@@ -134,7 +149,7 @@ public class PlatformInboundMeasurementController {
|
||||
@RequestPart("image") MultipartFile image) {
|
||||
requirePlatformTenant(user, tenantId);
|
||||
CartonMeasurementCommand command = new CartonMeasurementCommand(
|
||||
request.boxCode(), request.length(), request.width(), request.height(), request.weight(),
|
||||
request.boxCode(), request.length(), request.width(), request.height(),
|
||||
request.machineNo(), request.measuredAt(), image, request.scannedShipmentId());
|
||||
return ApiResponse.success(cartonMeasurements.upsertManualForInbound(
|
||||
tenantId, user.userId(), inboundId, command));
|
||||
@@ -142,7 +157,8 @@ public class PlatformInboundMeasurementController {
|
||||
|
||||
@BusinessAudit(event = "INBOUND_WEIGHT_OCR_RECORD", resourceType = "INBOUND",
|
||||
resourceId = "#inboundId", description = "登记称重图片 OCR 原值")
|
||||
@Operation(summary = "登记 OCR 重量结果", description = "按图片文件 ID 保存不可覆盖的 OCR 原值。")
|
||||
@Operation(summary = "登记 OCR 重量结果",
|
||||
description = "按图片文件 ID 和托盘 ID 保存 OCR 毛重原值及同仓托盘快照。")
|
||||
@PostMapping("/weight-results")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:inbound:weight:edit')")
|
||||
public ApiResponse<InboundMeasurementResponses.WeightResult> createWeightResult(
|
||||
@@ -173,7 +189,8 @@ public class PlatformInboundMeasurementController {
|
||||
@BusinessAudit(event = "INBOUND_WEIGHT_OCR_CONFIRM", resourceType = "INBOUND_WEIGHT_RESULT",
|
||||
resourceId = "#resultId", description = "确认 OCR 重量并累加实际总毛重")
|
||||
@Operation(summary = "确认 OCR 重量",
|
||||
description = "可在同一事务中携带人工修改值并确认;统一换算为 KG 后重算所有已确认图片的总重量。")
|
||||
description = "可在同一事务中携带人工毛重修改值并确认;统一换算 KG、扣除托盘皮重后,"
|
||||
+ "按所有已确认结果分别重算入库单毛重与扣皮后的货物净重。")
|
||||
@PostMapping("/weight-results/{resultId}/confirm")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:inbound:weight:confirm')"
|
||||
+ " and (!#request.hasAdjustment() or hasAuthority('platform:firstleg:inbound:weight:edit'))")
|
||||
|
||||
+7
-46
@@ -6,13 +6,12 @@ import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.dto.ExceptionReleaseRequest;
|
||||
import com.baiyue.firstleg.dto.MeasurementReviewRequest;
|
||||
import com.baiyue.firstleg.dto.MeasurementApprovalResponse;
|
||||
import com.baiyue.firstleg.entity.MeasureReviewRecordEntity;
|
||||
import com.baiyue.firstleg.service.MeasurementReviewService;
|
||||
import com.baiyue.firstleg.service.AutomaticInboundCompletionService;
|
||||
import com.baiyue.firstleg.service.InitialInboundPricingService;
|
||||
import com.baiyue.firstleg.service.FirstlegScopeGuard;
|
||||
import com.baiyue.firstleg.dto.FbaPricingDtos.FeeSnapshotResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -36,15 +35,15 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RequestMapping("/api/platform/v1/inbounds")
|
||||
public class PlatformInboundMeasurementReviewController {
|
||||
@Resource private MeasurementReviewService service;
|
||||
@Resource private AutomaticInboundCompletionService automaticCompletion;
|
||||
@Resource private InitialInboundPricingService initialPricing;
|
||||
|
||||
@BusinessAudit(event = "INBOUND_MEASURE_APPROVE", resourceType = "INBOUND",
|
||||
resourceId = "#inboundId", description = "测量审核通过")
|
||||
@Operation(summary = "测量审核通过",
|
||||
description = "客服确认测量数据后直接进入待分柜;费用和其他资料可在后续继续维护。")
|
||||
description = "客服确认测量数据后直接进入待分柜,并按已审核测量数据尝试生成初算费用;初算失败不阻塞状态流转。")
|
||||
@PostMapping("/{inboundId}/measurements/approve")
|
||||
@PreAuthorize("hasAuthority('firstleg:inbound:measurement-review')")
|
||||
public ApiResponse<FeeSnapshotResponse> approve(
|
||||
public ApiResponse<MeasurementApprovalResponse> approve(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long tenantId,
|
||||
@PathVariable long inboundId,
|
||||
@@ -52,47 +51,9 @@ public class PlatformInboundMeasurementReviewController {
|
||||
@RequestHeader("Idempotency-Key") String idempotencyKey,
|
||||
@Valid @RequestBody MeasurementReviewRequest request) {
|
||||
validateScope(user, tenantId);
|
||||
// 审核服务事务在此调用返回时已提交;随后自动编排从新事务/新查询读取 WAIT_PRICING,
|
||||
// 避免在未提交的审核事务中读取旧状态。自动流程失败时保留已审核状态,原样返回业务错误,
|
||||
// 使用同一幂等键重试会从持久化阶段继续推进。
|
||||
service.approve(tenantId, user.userId(), user.username(), inboundId, idempotencyKey, request);
|
||||
return ApiResponse.success(automaticCompletion.completeApproved(
|
||||
tenantId, inboundId, user.userId(), user.username(), request.measurementId(), request.expectedVersion() + 1));
|
||||
}
|
||||
|
||||
@BusinessAudit(event = "INBOUND_MEASURE_REJECT", resourceType = "INBOUND",
|
||||
resourceId = "#inboundId", description = "测量审核驳回")
|
||||
@Operation(summary = "测量审核驳回",
|
||||
description = "驳回原因必填,只退回测量数据修改;已确认的托盘和库位占用保持不动。")
|
||||
@PostMapping("/{inboundId}/measurements/reject")
|
||||
@PreAuthorize("hasAuthority('firstleg:inbound:measurement-review')")
|
||||
public ApiResponse<Void> reject(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long tenantId,
|
||||
@PathVariable long inboundId,
|
||||
@RequestHeader("Idempotency-Key") String idempotencyKey,
|
||||
@Valid @RequestBody MeasurementReviewRequest request) {
|
||||
validateScope(user, tenantId);
|
||||
service.reject(tenantId, user.userId(), user.username(), inboundId, idempotencyKey, request);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@BusinessAudit(event = "INBOUND_MEASURE_EXCEPTION_RELEASE", resourceType = "INBOUND",
|
||||
resourceId = "#inboundId", description = "测量驳回异常放行")
|
||||
@Operation(summary = "测量驳回异常放行",
|
||||
description = "Nacos 开关开启、有异常放行权限且原因非空时,才可从 MEASURE_REJECTED 推进至 WAIT_PRICING。")
|
||||
@PostMapping("/{inboundId}/measurements/exception-release")
|
||||
@PreAuthorize("hasAuthority('firstleg:inbound:exception-release')")
|
||||
public ApiResponse<Void> exceptionRelease(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long tenantId,
|
||||
@PathVariable long inboundId,
|
||||
@RequestHeader("Idempotency-Key") String idempotencyKey,
|
||||
@Valid @RequestBody ExceptionReleaseRequest request) {
|
||||
validateScope(user, tenantId);
|
||||
service.exceptionRelease(
|
||||
tenantId, user.userId(), user.username(), inboundId, idempotencyKey, request);
|
||||
return ApiResponse.success(null);
|
||||
return ApiResponse.success(initialPricing.tryCalculate(tenantId, user.userId(), inboundId,
|
||||
request.measurementId(), request.expectedVersion() + 1));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询测量审核历史")
|
||||
|
||||
+18
-3
@@ -9,6 +9,8 @@ import com.baiyue.firstleg.dto.LoadingListDtos.ConfirmedSummary;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.PreloadDetail;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.PreloadQuery;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.PreloadSummary;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.ConfirmPreloadRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.PreloadConfirmationResponse;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.TransportUpdateRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.BusinessDocument;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.DocumentAttachRequest;
|
||||
@@ -66,7 +68,7 @@ public class PlatformLoadingListController {
|
||||
return ApiResponse.success(service.preloadDetail(planId, warehouseId));
|
||||
}
|
||||
|
||||
@Operation(summary = "修改装柜运输字段", description = "仅装柜中可修改集装箱号与航次。")
|
||||
@Operation(summary = "修改装柜运输字段", description = "已推送且仍为 EDITING 的预装清单可补录或修改集装箱号与航次;LOADING 阶段按稳定柜次身份规则维护,不能清空已创建 PDA 任务的柜号。")
|
||||
@PatchMapping("/loading-plans/{planId}/transport")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:preload:edit')")
|
||||
public ApiResponse<PreloadDetail> updateTransport(
|
||||
@@ -80,6 +82,19 @@ public class PlatformLoadingListController {
|
||||
planId, warehouseId, user.userId(), user.username(), idempotencyKey, request));
|
||||
}
|
||||
|
||||
@Operation(summary = "确认整份预装计划", description = "按 loadingPlanId 原子确认全部关联入库预装票据;要求已推送、真实集装箱号已录入、关联订舱已放舱。成功后唯一创建待领取 PDA 任务,claim 后才进入 LOADING。")
|
||||
@PostMapping("/loading-plans/{planId}/confirm")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:preload:edit')")
|
||||
public ApiResponse<PreloadConfirmationResponse> confirmPreload(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long planId,
|
||||
@RequestHeader("Idempotency-Key") String idempotencyKey,
|
||||
@Valid @RequestBody ConfirmPreloadRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.confirmPreload(
|
||||
planId, user.userId(), user.username(), idempotencyKey, request));
|
||||
}
|
||||
|
||||
@Operation(summary = "分页查询完成装柜清单", description = "只读取 COMPLETED 状态的装柜计划。")
|
||||
@GetMapping("/loading-lists")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:loading-list:list')")
|
||||
@@ -129,8 +144,8 @@ public class PlatformLoadingListController {
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long planId,
|
||||
@RequestParam @Positive long warehouseId, @RequestHeader("Idempotency-Key") String key,
|
||||
@RequestPart("file") MultipartFile file,
|
||||
@RequestPart("expectedVersion") @Min(0) int expectedVersion,
|
||||
@RequestPart(value = "operationReason", required = false) @Size(max = 500) String operationReason) {
|
||||
@RequestParam("expectedVersion") @Min(0) int expectedVersion,
|
||||
@RequestParam(value = "operationReason", required = false) @Size(max = 500) String operationReason) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.uploadLoadingDeclaration(planId, warehouseId,
|
||||
user.userId(), user.username(), key, expectedVersion, operationReason, file));
|
||||
|
||||
+1
-2
@@ -45,12 +45,11 @@ public class PlatformLoadingPdaController {
|
||||
@GetMapping("/tasks")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:view')")
|
||||
public ApiResponse<Task> task(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam(required = false) String containerNo,
|
||||
@RequestParam(required = false) String cabinetNo,
|
||||
@RequestParam(required = false) Long loadingPlanId,
|
||||
@RequestParam @Positive long warehouseId) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.task(containerNo, cabinetNo, loadingPlanId, warehouseId));
|
||||
return ApiResponse.success(service.task(cabinetNo, loadingPlanId, warehouseId));
|
||||
}
|
||||
|
||||
@GetMapping("/tasks/pending")
|
||||
|
||||
+2
@@ -22,6 +22,8 @@ public class PlatformSmartLoadingController {
|
||||
public ApiResponse<Plan> recommend(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@Valid @RequestBody RecommendRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.recommend(planId,r,u.userId(),u.username()));}
|
||||
@PatchMapping("/plans/{planId}/items/{itemId}") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
|
||||
public ApiResponse<Plan> adjust(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@PathVariable @Positive long itemId,@Valid @RequestBody AdjustRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.adjust(planId,itemId,r,u.userId(),u.username()));}
|
||||
@PatchMapping("/plans/{planId}/items/{itemId}/warehouse-remark") @PreAuthorize("hasAuthority('platform:firstleg:preload:edit')")
|
||||
public ApiResponse<Plan> warehouseRemark(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@PathVariable @Positive long itemId,@Valid @RequestBody WarehouseRemarkRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.updateWarehouseRemark(planId,itemId,r,u.userId(),u.username()));}
|
||||
@DeleteMapping("/plans/{planId}/items/{itemId}") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:delete')")
|
||||
public ApiResponse<Plan> delete(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@PathVariable @Positive long itemId,@Valid @RequestBody DeleteRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.delete(planId,itemId,r,u.userId(),u.username()));}
|
||||
@PostMapping("/plans/{planId}/push-preload") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:push')")
|
||||
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
import com.baiyue.common.audit.BusinessAudit; import com.baiyue.common.core.api.*; import com.baiyue.common.security.AuthenticatedUser; import com.baiyue.firstleg.dto.TruckAppointmentDtos.*; import com.baiyue.firstleg.entity.TruckAppointmentEntity; import com.baiyue.firstleg.service.*; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; import jakarta.validation.constraints.Positive; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime;
|
||||
import com.baiyue.common.audit.BusinessAudit; import com.baiyue.common.core.api.*; import com.baiyue.common.security.AuthenticatedUser; import com.baiyue.firstleg.dto.TruckAppointmentDtos.*; import com.baiyue.firstleg.entity.TruckAppointmentEntity; import com.baiyue.firstleg.service.*; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Positive; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime;
|
||||
@Tag(name="拖车预约") @Validated @RestController @RequestMapping("/api/platform/v1/truck-appointments") public class PlatformTruckAppointmentController { @Resource TruckAppointmentService service;
|
||||
@GetMapping @PreAuthorize("hasAuthority('platform:firstleg:truck-appointment:list')") public ApiResponse<PageResult<TruckAppointmentEntity>> page(@AuthenticationPrincipal AuthenticatedUser u,@Valid Query q){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.page(q));}
|
||||
@GetMapping("/{id}") @PreAuthorize("hasAuthority('platform:firstleg:truck-appointment:list')") public ApiResponse<TruckAppointmentEntity> detail(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long id){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.detail(id));}
|
||||
@GetMapping("/{id}/history") @PreAuthorize("hasAuthority('platform:firstleg:truck-appointment:list')") public ApiResponse<java.util.List<com.baiyue.firstleg.entity.TruckAppointmentHistoryEntity>> history(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long id){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.history(id));}
|
||||
@GetMapping("/{id}/attachments") @PreAuthorize("hasAuthority('platform:firstleg:truck-appointment:list')") public ApiResponse<java.util.List<com.baiyue.firstleg.entity.TruckAppointmentAttachmentEntity>> attachments(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long id){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.attachments(id));}
|
||||
@PostMapping("/{id}/attachments") @PreAuthorize("hasAuthority('platform:firstleg:truck-appointment:edit')") public ApiResponse<com.baiyue.firstleg.entity.TruckAppointmentAttachmentEntity> attach(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long id,@Valid @RequestBody AttachmentRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.attach(id,r,u.userId()));}
|
||||
@PostMapping(value="/{id}/attachments/upload",consumes="multipart/form-data") @PreAuthorize("hasAuthority('platform:firstleg:truck-appointment:edit')") public ApiResponse<com.baiyue.firstleg.entity.TruckAppointmentAttachmentEntity> upload(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long id,@RequestParam(required=false) String attachmentType,@RequestPart("file") org.springframework.web.multipart.MultipartFile file){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.upload(id,attachmentType,file,u.userId()));}
|
||||
@PostMapping(value="/{id}/attachments/upload",consumes="multipart/form-data") @PreAuthorize("hasAuthority('platform:firstleg:truck-appointment:edit')") public ApiResponse<com.baiyue.firstleg.entity.TruckAppointmentAttachmentEntity> upload(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long id,@RequestParam @NotBlank @Pattern(regexp="BILL_OF_LADING|DISPATCH_ORDER|DRIVER_DOCUMENT|VEHICLE_DOCUMENT|OTHER") String attachmentType,@RequestPart("file") org.springframework.web.multipart.MultipartFile file){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.upload(id,attachmentType,file,u.userId()));}
|
||||
@DeleteMapping("/{id}/attachments/{attachmentId}") @PreAuthorize("hasAuthority('platform:firstleg:truck-appointment:edit')") public ApiResponse<Void> deleteAttachment(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long id,@PathVariable @Positive long attachmentId){FirstlegScopeGuard.platform(u);service.deleteAttachment(id,attachmentId);return ApiResponse.success(null);}
|
||||
@PostMapping @PreAuthorize("hasAuthority('platform:firstleg:truck-appointment:create')") @BusinessAudit(event="TRUCK_APPOINTMENT_CREATE",resourceType="TRUCK_APPOINTMENT",captureBefore=false) public ApiResponse<TruckAppointmentEntity> create(@AuthenticationPrincipal AuthenticatedUser u,@Valid @RequestBody SaveRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.create(u.userId(),u.username(),r));}
|
||||
@PutMapping("/{id}") @PreAuthorize("hasAuthority('platform:firstleg:truck-appointment:edit')") public ApiResponse<TruckAppointmentEntity> edit(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable long id,@Valid @RequestBody SaveRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.edit(id,u.userId(),u.username(),r));}
|
||||
|
||||
+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}
|
||||
|
||||
@@ -51,7 +51,8 @@ public final class CustomsDtos {
|
||||
LocalDateTime supplementDueAt,
|
||||
@Min(0) int expectedVersion) {}
|
||||
|
||||
public record SubmitBatchRequest(@NotBlank @Size(max = 128) String customsAgentName,
|
||||
public record SubmitBatchRequest(@NotNull @jakarta.validation.constraints.Positive Long customsAgentId,
|
||||
@Size(max = 128) String customsAgentName,
|
||||
@Size(max = 128) String externalReference,
|
||||
@Size(max = 500) String note,
|
||||
@Min(0) int expectedVersion) {}
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ public class CustomsSubmissionBatchEntity {
|
||||
private Long customsCaseId;
|
||||
private String batchNo;
|
||||
private Integer batchVersion;
|
||||
private Long customsAgentId;
|
||||
private String customsAgentName;
|
||||
private String externalReference;
|
||||
private String fileSnapshotJson;
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.baiyue.firstleg.customs.client.CustomsFileClient;
|
||||
import com.baiyue.firstleg.customs.dto.CustomsDtos.*;
|
||||
import com.baiyue.firstleg.customs.entity.*;
|
||||
import com.baiyue.firstleg.customs.repository.CustomsRepository;
|
||||
import com.baiyue.firstleg.service.WarehouseSupplierSnapshotService;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -36,6 +37,7 @@ public class CustomsService {
|
||||
@Resource private CustomsRepository repository;
|
||||
@Resource private CustomsFileClient files;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
@Resource private WarehouseSupplierSnapshotService suppliers;
|
||||
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
||||
|
||||
public PageResult<CustomsCaseEntity> page(Query query) { return repository.page(query); }
|
||||
@@ -177,7 +179,10 @@ public class CustomsService {
|
||||
}
|
||||
CustomsSubmissionBatchEntity batch = new CustomsSubmissionBatchEntity();
|
||||
batch.setId(IdWorker.getId()); batch.setCustomsCaseId(caseId); batch.setBatchVersion(repository.batches(caseId).size() + 1);
|
||||
batch.setBatchNo("CB" + batch.getId()); batch.setCustomsAgentName(request.customsAgentName().trim());
|
||||
batch.setBatchNo("CB" + batch.getId());
|
||||
var customsAgent = suppliers.customs(request.customsAgentId(), request.customsAgentName());
|
||||
batch.setCustomsAgentId(customsAgent == null ? null : customsAgent.supplierId());
|
||||
batch.setCustomsAgentName(customsAgent == null ? null : customsAgent.name());
|
||||
batch.setExternalReference(trim(request.externalReference())); batch.setFileSnapshotJson(json(current.stream()
|
||||
.map(v -> Map.of("documentId", v.getId(), "fileId", v.getFileId(), "itemCode", v.getChecklistItemCode(),
|
||||
"documentVersion", v.getDocumentVersion(), "fileHash", v.getFileHash())).toList()));
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.baiyue.firstleg.domain;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/** Platform-controlled container specifications shared by booking and simulation flows. */
|
||||
public final class ContainerSpecificationCatalog {
|
||||
private static final Map<String, Specification> VALUES = Map.of(
|
||||
"20GP", new Specification("20GP", new BigDecimal("33"), new BigDecimal("28000")),
|
||||
"40GP", new Specification("40GP", new BigDecimal("67"), new BigDecimal("28000")),
|
||||
"40HC", new Specification("40HC", new BigDecimal("76"), new BigDecimal("28000")),
|
||||
"40NR", new Specification("40NR", new BigDecimal("67"), new BigDecimal("28000")),
|
||||
"45HC", new Specification("45HC", new BigDecimal("86"), new BigDecimal("28000"))
|
||||
);
|
||||
|
||||
private ContainerSpecificationCatalog() {}
|
||||
|
||||
public static Specification require(String containerType) {
|
||||
String normalized = containerType == null ? "" : containerType.trim().toUpperCase(Locale.ROOT);
|
||||
Specification specification = VALUES.get(normalized);
|
||||
if (specification == null) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "柜型必须使用平台固定规格");
|
||||
}
|
||||
return specification;
|
||||
}
|
||||
|
||||
public static Specification requireMatch(String containerType, BigDecimal capacityCbm, BigDecimal maxWeightKg) {
|
||||
Specification specification = require(containerType);
|
||||
if (capacityCbm == null || maxWeightKg == null
|
||||
|| specification.capacityCbm().compareTo(capacityCbm) != 0
|
||||
|| specification.maxWeightKg().compareTo(maxWeightKg) != 0) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"柜型容量和限重必须使用平台固定规格");
|
||||
}
|
||||
return specification;
|
||||
}
|
||||
|
||||
public record Specification(String containerType, BigDecimal capacityCbm, BigDecimal maxWeightKg) {}
|
||||
}
|
||||
@@ -8,29 +8,33 @@ public final class PriceBookChannelCatalog {
|
||||
private PriceBookChannelCatalog() {}
|
||||
|
||||
public static final List<ChannelDefinition> CHANNELS = List.of(
|
||||
fba("MATSON_CLX_TRUCK", "美森正班海卡", "美森正班海卡"),
|
||||
fba("MATSON_MAX_TRUCK", "美森加班海卡", "美森加班海卡"),
|
||||
fba("EXX_TRUCK", "EXX海卡", "EXX海卡"),
|
||||
fba("HEDE_TRUCK", "合德海卡", "合德海卡"),
|
||||
fba("EXX_RAIL_NY", "纽约EXX海铁", "纽约EXX海铁"),
|
||||
fba("OA_LA_TRUCK", "洛杉矶普船海卡", "洛杉矶普船海卡"),
|
||||
fba("OA_LA_FULL_TRUCK", "洛杉矶普船全卡", "洛杉矶普船全卡"),
|
||||
fba("OA_CHICAGO_RAIL", "芝加哥普船海铁", "芝加哥普船海铁"),
|
||||
fba("OA_HOUSTON_TRUCK", "休斯顿普船海卡", "休斯顿普船海卡"),
|
||||
fba("OA_NEW_YORK_TRUCK", "纽约普船海卡", "纽约普船海卡"),
|
||||
fba("OA_SAVANNAH_TRUCK", "萨凡纳普船海卡", "萨凡纳普船海卡"),
|
||||
parcel("MATSON_CLX_PARCEL", "美森正班海派", "海派"),
|
||||
parcel("MATSON_MAX_PARCEL", "美森加班海派", "海派"),
|
||||
parcel("HEDE_PARCEL", "合德海派", "海派"),
|
||||
parcel("OA_PARCEL", "普船海派", "海派")
|
||||
fba("MATSON_CLX_TRUCK", "美森正班海卡", "美森正班海卡", "ZBYHK", "ZBYHWK"),
|
||||
fba("MATSON_MAX_TRUCK", "美森加班海卡", "美森加班海卡", "BYHK", "BYHWK"),
|
||||
fba("EXX_TRUCK", "EXX海卡", "EXX海卡", "EXXHK", "EXXHWK"),
|
||||
fba("HEDE_TRUCK", "合德海卡", "合德海卡", "HDHK", "HDHWK"),
|
||||
fba("EXX_RAIL_NY", "纽约EXX海铁", "纽约EXX海铁", "NYEXX", null),
|
||||
fba("OA_LA_TRUCK", "洛杉矶普船海卡", "洛杉矶普船海卡", "PCHK", "PCHWK"),
|
||||
fba("OA_LA_FULL_TRUCK", "洛杉矶普船全卡", "洛杉矶普船全卡", "PCKK", null),
|
||||
fba("OA_CHICAGO_RAIL", "芝加哥普船海铁", "芝加哥普船海铁", "CPHT", null),
|
||||
fba("OA_HOUSTON_TRUCK", "休斯顿普船海卡", "休斯顿普船海卡", "HPHK", null),
|
||||
fba("OA_NEW_YORK_TRUCK", "纽约普船海卡", "纽约普船海卡", "NPHK", "NPHWK"),
|
||||
fba("OA_SAVANNAH_TRUCK", "萨凡纳普船海卡", "萨凡纳普船海卡", "SPHK", null),
|
||||
parcel("MATSON_CLX_PARCEL", "美森正班海派", "海派", "ZBYHP", null),
|
||||
parcel("MATSON_MAX_PARCEL", "美森加班海派", "海派", "BYHP", null),
|
||||
parcel("HEDE_PARCEL", "合德海派", "海派", "HDHP", null),
|
||||
parcel("OA_PARCEL", "普船海派", "海派", "PCHP", null)
|
||||
);
|
||||
|
||||
private static ChannelDefinition fba(String code, String name, String sheet) {
|
||||
return new ChannelDefinition(code, name, PriceModel.FBA_WAREHOUSE_RATE, sheet, Set.of(name));
|
||||
private static ChannelDefinition fba(String code, String name, String sheet,
|
||||
String inboundCodePrefix, String overseasInboundCodePrefix) {
|
||||
return new ChannelDefinition(code, name, PriceModel.FBA_WAREHOUSE_RATE, sheet, Set.of(name),
|
||||
inboundCodePrefix, overseasInboundCodePrefix);
|
||||
}
|
||||
|
||||
private static ChannelDefinition parcel(String code, String name, String sheet) {
|
||||
return new ChannelDefinition(code, name, PriceModel.REGION_WEIGHT_TIER, sheet, Set.of(name));
|
||||
private static ChannelDefinition parcel(String code, String name, String sheet,
|
||||
String inboundCodePrefix, String overseasInboundCodePrefix) {
|
||||
return new ChannelDefinition(code, name, PriceModel.REGION_WEIGHT_TIER, sheet, Set.of(name),
|
||||
inboundCodePrefix, overseasInboundCodePrefix);
|
||||
}
|
||||
|
||||
public record ChannelDefinition(
|
||||
@@ -38,6 +42,8 @@ public final class PriceBookChannelCatalog {
|
||||
String displayName,
|
||||
PriceModel priceModel,
|
||||
String sourceSheetName,
|
||||
Set<String> aliases
|
||||
Set<String> aliases,
|
||||
String inboundCodePrefix,
|
||||
String overseasInboundCodePrefix
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ public record CartonMeasurementCommand(
|
||||
BigDecimal lengthCm,
|
||||
BigDecimal widthCm,
|
||||
BigDecimal heightCm,
|
||||
BigDecimal unitWeightKg,
|
||||
String machineNo,
|
||||
LocalDateTime measuredAt,
|
||||
MultipartFile image,
|
||||
|
||||
@@ -14,7 +14,6 @@ public record CartonMeasurementResult(
|
||||
BigDecimal lengthCm,
|
||||
BigDecimal widthCm,
|
||||
BigDecimal heightCm,
|
||||
BigDecimal unitWeightKg,
|
||||
Long imageFileId,
|
||||
String machineNo,
|
||||
LocalDateTime measuredAt,
|
||||
|
||||
@@ -25,6 +25,7 @@ public final class ContainerDtos {
|
||||
@Size(max = 64) String cabinetNo,
|
||||
@Size(max = 128) String billOfLadingNo,
|
||||
@Size(max = 128) String keyword,
|
||||
@Positive Long customerId,
|
||||
@Positive Long warehouseId,
|
||||
@Size(max = 32) String containerType,
|
||||
@Positive Long shippingCompanyId,
|
||||
@@ -46,7 +47,6 @@ public final class ContainerDtos {
|
||||
@Schema(name = "ContainerSaveRequest", description = "货柜基础资料;智能装柜草稿阶段可完整编辑")
|
||||
public record SaveRequest(
|
||||
@Size(max = 64) String containerNo,
|
||||
@NotBlank @Size(max = 64) String cabinetNo,
|
||||
@NotNull @Positive Long warehouseId,
|
||||
@Size(max = 128) String warehouseName,
|
||||
@Size(max = 128) String billOfLadingNo,
|
||||
@@ -63,6 +63,7 @@ public final class ContainerDtos {
|
||||
@Size(max = 128) String destinationWarehouseName,
|
||||
@DecimalMin(value = "0", inclusive = false) BigDecimal capacityCbm,
|
||||
@DecimalMin(value = "0", inclusive = false) BigDecimal maxWeightKg,
|
||||
@Positive Long terminalTruckingCompanyId,
|
||||
@Size(max = 128) String terminalTruckingCompany,
|
||||
LocalDateTime loadingAt,
|
||||
LocalDateTime estimatedSailingAt,
|
||||
@@ -91,4 +92,19 @@ public final class ContainerDtos {
|
||||
this(containerNo, voyageNo, version, "运输信息更新");
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "ContainerSimulationCreateRequest", description = "新增内部模拟柜;内部柜号由服务端生成")
|
||||
public record SimulationCreateRequest(@NotBlank @Size(max = 64) String channelCode,
|
||||
@NotNull @Positive Long warehouseId, @NotBlank @Size(max = 32) String containerType,
|
||||
@NotNull @DecimalMin(value = "0", inclusive = false) BigDecimal capacityCbm,
|
||||
@NotNull @DecimalMin(value = "0", inclusive = false) BigDecimal maxWeightKg) {}
|
||||
|
||||
@Schema(name = "ContainerOceanBookingLinkRequest", description = "将现有模拟柜原地关联到已发布订舱箱型需求并补录真实柜号")
|
||||
public record OceanBookingLinkRequest(@NotNull @Positive Long oceanBookingId,
|
||||
@NotNull @Positive Long requirementId, @NotBlank @Size(max = 64) String containerNo,
|
||||
@NotNull @PositiveOrZero Integer expectedVersion) {}
|
||||
|
||||
@Schema(name = "ContainerOceanBookingUnlinkRequest", description = "解除空模拟柜的真实订舱关联")
|
||||
public record OceanBookingUnlinkRequest(@NotNull @PositiveOrZero Integer expectedVersion,
|
||||
@NotBlank @Size(max = 500) String reason) {}
|
||||
}
|
||||
|
||||
@@ -20,29 +20,16 @@ public final class ContainerFeeDetailDtos {
|
||||
@NotNull LocalDate businessDate,
|
||||
@NotNull @DecimalMin("0.00") BigDecimal amount,
|
||||
@NotBlank @Pattern(regexp = "CNY") String currency,
|
||||
@NotNull Boolean settlementIncluded,
|
||||
Long partnerId,
|
||||
@Size(max = 200) String partnerName,
|
||||
Long supplierId,
|
||||
@Size(max = 500) String remark
|
||||
) {
|
||||
}
|
||||
|
||||
public record AdjustRequest(
|
||||
@NotNull @Min(1) Integer expectedSourceVersion,
|
||||
@NotNull @Min(1) Long feeItemId,
|
||||
@NotBlank @Pattern(regexp = "RECEIVABLE|PAYABLE") String direction,
|
||||
@NotNull LocalDate businessDate,
|
||||
@NotNull @DecimalMin("0.00") BigDecimal amount,
|
||||
@NotBlank @Pattern(regexp = "CNY") String currency,
|
||||
@NotNull Boolean settlementIncluded,
|
||||
Long partnerId,
|
||||
@Size(max = 200) String partnerName,
|
||||
@Size(max = 500) String remark
|
||||
@NotNull @DecimalMin("0.00") BigDecimal amount
|
||||
) {
|
||||
public SaveRequest values() {
|
||||
return new SaveRequest(feeItemId, direction, businessDate, amount, currency,
|
||||
settlementIncluded, partnerId, partnerName, remark);
|
||||
}
|
||||
}
|
||||
|
||||
public record VersionRequest(@NotNull @Min(1) Integer expectedSourceVersion) {
|
||||
|
||||
@@ -16,7 +16,7 @@ public final class ContainerOverviewDtos {
|
||||
String status, Long warehouseId, String warehouseName, String voyageNo, String vesselName,
|
||||
Integer version) {}
|
||||
public record PdaSummary(Long taskId, Long loadingPlanId, Long containerBookingId, String taskStatus,
|
||||
Integer taskVersion, Integer itemCount, Integer completedItemCount, Integer unresolvedItemCount,
|
||||
String workflowStatus, Integer taskVersion, Integer itemCount, Integer completedItemCount, Integer unresolvedItemCount,
|
||||
List<PhotoCount> photoCounts, List<Photo> photos, List<String> exceptionReasons) {}
|
||||
public record PhotoCount(String evidenceType, Integer count) {}
|
||||
public record Photo(Long evidenceId, Long tenantId, Long inboundId, String evidenceType,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 当前业务单元所关联柜次的正式运输事实。 */
|
||||
public record CurrentContainerTransport(
|
||||
Long inboundId, Long accountId, Long containerBookingId,
|
||||
String containerNo, String cabinetNo, String vesselName, String voyageNo,
|
||||
LocalDateTime estimatedSailingAt, LocalDateTime actualSailingAt,
|
||||
LocalDateTime estimatedPortArrivalAt, LocalDateTime actualPortArrivalAt
|
||||
) {}
|
||||
@@ -6,6 +6,7 @@ import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
@@ -52,7 +53,9 @@ public final class FbaPricingDtos {
|
||||
public record CalculateRequest(LocalDateTime pricingAt) {}
|
||||
|
||||
public record AdjustFeeRequest(
|
||||
@NotNull @DecimalMin("0.00") BigDecimal subtotalAmount,
|
||||
@NotNull @Positive Integer expectedFeeVersion,
|
||||
@NotNull @DecimalMin(value = "0", inclusive = false) BigDecimal chargeableQuantity,
|
||||
@NotNull @DecimalMin("0.00") BigDecimal unitPrice,
|
||||
@NotNull @DecimalMin("0.00") BigDecimal additionalAmount,
|
||||
@NotBlank @Size(max = 500) String reason
|
||||
) {}
|
||||
|
||||
@@ -14,6 +14,7 @@ public final class FeeItemDtos {
|
||||
@NotBlank @Size(max = 64) String category,
|
||||
@NotBlank @Pattern(regexp = "DOMESTIC|INTERNATIONAL") String tradeScope,
|
||||
@Size(max = 50) @Pattern(regexp = "[A-Za-z0-9_-]{1,50}") String feeCode,
|
||||
@Pattern(regexp = "BOOKING|CUSTOMS|TRUCKING|CERTIFICATE") String supplierCategory,
|
||||
@NotBlank @Size(max = 128) String feeTypeName,
|
||||
@NotNull Boolean pickupPayable,
|
||||
@NotNull Boolean pickupReceivable,
|
||||
@@ -22,12 +23,22 @@ public final class FeeItemDtos {
|
||||
@NotNull Boolean allocationFee,
|
||||
@Size(max = 500) String description
|
||||
) {
|
||||
public SaveRequest(String category, String tradeScope, String feeCode,
|
||||
String feeTypeName, Boolean pickupPayable,
|
||||
Boolean pickupReceivable, Boolean warehousePayable,
|
||||
Boolean warehouseReceivable, Boolean allocationFee,
|
||||
String description) {
|
||||
this(category, tradeScope, feeCode, null, feeTypeName, pickupPayable,
|
||||
pickupReceivable, warehousePayable, warehouseReceivable,
|
||||
allocationFee, description);
|
||||
}
|
||||
|
||||
/** Backward-compatible constructor for callers predating the stable fee code. */
|
||||
public SaveRequest(String category, String tradeScope, String feeTypeName,
|
||||
Boolean pickupPayable, Boolean pickupReceivable,
|
||||
Boolean warehousePayable, Boolean warehouseReceivable,
|
||||
Boolean allocationFee, String description) {
|
||||
this(category, tradeScope, null, feeTypeName, pickupPayable, pickupReceivable,
|
||||
this(category, tradeScope, null, null, feeTypeName, pickupPayable, pickupReceivable,
|
||||
warehousePayable, warehouseReceivable, allocationFee, description);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@ public record FinanceFeeSourceSnapshot(
|
||||
String containerNo,
|
||||
long customerId,
|
||||
String customerName,
|
||||
Long partnerId,
|
||||
String partnerName,
|
||||
Long supplierId,
|
||||
String supplierName,
|
||||
String supplierCategory,
|
||||
Long feeItemId,
|
||||
String feeItemCode,
|
||||
String feeItemName,
|
||||
@@ -29,7 +30,6 @@ public record FinanceFeeSourceSnapshot(
|
||||
BigDecimal totalAmount,
|
||||
String reviewStatus,
|
||||
String publicationStatus,
|
||||
boolean settlementIncluded,
|
||||
LocalDateTime publishedAt,
|
||||
BigDecimal chargeableQuantity,
|
||||
String chargeUnit,
|
||||
|
||||
@@ -21,15 +21,13 @@ public final class InboundFeeDetailDtos {
|
||||
@NotNull @DecimalMin("0.00") BigDecimal unitPrice,
|
||||
@NotBlank @Pattern(regexp = "[A-Z]{3}") String currency,
|
||||
@NotNull @DecimalMin("0.00") BigDecimal amount,
|
||||
@NotNull Boolean settlementIncluded,
|
||||
Long partnerId,
|
||||
@Size(max = 200) String partnerName,
|
||||
Long supplierId,
|
||||
@Size(max = 500) String remark
|
||||
) {
|
||||
public SaveRequest(Long feeItemId, String direction, LocalDate businessDate,
|
||||
BigDecimal unitPrice, String currency, BigDecimal amount,
|
||||
String remark) {
|
||||
this(feeItemId, direction, businessDate, unitPrice, currency, amount, false, null, null, remark);
|
||||
this(feeItemId, direction, businessDate, unitPrice, currency, amount, null, remark);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.DecimalMin;
|
||||
import jakarta.validation.constraints.DecimalMax;
|
||||
@@ -26,12 +27,13 @@ public final class InboundMeasurementRequests {
|
||||
public record SaveDraftRequest(
|
||||
Long measurementId,
|
||||
@NotNull @PositiveOrZero Integer version,
|
||||
@Size(max = 64) String originalDocumentNo,
|
||||
@DecimalMin("0.000") @Digits(integer = 15, fraction = 3) BigDecimal originalGrossWeightKg,
|
||||
@DecimalMin("0.0000") @Digits(integer = 14, fraction = 4) BigDecimal originalVolumeCbm,
|
||||
@Size(max = 255) String actualGoodsName,
|
||||
@NotEmpty @Size(max = 500) List<@Valid ItemRequest> items
|
||||
) {
|
||||
@JsonAnySetter
|
||||
public void rejectUnknownField(String fieldName, Object ignored) {
|
||||
throw new IllegalArgumentException("测量草稿不接受字段: " + fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
public record ItemRequest(
|
||||
@@ -42,7 +44,6 @@ public final class InboundMeasurementRequests {
|
||||
@NotNull @DecimalMin("0.001") @Digits(integer = 9, fraction = 3) BigDecimal lengthCm,
|
||||
@NotNull @DecimalMin("0.001") @Digits(integer = 9, fraction = 3) BigDecimal widthCm,
|
||||
@NotNull @DecimalMin("0.001") @Digits(integer = 9, fraction = 3) BigDecimal heightCm,
|
||||
@NotNull @DecimalMin("0.001") @Digits(integer = 15, fraction = 3) BigDecimal unitWeightKg,
|
||||
@Positive Long intelligentImageFileId,
|
||||
@Size(max = 500) String overLimitWarning,
|
||||
@DecimalMin("0.000") @Digits(integer = 9, fraction = 3) BigDecimal outerLengthCm,
|
||||
@@ -70,7 +71,6 @@ public final class InboundMeasurementRequests {
|
||||
@NotNull @DecimalMin("0.001") @Digits(integer = 9, fraction = 3) BigDecimal length,
|
||||
@NotNull @DecimalMin("0.001") @Digits(integer = 9, fraction = 3) BigDecimal width,
|
||||
@NotNull @DecimalMin("0.001") @Digits(integer = 9, fraction = 3) BigDecimal height,
|
||||
@DecimalMin("0.001") @Digits(integer = 15, fraction = 3) BigDecimal weight,
|
||||
@Size(max = 64) String scannedShipmentId,
|
||||
@NotBlank @Size(max = 64) String machineNo,
|
||||
@NotNull LocalDateTime measuredAt
|
||||
@@ -79,6 +79,7 @@ public final class InboundMeasurementRequests {
|
||||
|
||||
public record CreateWeightResultRequest(
|
||||
@NotNull @Positive Long fileId,
|
||||
@NotNull @Positive Long palletId,
|
||||
@Size(max = 64) String ocrProvider,
|
||||
@Size(max = 1000) String ocrRawText,
|
||||
@DecimalMin("0.000") @Digits(integer = 15, fraction = 3) BigDecimal ocrWeightValue,
|
||||
|
||||
@@ -40,6 +40,7 @@ public final class InboundMeasurementResponses {
|
||||
Integer originalPackageCount,
|
||||
Integer actualPackageCount,
|
||||
BigDecimal actualGrossWeightKg,
|
||||
BigDecimal actualNetWeightKg,
|
||||
BigDecimal actualVolumeCbm,
|
||||
BigDecimal volumeWeightKg,
|
||||
String actualGoodsName,
|
||||
@@ -58,9 +59,7 @@ public final class InboundMeasurementResponses {
|
||||
BigDecimal widthCm,
|
||||
BigDecimal heightCm,
|
||||
BigDecimal girthCm,
|
||||
BigDecimal unitWeightKg,
|
||||
BigDecimal unitVolumeCbm,
|
||||
BigDecimal totalWeightKg,
|
||||
BigDecimal totalVolumeCbm,
|
||||
BigDecimal volumeWeightKg,
|
||||
Long intelligentImageFileId,
|
||||
@@ -81,14 +80,20 @@ public final class InboundMeasurementResponses {
|
||||
public record WeightResult(
|
||||
Long id,
|
||||
Long fileId,
|
||||
Long palletId,
|
||||
String palletCode,
|
||||
String palletType,
|
||||
BigDecimal palletCapacityCbm,
|
||||
BigDecimal palletTareWeightKg,
|
||||
String ocrProvider,
|
||||
String ocrRawText,
|
||||
BigDecimal ocrWeightValue,
|
||||
String ocrWeightUnit,
|
||||
BigDecimal confidence,
|
||||
BigDecimal grossWeightKg,
|
||||
BigDecimal adjustedWeightValue,
|
||||
String adjustedWeightUnit,
|
||||
BigDecimal finalWeightKg,
|
||||
BigDecimal netWeightKg,
|
||||
String recognitionStatus,
|
||||
String adjustmentReason,
|
||||
Long adjustedBy,
|
||||
@@ -99,6 +104,15 @@ public final class InboundMeasurementResponses {
|
||||
) {
|
||||
}
|
||||
|
||||
public record PalletSnapshot(
|
||||
Long palletId,
|
||||
String palletCode,
|
||||
String palletType,
|
||||
BigDecimal capacityCbm,
|
||||
BigDecimal tareWeightKg
|
||||
) {
|
||||
}
|
||||
|
||||
public record DraftSaved(Long measurementId, Integer version, String status) {
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.baiyue.firstleg.dto;
|
||||
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.entity.InboundTransportSnapshotEntity;
|
||||
import com.baiyue.firstleg.entity.InboundPickupSnapshotEntity;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -12,7 +13,7 @@ public record InboundOrderSummary(
|
||||
String processStatus, String orderStatusSnapshot, String measureReviewStatus,
|
||||
String feeStatus, String feeReviewStatus, String notificationStatus,
|
||||
Integer version,
|
||||
LocalDateTime expectedArrivalAt,
|
||||
LocalDateTime expectedArrivalAt, LocalDateTime requestedPickupAt,
|
||||
Long customerId, String customerName, Long companyId, String companyName,
|
||||
Long warehouseId, String warehouseName,
|
||||
Long deliveryWarehouseId, String deliveryWarehouseName,
|
||||
@@ -26,18 +27,33 @@ public record InboundOrderSummary(
|
||||
String locationCodes,
|
||||
LocalDateTime measureCompletedAt, LocalDateTime inboundedAt,
|
||||
String expressCompanyCode, String expressCompanyName, String expressTrackingNo,
|
||||
String loadingStatus, String containerNo, String cabinetNo,
|
||||
String vesselName, String voyageNo, LocalDateTime sailingAt, LocalDateTime portArrivalAt,
|
||||
String containerNo, String cabinetNo,
|
||||
String vesselName, String voyageNo,
|
||||
LocalDateTime estimatedSailingAt, LocalDateTime actualSailingAt,
|
||||
LocalDateTime estimatedPortArrivalAt, LocalDateTime actualPortArrivalAt,
|
||||
LocalDateTime createdAt, LocalDateTime updatedAt
|
||||
) {
|
||||
/** 非统一入仓接口保留既有快照适配;统一订单不得调用此重载。 */
|
||||
public static InboundOrderSummary from(
|
||||
InboundOrderEntity order, InboundTransportSnapshotEntity transport, String locationCodes) {
|
||||
InboundOrderEntity order, InboundTransportSnapshotEntity transport,
|
||||
InboundPickupSnapshotEntity pickup, String locationCodes) {
|
||||
CurrentContainerTransport adapted = transport == null ? null : new CurrentContainerTransport(
|
||||
order.getId(), order.getTenantId(), null, transport.getContainerNo(), transport.getCabinetNo(),
|
||||
transport.getVesselName(), transport.getVoyageNo(), transport.getSailingAt(), null,
|
||||
transport.getPortArrivalAt(), null);
|
||||
return from(order, adapted, pickup, locationCodes);
|
||||
}
|
||||
|
||||
public static InboundOrderSummary from(
|
||||
InboundOrderEntity order, CurrentContainerTransport transport,
|
||||
InboundPickupSnapshotEntity pickup, String locationCodes) {
|
||||
return new InboundOrderSummary(
|
||||
order.getId(), order.getTenantId(), order.getOrderId(), order.getOrderNo(), order.getInboundCode(),
|
||||
order.getProcessStatus(), order.getOrderStatusSnapshot(), order.getMeasureReviewStatus(),
|
||||
order.getFeeStatus(), order.getFeeReviewStatus(), order.getNotificationStatus(),
|
||||
order.getVersion(),
|
||||
order.getExpectedArrivalAt(), order.getCustomerId(), order.getCustomerName(),
|
||||
order.getExpectedArrivalAt(), pickup == null ? null : pickup.getPickupAt(),
|
||||
order.getCustomerId(), order.getCustomerName(),
|
||||
order.getCompanyId(), order.getCompanyName(), order.getWarehouseId(), order.getWarehouseName(),
|
||||
order.getDeliveryWarehouseId(), order.getDeliveryWarehouseName(), order.getBusinessType(),
|
||||
order.getChannelCode(), order.getChannelName(), order.getTaxMode(), order.getPricingTaxMode(),
|
||||
@@ -50,13 +66,14 @@ public record InboundOrderSummary(
|
||||
locationCodes,
|
||||
order.getMeasureCompletedAt(), order.getInboundedAt(), order.getExpressCompanyCode(),
|
||||
order.getExpressCompanyName(), order.getExpressTrackingNo(),
|
||||
transport == null ? null : transport.getLoadingStatus(),
|
||||
transport == null ? null : transport.getContainerNo(),
|
||||
transport == null ? null : transport.getCabinetNo(),
|
||||
transport == null ? null : transport.getVesselName(),
|
||||
transport == null ? null : transport.getVoyageNo(),
|
||||
transport == null ? null : transport.getSailingAt(),
|
||||
transport == null ? null : transport.getPortArrivalAt(),
|
||||
transport == null ? null : transport.containerNo(),
|
||||
transport == null ? null : transport.cabinetNo(),
|
||||
transport == null ? null : transport.vesselName(),
|
||||
transport == null ? null : transport.voyageNo(),
|
||||
transport == null ? null : transport.estimatedSailingAt(),
|
||||
transport == null ? null : transport.actualSailingAt(),
|
||||
transport == null ? null : transport.estimatedPortArrivalAt(),
|
||||
transport == null ? null : transport.actualPortArrivalAt(),
|
||||
order.getCreatedAt(), order.getUpdatedAt()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ public final class LoadingListDtos {
|
||||
public record PreloadQuery(
|
||||
@Size(max = 128) String keyword,
|
||||
String status,
|
||||
@Positive Long customerId,
|
||||
@Min(1) Long warehouseId,
|
||||
LocalDateTime createdFrom,
|
||||
LocalDateTime createdTo,
|
||||
@@ -32,6 +33,7 @@ public final class LoadingListDtos {
|
||||
|
||||
public record ConfirmedQuery(
|
||||
@Size(max = 128) String keyword,
|
||||
@Positive Long customerId,
|
||||
@Min(1) Long warehouseId,
|
||||
LocalDateTime createdFrom,
|
||||
LocalDateTime createdTo,
|
||||
@@ -81,12 +83,43 @@ public final class LoadingListDtos {
|
||||
public record Customer(Long accountId, String customerName) {
|
||||
}
|
||||
|
||||
public record PreloadReadiness(
|
||||
boolean preloadPushed,
|
||||
boolean containerNoReady,
|
||||
int preloadLinkCount,
|
||||
int confirmedPreloadCount,
|
||||
Long oceanBookingId,
|
||||
String oceanBookingStatus,
|
||||
Long pdaTaskId,
|
||||
String pdaTaskStatus,
|
||||
String confirmationStatus,
|
||||
boolean canConfirm,
|
||||
boolean readyForPda,
|
||||
List<String> blockingReasons
|
||||
) {
|
||||
public PreloadReadiness {
|
||||
blockingReasons = blockingReasons == null ? List.of() : List.copyOf(blockingReasons);
|
||||
}
|
||||
}
|
||||
|
||||
public record ConfirmPreloadRequest(@NotNull @Min(0) Integer expectedVersion) {
|
||||
}
|
||||
|
||||
public record PreloadConfirmationResponse(
|
||||
Long planId,
|
||||
Long containerBookingId,
|
||||
String confirmationStatus,
|
||||
Integer version,
|
||||
PreloadReadiness readiness
|
||||
) {
|
||||
}
|
||||
|
||||
public record PreloadSummary(
|
||||
Long planId,
|
||||
Long containerBookingId,
|
||||
String preloadNo,
|
||||
String containerNo,
|
||||
String internalContainerNo,
|
||||
String cabinetNo,
|
||||
List<Customer> customers,
|
||||
Long warehouseId,
|
||||
String warehouseName,
|
||||
@@ -99,7 +132,8 @@ public final class LoadingListDtos {
|
||||
BigDecimal totalVolumeCbm,
|
||||
BigDecimal totalGrossWeightKg,
|
||||
LocalDateTime createdAt,
|
||||
Integer version
|
||||
Integer version,
|
||||
PreloadReadiness readiness
|
||||
) {
|
||||
public PreloadSummary {
|
||||
customers = customers == null ? List.of() : List.copyOf(customers);
|
||||
@@ -113,7 +147,7 @@ public final class LoadingListDtos {
|
||||
Long warehouseId,
|
||||
String warehouseName,
|
||||
String containerNo,
|
||||
String internalContainerNo,
|
||||
String cabinetNo,
|
||||
String status,
|
||||
long totalPackageCount,
|
||||
BigDecimal totalGrossWeightKg,
|
||||
@@ -132,9 +166,12 @@ public final class LoadingListDtos {
|
||||
Long itemId,
|
||||
Long accountId,
|
||||
String customerName,
|
||||
Long orderId,
|
||||
String orderNo,
|
||||
Long inboundId,
|
||||
Long inboundDetailId,
|
||||
String inboundCode,
|
||||
String containerNo,
|
||||
String skuCode,
|
||||
String destinationWarehouse,
|
||||
String chineseName,
|
||||
@@ -155,7 +192,8 @@ public final class LoadingListDtos {
|
||||
String customerRemark,
|
||||
String warehouseRemark,
|
||||
int loadSequence,
|
||||
LocalDateTime frozenAt
|
||||
LocalDateTime frozenAt,
|
||||
Integer itemVersion
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -60,17 +60,10 @@ public final class LoadingPickDtos {
|
||||
@NotNull @Min(1) Long warehouseId,
|
||||
@NotNull @Min(1) Long loadingPlanId,
|
||||
@NotNull @Min(1) Long containerBookingId,
|
||||
@Size(max = 64) String containerNo,
|
||||
@NotNull @Min(0) Integer expectedVersion,
|
||||
@NotBlank @Size(max = 64) String inventorySnapshotToken,
|
||||
@NotEmpty @Size(max = 500) List<@Valid BatchItemRequest> items
|
||||
) {
|
||||
@Deprecated
|
||||
public BatchRequest(Long warehouseId, String containerNo, Integer expectedVersion,
|
||||
String inventorySnapshotToken, List<BatchItemRequest> items) {
|
||||
this(warehouseId, null, null, containerNo, expectedVersion, inventorySnapshotToken, items);
|
||||
}
|
||||
}
|
||||
) {}
|
||||
|
||||
public record BatchItemRequest(
|
||||
@NotNull @Min(1) Long itemId,
|
||||
@@ -132,8 +125,10 @@ public final class LoadingPickDtos {
|
||||
Long operatorId, String operatorName, LocalDateTime createdAt) {
|
||||
}
|
||||
|
||||
public record Task(Long id, Long loadingPlanId, Long containerBookingId, String cabinetNo,
|
||||
String currentContainerNo, String taskStatus,
|
||||
public record Task(Long id, Long loadingPlanId, Long containerBookingId, Long inboundId,
|
||||
Integer inboundVersion, Integer loadingVersion, String cabinetNo,
|
||||
String currentContainerNo, String sourceType, String recordStatus,
|
||||
Long mergedIntoContainerBookingId, String taskStatus,
|
||||
String workflowStatus, Integer version, Assignment assignment,
|
||||
Review review, List<PickItem> items) {
|
||||
}
|
||||
@@ -141,6 +136,7 @@ public final class LoadingPickDtos {
|
||||
/** PDA 工作台只展示当前作业员可领取或已领取的进行中任务。 */
|
||||
public record PendingTask(Long taskId, Long loadingPlanId, Long containerBookingId,
|
||||
String cabinetNo, String currentContainerNo,
|
||||
String sourceType, String recordStatus, Long mergedIntoContainerBookingId,
|
||||
Long warehouseId, String warehouseName, String taskStatus,
|
||||
String workflowStatus, Integer version, Integer assignmentVersion,
|
||||
Integer itemCount, Integer pendingItemCount) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
|
||||
import com.baiyue.firstleg.dto.FbaPricingDtos.FeeSnapshotResponse;
|
||||
|
||||
public record MeasurementApprovalResponse(
|
||||
Long inboundId,
|
||||
Long measurementId,
|
||||
String processStatus,
|
||||
String feeStatus,
|
||||
boolean initialPricingSucceeded,
|
||||
String initialPricingMessage,
|
||||
FeeSnapshotResponse initialFee
|
||||
) {}
|
||||
@@ -35,7 +35,6 @@ public record MeasurementBatchCommand(
|
||||
BigDecimal lengthCm,
|
||||
BigDecimal widthCm,
|
||||
BigDecimal heightCm,
|
||||
BigDecimal unitWeightKg,
|
||||
Long intelligentImageFileId,
|
||||
String overLimitWarning,
|
||||
BigDecimal outerLengthCm,
|
||||
|
||||
@@ -19,7 +19,7 @@ public record MerchantInboundBillingResponse(InboundOrderDetail inbound,
|
||||
|
||||
public record MeasurementSummary(
|
||||
Long measurementId, String sourceType, String measurementStatus,
|
||||
Integer actualPackageCount, BigDecimal actualGrossWeightKg,
|
||||
Integer actualPackageCount, BigDecimal actualGrossWeightKg, BigDecimal actualNetWeightKg,
|
||||
BigDecimal actualVolumeCbm, BigDecimal volumeWeightKg, String actualGoodsName) {
|
||||
}
|
||||
|
||||
@@ -31,15 +31,16 @@ public record MerchantInboundBillingResponse(InboundOrderDetail inbound,
|
||||
public record MeasurementItem(
|
||||
Long id, String itemNo, String boxSpecification, Integer packageCount,
|
||||
BigDecimal lengthCm, BigDecimal widthCm, BigDecimal heightCm,
|
||||
BigDecimal unitWeightKg, BigDecimal totalWeightKg,
|
||||
BigDecimal unitVolumeCbm, BigDecimal totalVolumeCbm,
|
||||
BigDecimal volumeWeightKg) {
|
||||
}
|
||||
|
||||
public record WeightResult(
|
||||
Long id, Long fileId, BigDecimal recognizedWeightValue, String recognizedWeightUnit,
|
||||
BigDecimal adjustedWeightValue, String adjustedWeightUnit,
|
||||
BigDecimal finalWeightKg, String recognitionStatus) {
|
||||
Long id, Long fileId, Long palletId, String palletCode, String palletType,
|
||||
BigDecimal palletCapacityCbm, BigDecimal palletTareWeightKg,
|
||||
BigDecimal recognizedWeightValue, String recognizedWeightUnit,
|
||||
BigDecimal grossWeightKg, BigDecimal adjustedWeightValue, String adjustedWeightUnit,
|
||||
BigDecimal netWeightKg, String recognitionStatus) {
|
||||
}
|
||||
|
||||
public record BillingBasis(
|
||||
@@ -62,7 +63,7 @@ public record MerchantInboundBillingResponse(InboundOrderDetail inbound,
|
||||
BigDecimal chargeableQuantity, String chargeUnit,
|
||||
String feeTypeName, BigDecimal unitPrice, String currency, BigDecimal amount,
|
||||
String reviewStatus, String publicationStatus,
|
||||
LocalDateTime publishedAt, LocalDateTime inboundedAt, Boolean settlementIncluded
|
||||
LocalDateTime publishedAt, LocalDateTime inboundedAt
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ public final class MojiaMeasurementDtos {
|
||||
@NotNull @DecimalMin("0.001") @DecimalMax("999999999.999") BigDecimal length,
|
||||
@NotNull @DecimalMin("0.001") @DecimalMax("999999999.999") BigDecimal width,
|
||||
@NotNull @DecimalMin("0.001") @DecimalMax("999999999.999") BigDecimal height,
|
||||
@DecimalMin("0.001") @DecimalMax("999999999999999.999") BigDecimal weight,
|
||||
@NotBlank @Size(max = 64) String machineNo,
|
||||
LocalDateTime measuredAt,
|
||||
@NotBlank @Size(max = 7_100_000) String picture
|
||||
|
||||
@@ -47,7 +47,6 @@ public final class OpenApiMeasurementDtos {
|
||||
@NotNull @DecimalMin(value = "0", inclusive = false) @DecimalMax("999999999.999") BigDecimal widthCm,
|
||||
@NotNull @DecimalMin(value = "0", inclusive = false) @DecimalMax("999999999.999") BigDecimal heightCm,
|
||||
@NotNull @Min(1) @Max(1000000) Integer packageCount,
|
||||
@DecimalMin(value = "0", inclusive = false) @DecimalMax("999999999.999") BigDecimal unitWeightKg,
|
||||
@NotBlank @Size(max = 64) String boxSpecification,
|
||||
@Size(max = 500) String remark) {
|
||||
}
|
||||
|
||||
@@ -31,8 +31,25 @@ public record OrderApprovedInboundCommand(
|
||||
String publicRemark,
|
||||
String expressCompanyName,
|
||||
String expressTrackingNo,
|
||||
Integer totalPackageCount
|
||||
Integer totalPackageCount,
|
||||
String pickupMethod,
|
||||
LocalDateTime requestedPickupAt
|
||||
) {
|
||||
public OrderApprovedInboundCommand(
|
||||
long tenantId, long orderId, String orderNo, String inboundCode, Long customerId, String customerName,
|
||||
Long warehouseId, String warehouseName, Long deliveryWarehouseId, String deliveryWarehouseName,
|
||||
LocalDateTime expectedArrivalAt, String businessType, String channelCode, String channelName,
|
||||
Long carrierId, String carrierName, String taxMode, String pricingTaxMode, String containerMode,
|
||||
Boolean customsInspection, String destinationType, Long destinationWarehouseId, String destinationCode,
|
||||
String destinationName, String publicRemark, String expressCompanyName, String expressTrackingNo,
|
||||
Integer totalPackageCount
|
||||
) {
|
||||
this(tenantId, orderId, orderNo, inboundCode, customerId, customerName, warehouseId, warehouseName,
|
||||
deliveryWarehouseId, deliveryWarehouseName, expectedArrivalAt, businessType, channelCode, channelName,
|
||||
carrierId, carrierName, taxMode, pricingTaxMode, containerMode, customsInspection, destinationType,
|
||||
destinationWarehouseId, destinationCode, destinationName, publicRemark, expressCompanyName,
|
||||
expressTrackingNo, totalPackageCount, null, null);
|
||||
}
|
||||
/** 兼容尚未携带订单总箱数的历史事件。 */
|
||||
public OrderApprovedInboundCommand(
|
||||
long tenantId, long orderId, String orderNo, String inboundCode, Long customerId, String customerName,
|
||||
@@ -46,7 +63,7 @@ public record OrderApprovedInboundCommand(
|
||||
deliveryWarehouseId, deliveryWarehouseName, expectedArrivalAt, businessType, channelCode, channelName,
|
||||
carrierId, carrierName, taxMode, pricingTaxMode, containerMode, customsInspection, destinationType,
|
||||
destinationWarehouseId, destinationCode, destinationName, publicRemark, expressCompanyName,
|
||||
expressTrackingNo, null);
|
||||
expressTrackingNo, null, null, null);
|
||||
}
|
||||
|
||||
/** 兼容未携带报价包税维度的历史调用方。 */
|
||||
@@ -62,6 +79,6 @@ public record OrderApprovedInboundCommand(
|
||||
deliveryWarehouseId, deliveryWarehouseName, expectedArrivalAt, businessType, channelCode, channelName,
|
||||
carrierId, carrierName, taxMode, null, containerMode, customsInspection, destinationType,
|
||||
destinationWarehouseId, destinationCode, destinationName, publicRemark, expressCompanyName,
|
||||
expressTrackingNo, null);
|
||||
expressTrackingNo, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.dto;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
public final class PriceBookChannelValidation {
|
||||
private PriceBookChannelValidation() {}
|
||||
@@ -10,6 +11,10 @@ public final class PriceBookChannelValidation {
|
||||
public record Request(
|
||||
@Min(1) long tenantId,
|
||||
@Min(1) long originWarehouseId,
|
||||
@NotBlank @Size(max = 64) String channelCode
|
||||
@NotBlank @Size(max = 64) String channelCode,
|
||||
@NotBlank @Pattern(regexp = "FBA|OVERSEAS|PRIVATE") String destinationType,
|
||||
boolean requireInboundCodePrefix
|
||||
) {}
|
||||
|
||||
public record Response(String channelCode, String inboundCodePrefix) {}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@ import com.baiyue.firstleg.entity.LoadingPlanItemEntity; import jakarta.validati
|
||||
public final class SmartLoadingDtos { private SmartLoadingDtos(){}
|
||||
public record Candidate(Long accountId, Long inboundId, Long inboundDetailId, Long orderId, String orderNo, String inboundCode,
|
||||
Long warehouseId, String channelCode, String channelName, Long destinationWarehouseId, String destinationWarehouseCode, String destinationWarehouseName, String customerName, String goodsName, Integer availablePackageCount,
|
||||
BigDecimal unitWeightKg, BigDecimal unitVolumeCbm) {
|
||||
BigDecimal unitGrossWeightKg, BigDecimal unitNetWeightKg, BigDecimal unitVolumeCbm) {
|
||||
public Candidate(Long accountId, Long inboundId, Long inboundDetailId, Long orderId, String orderNo, String inboundCode,
|
||||
Long warehouseId, String destinationWarehouseCode, String customerName, String goodsName, Integer availablePackageCount,
|
||||
BigDecimal unitWeightKg, BigDecimal unitVolumeCbm) {
|
||||
this(accountId,inboundId,inboundDetailId,orderId,orderNo,inboundCode,warehouseId,null,null,null,destinationWarehouseCode,null,customerName,goodsName,availablePackageCount,unitWeightKg,unitVolumeCbm);
|
||||
BigDecimal unitGrossWeightKg, BigDecimal unitNetWeightKg, BigDecimal unitVolumeCbm) {
|
||||
this(accountId,inboundId,inboundDetailId,orderId,orderNo,inboundCode,warehouseId,null,null,null,destinationWarehouseCode,null,customerName,goodsName,availablePackageCount,unitGrossWeightKg,unitNetWeightKg,unitVolumeCbm);
|
||||
}
|
||||
public Candidate(Long accountId, Long inboundId, Long inboundDetailId, Long orderId, String orderNo, String inboundCode,
|
||||
Long warehouseId, Long destinationWarehouseId, String destinationWarehouseCode, String destinationWarehouseName,
|
||||
String customerName, String goodsName, Integer availablePackageCount, BigDecimal unitWeightKg, BigDecimal unitVolumeCbm) {
|
||||
String customerName, String goodsName, Integer availablePackageCount, BigDecimal unitGrossWeightKg, BigDecimal unitNetWeightKg, BigDecimal unitVolumeCbm) {
|
||||
this(accountId,inboundId,inboundDetailId,orderId,orderNo,inboundCode,warehouseId,null,null,destinationWarehouseId,
|
||||
destinationWarehouseCode,destinationWarehouseName,customerName,goodsName,availablePackageCount,unitWeightKg,unitVolumeCbm);
|
||||
destinationWarehouseCode,destinationWarehouseName,customerName,goodsName,availablePackageCount,unitGrossWeightKg,unitNetWeightKg,unitVolumeCbm);
|
||||
}
|
||||
}
|
||||
public record CreateRequest(@NotNull @Positive Long bookingId, @Size(max=500) String remark) {}
|
||||
@@ -25,6 +25,11 @@ public final class SmartLoadingDtos { private SmartLoadingDtos(){}
|
||||
@NotNull @Min(0) Integer expectedItemVersion,@NotBlank @Size(max=128) String operationId) {}
|
||||
public record DeleteRequest(@NotNull @Min(0) Integer expectedPlanVersion,
|
||||
@NotNull @Min(0) Integer expectedItemVersion,@NotBlank @Size(max=128) String operationId) {}
|
||||
public record WarehouseRemarkRequest(@Size(max=1000) String warehouseRemark,
|
||||
@NotNull @Min(0) Integer expectedPlanVersion, @NotNull @Min(0) Integer expectedItemVersion,
|
||||
@NotBlank @Size(max=128) String operationId) {}
|
||||
public record PushRequest(@NotNull @Min(0) Integer expectedVersion) {}
|
||||
public record Plan(Long id,String planNo,String status,Long bookingId,String containerNo,String voyageNo,Integer version,List<LoadingPlanItemEntity> items) {}
|
||||
public record Plan(Long id,String planNo,String status,Long bookingId,String containerNo,String voyageNo,
|
||||
Integer version,Boolean canAssign,Boolean canEdit,String blockedReason,String finalCompletionStatus,
|
||||
Long pdaTaskId,String pdaTaskStatus,Integer pdaTaskVersion,List<LoadingPlanItemEntity> items) {}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
import jakarta.validation.constraints.*; import java.math.BigDecimal; import java.time.LocalDateTime;
|
||||
public final class TruckAppointmentDtos { private TruckAppointmentDtos(){}
|
||||
public record Query(@Size(max=64) String keyword, com.baiyue.firstleg.domain.TruckAppointmentStatus status, @Positive Long containerBookingId, @Min(1) long page, @Min(1) @Max(200) long size){ public Query{if(page==0)page=1;if(size==0)size=20;} }
|
||||
public record SaveRequest(@Positive Long outboundOrderId,@Positive Long oceanBookingId,@Positive Long containerBookingId,@Positive Long loadingPlanId,@Positive Long warehouseId,@Positive Long customerId,@Size(max=128) String customerName,@Positive Long carrierId,@Size(max=128) String carrierName,@Size(max=64) String fleetContact,@Size(max=32) String fleetPhone,@Size(max=64) String driverName,@Size(max=32) String driverPhone,@Size(max=32) String driverIdCard,@Size(max=32) String vehicleNo,@Size(max=128) String storeName,@Size(max=64) String storeContact,@Size(max=32) String storePhone,@Size(max=255) String storeAddress,@Size(max=64) String storeCity,@Size(max=16) String storeZipcode,@PositiveOrZero Integer packageCount,@DecimalMin("0") BigDecimal grossWeightKg,@DecimalMin("0") BigDecimal volumeCbm,@PositiveOrZero Integer palletCount,@Size(max=255) String poNo,@NotNull LocalDateTime plannedLoadingAt,@PositiveOrZero Integer transitMinutes,@DecimalMin("0") BigDecimal payableAmount,@DecimalMin("0") BigDecimal receivableAmount,@Size(max=8) String currency,@Size(max=1000) String remark,@NotNull @PositiveOrZero Integer expectedVersion){}
|
||||
public record Query(@Size(max=64) String keyword, com.baiyue.firstleg.domain.TruckAppointmentStatus status, @Positive Long customerId, @Positive Long containerBookingId, @Min(1) long page, @Min(1) @Max(200) long size){ public Query{if(page==0)page=1;if(size==0)size=20;} }
|
||||
public record SaveRequest(@Positive Long outboundOrderId,@Positive Long oceanBookingId,@Positive Long containerBookingId,@Positive Long loadingPlanId,@Positive Long warehouseId,@NotNull @Positive Long customerId,@Size(max=128) String customerName,@NotNull @Positive Long carrierId,@Size(max=128) String carrierName,@Size(max=128) String billOfLadingNo,@Size(max=64) String fleetContact,@Size(max=32) String fleetPhone,@Size(max=64) String driverName,@Size(max=32) String driverPhone,@Size(max=32) String driverIdCard,@Size(max=32) String vehicleNo,@Size(max=128) String storeName,@Size(max=64) String storeContact,@Size(max=32) String storePhone,@Size(max=255) String storeAddress,@Size(max=64) String storeCity,@Size(max=16) String storeZipcode,@PositiveOrZero Integer packageCount,@DecimalMin("0") BigDecimal grossWeightKg,@DecimalMin("0") BigDecimal volumeCbm,@PositiveOrZero Integer palletCount,@Size(max=255) String poNo,@NotNull LocalDateTime plannedLoadingAt,@PositiveOrZero Integer transitMinutes,@DecimalMin("0") BigDecimal payableAmount,@DecimalMin("0") BigDecimal receivableAmount,@Size(max=8) String currency,@Size(max=1000) String remark,@NotNull @PositiveOrZero Integer expectedVersion){}
|
||||
public record ActionRequest(@NotNull @PositiveOrZero Integer expectedVersion,@Size(max=500) String reason){}
|
||||
public record AttachmentRequest(@NotNull @Positive Long fileId,@Size(max=64) String attachmentType,@Size(max=255) String fileName,@Size(max=128) String fileHash){ }
|
||||
public record AttachmentRequest(@NotNull @Positive Long fileId,@NotBlank @Pattern(regexp="BILL_OF_LADING|DISPATCH_ORDER|DRIVER_DOCUMENT|VEHICLE_DOCUMENT|OTHER") String attachmentType,@Size(max=255) String fileName,@Size(max=128) String fileHash){ }
|
||||
}
|
||||
|
||||
@@ -20,10 +20,15 @@ public class ContainerBookingEntity {
|
||||
private String bookingNo;
|
||||
private String containerNo;
|
||||
private String cabinetNo;
|
||||
private String sourceType;
|
||||
private String recordStatus;
|
||||
private Long mergedIntoContainerBookingId;
|
||||
private String channelCode;
|
||||
private String channelName;
|
||||
private Long oceanBookingId;
|
||||
private Long oceanBookingRequirementId;
|
||||
private Long customerId;
|
||||
private String customerName;
|
||||
private Long warehouseId;
|
||||
private String warehouseName;
|
||||
private String billOfLadingNo;
|
||||
@@ -40,6 +45,7 @@ public class ContainerBookingEntity {
|
||||
private String destinationWarehouseName;
|
||||
private BigDecimal capacityCbm;
|
||||
private BigDecimal maxWeightKg;
|
||||
private Long terminalTruckingCompanyId;
|
||||
private String terminalTruckingCompany;
|
||||
private LocalDateTime loadingAt;
|
||||
private LocalDateTime estimatedSailingAt;
|
||||
@@ -56,5 +62,11 @@ public class ContainerBookingEntity {
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedAt;
|
||||
@Version @TableField(fill = FieldFill.INSERT) private Integer version;
|
||||
@TableField(exist = false) private Long planId;
|
||||
@TableField(exist = false) private String planNo;
|
||||
@TableField(exist = false) private String planStatus;
|
||||
@TableField(exist = false) private Boolean exceptionActive;
|
||||
@TableField(exist = false) private String destinationClearanceDocumentStatus;
|
||||
@TableField(exist = false) private String customsDeclarationDocumentStatus;
|
||||
@TableField(exist = false) private String deliveryOrderDocumentStatus;
|
||||
@TableField(exist = false) private String pickupOrderDocumentStatus;
|
||||
}
|
||||
|
||||
@@ -25,12 +25,12 @@ public class ContainerFeeDetailEntity {
|
||||
private String feeItemCodeSnapshot;
|
||||
private String feeItemNameSnapshot;
|
||||
private String direction;
|
||||
private Long partnerId;
|
||||
private String partnerName;
|
||||
private Long supplierId;
|
||||
private String supplierName;
|
||||
private String supplierCategory;
|
||||
private LocalDate businessDate;
|
||||
private BigDecimal amount;
|
||||
private String currency;
|
||||
private Boolean settlementIncluded;
|
||||
private String reviewStatus;
|
||||
private String sourceStatus;
|
||||
private Integer sourceVersion;
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.baiyue.firstleg.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 柜次费用审核发布时固化的不可变版本快照。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("fl_container_fee_source_snapshot")
|
||||
public class ContainerFeeSourceSnapshotEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long sourceFeeId;
|
||||
private Integer sourceVersion;
|
||||
private Long tenantId;
|
||||
private Long loadingPlanId;
|
||||
private Long containerBookingId;
|
||||
private String containerNo;
|
||||
private Long feeItemId;
|
||||
private String feeItemCode;
|
||||
private String feeItemName;
|
||||
private String direction;
|
||||
private Long supplierId;
|
||||
private String supplierName;
|
||||
private String supplierCategory;
|
||||
private LocalDate businessDate;
|
||||
private BigDecimal amount;
|
||||
private String currency;
|
||||
private String reviewStatus;
|
||||
private LocalDateTime reviewedAt;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -18,6 +18,7 @@ public class FeeItemEntity extends BaseEntity {
|
||||
private String category;
|
||||
private String tradeScope;
|
||||
private String feeCode;
|
||||
private String supplierCategory;
|
||||
private String feeTypeName;
|
||||
private Boolean pickupPayable;
|
||||
private Boolean pickupReceivable;
|
||||
@@ -28,4 +29,5 @@ public class FeeItemEntity extends BaseEntity {
|
||||
private String status;
|
||||
private String createdByName;
|
||||
private String updatedByName;
|
||||
|
||||
}
|
||||
|
||||
@@ -27,8 +27,9 @@ public class InboundFeeDetailEntity extends BaseEntity {
|
||||
private String sourceFeeNo;
|
||||
private String inboundCode;
|
||||
private String direction;
|
||||
private Long partnerId;
|
||||
private String partnerName;
|
||||
private Long supplierId;
|
||||
private String supplierName;
|
||||
private String supplierCategory;
|
||||
private LocalDate businessDate;
|
||||
private Long customerId;
|
||||
private String customerName;
|
||||
@@ -62,5 +63,4 @@ public class InboundFeeDetailEntity extends BaseEntity {
|
||||
private String publishedByName;
|
||||
private LocalDateTime publishedAt;
|
||||
private LocalDateTime inboundedAt;
|
||||
private Boolean settlementIncluded;
|
||||
}
|
||||
|
||||
@@ -29,11 +29,9 @@ public class InboundMeasurementItemEntity {
|
||||
private BigDecimal widthCm;
|
||||
private BigDecimal heightCm;
|
||||
private BigDecimal girthCm;
|
||||
private BigDecimal unitWeightKg;
|
||||
private BigDecimal unitVolumeCbm;
|
||||
private BigDecimal volumeRawCbm;
|
||||
private BigDecimal volumeCbm;
|
||||
private BigDecimal totalWeightKg;
|
||||
private BigDecimal totalVolumeCbm;
|
||||
private BigDecimal volumeWeightKg;
|
||||
private String calculationRuleVersion;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.baiyue.firstleg.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 客户上门提货要求在入库域的快照。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("fl_inbound_pickup_snapshot")
|
||||
public class InboundPickupSnapshotEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long tenantId;
|
||||
private Long inboundId;
|
||||
private String pickupMethod;
|
||||
private LocalDateTime pickupAt;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -36,6 +36,7 @@ public class InboundPricingEntity extends BaseEntity {
|
||||
private Long destinationWarehouseId;
|
||||
private String destinationName;
|
||||
private BigDecimal actualGrossWeightKg;
|
||||
private BigDecimal actualNetWeightKg;
|
||||
private BigDecimal actualVolumeCbm;
|
||||
private BigDecimal chargeableWeightKg;
|
||||
private BigDecimal chargeableVolumeCbm;
|
||||
|
||||
@@ -10,7 +10,7 @@ import lombok.Setter;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 称重图片 OCR 原值、人工修正值和最终确认重量。 */
|
||||
/** 称重图片、托盘皮重快照、OCR 原值和最终确认净重。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("fl_inbound_weight_result")
|
||||
@@ -20,6 +20,11 @@ public class InboundWeightResultEntity extends BaseEntity {
|
||||
private Long tenantId;
|
||||
private Long inboundId;
|
||||
private Long fileId;
|
||||
private Long palletId;
|
||||
private String palletCode;
|
||||
private String palletType;
|
||||
private BigDecimal palletCapacityCbm;
|
||||
private BigDecimal palletTareWeightKg;
|
||||
private String ocrProvider;
|
||||
private String ocrRawText;
|
||||
private BigDecimal ocrWeightValue;
|
||||
@@ -27,7 +32,8 @@ public class InboundWeightResultEntity extends BaseEntity {
|
||||
private BigDecimal confidence;
|
||||
private BigDecimal adjustedWeightValue;
|
||||
private String adjustedWeightUnit;
|
||||
private BigDecimal finalWeightKg;
|
||||
private BigDecimal grossWeightKg;
|
||||
private BigDecimal netWeightKg;
|
||||
private String recognitionStatus;
|
||||
private String adjustmentReason;
|
||||
private Long adjustedBy;
|
||||
|
||||
@@ -25,7 +25,8 @@ public class InboundWeightRevisionEntity {
|
||||
private String beforeWeightUnit;
|
||||
private BigDecimal afterWeightValue;
|
||||
private String afterWeightUnit;
|
||||
private BigDecimal finalWeightKg;
|
||||
private BigDecimal grossWeightKg;
|
||||
private BigDecimal netWeightKg;
|
||||
private String reason;
|
||||
private Long operatorId;
|
||||
private String operatorName;
|
||||
|
||||
@@ -10,6 +10,11 @@ public class LoadingPlanItemEntity {
|
||||
private String fbaTrackingNo; private String shipmentNo; private String shippingMark; private Integer loadSequence; private String positionSnapshot;
|
||||
private String internalRemark; private String customerRemark; private String warehouseRemark; private LocalDateTime frozenAt; private Long frozenBy;
|
||||
private Long removedBy; private String removedByName; private LocalDateTime removedAt; @TableLogic private Boolean deleted;
|
||||
@TableField(exist=false) private Long activeInboundDetailId; private Long createdBy; private String createdByName; private LocalDateTime createdAt;
|
||||
@TableField(exist=false) private Long activeInboundDetailId;
|
||||
@TableField(exist=false) private Integer pickedPackageCount;
|
||||
@TableField(exist=false) private Integer minPackageCount;
|
||||
@TableField(exist=false) private Boolean canAdjust;
|
||||
@TableField(exist=false) private Boolean canRemove;
|
||||
private Long createdBy; private String createdByName; private LocalDateTime createdAt;
|
||||
private Long updatedBy; private String updatedByName; private LocalDateTime updatedAt; @Version private Integer version;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ public class MojiaMeasurementReceiptEntity {
|
||||
private BigDecimal lengthCm;
|
||||
private BigDecimal widthCm;
|
||||
private BigDecimal heightCm;
|
||||
private BigDecimal weightKg;
|
||||
private String machineNo;
|
||||
private LocalDateTime measuredAt;
|
||||
private Long imageFileId;
|
||||
|
||||
@@ -15,5 +15,7 @@ public class PriceChannelCatalogEntity {
|
||||
private String priceModel;
|
||||
private String sourceSheetName;
|
||||
private String aliasesJson;
|
||||
private String inboundCodePrefix;
|
||||
private String overseasInboundCodePrefix;
|
||||
private String status;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.entity;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import java.math.BigDecimal;
|
||||
@@ -22,6 +23,7 @@ public class TruckAppointmentEntity {
|
||||
private String customerName;
|
||||
private Long carrierId;
|
||||
private String carrierName;
|
||||
private String billOfLadingNo;
|
||||
private String fleetContact;
|
||||
private String fleetPhone;
|
||||
private String driverName;
|
||||
@@ -53,4 +55,13 @@ public class TruckAppointmentEntity {
|
||||
private String remark;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
@TableField(exist = false) private String oceanBookingNo;
|
||||
@TableField(exist = false) private String warehouseName;
|
||||
@TableField(exist = false) private Long shippingCompanyId;
|
||||
@TableField(exist = false) private String shippingCompanyName;
|
||||
@TableField(exist = false) private Long bookingAgentId;
|
||||
@TableField(exist = false) private String bookingAgentName;
|
||||
@TableField(exist = false) private String cabinetNo;
|
||||
@TableField(exist = false) private String containerNo;
|
||||
@TableField(exist = false) private Long attachmentCount;
|
||||
}
|
||||
|
||||
+25
-2
@@ -17,7 +17,7 @@ public interface LoadingModelBridgeMapper extends BaseMapper<PreloadLoadingPlanL
|
||||
PreloadSnapshot preload(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId);
|
||||
|
||||
@Select("SELECT p.id,p.container_booking_id containerBookingId,b.container_no containerNo,"
|
||||
+ "p.plan_status status,p.version FROM fl_loading_plan p JOIN fl_container_booking b "
|
||||
+ "p.plan_status status,p.version,p.preload_pushed_at preloadPushedAt FROM fl_loading_plan p JOIN fl_container_booking b "
|
||||
+ "ON b.id=p.container_booking_id WHERE p.id=#{loadingPlanId}")
|
||||
LoadingPlanSnapshot loadingPlan(@Param("loadingPlanId") long loadingPlanId);
|
||||
|
||||
@@ -27,6 +27,11 @@ public interface LoadingModelBridgeMapper extends BaseMapper<PreloadLoadingPlanL
|
||||
int allocatedPackages(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId,
|
||||
@Param("loadingPlanId") long loadingPlanId);
|
||||
|
||||
@Select("SELECT account_id tenantId,inbound_id inboundId,COALESCE(SUM(package_count),0) allocatedPackageCount "
|
||||
+ "FROM fl_loading_plan_item WHERE loading_plan_id=#{loadingPlanId} AND deleted=0 "
|
||||
+ "GROUP BY account_id,inbound_id HAVING SUM(package_count)>0")
|
||||
List<AllocationScope> allocatedScopes(@Param("loadingPlanId") long loadingPlanId);
|
||||
|
||||
@Update("UPDATE fl_preload_plan SET updated_by=#{operatorId},updated_by_name=#{operatorName},"
|
||||
+ "version=version+1 WHERE id=#{preloadPlanId} AND tenant_id=#{tenantId} "
|
||||
+ "AND inbound_id=#{inboundId} AND status='PENDING' AND version=#{expectedVersion}")
|
||||
@@ -34,6 +39,18 @@ public interface LoadingModelBridgeMapper extends BaseMapper<PreloadLoadingPlanL
|
||||
@Param("inboundId") long inboundId, @Param("expectedVersion") int expectedVersion,
|
||||
@Param("operatorId") long operatorId, @Param("operatorName") String operatorName);
|
||||
|
||||
@Update("UPDATE fl_preload_plan SET status='CONFIRMED',confirmed_by=#{operatorId},"
|
||||
+ "confirmed_by_name=#{operatorName},confirmed_at=NOW(),updated_by=#{operatorId},"
|
||||
+ "updated_by_name=#{operatorName},version=version+1 WHERE id=#{preloadPlanId} "
|
||||
+ "AND tenant_id=#{tenantId} AND inbound_id=#{inboundId} AND status='PENDING' "
|
||||
+ "AND version=#{expectedVersion}")
|
||||
int confirmIncrementalPreload(@Param("preloadPlanId") long preloadPlanId,
|
||||
@Param("tenantId") long tenantId,
|
||||
@Param("inboundId") long inboundId,
|
||||
@Param("expectedVersion") int expectedVersion,
|
||||
@Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Update("UPDATE fl_loading_plan SET updated_by=#{operatorId},updated_by_name=#{operatorName},"
|
||||
+ "version=version+1 WHERE id=#{loadingPlanId} AND plan_status='EDITING' "
|
||||
+ "AND version=#{expectedVersion}")
|
||||
@@ -69,5 +86,11 @@ public interface LoadingModelBridgeMapper extends BaseMapper<PreloadLoadingPlanL
|
||||
|
||||
record PreloadSnapshot(Long id, Long tenantId, Long inboundId, String status, Integer version) {}
|
||||
record LoadingPlanSnapshot(Long id, Long containerBookingId, String containerNo,
|
||||
String status, Integer version) {}
|
||||
String status, Integer version, java.time.LocalDateTime preloadPushedAt) {
|
||||
public LoadingPlanSnapshot(Long id, Long containerBookingId, String containerNo,
|
||||
String status, Integer version) {
|
||||
this(id, containerBookingId, containerNo, status, version, null);
|
||||
}
|
||||
}
|
||||
record AllocationScope(Long tenantId, Long inboundId, Integer allocatedPackageCount) {}
|
||||
}
|
||||
|
||||
+10
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.Link;
|
||||
import com.baiyue.firstleg.loading.bridge.entity.PreloadLoadingPlanLinkEntity;
|
||||
import com.baiyue.firstleg.loading.bridge.mapper.LoadingModelBridgeMapper;
|
||||
import com.baiyue.firstleg.loading.bridge.mapper.LoadingModelBridgeMapper.AllocationScope;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@@ -24,6 +25,7 @@ public class LoadingModelBridgeRepository {
|
||||
public int allocatedPackages(long tenantId, long inboundId, long loadingPlanId) {
|
||||
return mapper.allocatedPackages(tenantId, inboundId, loadingPlanId);
|
||||
}
|
||||
public List<AllocationScope> allocatedScopes(long loadingPlanId) { return mapper.allocatedScopes(loadingPlanId); }
|
||||
|
||||
public PreloadLoadingPlanLinkEntity byPair(long tenantId, long inboundId, long loadingPlanId) {
|
||||
return mapper.selectOne(new LambdaQueryWrapper<PreloadLoadingPlanLinkEntity>()
|
||||
@@ -40,11 +42,19 @@ public class LoadingModelBridgeRepository {
|
||||
|
||||
public void insert(PreloadLoadingPlanLinkEntity value) { mapper.insert(value); }
|
||||
|
||||
public void delete(PreloadLoadingPlanLinkEntity value) { mapper.deleteById(value); }
|
||||
|
||||
public int touchPreload(long preloadPlanId, long tenantId, long inboundId, int version,
|
||||
long operatorId, String operatorName) {
|
||||
return mapper.touchPreload(preloadPlanId, tenantId, inboundId, version, operatorId, operatorName);
|
||||
}
|
||||
|
||||
public int confirmIncrementalPreload(long preloadPlanId, long tenantId, long inboundId, int version,
|
||||
long operatorId, String operatorName) {
|
||||
return mapper.confirmIncrementalPreload(preloadPlanId, tenantId, inboundId, version,
|
||||
operatorId, operatorName);
|
||||
}
|
||||
|
||||
public int touchLoadingPlan(long loadingPlanId, int version, long operatorId, String operatorName) {
|
||||
return mapper.touchLoadingPlan(loadingPlanId, version, operatorId, operatorName);
|
||||
}
|
||||
|
||||
+109
@@ -7,6 +7,7 @@ import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.BindRequest
|
||||
import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.Link;
|
||||
import com.baiyue.firstleg.loading.bridge.entity.PreloadLoadingPlanLinkEntity;
|
||||
import com.baiyue.firstleg.loading.bridge.repository.LoadingModelBridgeRepository;
|
||||
import com.baiyue.firstleg.loading.bridge.mapper.LoadingModelBridgeMapper.AllocationScope;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -68,9 +69,117 @@ public class LoadingModelBridgeService {
|
||||
return requireLink(tenantId, inboundId, loadingPlanId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates every preload link required by a pushed loading plan before PDA can start.
|
||||
* The caller owns the surrounding push transaction, so a missing preload or stale
|
||||
* version rolls back the push and cannot leave a LOADING task without a link.
|
||||
*/
|
||||
@Transactional
|
||||
public List<Link> bindAllocatedBeforeLoading(long loadingPlanId, long operatorId,
|
||||
String operatorName, String orchestrationKey) {
|
||||
var loadingPlan = repository.loadingPlan(loadingPlanId);
|
||||
if (loadingPlan == null) throw new BizException(ResultCode.NOT_FOUND, "柜次装柜计划不存在");
|
||||
List<AllocationScope> scopes = repository.allocatedScopes(loadingPlanId);
|
||||
if (scopes.isEmpty()) throw conflict("装柜计划尚未分配入库单货物,不能开始PDA作业");
|
||||
if (!"EDITING".equals(loadingPlan.status())) {
|
||||
if (Set.of("LOADING", "COMPLETED").contains(loadingPlan.status())
|
||||
&& scopes.stream().allMatch(scope -> repository.byPair(
|
||||
scope.tenantId(), scope.inboundId(), loadingPlanId) != null)) {
|
||||
return repository.byLoadingPlan(loadingPlanId);
|
||||
}
|
||||
throw conflict("PDA开始前的预装关联不完整,禁止在LOADING后补关联");
|
||||
}
|
||||
for (AllocationScope scope : scopes) {
|
||||
// Internal push replay is pair-idempotent: an existing association has
|
||||
// already advanced both versions, so calling public bind again would
|
||||
// derive a different request hash for the same AUTO key.
|
||||
if (repository.byPair(scope.tenantId(), scope.inboundId(), loadingPlanId) != null) {
|
||||
continue;
|
||||
}
|
||||
var preload = repository.preload(scope.tenantId(), scope.inboundId());
|
||||
var currentPlan = repository.loadingPlan(loadingPlanId);
|
||||
if (preload == null || currentPlan == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "入库单预装计划不存在");
|
||||
}
|
||||
String key = "AUTO:" + hash(orchestrationKey + "|" + scope.tenantId() + "|"
|
||||
+ scope.inboundId() + "|" + loadingPlanId).substring(0, 60);
|
||||
bind(scope.tenantId(), scope.inboundId(), loadingPlanId, operatorId, operatorName, key,
|
||||
new BindRequest(preload.version(), currentPlan.version()));
|
||||
}
|
||||
return repository.byLoadingPlan(loadingPlanId);
|
||||
}
|
||||
|
||||
/** Adds a newly allocated inbound to an already pushed EDITING/LOADING plan. */
|
||||
@Transactional
|
||||
public void bindIncrementalAllocation(long loadingPlanId, long tenantId, long inboundId,
|
||||
long operatorId, String operatorName, String operationId) {
|
||||
if (repository.byPair(tenantId, inboundId, loadingPlanId) != null) return;
|
||||
var plan = repository.loadingPlan(loadingPlanId);
|
||||
if (plan == null) throw new BizException(ResultCode.NOT_FOUND, "柜次装柜计划不存在");
|
||||
if (plan.preloadPushedAt() == null) return;
|
||||
if (!Set.of("EDITING", "LOADING").contains(plan.status())) {
|
||||
throw conflict("PDA任务正在最终确认或已经完成,不能新增预装关联");
|
||||
}
|
||||
if (repository.allocatedPackages(tenantId, inboundId, loadingPlanId) <= 0) {
|
||||
throw conflict("柜次计划尚未分配当前入库单货物");
|
||||
}
|
||||
var preload = repository.preload(tenantId, inboundId);
|
||||
if (preload == null) throw new BizException(ResultCode.NOT_FOUND, "入库单预装计划不存在");
|
||||
if (!Set.of("PENDING", "CONFIRMED").contains(preload.status())) {
|
||||
throw conflict("新增入库单预装状态不允许进入当前柜次");
|
||||
}
|
||||
PreloadLoadingPlanLinkEntity link = new PreloadLoadingPlanLinkEntity();
|
||||
link.setTenantId(tenantId); link.setInboundId(inboundId); link.setPreloadPlanId(preload.id());
|
||||
link.setLoadingPlanId(loadingPlanId);
|
||||
link.setIdempotencyKey("INCREMENTAL:" + hash(operationId + "|" + tenantId + "|" + inboundId
|
||||
+ "|" + loadingPlanId).substring(0, 52));
|
||||
link.setRequestHash(hash("INCREMENTAL|" + tenantId + "|" + inboundId + "|" + loadingPlanId));
|
||||
link.setCreatedBy(operatorId); link.setCreatedByName(operatorName);
|
||||
try {
|
||||
repository.insert(link);
|
||||
} catch (DuplicateKeyException exception) {
|
||||
if (repository.byPair(tenantId, inboundId, loadingPlanId) == null) {
|
||||
throw conflict("新增入库单预装关联并发冲突,请刷新后重试");
|
||||
}
|
||||
}
|
||||
// A running PDA task has already passed plan-level confirmation. Adding an inbound is the
|
||||
// explicit Admin approval for that new allocation, so its fresh PENDING preload joins the
|
||||
// same confirmed plan atomically instead of leaving an unreachable pending aggregate.
|
||||
if ("LOADING".equals(plan.status()) && "PENDING".equals(preload.status())
|
||||
&& repository.confirmIncrementalPreload(preload.id(), tenantId, inboundId,
|
||||
preload.version(), operatorId, operatorName) != 1) {
|
||||
throw conflict("新增入库单预装确认并发冲突,请刷新后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes only a now-empty allocation link; picked-item protection is enforced by the PDA task lock. */
|
||||
@Transactional
|
||||
public void removeIfUnallocated(long loadingPlanId, long tenantId, long inboundId) {
|
||||
if (repository.allocatedPackages(tenantId, inboundId, loadingPlanId) > 0) return;
|
||||
PreloadLoadingPlanLinkEntity link = repository.byPair(tenantId, inboundId, loadingPlanId);
|
||||
if (link != null) repository.delete(link);
|
||||
}
|
||||
|
||||
public List<Link> byInbound(long tenantId, long inboundId) { return repository.byInbound(tenantId, inboundId); }
|
||||
public List<Link> byLoadingPlan(long loadingPlanId) { return repository.byLoadingPlan(loadingPlanId); }
|
||||
|
||||
public List<Link> requireAllocatedLinksForConfirmation(long loadingPlanId) {
|
||||
var loadingPlan = repository.loadingPlan(loadingPlanId);
|
||||
if (loadingPlan == null) throw new BizException(ResultCode.NOT_FOUND, "柜次装柜计划不存在");
|
||||
if (loadingPlan.preloadPushedAt() == null || !"EDITING".equals(loadingPlan.status())) {
|
||||
throw conflict("仅已推送且仍为EDITING的预装计划可以确认");
|
||||
}
|
||||
List<AllocationScope> scopes = repository.allocatedScopes(loadingPlanId);
|
||||
if (scopes.isEmpty()) throw conflict("装柜计划尚未分配入库单货物");
|
||||
List<Link> links = repository.byLoadingPlan(loadingPlanId);
|
||||
boolean complete = scopes.stream().allMatch(scope -> links.stream().anyMatch(link ->
|
||||
Objects.equals(link.tenantId(), scope.tenantId())
|
||||
&& Objects.equals(link.inboundId(), scope.inboundId())
|
||||
&& link.allocatedPackageCount() != null && link.allocatedPackageCount() > 0));
|
||||
if (!complete) throw conflict("装柜计划的预装关联不完整");
|
||||
return links;
|
||||
}
|
||||
|
||||
public void requireStarted(long tenantId, long inboundId) {
|
||||
List<Link> links = requireLinks(tenantId, inboundId);
|
||||
if (links.stream().anyMatch(link -> link.allocatedPackageCount() == null || link.allocatedPackageCount() <= 0
|
||||
|
||||
+11
@@ -18,6 +18,7 @@ import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/** 平台端装柜证据和国内装柜完成操作。 */
|
||||
@@ -58,6 +59,16 @@ public class PlatformLoadingDeliveryController {
|
||||
return ApiResponse.success(service.evidence(tenantId, inboundId));
|
||||
}
|
||||
|
||||
@Operation(summary = "PDA查看装柜证据照片")
|
||||
@GetMapping("/{inboundId}/loading/evidence/{evidenceId}/content/pda")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ResponseEntity<byte[]> pdaEvidenceContent(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam long tenantId, @PathVariable long inboundId,
|
||||
@PathVariable long evidenceId) {
|
||||
FirstlegScopeGuard.platform(user, tenantId);
|
||||
return service.evidenceContent(tenantId, inboundId, evidenceId);
|
||||
}
|
||||
|
||||
@Operation(summary = "PDA作废错误装柜证据")
|
||||
@PostMapping("/{inboundId}/loading/evidence/{evidenceId}/void/pda")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
|
||||
@@ -7,7 +7,7 @@ public final class LoadingResponses {
|
||||
private LoadingResponses() {}
|
||||
|
||||
public record EvidenceResponse(Long id, Long inboundId, Long tenantId, String evidenceType,
|
||||
Long fileId, String fileName, String sealNo, String remark,
|
||||
Long fileId, String fileName, String previewUrl, String sealNo, String remark,
|
||||
Integer version, String status, String voidReason,
|
||||
Long voidedBy, String voidedByName, LocalDateTime voidedAt,
|
||||
LocalDateTime createdAt, LocalDateTime updatedAt) {}
|
||||
|
||||
+2
-7
@@ -12,20 +12,15 @@ public interface LoadingCompletionGateMapper {
|
||||
COALESCE(SUM(CASE WHEN EXISTS (
|
||||
SELECT 1 FROM fl_ocean_booking_container_allocation a
|
||||
JOIN fl_ocean_booking b ON b.id=a.ocean_booking_id
|
||||
WHERE a.container_booking_id=p.container_booking_id AND b.status='RELEASED') THEN 1 ELSE 0 END),0) oceanReady,
|
||||
COALESCE(SUM(CASE WHEN EXISTS (
|
||||
SELECT 1 FROM fl_truck_appointment t
|
||||
WHERE t.container_booking_id=p.container_booking_id AND t.loading_plan_id=p.id
|
||||
AND t.status='COMPLETED') THEN 1 ELSE 0 END),0) truckReady
|
||||
WHERE a.container_booking_id=p.container_booking_id AND b.status='RELEASED') THEN 1 ELSE 0 END),0) oceanReady
|
||||
FROM fl_preload_loading_plan_link l
|
||||
JOIN fl_loading_plan p ON p.id=l.loading_plan_id
|
||||
WHERE l.tenant_id=#{tenantId} AND l.inbound_id=#{inboundId}
|
||||
""")
|
||||
GateSnapshot snapshot(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId);
|
||||
|
||||
record GateSnapshot(Integer linkCount, Integer oceanReady, Integer truckReady) {
|
||||
record GateSnapshot(Integer linkCount, Integer oceanReady) {
|
||||
public boolean allBookingReady() { return count(linkCount) > 0 && count(linkCount) == count(oceanReady); }
|
||||
public boolean allTruckReady() { return count(linkCount) > 0 && count(linkCount) == count(truckReady); }
|
||||
private static int count(Integer value) { return value == null ? 0 : value; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,16 @@ public interface LoadingInboundMapper {
|
||||
+ "FROM fl_inbound_order WHERE tenant_id=#{tenantId} AND id=#{inboundId} AND deleted=0")
|
||||
Snapshot find(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId);
|
||||
|
||||
@Select("SELECT id, tenant_id AS tenantId, process_status AS processStatus, version "
|
||||
+ "FROM fl_inbound_order WHERE tenant_id=#{tenantId} AND id=#{inboundId} AND deleted=0 FOR UPDATE")
|
||||
Snapshot findForUpdate(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId);
|
||||
|
||||
@Update("UPDATE fl_inbound_order SET process_status='LOADING', updated_by=#{operatorId}, "
|
||||
+ "version=version+1 WHERE tenant_id=#{tenantId} AND id=#{inboundId} AND deleted=0 "
|
||||
+ "AND process_status='WAIT_SPLIT' AND version=#{expectedVersion}")
|
||||
int beginLoading(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId,
|
||||
@Param("operatorId") long operatorId, @Param("expectedVersion") int expectedVersion);
|
||||
|
||||
@Select("""
|
||||
SELECT batch.source_inbound_id AS sourceInboundId,
|
||||
batch.loaded_child_inbound_id AS loadedChildInboundId,
|
||||
|
||||
@@ -25,6 +25,10 @@ public class LoadingRepository {
|
||||
return inbounds.find(tenantId, inboundId);
|
||||
}
|
||||
|
||||
public LoadingInboundMapper.Snapshot findInboundForUpdate(long tenantId, long inboundId) {
|
||||
return inbounds.findForUpdate(tenantId, inboundId);
|
||||
}
|
||||
|
||||
public int completeLoading(long tenantId, long inboundId, long operatorId, int expectedVersion) {
|
||||
return inbounds.completeLoading(tenantId, inboundId, operatorId, expectedVersion);
|
||||
}
|
||||
|
||||
+1
-2
@@ -11,13 +11,12 @@ import org.springframework.stereotype.Service;
|
||||
public class LoadingCompletionGateService {
|
||||
@Resource private LoadingCompletionGateMapper mapper;
|
||||
|
||||
public void requireBookingAndTransport(long tenantId, long inboundId) {
|
||||
public void requireReleasedBooking(long tenantId, long inboundId) {
|
||||
LoadingCompletionGateMapper.GateSnapshot gate = mapper.snapshot(tenantId, inboundId);
|
||||
if (gate == null || gate.linkCount() == null || gate.linkCount() == 0) {
|
||||
throw blocked("未找到有效柜次关联");
|
||||
}
|
||||
if (!gate.allBookingReady()) throw blocked("订舱尚未完成(必须为 RELEASED)");
|
||||
if (!gate.allTruckReady()) throw blocked("拖车尚未完成(必须为 COMPLETED)");
|
||||
}
|
||||
|
||||
private static BizException blocked(String message) {
|
||||
|
||||
+57
-3
@@ -24,6 +24,7 @@ import feign.FeignException;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -111,7 +112,7 @@ public class LoadingDeliveryService {
|
||||
}
|
||||
if (!"LOADING".equals(inbound.processStatus())) throw new BizException(ResultCode.CONFLICT, "当前订单不在装柜中");
|
||||
loadingModelBridge.requireCompleted(tenantId, inboundId);
|
||||
completionGate.requireBookingAndTransport(tenantId, inboundId);
|
||||
completionGate.requireReleasedBooking(tenantId, inboundId);
|
||||
requireCompletedPicking(tenantId, inboundId);
|
||||
requireCompletionEvidence(tenantId, inboundId);
|
||||
if (repository.completeLoading(tenantId, inboundId, operatorId, request.expectedVersion()) != 1) throw concurrent();
|
||||
@@ -120,6 +121,38 @@ public class LoadingDeliveryService {
|
||||
new LoadingActionResponse("LOADED", evidenceResponse(tenantId, inboundId)));
|
||||
}
|
||||
|
||||
/**
|
||||
* PDA settlement may increment the source version and may create a loaded fulfillment slice.
|
||||
* Resolve and lock the canonical completion row after settlement, then retain optimistic locking
|
||||
* with that current server version instead of reusing the pre-settlement task snapshot.
|
||||
*/
|
||||
public LoadingActionResponse completeAfterPdaSettlement(long tenantId, long inboundId,
|
||||
long operatorId, String operatorName,
|
||||
String key, String remark) {
|
||||
CompletionTarget target = repository.completionTarget(tenantId, inboundId);
|
||||
long completionInboundId = target == null ? inboundId : target.completionInboundId();
|
||||
Snapshot current = repository.findInboundForUpdate(tenantId, completionInboundId);
|
||||
if (current == null || current.version() == null) {
|
||||
throw new BizException(ResultCode.CONFLICT, "PDA结算后的实装履约分片不存在,请刷新后重试");
|
||||
}
|
||||
return completeLoading(tenantId, inboundId, operatorId, operatorName, key,
|
||||
new CompleteLoadingRequest(current.version(), remark));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read-only preflight used before PDA settlement. The current plan may still be LOADING here;
|
||||
* every external/business hard gate must pass before the PDA task is mutated.
|
||||
*/
|
||||
public void requirePdaCompletionReady(long tenantId, long inboundId) {
|
||||
Snapshot inbound = requireInbound(tenantId, inboundId);
|
||||
if (!"LOADING".equals(inbound.processStatus())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "PDA确认装柜完成前订单必须处于LOADING");
|
||||
}
|
||||
loadingModelBridge.requireStarted(tenantId, inboundId);
|
||||
completionGate.requireReleasedBooking(tenantId, inboundId);
|
||||
requireCompletionEvidence(tenantId, inboundId);
|
||||
}
|
||||
|
||||
private LoadingActionResponse completeFulfillmentSlice(long tenantId, long evidenceInboundId,
|
||||
long operatorId, String operatorName, String key,
|
||||
CompleteLoadingRequest request,
|
||||
@@ -132,7 +165,7 @@ public class LoadingDeliveryService {
|
||||
throw new BizException(ResultCode.CONFLICT, "实装履约分片不在装柜中");
|
||||
}
|
||||
loadingModelBridge.requireCompleted(tenantId, target.sourceInboundId());
|
||||
completionGate.requireBookingAndTransport(tenantId, target.sourceInboundId());
|
||||
completionGate.requireReleasedBooking(tenantId, target.sourceInboundId());
|
||||
requireCompletedPicking(tenantId, target.sourceInboundId());
|
||||
requireCompletionEvidence(tenantId, target.sourceInboundId());
|
||||
if (repository.completeLoadedChild(tenantId, target.sourceInboundId(), operatorId,
|
||||
@@ -149,6 +182,22 @@ public class LoadingDeliveryService {
|
||||
return evidenceResponse(tenantId, inboundId);
|
||||
}
|
||||
|
||||
public ResponseEntity<byte[]> evidenceContent(long tenantId, long inboundId, long evidenceId) {
|
||||
requireLoadingStage(tenantId, inboundId);
|
||||
LoadingEvidenceEntity evidence = repository.listEvidence(tenantId, inboundId).stream()
|
||||
.filter(item -> Long.valueOf(evidenceId).equals(item.getId()) && isActive(item))
|
||||
.findFirst().orElseThrow(() -> new BizException(ResultCode.NOT_FOUND, "装柜证据不存在"));
|
||||
try {
|
||||
ResponseEntity<byte[]> response = files.download(internalToken, tenantId, inboundId, evidence.getFileId());
|
||||
if (response == null || response.getBody() == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "装柜证据文件不存在");
|
||||
}
|
||||
return response;
|
||||
} catch (FeignException ex) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "文件服务暂不可用");
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public LoadingActionResponse voidEvidence(long tenantId, long inboundId, long evidenceId, long operatorId,
|
||||
String operatorName, String key, VoidEvidenceRequest request) {
|
||||
@@ -333,11 +382,16 @@ public class LoadingDeliveryService {
|
||||
private EvidenceListResponse evidenceResponse(long tenantId, long inboundId) {
|
||||
List<EvidenceResponse> values = repository.listEvidence(tenantId, inboundId).stream().map(e ->
|
||||
new EvidenceResponse(e.getId(), e.getInboundId(), e.getTenantId(), e.getEvidenceType(), e.getFileId(),
|
||||
e.getFileName(), e.getSealNo(), e.getRemark(), e.getVersion(), activeStatus(e), e.getVoidReason(),
|
||||
e.getFileName(), evidencePreviewUrl(tenantId, inboundId, e.getId()), e.getSealNo(), e.getRemark(), e.getVersion(), activeStatus(e), e.getVoidReason(),
|
||||
e.getVoidedBy(), e.getVoidedByName(), e.getVoidedAt(), e.getCreatedAt(), e.getUpdatedAt())).toList();
|
||||
return new EvidenceListResponse(inboundId, values);
|
||||
}
|
||||
|
||||
private static String evidencePreviewUrl(long tenantId, long inboundId, long evidenceId) {
|
||||
return "/api/platform/v1/inbounds/" + inboundId + "/loading/evidence/" + evidenceId
|
||||
+ "/content/pda?tenantId=" + tenantId;
|
||||
}
|
||||
|
||||
private static boolean isActive(LoadingEvidenceEntity evidence) {
|
||||
return evidence != null && (evidence.getStatus() == null || "ACTIVE".equals(evidence.getStatus()));
|
||||
}
|
||||
|
||||
@@ -11,9 +11,24 @@ import org.apache.ibatis.annotations.Update;
|
||||
|
||||
@Mapper
|
||||
public interface ContainerBookingMapper extends BaseMapper<ContainerBookingEntity> {
|
||||
@Update("UPDATE fl_container_booking SET container_no=#{containerNo},ocean_booking_id=#{oceanBookingId},ocean_booking_requirement_id=#{requirementId},record_status='ACTIVE',bill_of_lading_no=#{billOfLadingNo},vessel_name=#{vesselName},voyage_no=#{voyageNo},shipping_company_id=#{shippingCompanyId},shipping_company_name=#{shippingCompanyName},departure_port_code=#{departurePortCode},departure_port_name=#{departurePortName},destination_port_code=#{destinationPortCode},destination_port_name=#{destinationPortName},channel_code=#{channelCode},channel_name=#{channelName},customer_id=#{customerId},customer_name=#{customerName},estimated_sailing_at=#{estimatedSailingAt},estimated_port_arrival_at=#{estimatedArrivalAt},updated_by=#{operatorId},updated_by_name=#{operatorName},updated_at=NOW(),version=version+1 WHERE id=#{id} AND version=#{version} AND source_type='SIMULATION' AND record_status='ACTIVE' AND ocean_booking_id IS NULL")
|
||||
int linkReleasedBooking(@Param("id") long id, @Param("oceanBookingId") long oceanBookingId, @Param("requirementId") long requirementId, @Param("containerNo") String containerNo, @Param("billOfLadingNo") String billOfLadingNo, @Param("vesselName") String vesselName, @Param("voyageNo") String voyageNo, @Param("shippingCompanyId") Long shippingCompanyId, @Param("shippingCompanyName") String shippingCompanyName, @Param("departurePortCode") String departurePortCode, @Param("departurePortName") String departurePortName, @Param("destinationPortCode") String destinationPortCode, @Param("destinationPortName") String destinationPortName, @Param("channelCode") String channelCode, @Param("channelName") String channelName, @Param("customerId") Long customerId, @Param("customerName") String customerName, @Param("estimatedSailingAt") java.time.LocalDateTime estimatedSailingAt, @Param("estimatedPortArrivalAt") java.time.LocalDateTime estimatedPortArrivalAt, @Param("version") int version, @Param("operatorId") long operatorId, @Param("operatorName") String operatorName);
|
||||
@Select("""
|
||||
<script>
|
||||
SELECT b.*, p.id AS plan_id,
|
||||
SELECT b.*, p.id AS plan_id, p.plan_no AS plan_no,
|
||||
CASE WHEN (SELECT eh.action FROM fl_container_exception_history eh
|
||||
WHERE eh.container_booking_id=b.id ORDER BY eh.occurred_at DESC,eh.id DESC LIMIT 1)='MARK'
|
||||
THEN TRUE ELSE FALSE END AS exception_active,
|
||||
COALESCE((SELECT CASE WHEN d.completed_at IS NOT NULL THEN 'COMPLETED' ELSE 'UPLOADED' END
|
||||
FROM fl_container_document d WHERE d.container_booking_id=b.id
|
||||
AND d.document_type='DESTINATION_CLEARANCE' LIMIT 1),'MISSING') AS destination_clearance_document_status,
|
||||
COALESCE((SELECT CASE WHEN d.completed_at IS NOT NULL THEN 'COMPLETED' ELSE 'UPLOADED' END
|
||||
FROM fl_container_document d WHERE d.container_booking_id=b.id
|
||||
AND d.document_type='CUSTOMS_DECLARATION' LIMIT 1),'MISSING') AS customs_declaration_document_status,
|
||||
COALESCE((SELECT 'UPLOADED' FROM fl_container_document d WHERE d.container_booking_id=b.id
|
||||
AND d.document_type='DELIVERY_ORDER' LIMIT 1),'MISSING') AS delivery_order_document_status,
|
||||
COALESCE((SELECT 'UPLOADED' FROM fl_container_document d WHERE d.container_booking_id=b.id
|
||||
AND d.document_type='PICKUP_ORDER' LIMIT 1),'MISSING') AS pickup_order_document_status,
|
||||
CASE WHEN p.id IS NULL THEN 'WAIT_LOADING'
|
||||
WHEN p.plan_status != 'COMPLETED' AND NOT EXISTS (
|
||||
SELECT 1 FROM fl_loading_plan_item active_item
|
||||
@@ -21,9 +36,21 @@ public interface ContainerBookingMapper extends BaseMapper<ContainerBookingEntit
|
||||
) THEN 'WAIT_LOADING'
|
||||
ELSE p.plan_status END AS plan_status
|
||||
FROM fl_container_booking b LEFT JOIN fl_loading_plan p ON p.container_booking_id = b.id
|
||||
WHERE (#{query.containerNo} IS NULL OR b.container_no = #{query.containerNo})
|
||||
WHERE b.record_status = 'ACTIVE'
|
||||
AND (#{query.containerNo} IS NULL OR b.container_no = #{query.containerNo})
|
||||
AND (#{query.cabinetNo} IS NULL OR b.cabinet_no = #{query.cabinetNo})
|
||||
AND (#{query.billOfLadingNo} IS NULL OR b.bill_of_lading_no = #{query.billOfLadingNo})
|
||||
AND (#{query.customerId} IS NULL OR b.customer_id = #{query.customerId} OR EXISTS (
|
||||
SELECT 1 FROM fl_ocean_booking_container_allocation customer_allocation
|
||||
JOIN fl_ocean_booking customer_booking ON customer_booking.id = customer_allocation.ocean_booking_id
|
||||
WHERE customer_allocation.container_booking_id = b.id
|
||||
AND customer_booking.customer_id = #{query.customerId}
|
||||
) OR EXISTS (
|
||||
SELECT 1 FROM fl_loading_plan_item customer_item
|
||||
WHERE customer_item.loading_plan_id = p.id
|
||||
AND customer_item.deleted = 0
|
||||
AND customer_item.account_id = #{query.customerId}
|
||||
))
|
||||
AND (#{query.warehouseId} IS NULL OR b.warehouse_id = #{query.warehouseId})
|
||||
AND (#{query.containerType} IS NULL OR b.container_type = #{query.containerType})
|
||||
AND (#{query.shippingCompanyId} IS NULL OR b.shipping_company_id = #{query.shippingCompanyId})
|
||||
@@ -49,7 +76,20 @@ public interface ContainerBookingMapper extends BaseMapper<ContainerBookingEntit
|
||||
Page<ContainerBookingEntity> selectBookingPage(Page<ContainerBookingEntity> page, @Param("query") Query query);
|
||||
|
||||
@Select("""
|
||||
SELECT b.*, p.id AS plan_id,
|
||||
SELECT b.*, p.id AS plan_id, p.plan_no AS plan_no,
|
||||
CASE WHEN (SELECT eh.action FROM fl_container_exception_history eh
|
||||
WHERE eh.container_booking_id=b.id ORDER BY eh.occurred_at DESC,eh.id DESC LIMIT 1)='MARK'
|
||||
THEN TRUE ELSE FALSE END AS exception_active,
|
||||
COALESCE((SELECT CASE WHEN d.completed_at IS NOT NULL THEN 'COMPLETED' ELSE 'UPLOADED' END
|
||||
FROM fl_container_document d WHERE d.container_booking_id=b.id
|
||||
AND d.document_type='DESTINATION_CLEARANCE' LIMIT 1),'MISSING') AS destination_clearance_document_status,
|
||||
COALESCE((SELECT CASE WHEN d.completed_at IS NOT NULL THEN 'COMPLETED' ELSE 'UPLOADED' END
|
||||
FROM fl_container_document d WHERE d.container_booking_id=b.id
|
||||
AND d.document_type='CUSTOMS_DECLARATION' LIMIT 1),'MISSING') AS customs_declaration_document_status,
|
||||
COALESCE((SELECT 'UPLOADED' FROM fl_container_document d WHERE d.container_booking_id=b.id
|
||||
AND d.document_type='DELIVERY_ORDER' LIMIT 1),'MISSING') AS delivery_order_document_status,
|
||||
COALESCE((SELECT 'UPLOADED' FROM fl_container_document d WHERE d.container_booking_id=b.id
|
||||
AND d.document_type='PICKUP_ORDER' LIMIT 1),'MISSING') AS pickup_order_document_status,
|
||||
CASE WHEN p.id IS NULL THEN 'WAIT_LOADING'
|
||||
WHEN p.plan_status != 'COMPLETED' AND NOT EXISTS (
|
||||
SELECT 1 FROM fl_loading_plan_item active_item
|
||||
@@ -57,13 +97,33 @@ public interface ContainerBookingMapper extends BaseMapper<ContainerBookingEntit
|
||||
) THEN 'WAIT_LOADING'
|
||||
ELSE p.plan_status END AS plan_status
|
||||
FROM fl_container_booking b LEFT JOIN fl_loading_plan p ON p.container_booking_id = b.id
|
||||
WHERE b.id = #{id}
|
||||
WHERE b.id = #{id} AND b.record_status = 'ACTIVE'
|
||||
""")
|
||||
ContainerBookingEntity selectBooking(@Param("id") long id);
|
||||
|
||||
@Select("SELECT * FROM fl_container_booking WHERE id=#{id} FOR UPDATE")
|
||||
ContainerBookingEntity selectBookingForUpdate(@Param("id") long id);
|
||||
|
||||
@Select("SELECT * FROM fl_container_booking WHERE id=#{id} AND record_status='ACTIVE' FOR UPDATE")
|
||||
ContainerBookingEntity selectActiveBookingForUpdate(@Param("id") long id);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_container_booking SET container_no=NULL, ocean_booking_id=NULL,
|
||||
ocean_booking_requirement_id=NULL, customer_id=NULL, customer_name=NULL,
|
||||
bill_of_lading_no=NULL, vessel_name=NULL, voyage_no=NULL,
|
||||
shipping_company_id=NULL, shipping_company_name=NULL,
|
||||
departure_port_code=NULL, departure_port_name=NULL,
|
||||
destination_port_code=NULL, destination_port_name=NULL,
|
||||
estimated_sailing_at=NULL, estimated_port_arrival_at=NULL,
|
||||
updated_by=#{operatorId}, updated_by_name=#{operatorName},
|
||||
updated_at=CURRENT_TIMESTAMP(3), version=version+1
|
||||
WHERE id=#{targetId} AND version=#{expectedVersion} AND source_type='SIMULATION'
|
||||
AND record_status='ACTIVE' AND ocean_booking_id IS NOT NULL
|
||||
""")
|
||||
int unlinkReleasedBooking(@Param("targetId") long targetId, @Param("expectedVersion") int expectedVersion,
|
||||
@Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_container_booking SET
|
||||
container_no=#{value.containerNo}, cabinet_no=#{value.cabinetNo},
|
||||
@@ -74,7 +134,8 @@ public interface ContainerBookingMapper extends BaseMapper<ContainerBookingEntit
|
||||
departure_port_name=#{value.departurePortName}, destination_port_code=#{value.destinationPortCode},
|
||||
destination_port_name=#{value.destinationPortName}, destination_warehouse_code=#{value.destinationWarehouseCode},
|
||||
destination_warehouse_name=#{value.destinationWarehouseName}, capacity_cbm=#{value.capacityCbm},
|
||||
max_weight_kg=#{value.maxWeightKg}, terminal_trucking_company=#{value.terminalTruckingCompany},
|
||||
max_weight_kg=#{value.maxWeightKg}, terminal_trucking_company_id=#{value.terminalTruckingCompanyId},
|
||||
terminal_trucking_company=#{value.terminalTruckingCompany},
|
||||
loading_at=#{value.loadingAt}, estimated_sailing_at=#{value.estimatedSailingAt},
|
||||
actual_sailing_at=#{value.actualSailingAt}, estimated_port_arrival_at=#{value.estimatedPortArrivalAt},
|
||||
actual_port_arrival_at=#{value.actualPortArrivalAt},
|
||||
@@ -87,6 +148,12 @@ public interface ContainerBookingMapper extends BaseMapper<ContainerBookingEntit
|
||||
""")
|
||||
int updateDraft(@Param("value") ContainerBookingEntity value, @Param("expectedVersion") int expectedVersion,
|
||||
@Param("operatorId") long operatorId, @Param("operatorName") String operatorName);
|
||||
@Select("SELECT COUNT(*) FROM fl_loading_plan WHERE container_booking_id=#{id}") int hasLoadingPlan(@Param("id") long id);
|
||||
@Select("SELECT COUNT(*) FROM fl_loading_plan_item i JOIN fl_loading_plan p ON p.id=i.loading_plan_id WHERE p.container_booking_id=#{id} AND i.deleted=0") int hasActiveLoadingItems(@Param("id") long id);
|
||||
@Select("SELECT COUNT(*) FROM fl_loading_pick_task t JOIN fl_loading_plan p ON p.id=t.loading_plan_id WHERE p.container_booking_id=#{id}") int hasPickTask(@Param("id") long id);
|
||||
@Select("SELECT COUNT(*) FROM fl_loading_evidence e JOIN fl_loading_plan_item i ON i.inbound_id=e.inbound_id AND i.account_id=e.tenant_id JOIN fl_loading_plan p ON p.id=i.loading_plan_id WHERE p.container_booking_id=#{id} AND i.deleted=0 AND e.status='ACTIVE'") int hasEvidence(@Param("id") long id);
|
||||
@Select("SELECT COUNT(*) FROM fl_container_document WHERE container_booking_id=#{id}") int hasDocuments(@Param("id") long id);
|
||||
@Select("SELECT COUNT(*) FROM fl_loading_plan_item i JOIN fl_loading_plan p ON p.id=i.loading_plan_id WHERE p.container_booking_id=#{id} AND i.deleted=0 AND i.account_id<>#{customerId}") int hasMismatchedCustomerItems(@Param("id") long id, @Param("customerId") long customerId);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_container_booking SET container_no=#{containerNo}, voyage_no=#{voyageNo},
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.firstleg.entity.ContainerFeeSourceSnapshotEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ContainerFeeSourceSnapshotMapper extends BaseMapper<ContainerFeeSourceSnapshotEntity> {
|
||||
}
|
||||
@@ -6,11 +6,13 @@ import java.util.List;
|
||||
@Mapper public interface ContainerOverviewMapper {
|
||||
@Select("SELECT * FROM fl_container_document WHERE container_booking_id=#{id} AND document_type IN ('DELIVERY_ORDER','PICKUP_ORDER') ORDER BY document_type") List<ContainerDocumentEntity> documents(@Param("id") long id);
|
||||
@Select("SELECT * FROM fl_container_transport_history WHERE container_booking_id=#{id} ORDER BY created_at,id") List<ContainerTransportHistoryEntity> transport(@Param("id") long id);
|
||||
@Select("SELECT id,action,reason,operator_id,operator_name,occurred_at FROM fl_container_exception_history WHERE container_booking_id=#{id} ORDER BY occurred_at,id") List<ExceptionRow> exceptions(@Param("id") long id);
|
||||
@Select("SELECT id,action,reason,operator_id operatorId,operator_name operatorName,occurred_at occurredAt,request_hash requestHash FROM fl_container_exception_history WHERE container_booking_id=#{id} ORDER BY occurred_at,id") List<ExceptionRow> exceptions(@Param("id") long id);
|
||||
@Insert("INSERT INTO fl_container_exception_history(id,container_booking_id,action,reason,operator_id,operator_name,operation_id,request_hash,occurred_at) VALUES(#{id},#{bookingId},#{action},#{reason},#{operatorId},#{operatorName},#{operationId},#{requestHash},CURRENT_TIMESTAMP(3))") int insertException(@Param("id") long id,@Param("bookingId") long bookingId,@Param("action") String action,@Param("reason") String reason,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName,@Param("operationId") String operationId,@Param("requestHash") String requestHash);
|
||||
@Select("SELECT action,reason,operator_id operatorId,operator_name operatorName,occurred_at occurredAt FROM fl_container_exception_history WHERE container_booking_id=#{id} ORDER BY id DESC LIMIT 1") ExceptionRow latestException(@Param("id") long id);
|
||||
@Select("SELECT id,action,reason,operator_id operatorId,operator_name operatorName,occurred_at occurredAt,request_hash requestHash FROM fl_container_exception_history WHERE container_booking_id=#{id} ORDER BY id DESC LIMIT 1") ExceptionRow latestException(@Param("id") long id);
|
||||
@Select("SELECT id,action,reason,operator_id operatorId,operator_name operatorName,occurred_at occurredAt,request_hash requestHash FROM fl_container_exception_history WHERE container_booking_id=#{bookingId} AND operator_id=#{operatorId} AND operation_id=#{operationId} LIMIT 1") ExceptionRow exceptionOperation(@Param("bookingId") long bookingId,@Param("operatorId") long operatorId,@Param("operationId") String operationId);
|
||||
@Select("SELECT * FROM fl_loading_pick_task WHERE container_booking_id=#{id} ORDER BY id DESC LIMIT 1") PickTaskRow pickTask(@Param("id") long id);
|
||||
@Select("SELECT id,loading_plan_id AS loadingPlanId,container_booking_id AS containerBookingId,"
|
||||
+ "task_status AS taskStatus,version FROM fl_loading_pick_task "
|
||||
+ "WHERE container_booking_id=#{id} ORDER BY id DESC LIMIT 1") PickTaskRow pickTask(@Param("id") long id);
|
||||
@Select("SELECT COUNT(*) FROM fl_loading_pick_item WHERE pick_task_id=#{id}") int itemCount(@Param("id") long id);
|
||||
@Select("SELECT COUNT(*) FROM fl_loading_pick_item WHERE pick_task_id=#{id} AND item_status IN ('PICKED','PARTIAL','SKIPPED')") int completedItemCount(@Param("id") long id);
|
||||
@Select("SELECT COUNT(*) FROM fl_loading_pick_item WHERE pick_task_id=#{id} AND item_status NOT IN ('PICKED','PARTIAL','SKIPPED')") int unresolvedItemCount(@Param("id") long id);
|
||||
|
||||
@@ -11,17 +11,22 @@ import org.apache.ibatis.annotations.Update;
|
||||
public interface InboundOrderMapper extends BaseMapper<InboundOrderEntity> {
|
||||
@Update("""
|
||||
UPDATE fl_inbound_order
|
||||
SET process_status = 'WAIT_PRICING', measure_review_status = 'APPROVED',
|
||||
actual_gross_weight_kg = (SELECT COALESCE(SUM(item.total_weight_kg), 0)
|
||||
FROM fl_inbound_measurement_item item
|
||||
WHERE item.tenant_id = #{tenantId} AND item.measurement_id = #{measurementId}),
|
||||
SET process_status = 'WAIT_SPLIT', measure_review_status = 'APPROVED',
|
||||
actual_gross_weight_kg = (SELECT SUM(weight.gross_weight_kg)
|
||||
FROM fl_inbound_weight_result weight
|
||||
WHERE weight.tenant_id = #{tenantId} AND weight.inbound_id = #{inboundId}
|
||||
AND weight.deleted = 0 AND weight.recognition_status = 'CONFIRMED'),
|
||||
actual_net_weight_kg = (SELECT SUM(weight.net_weight_kg)
|
||||
FROM fl_inbound_weight_result weight
|
||||
WHERE weight.tenant_id = #{tenantId} AND weight.inbound_id = #{inboundId}
|
||||
AND weight.deleted = 0 AND weight.recognition_status = 'CONFIRMED'),
|
||||
actual_volume_cbm = (SELECT COALESCE(SUM(item.total_volume_cbm), 0)
|
||||
FROM fl_inbound_measurement_item item
|
||||
WHERE item.tenant_id = #{tenantId} AND item.measurement_id = #{measurementId}),
|
||||
volume_weight_kg = (SELECT COALESCE(SUM(item.volume_weight_kg), 0)
|
||||
FROM fl_inbound_measurement_item item
|
||||
WHERE item.tenant_id = #{tenantId} AND item.measurement_id = #{measurementId}),
|
||||
updated_by = #{operatorId}, version = version + 1
|
||||
inbounded_at = CURRENT_TIMESTAMP(3), updated_by = #{operatorId}, version = version + 1
|
||||
WHERE tenant_id = #{tenantId} AND id = #{inboundId} AND deleted = 0
|
||||
AND process_status = 'MEASURE_PENDING' AND measure_review_status = 'PENDING'
|
||||
AND version = #{expectedVersion}
|
||||
@@ -45,7 +50,8 @@ public interface InboundOrderMapper extends BaseMapper<InboundOrderEntity> {
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_inbound_order
|
||||
SET process_status = 'WAIT_PRICING', updated_by = #{operatorId}, version = version + 1
|
||||
SET process_status = 'WAIT_SPLIT', inbounded_at = CURRENT_TIMESTAMP(3),
|
||||
updated_by = #{operatorId}, version = version + 1
|
||||
WHERE tenant_id = #{tenantId} AND id = #{inboundId} AND deleted = 0
|
||||
AND process_status = 'MEASURE_REJECTED' AND measure_review_status = 'REJECTED'
|
||||
AND version = #{expectedVersion}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baiyue.firstleg.entity.InboundPickupSnapshotEntity;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
public interface InboundPickupSnapshotMapper extends BaseMapper<InboundPickupSnapshotEntity> {
|
||||
}
|
||||
+11
-11
@@ -51,8 +51,12 @@ public interface InboundSplitSettlementMapper {
|
||||
company_id,company_name,warehouse_id,warehouse_name,delivery_warehouse_id,delivery_warehouse_name,business_type,
|
||||
channel_code,channel_name,carrier_id,carrier_name,tax_mode,pricing_tax_mode,container_mode,customs_inspection,
|
||||
destination_type,destination_warehouse_id,destination_code,destination_name,hold_flag,current_hold_reason,
|
||||
#{packageCount},#{packageCount},actual_gross_weight_kg,chargeable_weight_kg,actual_volume_cbm,chargeable_volume_cbm,
|
||||
pallet_count,volume_weight_kg,actual_net_weight_kg,measure_completed_at,inbounded_at,express_company_code,
|
||||
#{packageCount},#{packageCount},
|
||||
actual_gross_weight_kg * #{packageCount} / NULLIF(total_package_count, 0),
|
||||
chargeable_weight_kg,actual_volume_cbm,chargeable_volume_cbm,
|
||||
pallet_count,volume_weight_kg,
|
||||
actual_net_weight_kg * #{packageCount} / NULLIF(total_package_count, 0),
|
||||
measure_completed_at,inbounded_at,express_company_code,
|
||||
express_company_name,express_tracking_no,public_remark,#{operatorId},#{operatorName},CURRENT_TIMESTAMP(3),
|
||||
#{operatorId},CURRENT_TIMESTAMP(3),0,0,'CHILD',#{rootInboundId},#{rootInboundId},#{splitSequence},'NOT_SPLIT',#{splitPurpose}
|
||||
FROM fl_inbound_order WHERE id=#{parentInboundId} AND tenant_id=#{accountId} AND deleted=0
|
||||
@@ -124,14 +128,13 @@ public interface InboundSplitSettlementMapper {
|
||||
INSERT INTO fl_inbound_measurement_item
|
||||
(id, tenant_id, measurement_id, split_item_id, source_measurement_item_id, inbound_detail_id,
|
||||
external_detail_no, box_code, item_no, box_specification, package_count, length_cm, width_cm,
|
||||
height_cm, girth_cm, unit_weight_kg, unit_volume_cbm, volume_raw_cbm, volume_cbm,
|
||||
total_weight_kg, total_volume_cbm, volume_weight_kg, calculation_rule_version,
|
||||
height_cm, girth_cm, unit_volume_cbm, volume_raw_cbm, volume_cbm,
|
||||
total_volume_cbm, volume_weight_kg, calculation_rule_version,
|
||||
source_image_file_id, machine_no, device_measured_at, over_limit_warning,
|
||||
outer_length_cm, outer_width_cm, outer_height_cm, remark, created_at, updated_at)
|
||||
SELECT #{childMeasurementItemId}, tenant_id, #{childMeasurementId}, #{splitItemId}, id, inbound_detail_id,
|
||||
NULL, NULL, CAST(#{lineNo} AS CHAR), box_specification, #{packageCount}, length_cm, width_cm,
|
||||
height_cm, girth_cm, unit_weight_kg, unit_volume_cbm, volume_raw_cbm, volume_cbm,
|
||||
CASE WHEN unit_weight_kg IS NULL THEN NULL ELSE unit_weight_kg * #{packageCount} END,
|
||||
height_cm, girth_cm, unit_volume_cbm, volume_raw_cbm, volume_cbm,
|
||||
unit_volume_cbm * #{packageCount}, volume_weight_kg / NULLIF(package_count, 0) * #{packageCount},
|
||||
calculation_rule_version, source_image_file_id, machine_no, device_measured_at,
|
||||
over_limit_warning, outer_length_cm, outer_width_cm, outer_height_cm, remark,
|
||||
@@ -175,7 +178,6 @@ public interface InboundSplitSettlementMapper {
|
||||
@Update("""
|
||||
UPDATE fl_inbound_measurement_item
|
||||
SET package_count=#{packageCount},
|
||||
total_weight_kg=CASE WHEN unit_weight_kg IS NULL THEN NULL ELSE unit_weight_kg*#{packageCount} END,
|
||||
total_volume_cbm=unit_volume_cbm*#{packageCount},
|
||||
volume_weight_kg=volume_weight_kg/NULLIF(package_count,0)*#{packageCount},
|
||||
updated_at=CURRENT_TIMESTAMP(3)
|
||||
@@ -278,14 +280,12 @@ public interface InboundSplitSettlementMapper {
|
||||
UPDATE fl_inbound_order child
|
||||
JOIN fl_inbound_measurement measurement ON measurement.tenant_id=child.tenant_id
|
||||
AND measurement.inbound_id=child.id AND measurement.id=#{measurementId}
|
||||
SET child.actual_gross_weight_kg=(SELECT COALESCE(SUM(item.total_weight_kg),0)
|
||||
FROM fl_inbound_measurement_item item WHERE item.measurement_id=measurement.id),
|
||||
child.actual_volume_cbm=(SELECT COALESCE(SUM(item.total_volume_cbm),0)
|
||||
SET child.actual_volume_cbm=(SELECT COALESCE(SUM(item.total_volume_cbm),0)
|
||||
FROM fl_inbound_measurement_item item WHERE item.measurement_id=measurement.id),
|
||||
child.chargeable_weight_kg=NULL,child.chargeable_volume_cbm=NULL,
|
||||
child.volume_weight_kg=(SELECT COALESCE(SUM(item.volume_weight_kg),0)
|
||||
FROM fl_inbound_measurement_item item WHERE item.measurement_id=measurement.id),
|
||||
child.actual_net_weight_kg=NULL,child.pallet_count=0,child.version=child.version+1
|
||||
child.pallet_count=0,child.version=child.version+1
|
||||
WHERE child.id=#{childInboundId} AND child.tenant_id=#{accountId} AND child.deleted=0
|
||||
""")
|
||||
int normalizeChildMetrics(@Param("accountId") long accountId, @Param("childInboundId") long childInboundId,
|
||||
|
||||
@@ -25,12 +25,14 @@ public interface LoadingCompletedEventMapper {
|
||||
i.product_name_zh, i.product_name_en, i.package_count, i.product_quantity,
|
||||
i.pallet_count, i.gross_weight_kg, i.net_weight_kg, i.dimensional_weight_kg,
|
||||
i.volume_cbm, i.fba_tracking_no, i.shipment_no, i.shipping_mark
|
||||
,i.customer_remark, i.internal_remark, i.warehouse_remark
|
||||
FROM fl_loading_plan p
|
||||
JOIN fl_container_booking b ON b.id = p.container_booking_id
|
||||
JOIN fl_loading_plan_item i ON i.loading_plan_id = p.id AND i.deleted = 0
|
||||
JOIN fl_inbound_order o ON o.id = i.inbound_id AND o.tenant_id = i.account_id AND o.deleted = 0
|
||||
LEFT JOIN fl_inbound_order root_order ON root_order.id = COALESCE(o.root_inbound_id, o.id)
|
||||
WHERE p.id = #{planId} AND p.plan_status = 'COMPLETED'
|
||||
AND o.process_status = 'LOADED'
|
||||
AND i.package_count > 0
|
||||
ORDER BY i.account_id, i.inbound_id, i.inbound_detail_id, i.id
|
||||
""")
|
||||
@@ -71,6 +73,7 @@ public interface LoadingCompletedEventMapper {
|
||||
private BigDecimal netWeightKg; private BigDecimal dimensionalWeightKg; private BigDecimal volumeCbm;
|
||||
private String fbaTrackingNo; private String shipmentNo; private String shippingMark;
|
||||
private String businessType;
|
||||
private String customerRemark; private String internalRemark; private String warehouseRemark;
|
||||
}
|
||||
|
||||
@Getter @Setter
|
||||
|
||||
@@ -21,9 +21,24 @@ import java.util.List;
|
||||
public interface LoadingListQueryMapper {
|
||||
String AGGREGATE_COLUMNS = """
|
||||
p.id AS plan_id, p.plan_no, p.container_booking_id, p.plan_status, p.remark,
|
||||
p.created_by, p.created_by_name, p.created_at, p.version,
|
||||
p.created_by, p.created_by_name, p.created_at, p.version, p.preload_pushed_at,
|
||||
b.container_no, b.cabinet_no, b.warehouse_id, b.warehouse_name,
|
||||
b.vessel_name, b.voyage_no, b.container_type,
|
||||
(SELECT COUNT(*) FROM fl_preload_loading_plan_link preload_link
|
||||
WHERE preload_link.loading_plan_id = p.id) AS preload_link_count,
|
||||
(SELECT COUNT(*) FROM fl_preload_loading_plan_link confirmed_link
|
||||
JOIN fl_preload_plan confirmed_preload ON confirmed_preload.id = confirmed_link.preload_plan_id
|
||||
WHERE confirmed_link.loading_plan_id = p.id
|
||||
AND confirmed_preload.status = 'CONFIRMED') AS confirmed_preload_count,
|
||||
(SELECT allocation.ocean_booking_id FROM fl_ocean_booking_container_allocation allocation
|
||||
WHERE allocation.container_booking_id = p.container_booking_id LIMIT 1) AS ocean_booking_id,
|
||||
(SELECT ocean.status FROM fl_ocean_booking_container_allocation allocation
|
||||
JOIN fl_ocean_booking ocean ON ocean.id = allocation.ocean_booking_id
|
||||
WHERE allocation.container_booking_id = p.container_booking_id LIMIT 1) AS ocean_booking_status,
|
||||
(SELECT pick_task.id FROM fl_loading_pick_task pick_task
|
||||
WHERE pick_task.loading_plan_id = p.id LIMIT 1) AS pda_task_id,
|
||||
(SELECT pick_task.task_status FROM fl_loading_pick_task pick_task
|
||||
WHERE pick_task.loading_plan_id = p.id LIMIT 1) AS pda_task_status,
|
||||
COUNT(DISTINCT NULLIF(i.sku_code, '')) AS sku_count,
|
||||
COALESCE(SUM(i.package_count), 0) AS total_package_count,
|
||||
COALESCE(SUM(i.gross_weight_kg), 0) AS total_gross_weight_kg,
|
||||
@@ -34,7 +49,7 @@ public interface LoadingListQueryMapper {
|
||||
|
||||
String AGGREGATE_GROUP = """
|
||||
p.id, p.plan_no, p.container_booking_id, p.plan_status, p.remark,
|
||||
p.created_by, p.created_by_name, p.created_at, p.version,
|
||||
p.created_by, p.created_by_name, p.created_at, p.version, p.preload_pushed_at,
|
||||
b.container_no, b.cabinet_no, b.warehouse_id, b.warehouse_name,
|
||||
b.vessel_name, b.voyage_no, b.container_type
|
||||
""";
|
||||
@@ -51,6 +66,7 @@ public interface LoadingListQueryMapper {
|
||||
AND EXISTS (SELECT 1 FROM fl_loading_plan_item active_item
|
||||
WHERE active_item.loading_plan_id = p.id AND active_item.deleted = 0)
|
||||
<if test="query.status != null and query.status != ''">AND p.plan_status = #{query.status}</if>
|
||||
<if test="query.customerId != null">AND EXISTS (SELECT 1 FROM fl_loading_plan_item customer_item WHERE customer_item.loading_plan_id = p.id AND customer_item.deleted = 0 AND customer_item.account_id = #{query.customerId})</if>
|
||||
<if test="query.keyword != null and query.keyword != ''">
|
||||
AND (p.plan_no LIKE CONCAT('%', #{query.keyword}, '%')
|
||||
OR b.container_no LIKE CONCAT('%', #{query.keyword}, '%')
|
||||
@@ -74,6 +90,7 @@ public interface LoadingListQueryMapper {
|
||||
JOIN fl_container_booking b ON b.id = p.container_booking_id
|
||||
LEFT JOIN fl_loading_plan_item i ON i.loading_plan_id = p.id AND i.deleted = 0
|
||||
WHERE p.plan_status = 'COMPLETED'
|
||||
<if test="query.customerId != null">AND EXISTS (SELECT 1 FROM fl_loading_plan_item customer_item WHERE customer_item.loading_plan_id = p.id AND customer_item.deleted = 0 AND customer_item.account_id = #{query.customerId})</if>
|
||||
<if test="query.keyword != null and query.keyword != ''">
|
||||
AND (p.plan_no LIKE CONCAT('%', #{query.keyword}, '%')
|
||||
OR b.container_no LIKE CONCAT('%', #{query.keyword}, '%')
|
||||
@@ -154,16 +171,19 @@ public interface LoadingListQueryMapper {
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Select("""
|
||||
SELECT id AS item_id, account_id, customer_name, inbound_id, inbound_detail_id,
|
||||
inbound_code, sku_code, destination_warehouse_name, product_name_zh,
|
||||
SELECT i.id AS item_id, i.account_id, i.customer_name, i.order_id, i.order_no,
|
||||
i.inbound_id, i.inbound_detail_id, i.inbound_code, b.container_no,
|
||||
i.sku_code, i.destination_warehouse_name, i.product_name_zh,
|
||||
product_name_en, product_image_url, package_count, pallet_count,
|
||||
gross_weight_kg, net_weight_kg, volume_cbm, dimensional_weight_kg,
|
||||
product_quantity, units_per_carton, fba_tracking_no, shipment_no,
|
||||
shipping_mark, internal_remark, customer_remark, warehouse_remark,
|
||||
load_sequence, frozen_at
|
||||
FROM fl_loading_plan_item
|
||||
WHERE loading_plan_id = #{planId} AND deleted = 0
|
||||
ORDER BY load_sequence, id
|
||||
load_sequence, frozen_at, i.version AS item_version
|
||||
FROM fl_loading_plan_item i
|
||||
JOIN fl_loading_plan p ON p.id=i.loading_plan_id
|
||||
JOIN fl_container_booking b ON b.id=p.container_booking_id AND b.record_status='ACTIVE'
|
||||
WHERE i.loading_plan_id = #{planId} AND i.deleted = 0
|
||||
ORDER BY i.load_sequence, i.id
|
||||
""")
|
||||
List<ItemRow> selectItems(@Param("planId") long planId);
|
||||
|
||||
@@ -200,7 +220,8 @@ public interface LoadingListQueryMapper {
|
||||
UPDATE fl_loading_plan
|
||||
SET updated_by = #{operatorId}, updated_by_name = #{operatorName},
|
||||
updated_at = CURRENT_TIMESTAMP(3), version = version + 1
|
||||
WHERE id = #{planId} AND plan_status = 'LOADING'
|
||||
WHERE id = #{planId} AND plan_status IN ('EDITING', 'LOADING')
|
||||
AND (plan_status = 'LOADING' OR preload_pushed_at IS NOT NULL)
|
||||
AND version = #{expectedVersion}
|
||||
""")
|
||||
int touchEditable(@Param("planId") long planId, @Param("expectedVersion") int expectedVersion,
|
||||
@@ -212,7 +233,8 @@ public interface LoadingListQueryMapper {
|
||||
SET b.container_no = #{containerNo}, b.voyage_no = #{voyageNo},
|
||||
b.updated_by = #{operatorId}, b.updated_by_name = #{operatorName},
|
||||
b.updated_at = CURRENT_TIMESTAMP(3), b.version = b.version + 1
|
||||
WHERE p.id = #{planId} AND p.plan_status = 'LOADING'
|
||||
WHERE p.id = #{planId} AND p.plan_status IN ('EDITING', 'LOADING')
|
||||
AND (p.plan_status = 'LOADING' OR p.preload_pushed_at IS NOT NULL)
|
||||
AND (#{containerNo} IS NOT NULL OR NOT EXISTS (
|
||||
SELECT 1 FROM fl_loading_pick_task t WHERE t.loading_plan_id = p.id))
|
||||
""")
|
||||
@@ -220,6 +242,30 @@ public interface LoadingListQueryMapper {
|
||||
@Param("voyageNo") String voyageNo, @Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_preload_plan
|
||||
SET status = 'CONFIRMED', updated_by = #{operatorId}, updated_by_name = #{operatorName},
|
||||
updated_at = CURRENT_TIMESTAMP(3), version = version + 1
|
||||
WHERE id = #{preloadPlanId} AND status = #{fromStatus} AND version = #{expectedVersion}
|
||||
""")
|
||||
int confirmLinkedPreload(@Param("preloadPlanId") long preloadPlanId,
|
||||
@Param("fromStatus") String fromStatus,
|
||||
@Param("expectedVersion") int expectedVersion,
|
||||
@Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_loading_plan
|
||||
SET updated_by = #{operatorId}, updated_by_name = #{operatorName},
|
||||
updated_at = CURRENT_TIMESTAMP(3), version = version + 1
|
||||
WHERE id = #{planId} AND plan_status = 'EDITING' AND preload_pushed_at IS NOT NULL
|
||||
AND version = #{expectedVersion}
|
||||
""")
|
||||
int touchForPreloadConfirmation(@Param("planId") long planId,
|
||||
@Param("expectedVersion") int expectedVersion,
|
||||
@Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_loading_plan
|
||||
SET plan_status = #{toStatus},
|
||||
@@ -266,6 +312,7 @@ public interface LoadingListQueryMapper {
|
||||
private String createdByName;
|
||||
private LocalDateTime createdAt;
|
||||
private Integer version;
|
||||
private LocalDateTime preloadPushedAt;
|
||||
private String containerNo;
|
||||
private String cabinetNo;
|
||||
private Long warehouseId;
|
||||
@@ -273,6 +320,12 @@ public interface LoadingListQueryMapper {
|
||||
private String vesselName;
|
||||
private String voyageNo;
|
||||
private String containerType;
|
||||
private Long preloadLinkCount;
|
||||
private Long confirmedPreloadCount;
|
||||
private Long oceanBookingId;
|
||||
private String oceanBookingStatus;
|
||||
private Long pdaTaskId;
|
||||
private String pdaTaskStatus;
|
||||
private Long skuCount;
|
||||
private Long totalPackageCount;
|
||||
private BigDecimal totalGrossWeightKg;
|
||||
@@ -287,9 +340,12 @@ public interface LoadingListQueryMapper {
|
||||
private Long itemId;
|
||||
private Long accountId;
|
||||
private String customerName;
|
||||
private Long orderId;
|
||||
private String orderNo;
|
||||
private Long inboundId;
|
||||
private Long inboundDetailId;
|
||||
private String inboundCode;
|
||||
private String containerNo;
|
||||
private String skuCode;
|
||||
private String destinationWarehouseName;
|
||||
private String productNameZh;
|
||||
@@ -311,6 +367,7 @@ public interface LoadingListQueryMapper {
|
||||
private String warehouseRemark;
|
||||
private Integer loadSequence;
|
||||
private LocalDateTime frozenAt;
|
||||
private Integer itemVersion;
|
||||
}
|
||||
|
||||
@Getter
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.firstleg.entity.LoadingPickTaskEntity;
|
||||
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
@@ -24,7 +25,8 @@ public interface LoadingPickTaskMapper extends BaseMapper<LoadingPickTaskEntity>
|
||||
JOIN fl_loading_pick_item pick_item ON pick_item.pick_task_id = task.id
|
||||
JOIN fl_loading_plan plan ON plan.id = task.loading_plan_id
|
||||
JOIN fl_container_booking booking ON booking.id = plan.container_booking_id
|
||||
WHERE booking.container_no = #{containerNo} AND task.task_status = 'LOADING'
|
||||
WHERE booking.container_no = #{containerNo} AND booking.record_status = 'ACTIVE'
|
||||
AND task.task_status = 'LOADING'
|
||||
AND booking.warehouse_id = #{warehouseId}
|
||||
LIMIT 1
|
||||
""")
|
||||
@@ -33,21 +35,35 @@ public interface LoadingPickTaskMapper extends BaseMapper<LoadingPickTaskEntity>
|
||||
|
||||
@Select("SELECT task.* FROM fl_loading_pick_task task JOIN fl_loading_plan plan ON plan.id=task.loading_plan_id "
|
||||
+ "JOIN fl_container_booking booking ON booking.id=plan.container_booking_id "
|
||||
+ "WHERE task.task_status='LOADING' AND booking.cabinet_no=#{cabinetNo} AND booking.warehouse_id=#{warehouseId} LIMIT 1")
|
||||
+ "WHERE task.task_status='LOADING' AND booking.record_status='ACTIVE' "
|
||||
+ "AND booking.cabinet_no=#{cabinetNo} AND booking.warehouse_id=#{warehouseId} LIMIT 1")
|
||||
LoadingPickTaskEntity selectLoadingByCabinet(@Param("cabinetNo") String cabinetNo, @Param("warehouseId") long warehouseId);
|
||||
|
||||
@Select("SELECT task.* FROM fl_loading_pick_task task JOIN fl_loading_plan plan ON plan.id=task.loading_plan_id "
|
||||
+ "JOIN fl_container_booking booking ON booking.id=plan.container_booking_id "
|
||||
+ "WHERE task.task_status='LOADING' AND task.loading_plan_id=#{loadingPlanId} AND booking.warehouse_id=#{warehouseId} LIMIT 1")
|
||||
+ "WHERE task.task_status='LOADING' AND booking.record_status='ACTIVE' "
|
||||
+ "AND task.loading_plan_id=#{loadingPlanId} AND booking.warehouse_id=#{warehouseId} LIMIT 1")
|
||||
LoadingPickTaskEntity selectLoadingByPlan(@Param("loadingPlanId") long loadingPlanId, @Param("warehouseId") long warehouseId);
|
||||
|
||||
@Select("SELECT booking.* FROM fl_loading_pick_task task "
|
||||
+ "JOIN fl_loading_plan plan ON plan.id=task.loading_plan_id "
|
||||
+ "JOIN fl_container_booking booking ON booking.id=plan.container_booking_id "
|
||||
+ "WHERE task.id=#{taskId} AND booking.record_status='ACTIVE'")
|
||||
ContainerBookingEntity selectActiveBookingByTask(@Param("taskId") long taskId);
|
||||
|
||||
@Select("""
|
||||
<script>
|
||||
SELECT task.id AS taskId, task.loading_plan_id AS loadingPlanId,
|
||||
task.container_booking_id AS containerBookingId, task.cabinet_no AS cabinetNo,
|
||||
booking.container_no AS containerNo, booking.warehouse_id AS warehouseId,
|
||||
booking.source_type AS sourceType, booking.record_status AS recordStatus,
|
||||
booking.merged_into_container_booking_id AS mergedIntoContainerBookingId,
|
||||
booking.warehouse_name AS warehouseName, task.task_status AS taskStatus,
|
||||
CASE WHEN assignment.pick_task_id IS NULL THEN 'UNASSIGNED' ELSE 'IN_PROGRESS' END AS workflowStatus,
|
||||
CASE WHEN task.task_status = 'COMPLETED' THEN 'COMPLETED'
|
||||
WHEN assignment.pick_task_id IS NULL THEN 'UNASSIGNED'
|
||||
WHEN SUM(CASE WHEN pick_item.item_status IN ('PENDING', 'PICKING') THEN 1 ELSE 0 END) = 0
|
||||
THEN 'AWAITING_REVIEW'
|
||||
ELSE 'IN_PROGRESS' END AS workflowStatus,
|
||||
task.version AS version, COALESCE(assignment.version, 0) AS assignmentVersion,
|
||||
COUNT(pick_item.id) AS itemCount,
|
||||
SUM(CASE WHEN pick_item.item_status IN ('PENDING', 'PICKING') THEN 1 ELSE 0 END) AS pendingItemCount
|
||||
@@ -56,7 +72,7 @@ public interface LoadingPickTaskMapper extends BaseMapper<LoadingPickTaskEntity>
|
||||
JOIN fl_container_booking booking ON booking.id = plan.container_booking_id
|
||||
JOIN fl_loading_pick_item pick_item ON pick_item.pick_task_id = task.id
|
||||
LEFT JOIN fl_loading_pick_assignment assignment ON assignment.pick_task_id = task.id
|
||||
WHERE task.task_status = 'LOADING'
|
||||
WHERE task.task_status = 'LOADING' AND booking.record_status = 'ACTIVE'
|
||||
AND (assignment.pick_task_id IS NULL OR assignment.assignee_id = #{operatorId})
|
||||
<if test="keyword != null and keyword != ''">
|
||||
AND (booking.container_no LIKE CONCAT('%', #{keyword}, '%')
|
||||
@@ -64,6 +80,7 @@ public interface LoadingPickTaskMapper extends BaseMapper<LoadingPickTaskEntity>
|
||||
</if>
|
||||
GROUP BY task.id, task.loading_plan_id, task.container_booking_id, task.cabinet_no,
|
||||
booking.container_no, booking.warehouse_id,
|
||||
booking.source_type, booking.record_status, booking.merged_into_container_booking_id,
|
||||
booking.warehouse_name, task.task_status, task.version,
|
||||
assignment.pick_task_id, assignment.version
|
||||
ORDER BY task.id DESC
|
||||
@@ -79,6 +96,9 @@ public interface LoadingPickTaskMapper extends BaseMapper<LoadingPickTaskEntity>
|
||||
private String containerNo;
|
||||
private Long containerBookingId;
|
||||
private String cabinetNo;
|
||||
private String sourceType;
|
||||
private String recordStatus;
|
||||
private Long mergedIntoContainerBookingId;
|
||||
private Long warehouseId;
|
||||
private String warehouseName;
|
||||
private String taskStatus;
|
||||
@@ -98,6 +118,12 @@ public interface LoadingPickTaskMapper extends BaseMapper<LoadingPickTaskEntity>
|
||||
public void setContainerBookingId(Long value) { this.containerBookingId = value; }
|
||||
public String getCabinetNo() { return cabinetNo; }
|
||||
public void setCabinetNo(String value) { this.cabinetNo = value; }
|
||||
public String getSourceType() { return sourceType; }
|
||||
public void setSourceType(String sourceType) { this.sourceType = sourceType; }
|
||||
public String getRecordStatus() { return recordStatus; }
|
||||
public void setRecordStatus(String recordStatus) { this.recordStatus = recordStatus; }
|
||||
public Long getMergedIntoContainerBookingId() { return mergedIntoContainerBookingId; }
|
||||
public void setMergedIntoContainerBookingId(Long value) { this.mergedIntoContainerBookingId = value; }
|
||||
public Long getWarehouseId() { return warehouseId; }
|
||||
public void setWarehouseId(Long warehouseId) { this.warehouseId = warehouseId; }
|
||||
public String getWarehouseName() { return warehouseName; }
|
||||
|
||||
@@ -9,4 +9,21 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baiyue.firstl
|
||||
AND version=#{expectedVersion}
|
||||
""")
|
||||
int removeEditing(@Param("planId") long planId,@Param("itemId") long itemId,@Param("expectedVersion") int expectedVersion,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
@Update("""
|
||||
UPDATE fl_loading_plan_item i
|
||||
SET i.warehouse_remark=#{remark}, i.updated_by=#{operatorId}, i.updated_by_name=#{operatorName},
|
||||
i.updated_at=CURRENT_TIMESTAMP(3), i.version=i.version+1
|
||||
WHERE i.id=#{itemId} AND i.loading_plan_id=#{planId} AND i.deleted=0 AND i.frozen_at IS NULL
|
||||
AND i.version=#{expectedItemVersion}
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM fl_loading_plan p
|
||||
WHERE p.id=#{planId} AND p.version=#{touchedPlanVersion}
|
||||
AND p.plan_status IN ('EDITING','LOADING')
|
||||
)
|
||||
""")
|
||||
int updateWarehouseRemark(@Param("planId") long planId, @Param("itemId") long itemId,
|
||||
@Param("touchedPlanVersion") int touchedPlanVersion,
|
||||
@Param("expectedItemVersion") int expectedItemVersion,
|
||||
@Param("remark") String remark, @Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
}
|
||||
|
||||
@@ -20,16 +20,4 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baiyue.firstl
|
||||
WHERE id=#{planId} AND plan_status='EDITING' AND preload_pushed_at IS NOT NULL
|
||||
""")
|
||||
int startPicking(@Param("planId") long planId,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
@Update("""
|
||||
UPDATE fl_loading_plan plan SET plan_status='EDITING',updated_by=#{operatorId},updated_by_name=#{operatorName},
|
||||
updated_at=CURRENT_TIMESTAMP(3),version=version+1
|
||||
WHERE plan.id=#{planId} AND plan.plan_status='LOADING' AND plan.version=#{expectedVersion}
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM fl_loading_pick_item pick_item
|
||||
JOIN fl_loading_pick_task pick_task ON pick_task.id=pick_item.pick_task_id
|
||||
WHERE pick_task.loading_plan_id=plan.id AND pick_item.picked_package_count>0
|
||||
)
|
||||
""")
|
||||
int recycleUnpicked(@Param("planId") long planId,@Param("expectedVersion") int expectedVersion,
|
||||
@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.mapper;
|
||||
import com.baiyue.firstleg.repository.PriceBookQueryRows.ChannelRow;
|
||||
import com.baiyue.firstleg.repository.PriceBookQueryRows.ChannelTaxModeRow;
|
||||
import com.baiyue.firstleg.repository.PriceBookQueryRows.RateRow;
|
||||
import com.baiyue.firstleg.repository.PriceBookQueryRows.ChannelPrefixRow;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
@@ -12,6 +13,15 @@ import java.util.List;
|
||||
/** Dedicated read model for merchant channel selection and reference estimates. */
|
||||
@Mapper
|
||||
public interface PriceBookQueryMapper {
|
||||
@Select("""
|
||||
SELECT channel_code AS channelCode, inbound_code_prefix AS inboundCodePrefix,
|
||||
overseas_inbound_code_prefix AS overseasInboundCodePrefix
|
||||
FROM fl_price_channel_catalog
|
||||
WHERE channel_code = #{channelCode} AND status = 'ACTIVE'
|
||||
LIMIT 1
|
||||
""")
|
||||
ChannelPrefixRow findChannelPrefix(@Param("channelCode") String channelCode);
|
||||
|
||||
@Select("""
|
||||
SELECT id
|
||||
FROM fl_price_book_version
|
||||
|
||||
@@ -12,10 +12,12 @@ import com.baiyue.firstleg.dto.SmartLoadingDtos.Candidate; import org.apache.iba
|
||||
AND li.inbound_detail_id=i.inbound_detail_id AND li.deleted=0),0)
|
||||
-COALESCE((SELECT SUM(split_item.package_count)
|
||||
FROM fl_inbound_split_item split_item
|
||||
WHERE o.inbound_role='MASTER'
|
||||
WHERE o.inbound_role='MASTER'
|
||||
AND split_item.tenant_id=o.tenant_id
|
||||
AND split_item.parent_inbound_id=o.id
|
||||
AND split_item.source_measurement_item_id=i.id),0) availablePackageCount,COALESCE(i.unit_weight_kg,0) unitWeightKg,
|
||||
AND split_item.source_measurement_item_id=i.id),0) availablePackageCount,
|
||||
COALESCE(o.actual_gross_weight_kg / NULLIF(o.total_package_count, 0), 0) unitGrossWeightKg,
|
||||
COALESCE(o.actual_net_weight_kg / NULLIF(o.total_package_count, 0), 0) unitNetWeightKg,
|
||||
COALESCE(i.unit_volume_cbm,0) unitVolumeCbm
|
||||
FROM fl_inbound_order o
|
||||
JOIN fl_inbound_measurement m ON m.tenant_id=o.tenant_id AND m.inbound_id=o.id
|
||||
@@ -24,6 +26,7 @@ import com.baiyue.firstleg.dto.SmartLoadingDtos.Candidate; import org.apache.iba
|
||||
WHERE m2.tenant_id=o.tenant_id AND m2.inbound_id=o.id AND m2.status='APPROVED')
|
||||
JOIN fl_inbound_measurement_item i ON i.tenant_id=o.tenant_id AND i.measurement_id=m.id
|
||||
WHERE o.warehouse_id=#{warehouseId} AND o.process_status='WAIT_SPLIT'
|
||||
AND o.actual_gross_weight_kg IS NOT NULL AND o.actual_net_weight_kg IS NOT NULL
|
||||
AND (o.inbound_role='MASTER' OR (o.split_purpose='REMAINDER' AND o.split_status='NOT_SPLIT'))
|
||||
AND i.inbound_detail_id IS NOT NULL
|
||||
AND i.package_count>COALESCE((SELECT SUM(li.package_count) FROM fl_loading_plan_item li
|
||||
@@ -53,7 +56,9 @@ import com.baiyue.firstleg.dto.SmartLoadingDtos.Candidate; import org.apache.iba
|
||||
WHERE o.inbound_role='MASTER'
|
||||
AND split_item.tenant_id=o.tenant_id
|
||||
AND split_item.parent_inbound_id=o.id
|
||||
AND split_item.source_measurement_item_id=i.id),0) availablePackageCount,COALESCE(i.unit_weight_kg,0) unitWeightKg,
|
||||
AND split_item.source_measurement_item_id=i.id),0) availablePackageCount,
|
||||
COALESCE(o.actual_gross_weight_kg / NULLIF(o.total_package_count, 0), 0) unitGrossWeightKg,
|
||||
COALESCE(o.actual_net_weight_kg / NULLIF(o.total_package_count, 0), 0) unitNetWeightKg,
|
||||
COALESCE(i.unit_volume_cbm,0) unitVolumeCbm
|
||||
FROM fl_inbound_order o
|
||||
JOIN fl_inbound_measurement m ON m.tenant_id=o.tenant_id AND m.inbound_id=o.id
|
||||
@@ -62,6 +67,7 @@ import com.baiyue.firstleg.dto.SmartLoadingDtos.Candidate; import org.apache.iba
|
||||
WHERE m2.tenant_id=o.tenant_id AND m2.inbound_id=o.id AND m2.status='APPROVED')
|
||||
JOIN fl_inbound_measurement_item i ON i.tenant_id=o.tenant_id AND i.measurement_id=m.id
|
||||
WHERE o.warehouse_id=#{warehouseId} AND o.id=#{inboundId} AND o.process_status='WAIT_SPLIT'
|
||||
AND o.actual_gross_weight_kg IS NOT NULL AND o.actual_net_weight_kg IS NOT NULL
|
||||
AND (o.inbound_role='MASTER' OR (o.split_purpose='REMAINDER' AND o.split_status='NOT_SPLIT'))
|
||||
AND i.inbound_detail_id=#{detailId}
|
||||
ORDER BY m.id DESC LIMIT 1 FOR UPDATE
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baiyue.firstleg.dto.TruckAppointmentDtos.Query; import com.baiyue.firstleg.entity.TruckAppointmentEntity; import org.apache.ibatis.annotations.*;
|
||||
@Mapper public interface TruckAppointmentMapper extends BaseMapper<TruckAppointmentEntity>{
|
||||
@Select("<script>SELECT * FROM fl_truck_appointment WHERE (#{q.keyword} IS NULL OR appointment_no LIKE CONCAT('%',#{q.keyword},'%') OR vehicle_no LIKE CONCAT('%',#{q.keyword},'%') OR carrier_name LIKE CONCAT('%',#{q.keyword},'%')) AND (#{q.status} IS NULL OR status=#{q.status}) AND (#{q.containerBookingId} IS NULL OR container_booking_id=#{q.containerBookingId}) ORDER BY updated_at DESC,id DESC</script>") Page<TruckAppointmentEntity> page(Page<TruckAppointmentEntity> p,@Param("q") Query q);
|
||||
@Select("SELECT * FROM fl_truck_appointment WHERE container_booking_id=#{containerBookingId} AND status NOT IN ('COMPLETED','CANCELLED','EXCEPTION') ORDER BY id FOR UPDATE") java.util.List<TruckAppointmentEntity> activeForUpdate(@Param("containerBookingId") long containerBookingId);
|
||||
@Select("<script>SELECT a.*,o.booking_no ocean_booking_no,COALESCE(o.warehouse_name,c.warehouse_name) warehouse_name,o.shipping_company_id,o.shipping_company_name,o.booking_agent_id,o.booking_agent_name,c.cabinet_no,c.container_no,(SELECT COUNT(*) FROM fl_truck_appointment_attachment x WHERE x.appointment_id=a.id) attachment_count FROM fl_truck_appointment a LEFT JOIN fl_ocean_booking o ON o.id=a.ocean_booking_id LEFT JOIN fl_container_booking c ON c.id=a.container_booking_id AND c.record_status='ACTIVE' WHERE (#{q.keyword} IS NULL OR a.appointment_no LIKE CONCAT('%',#{q.keyword},'%') OR a.vehicle_no LIKE CONCAT('%',#{q.keyword},'%') OR a.carrier_name LIKE CONCAT('%',#{q.keyword},'%')) AND (#{q.status} IS NULL OR a.status=#{q.status}) AND (#{q.customerId} IS NULL OR a.customer_id=#{q.customerId}) AND (#{q.containerBookingId} IS NULL OR a.container_booking_id=#{q.containerBookingId}) ORDER BY a.updated_at DESC,a.id DESC</script>") Page<TruckAppointmentEntity> page(Page<TruckAppointmentEntity> p,@Param("q") Query q);
|
||||
@Update("UPDATE fl_truck_appointment SET status=#{toStatus}, actual_arrived_warehouse_at=CASE WHEN #{toStatus}='ARRIVED_WAREHOUSE' THEN CURRENT_TIMESTAMP(3) ELSE actual_arrived_warehouse_at END, actual_loading_at=CASE WHEN #{toStatus}='LOADING' THEN CURRENT_TIMESTAMP(3) ELSE actual_loading_at END, actual_departed_at=CASE WHEN #{toStatus}='DEPARTED' THEN CURRENT_TIMESTAMP(3) ELSE actual_departed_at END, revision=revision+1, version=version+1, updated_at=CURRENT_TIMESTAMP(3) WHERE id=#{id} AND status=#{fromStatus} AND version=#{version}") int transition(@Param("id") long id,@Param("fromStatus") String from,@Param("toStatus") String to,@Param("version") int version);
|
||||
@Update("UPDATE fl_truck_appointment SET status='DISPATCHED',bill_of_lading_no=#{billOfLadingNo},revision=revision+1,version=version+1,updated_at=CURRENT_TIMESTAMP(3) WHERE id=#{id} AND status=#{fromStatus} AND version=#{version}") int dispatch(@Param("id") long id,@Param("fromStatus") String fromStatus,@Param("billOfLadingNo") String billOfLadingNo,@Param("version") int version);
|
||||
@Update("UPDATE fl_truck_appointment SET status='COMPLETED',actual_loading_at=COALESCE(actual_loading_at,CURRENT_TIMESTAMP(3)),revision=revision+1,version=version+1,updated_at=CURRENT_TIMESTAMP(3) WHERE id=#{id} AND status=#{fromStatus} AND version=#{version}") int completeLoadingFromPda(@Param("id") long id,@Param("fromStatus") String fromStatus,@Param("version") int version);
|
||||
@Update("UPDATE fl_truck_appointment SET planned_loading_at=#{at}, revision=revision+1, version=version+1, status='RESCHEDULED', remark=#{reason}, updated_at=CURRENT_TIMESTAMP(3) WHERE id=#{id} AND version=#{version}") int reschedule(@Param("id") long id,@Param("at") java.time.LocalDateTime at,@Param("reason") String reason,@Param("version") int version);
|
||||
@Update("UPDATE fl_truck_appointment SET vehicle_no=#{e.vehicleNo},driver_name=#{e.driverName},driver_phone=#{e.driverPhone},payable_amount=#{e.payableAmount},receivable_amount=#{e.receivableAmount},planned_loading_at=#{e.plannedLoadingAt},remark=#{e.remark},version=version+1,revision=revision+1,updated_at=CURRENT_TIMESTAMP(3) WHERE id=#{e.id} AND version=#{version}") int updateEdit(@Param("e") TruckAppointmentEntity e,@Param("version") int version);
|
||||
@Update("UPDATE fl_truck_appointment SET outbound_order_id=#{e.outboundOrderId},ocean_booking_id=#{e.oceanBookingId},container_booking_id=#{e.containerBookingId},loading_plan_id=#{e.loadingPlanId},warehouse_id=#{e.warehouseId},customer_id=#{e.customerId},customer_name=#{e.customerName},carrier_id=#{e.carrierId},carrier_name=#{e.carrierName},bill_of_lading_no=#{e.billOfLadingNo},fleet_contact=#{e.fleetContact},fleet_phone=#{e.fleetPhone},driver_name=#{e.driverName},driver_phone=#{e.driverPhone},driver_id_card=#{e.driverIdCard},vehicle_no=#{e.vehicleNo},store_name=#{e.storeName},store_contact=#{e.storeContact},store_phone=#{e.storePhone},store_address=#{e.storeAddress},store_city=#{e.storeCity},store_zipcode=#{e.storeZipcode},package_count=#{e.packageCount},gross_weight_kg=#{e.grossWeightKg},volume_cbm=#{e.volumeCbm},pallet_count=#{e.palletCount},po_no=#{e.poNo},planned_loading_at=#{e.plannedLoadingAt},transit_minutes=#{e.transitMinutes},payable_amount=#{e.payableAmount},receivable_amount=#{e.receivableAmount},currency=#{e.currency},remark=#{e.remark},version=version+1,revision=revision+1,updated_at=CURRENT_TIMESTAMP(3) WHERE id=#{e.id} AND version=#{version}") int updateEdit(@Param("e") TruckAppointmentEntity e,@Param("version") int version);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baiyue.firstleg.dto.CurrentContainerTransport;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 统一订单按当前有效装柜明细批量读取柜次船期。 */
|
||||
@Mapper
|
||||
public interface UnifiedInboundTransportMapper {
|
||||
@Select("""
|
||||
<script>
|
||||
SELECT DISTINCT i.inbound_id, i.account_id, p.container_booking_id,
|
||||
b.container_no, b.cabinet_no, b.vessel_name, b.voyage_no,
|
||||
b.estimated_sailing_at, b.actual_sailing_at,
|
||||
b.estimated_port_arrival_at, b.actual_port_arrival_at
|
||||
FROM fl_loading_plan_item i
|
||||
JOIN fl_loading_plan p ON p.id = i.loading_plan_id
|
||||
JOIN fl_container_booking b ON b.id = p.container_booking_id
|
||||
WHERE i.deleted = 0
|
||||
AND p.plan_status IN ('LOADING', 'COMPLETED')
|
||||
AND i.inbound_id IN
|
||||
<foreach collection="inboundIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</script>
|
||||
""")
|
||||
List<CurrentContainerTransport> findCurrentByInboundIds(@Param("inboundIds") List<Long> inboundIds);
|
||||
}
|
||||
+14
-28
@@ -16,6 +16,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
|
||||
@Validated
|
||||
@RestController
|
||||
@@ -26,40 +27,25 @@ public class PlatformOceanBookingController {
|
||||
|
||||
@GetMapping @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:list')")
|
||||
public ApiResponse<PageResult<OceanBookingEntity>> page(@AuthenticationPrincipal AuthenticatedUser user, @Valid Query query) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.page(query)); }
|
||||
@GetMapping("/channels") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:list')")
|
||||
public ApiResponse<List<ChannelOption>> channels(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long originWarehouseId) {
|
||||
FirstlegScopeGuard.platform(user); return ApiResponse.success(service.channels(originWarehouseId));
|
||||
}
|
||||
@GetMapping("/{oceanBookingId}") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:list')")
|
||||
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)
|
||||
public ApiResponse<Detail> create(@AuthenticationPrincipal AuthenticatedUser user, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody CreateRequest request) { FirstlegScopeGuard.platform(user, request.data().customerId()); return ApiResponse.success(service.create(user.userId(), user.username(), key, request)); }
|
||||
@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) { FirstlegScopeGuard.platform(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)); }
|
||||
@BusinessAudit(event="OCEAN_BOOKING_UPDATE_DRAFT", resourceType="OCEAN_BOOKING", resourceId="#oceanBookingId", description="编辑海运订舱草稿")
|
||||
public ApiResponse<Detail> updateDraft(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long oceanBookingId, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody UpdateRequest request) { validateOptionalCustomer(user, request.data().customerId()); return ApiResponse.success(service.updateDraft(oceanBookingId, user.userId(), user.username(), key, request)); }
|
||||
@DeleteMapping("/{oceanBookingId}") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:delete')")
|
||||
@BusinessAudit(event="OCEAN_BOOKING_DELETE_DRAFT", resourceType="OCEAN_BOOKING", resourceId="#oceanBookingId", description="删除海运订舱草稿")
|
||||
public ApiResponse<Void> deleteDraft(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long oceanBookingId, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody ActionRequest request) { FirstlegScopeGuard.platform(user); service.deleteDraft(oceanBookingId, user.userId(), user.username(), key, request); return ApiResponse.success(null); }
|
||||
@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)); }
|
||||
@PostMapping("/{oceanBookingId}/change-request") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:change')")
|
||||
@BusinessAudit(event="OCEAN_BOOKING_CHANGE_REQUEST", resourceType="OCEAN_BOOKING", resourceId="#oceanBookingId", description="申请变更船期")
|
||||
public ApiResponse<Detail> requestChange(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long oceanBookingId, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody ChangeRequest request) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.requestChange(oceanBookingId, user.userId(), user.username(), key, request)); }
|
||||
@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)); }
|
||||
@PostMapping("/{oceanBookingId}/allocations") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:allocate')")
|
||||
@BusinessAudit(event="OCEAN_BOOKING_ALLOCATE_CONTAINER", resourceType="OCEAN_BOOKING", resourceId="#oceanBookingId", description="分配订舱柜次")
|
||||
public ApiResponse<Detail> allocate(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long oceanBookingId, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody AllocationRequest request) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.allocate(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); }
|
||||
}
|
||||
|
||||
+1
-6
@@ -2,10 +2,5 @@ package com.baiyue.firstleg.oceanbooking.domain;
|
||||
|
||||
public enum OceanBookingStatus {
|
||||
DRAFT,
|
||||
SUBMITTED,
|
||||
CONFIRMED,
|
||||
RELEASED,
|
||||
FAILED,
|
||||
CHANGE_PENDING,
|
||||
CANCELLED
|
||||
RELEASED
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package com.baiyue.firstleg.oceanbooking.dto;
|
||||
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingContainerAllocationEntity;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingContainerRequirementEntity;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingEntity;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingPartyEntity;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingContainerRequirementEntity;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingStatusHistoryEntity;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Max;
|
||||
@@ -34,17 +33,14 @@ public final class OceanBookingDtos {
|
||||
@Size(max = 64) String contactPhone) {}
|
||||
|
||||
public record ContainerRequirementRequest(@NotBlank @Size(max = 32) String containerType,
|
||||
@NotBlank @Size(max = 32) String containerKind,
|
||||
@NotNull @jakarta.validation.constraints.DecimalMin(value = "0", inclusive = false) BigDecimal capacityCbm,
|
||||
@NotNull @jakarta.validation.constraints.DecimalMin(value = "0", inclusive = false) BigDecimal maxWeightKg,
|
||||
@NotNull @Positive Integer quantity) {}
|
||||
|
||||
public record DraftData(@NotNull @Positive Long customerId,
|
||||
@NotBlank @Size(max = 128) String customerName,
|
||||
@NotBlank @Size(max = 64) String channelCode,
|
||||
@NotBlank @Size(max = 128) String channelName,
|
||||
@NotNull @Positive Long warehouseId,
|
||||
@NotBlank @Size(max = 128) String warehouseName,
|
||||
@NotBlank @Size(max = 32) String containerKind,
|
||||
@NotNull @Min(1) @Max(100) Integer quantity,
|
||||
@NotNull @jakarta.validation.constraints.DecimalMin(value = "0", inclusive = false) BigDecimal capacityCbm,
|
||||
@NotNull @jakarta.validation.constraints.DecimalMin(value = "0", inclusive = false) BigDecimal maxWeightKg) {}
|
||||
public record DraftData(@Positive Long customerId,
|
||||
@Size(max = 128) String customerName,
|
||||
@Size(max = 64) String channelCode,
|
||||
@Positive Long warehouseId,
|
||||
@Size(max = 128) String billOfLadingNo,
|
||||
@Positive Long shippingCompanyId,
|
||||
@Size(max = 128) String shippingCompanyName,
|
||||
@@ -62,34 +58,29 @@ public final class OceanBookingDtos {
|
||||
LocalDateTime estimatedSailingAt,
|
||||
LocalDateTime estimatedArrivalAt,
|
||||
@Size(max = 1000) String remark,
|
||||
@Valid @NotEmpty @Size(max = 3) List<PartyRequest> parties,
|
||||
@Valid @NotEmpty @Size(max = 20) List<ContainerRequirementRequest> containerRequirements) {}
|
||||
@Valid @Size(max = 3) List<PartyRequest> parties,
|
||||
@Valid @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 UpdateRequest(@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,
|
||||
@NotBlank @Size(max = 128) String billOfLadingNo,
|
||||
@NotBlank @Size(max = 128) String vesselName,
|
||||
@NotBlank @Size(max = 64) String voyageNo,
|
||||
@NotNull LocalDateTime estimatedSailingAt,
|
||||
@NotNull LocalDateTime estimatedArrivalAt,
|
||||
@Size(max = 1000) String reason) {}
|
||||
public record ChangeRequest(@NotNull @Min(0) Integer expectedVersion,
|
||||
@NotBlank @Size(max = 128) String vesselName,
|
||||
@NotBlank @Size(max = 64) String voyageNo,
|
||||
LocalDateTime estimatedSailingAt,
|
||||
LocalDateTime estimatedArrivalAt,
|
||||
@NotBlank @Size(max = 1000) String reason) {}
|
||||
public record AllocationRequest(@NotNull @Positive Long requirementId,
|
||||
@NotNull @Positive Long containerBookingId,
|
||||
@NotNull @Min(0) Integer expectedVersion) {}
|
||||
public record RealContainer(Long containerBookingId, String cabinetNo, String containerNo,
|
||||
Long requirementId, Long allocationId, String containerType,
|
||||
String containerKind, BigDecimal capacityCbm, BigDecimal maxWeightKg,
|
||||
Long warehouseId, String warehouseName, String channelCode,
|
||||
String channelName, Long customerId, String customerName) {}
|
||||
|
||||
public record Detail(OceanBookingEntity oceanBooking,
|
||||
List<OceanBookingPartyEntity> parties,
|
||||
List<OceanBookingContainerRequirementEntity> containerRequirements,
|
||||
List<OceanBookingContainerAllocationEntity> containerAllocations,
|
||||
List<RealContainer> containers,
|
||||
List<OceanBookingStatusHistoryEntity> histories) {}
|
||||
|
||||
public record ChannelOption(Long priceBookVersionId, String channelCode, String channelName,
|
||||
String priceModel, List<String> pricingTaxModes) {}
|
||||
}
|
||||
|
||||
+3
-7
@@ -40,13 +40,6 @@ public class OceanBookingEntity {
|
||||
private String freightTerm;
|
||||
private LocalDateTime estimatedSailingAt;
|
||||
private LocalDateTime estimatedArrivalAt;
|
||||
private String pendingVesselName;
|
||||
private String pendingVoyageNo;
|
||||
private LocalDateTime pendingEstimatedSailingAt;
|
||||
private LocalDateTime pendingEstimatedArrivalAt;
|
||||
private String changeReason;
|
||||
private String failureReason;
|
||||
private String cancellationReason;
|
||||
private String remark;
|
||||
private Long createdBy;
|
||||
private String createdByName;
|
||||
@@ -55,4 +48,7 @@ public class OceanBookingEntity {
|
||||
private String updatedByName;
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedAt;
|
||||
@Version @TableField(fill = FieldFill.INSERT) private Integer version;
|
||||
@TableField(exist = false) private String shipperName;
|
||||
@TableField(exist = false) private String consigneeName;
|
||||
@TableField(exist = false) private Long containerCount;
|
||||
}
|
||||
|
||||
+6
-1
@@ -2,4 +2,9 @@ package com.baiyue.firstleg.oceanbooking.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingContainerAllocationEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper public interface OceanBookingContainerAllocationMapper extends BaseMapper<OceanBookingContainerAllocationEntity> {}
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
@Mapper public interface OceanBookingContainerAllocationMapper extends BaseMapper<OceanBookingContainerAllocationEntity> {
|
||||
@Update("UPDATE fl_ocean_booking_container_allocation SET container_booking_id=#{targetId} WHERE container_booking_id=#{sourceId}")
|
||||
int reassign(@Param("sourceId") long sourceId, @Param("targetId") long targetId);
|
||||
}
|
||||
|
||||
+26
-79
@@ -5,103 +5,50 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.Query;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Mapper
|
||||
public interface OceanBookingMapper extends BaseMapper<OceanBookingEntity> {
|
||||
@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},version=version+1,updated_at=NOW() WHERE id=#{id} AND status='DRAFT' AND version=#{version}")
|
||||
int updateDraft(@Param("id") long id, @Param("value") OceanBookingEntity value, @Param("version") int version, @Param("operatorId") long operatorId, @Param("operatorName") String operatorName);
|
||||
@Delete("DELETE FROM fl_ocean_booking WHERE id=#{id} AND status='DRAFT' AND version=#{version}")
|
||||
int deleteDraft(@Param("id") long id, @Param("version") int version);
|
||||
@Select("""
|
||||
<script>
|
||||
SELECT * FROM fl_ocean_booking
|
||||
WHERE (#{query.customerId} IS NULL OR customer_id=#{query.customerId})
|
||||
AND (#{query.status} IS NULL OR status=#{query.status})
|
||||
AND (#{query.keyword} IS NULL OR booking_no LIKE CONCAT('%',#{query.keyword},'%')
|
||||
OR customer_name LIKE CONCAT('%',#{query.keyword},'%')
|
||||
OR bill_of_lading_no LIKE CONCAT('%',#{query.keyword},'%')
|
||||
OR vessel_name LIKE CONCAT('%',#{query.keyword},'%')
|
||||
OR voyage_no LIKE CONCAT('%',#{query.keyword},'%'))
|
||||
ORDER BY updated_at DESC,id DESC
|
||||
SELECT booking.*,
|
||||
(SELECT party.party_name FROM fl_ocean_booking_party party
|
||||
WHERE party.ocean_booking_id=booking.id AND party.party_type='SHIPPER' LIMIT 1) shipper_name,
|
||||
(SELECT party.party_name FROM fl_ocean_booking_party party
|
||||
WHERE party.ocean_booking_id=booking.id AND party.party_type='CONSIGNEE' LIMIT 1) consignee_name,
|
||||
(SELECT COUNT(*) FROM fl_ocean_booking_container_allocation allocation
|
||||
JOIN fl_container_booking container ON container.id=allocation.container_booking_id
|
||||
WHERE allocation.ocean_booking_id=booking.id AND container.record_status='ACTIVE') container_count
|
||||
FROM fl_ocean_booking booking
|
||||
WHERE (#{query.customerId} IS NULL OR booking.customer_id=#{query.customerId})
|
||||
AND booking.status IN ('DRAFT','RELEASED')
|
||||
AND (#{query.status} IS NULL OR booking.status=#{query.status})
|
||||
AND (#{query.keyword} IS NULL OR booking.booking_no LIKE CONCAT('%',#{query.keyword},'%')
|
||||
OR booking.customer_name LIKE CONCAT('%',#{query.keyword},'%')
|
||||
OR booking.bill_of_lading_no LIKE CONCAT('%',#{query.keyword},'%')
|
||||
OR booking.vessel_name LIKE CONCAT('%',#{query.keyword},'%')
|
||||
OR booking.voyage_no LIKE CONCAT('%',#{query.keyword},'%'))
|
||||
ORDER BY booking.updated_at DESC,booking.id DESC
|
||||
</script>
|
||||
""")
|
||||
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},
|
||||
cancellation_reason=#{cancellationReason},updated_by=#{operatorId},updated_by_name=#{operatorName},
|
||||
UPDATE fl_ocean_booking SET status=#{toStatus},
|
||||
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("expectedVersion") int expectedVersion, @Param("operatorId") long operatorId,
|
||||
@Param("toStatus") String toStatus, @Param("expectedVersion") int expectedVersion, @Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_ocean_booking SET status='RELEASED',bill_of_lading_no=#{billOfLadingNo},
|
||||
vessel_name=#{vesselName},voyage_no=#{voyageNo},estimated_sailing_at=#{estimatedSailingAt},
|
||||
estimated_arrival_at=#{estimatedArrivalAt},updated_by=#{operatorId},updated_by_name=#{operatorName},
|
||||
updated_at=CURRENT_TIMESTAMP(3),version=version+1
|
||||
WHERE id=#{oceanBookingId} AND status='CONFIRMED' AND version=#{expectedVersion}
|
||||
""")
|
||||
int release(@Param("oceanBookingId") long oceanBookingId, @Param("billOfLadingNo") String billOfLadingNo,
|
||||
@Param("vesselName") String vesselName, @Param("voyageNo") String voyageNo,
|
||||
@Param("estimatedSailingAt") LocalDateTime estimatedSailingAt,
|
||||
@Param("estimatedArrivalAt") LocalDateTime estimatedArrivalAt,
|
||||
@Param("expectedVersion") int expectedVersion, @Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_ocean_booking SET status='CHANGE_PENDING',pending_vessel_name=#{vesselName},
|
||||
pending_voyage_no=#{voyageNo},pending_estimated_sailing_at=#{estimatedSailingAt},
|
||||
pending_estimated_arrival_at=#{estimatedArrivalAt},change_reason=#{reason},
|
||||
updated_by=#{operatorId},updated_by_name=#{operatorName},updated_at=CURRENT_TIMESTAMP(3),version=version+1
|
||||
WHERE id=#{oceanBookingId} AND status='CONFIRMED' AND version=#{expectedVersion}
|
||||
""")
|
||||
int requestChange(@Param("oceanBookingId") long oceanBookingId, @Param("vesselName") String vesselName,
|
||||
@Param("voyageNo") String voyageNo, @Param("estimatedSailingAt") LocalDateTime estimatedSailingAt,
|
||||
@Param("estimatedArrivalAt") LocalDateTime estimatedArrivalAt, @Param("reason") String reason,
|
||||
@Param("expectedVersion") int expectedVersion, @Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_ocean_booking SET status='CONFIRMED',vessel_name=pending_vessel_name,voyage_no=pending_voyage_no,
|
||||
estimated_sailing_at=pending_estimated_sailing_at,estimated_arrival_at=pending_estimated_arrival_at,
|
||||
pending_vessel_name=NULL,pending_voyage_no=NULL,pending_estimated_sailing_at=NULL,
|
||||
pending_estimated_arrival_at=NULL,updated_by=#{operatorId},updated_by_name=#{operatorName},
|
||||
updated_at=CURRENT_TIMESTAMP(3),version=version+1
|
||||
WHERE id=#{oceanBookingId} AND status='CHANGE_PENDING' AND version=#{expectedVersion}
|
||||
""")
|
||||
int confirmChange(@Param("oceanBookingId") long oceanBookingId, @Param("expectedVersion") int expectedVersion,
|
||||
@Param("operatorId") long operatorId, @Param("operatorName") String operatorName);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_ocean_booking SET version=version+1,updated_by=#{operatorId},updated_by_name=#{operatorName},
|
||||
updated_at=CURRENT_TIMESTAMP(3)
|
||||
WHERE id=#{oceanBookingId} AND version=#{expectedVersion}
|
||||
AND status IN ('SUBMITTED','CONFIRMED','CHANGE_PENDING')
|
||||
""")
|
||||
int touchForAllocation(@Param("oceanBookingId") long oceanBookingId,
|
||||
@Param("expectedVersion") int expectedVersion,
|
||||
@Param("operatorId") long operatorId, @Param("operatorName") String operatorName);
|
||||
}
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ public interface OceanBookingOperationMapper extends BaseMapper<OceanBookingOper
|
||||
@Update("UPDATE fl_ocean_booking_operation SET ocean_booking_id=#{oceanBookingId},status='SUCCESS',"
|
||||
+ "result_json=#{resultJson},completed_at=CURRENT_TIMESTAMP(3) WHERE operator_id=#{operatorId} "
|
||||
+ "AND operation=#{operation} AND idempotency_key=#{key} AND status='PROCESSING'")
|
||||
int complete(@Param("oceanBookingId") long oceanBookingId, @Param("operatorId") long operatorId,
|
||||
int complete(@Param("oceanBookingId") Long oceanBookingId, @Param("operatorId") long operatorId,
|
||||
@Param("operation") String operation, @Param("key") String key,
|
||||
@Param("resultJson") String resultJson);
|
||||
}
|
||||
|
||||
+41
-35
@@ -3,18 +3,19 @@ package com.baiyue.firstleg.oceanbooking.repository;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||
import com.baiyue.firstleg.mapper.ContainerBookingMapper;
|
||||
import com.baiyue.firstleg.mapper.LoadingPlanMapper;
|
||||
import com.baiyue.firstleg.entity.LoadingPlanEntity;
|
||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.Query;
|
||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.ReleaseRequest;
|
||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.RealContainer;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.*;
|
||||
import com.baiyue.firstleg.oceanbooking.mapper.*;
|
||||
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||
import com.baiyue.firstleg.repository.ContainerRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Repository
|
||||
public class OceanBookingRepository {
|
||||
@@ -24,55 +25,60 @@ public class OceanBookingRepository {
|
||||
@Resource private OceanBookingContainerAllocationMapper allocations;
|
||||
@Resource private OceanBookingStatusHistoryMapper histories;
|
||||
@Resource private OceanBookingOperationMapper operations;
|
||||
@Resource private ContainerBookingMapper containerBookings;
|
||||
@Resource private LoadingPlanMapper loadingPlans;
|
||||
@Resource private ContainerRepository containerBookings;
|
||||
|
||||
public PageResult<OceanBookingEntity> page(Query query) {
|
||||
return PageResult.from(bookings.selectPage(new Page<>(query.page(), query.size()), query));
|
||||
}
|
||||
public OceanBookingEntity find(long id) { return bookings.selectById(id); }
|
||||
public void insert(OceanBookingEntity value) { bookings.insert(value); }
|
||||
public int transition(long id, String from, String to, int version, long operatorId, String operatorName) {
|
||||
return bookings.transition(id, from, to, version, operatorId, operatorName);
|
||||
}
|
||||
public int updateDraft(OceanBookingEntity value, int version, long operatorId, String operatorName) {
|
||||
return bookings.updateDraft(value, version, operatorId, operatorName);
|
||||
return bookings.updateDraft(value.getId(), value, version, operatorId, operatorName);
|
||||
}
|
||||
public int transition(long id, String from, String to, String failureReason, String cancellationReason,
|
||||
int version, long operatorId, String operatorName) {
|
||||
return bookings.transition(id, from, to, failureReason, 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(),
|
||||
request.voyageNo().trim(), request.estimatedSailingAt(), request.estimatedArrivalAt(),
|
||||
request.expectedVersion(), operatorId, operatorName);
|
||||
}
|
||||
public int requestChange(long id, OceanBookingEntity pending, String reason, int version,
|
||||
long operatorId, String operatorName) {
|
||||
return bookings.requestChange(id, pending.getPendingVesselName(), pending.getPendingVoyageNo(),
|
||||
pending.getPendingEstimatedSailingAt(), pending.getPendingEstimatedArrivalAt(), reason,
|
||||
version, operatorId, operatorName);
|
||||
}
|
||||
public int confirmChange(long id, int version, long operatorId, String operatorName) {
|
||||
return bookings.confirmChange(id, version, operatorId, operatorName);
|
||||
}
|
||||
public int touchForAllocation(long id, int version, long operatorId, String operatorName) {
|
||||
return bookings.touchForAllocation(id, version, operatorId, operatorName);
|
||||
public int deleteDraft(long id, int version) {
|
||||
return bookings.deleteDraft(id, version);
|
||||
}
|
||||
public List<OceanBookingPartyEntity> parties(long id) { return parties.selectList(new LambdaQueryWrapper<OceanBookingPartyEntity>().eq(OceanBookingPartyEntity::getOceanBookingId, id).orderByAsc(OceanBookingPartyEntity::getId)); }
|
||||
public List<OceanBookingContainerRequirementEntity> requirements(long id) { return requirements.selectList(new LambdaQueryWrapper<OceanBookingContainerRequirementEntity>().eq(OceanBookingContainerRequirementEntity::getOceanBookingId, id).orderByAsc(OceanBookingContainerRequirementEntity::getSortNo)); }
|
||||
public List<OceanBookingContainerAllocationEntity> allocations(long id) { return allocations.selectList(new LambdaQueryWrapper<OceanBookingContainerAllocationEntity>().eq(OceanBookingContainerAllocationEntity::getOceanBookingId, id).orderByAsc(OceanBookingContainerAllocationEntity::getAllocatedAt)); }
|
||||
public List<RealContainer> containers(long id) {
|
||||
Map<Long, OceanBookingContainerRequirementEntity> requirementById = requirements(id).stream()
|
||||
.collect(Collectors.toMap(OceanBookingContainerRequirementEntity::getId, Function.identity()));
|
||||
return allocations(id).stream().map(allocation -> {
|
||||
OceanBookingContainerRequirementEntity requirement = requirementById.get(allocation.getRequirementId());
|
||||
ContainerBookingEntity container = containerBookings.find(allocation.getContainerBookingId());
|
||||
if (requirement == null || container == null) {
|
||||
throw new IllegalStateException("订舱真实柜聚合关系不完整");
|
||||
}
|
||||
return new RealContainer(container.getId(), container.getCabinetNo(), container.getContainerNo(),
|
||||
requirement.getId(), allocation.getId(), requirement.getContainerType(),
|
||||
requirement.getContainerKind(), requirement.getCapacityCbm(), requirement.getMaxWeightKg(),
|
||||
container.getWarehouseId(), container.getWarehouseName(), container.getChannelCode(),
|
||||
container.getChannelName(), container.getCustomerId(), container.getCustomerName());
|
||||
}).toList();
|
||||
}
|
||||
public OceanBookingContainerAllocationEntity allocationByContainer(long containerBookingId) {
|
||||
return allocations.selectOne(new LambdaQueryWrapper<OceanBookingContainerAllocationEntity>()
|
||||
.eq(OceanBookingContainerAllocationEntity::getContainerBookingId, containerBookingId));
|
||||
}
|
||||
public List<OceanBookingStatusHistoryEntity> histories(long id) { return histories.selectList(new LambdaQueryWrapper<OceanBookingStatusHistoryEntity>().eq(OceanBookingStatusHistoryEntity::getOceanBookingId, id).orderByAsc(OceanBookingStatusHistoryEntity::getCreatedAt)); }
|
||||
public void replaceParties(long id, List<OceanBookingPartyEntity> values) { parties.delete(new LambdaQueryWrapper<OceanBookingPartyEntity>().eq(OceanBookingPartyEntity::getOceanBookingId, id)); values.forEach(parties::insert); }
|
||||
public void replaceRequirements(long id, List<OceanBookingContainerRequirementEntity> values) { requirements.delete(new LambdaQueryWrapper<OceanBookingContainerRequirementEntity>().eq(OceanBookingContainerRequirementEntity::getOceanBookingId, id)); values.forEach(requirements::insert); }
|
||||
public OceanBookingContainerRequirementEntity requirement(long id, long requirementId) { return requirements.selectOne(new LambdaQueryWrapper<OceanBookingContainerRequirementEntity>().eq(OceanBookingContainerRequirementEntity::getOceanBookingId, id).eq(OceanBookingContainerRequirementEntity::getId, requirementId)); }
|
||||
public ContainerBookingEntity containerBooking(long id) { return containerBookings.selectById(id); }
|
||||
public void insert(ContainerBookingEntity value) { containerBookings.insert(value); }
|
||||
public void insert(LoadingPlanEntity value) { loadingPlans.insert(value); }
|
||||
public int syncReleasedTransport(long id, ReleaseRequest request, long operatorId, String operatorName) {
|
||||
return containerBookings.syncReleasedTransport(id, request.billOfLadingNo().trim(), request.vesselName().trim(),
|
||||
request.voyageNo().trim(), request.estimatedSailingAt(), operatorId, operatorName);
|
||||
public boolean containerNoExists(String containerNo) {
|
||||
return containerBookings.containerNoExists(containerNo, null);
|
||||
}
|
||||
public void insertContainer(ContainerBookingEntity value) { containerBookings.insert(value); }
|
||||
public void insert(OceanBookingContainerAllocationEntity value) { allocations.insert(value); }
|
||||
public int reassignAllocation(long sourceId, long targetId) {
|
||||
return allocations.reassign(sourceId, targetId);
|
||||
}
|
||||
public void deleteAllocation(long containerBookingId) { allocations.delete(new LambdaQueryWrapper<OceanBookingContainerAllocationEntity>().eq(OceanBookingContainerAllocationEntity::getContainerBookingId, containerBookingId)); }
|
||||
public void insert(OceanBookingStatusHistoryEntity value) { histories.insert(value); }
|
||||
public boolean claimOperation(Long id, long operatorId, String action, String key, String hash) { return operations.claim(com.baomidou.mybatisplus.core.toolkit.IdWorker.getId(), id, operatorId, action, key, hash) == 1; }
|
||||
public OceanBookingOperationEntity operation(long operatorId, String action, String key) { return operations.selectOne(new LambdaQueryWrapper<OceanBookingOperationEntity>().eq(OceanBookingOperationEntity::getOperatorId, operatorId).eq(OceanBookingOperationEntity::getOperation, action).eq(OceanBookingOperationEntity::getIdempotencyKey, key)); }
|
||||
public int completeOperation(long id, long operatorId, String action, String key, String json) { return operations.complete(id, operatorId, action, key, json); }
|
||||
public int completeOperation(Long id, long operatorId, String action, String key, String json) { return operations.complete(id, operatorId, action, key, json); }
|
||||
}
|
||||
|
||||
+165
-238
@@ -2,19 +2,25 @@ package com.baiyue.firstleg.oceanbooking.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPlanEntity;
|
||||
import com.baiyue.firstleg.client.WarehouseMasterDataClient;
|
||||
import com.baiyue.firstleg.oceanbooking.domain.OceanBookingStatus;
|
||||
import com.baiyue.firstleg.domain.ContainerSpecificationCatalog;
|
||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.*;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.*;
|
||||
import com.baiyue.firstleg.oceanbooking.repository.OceanBookingRepository;
|
||||
import com.baiyue.firstleg.repository.PriceBookQueryRepository;
|
||||
import com.baiyue.firstleg.repository.PriceBookQueryRows.ChannelRow;
|
||||
import com.baiyue.firstleg.repository.PriceBookQueryRows.ChannelTaxModeRow;
|
||||
import com.baiyue.firstleg.service.WarehouseSupplierSnapshotService;
|
||||
import com.baiyue.firstleg.service.FirstlegDocumentNumberGenerator;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -24,207 +30,117 @@ import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
public class OceanBookingService {
|
||||
private static final Set<String> PARTY_TYPES = Set.of("SHIPPER", "CONSIGNEE", "NOTIFY_PARTY");
|
||||
private static final Set<OceanBookingStatus> OPERATIONAL_LIST_STATUSES = Set.of(
|
||||
OceanBookingStatus.DRAFT, OceanBookingStatus.RELEASED);
|
||||
@Resource private OceanBookingRepository repository;
|
||||
@Resource private PriceBookQueryRepository priceBooks;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
@Resource private WarehouseSupplierSnapshotService suppliers;
|
||||
@Resource private WarehouseMasterDataClient warehouses;
|
||||
@Resource private FirstlegDocumentNumberGenerator documentNumbers;
|
||||
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
|
||||
@Value("${baiyue.warehouse.public-tenant-id:0}") private long publicWarehouseTenantId;
|
||||
|
||||
public PageResult<OceanBookingEntity> page(Query query) {
|
||||
String status = trim(query.status());
|
||||
if (status != null) parseStatus(status);
|
||||
if (status != null && !OPERATIONAL_LIST_STATUSES.contains(parseStatus(status))) {
|
||||
throw badRequest("订舱列表仅支持草稿或已放舱状态");
|
||||
}
|
||||
return repository.page(new Query(query.customerId(), status, trim(query.keyword()), query.page(), query.size()));
|
||||
}
|
||||
|
||||
public List<ChannelOption> channels(long originWarehouseId) {
|
||||
if (originWarehouseId <= 0) throw badRequest("请选择有效起运仓");
|
||||
Long versionId = priceBooks.activeVersionId();
|
||||
if (versionId == null) return List.of();
|
||||
List<ChannelRow> rows = priceBooks.channels(versionId, originWarehouseId);
|
||||
Map<String, List<String>> taxModes = priceBooks.channelTaxModes(versionId, originWarehouseId).stream()
|
||||
.filter(row -> row != null && StringUtils.hasText(row.getChannelCode())
|
||||
&& StringUtils.hasText(row.getPricingTaxMode()))
|
||||
.collect(java.util.stream.Collectors.groupingBy(row -> row.getChannelCode().trim(),
|
||||
java.util.stream.Collectors.mapping(ChannelTaxModeRow::getPricingTaxMode,
|
||||
java.util.stream.Collectors.toList())));
|
||||
return rows.stream().map(row -> channelOption(row, taxModes)).toList();
|
||||
}
|
||||
|
||||
public Detail detail(long oceanBookingId) { return detail(required(oceanBookingId)); }
|
||||
|
||||
@Transactional
|
||||
public Detail create(long operatorId, String operatorName, String key, CreateRequest request) {
|
||||
if (request.expectedVersion() != 0) throw concurrent();
|
||||
validateDraft(request.data());
|
||||
validateDraftStructure(request.data());
|
||||
String action = "CREATE";
|
||||
String hash = hash(action, null, request);
|
||||
Detail replay = claimOrReplay(null, operatorId, action, key, hash);
|
||||
if (replay != null) return replay;
|
||||
ResolvedDraft resolved = resolveDraft(request.data());
|
||||
OceanBookingEntity value = new OceanBookingEntity();
|
||||
value.setId(IdWorker.getId());
|
||||
value.setBookingNo("OB" + value.getId());
|
||||
value.setBookingNo(documentNumbers.next("OB"));
|
||||
value.setStatus(OceanBookingStatus.DRAFT.name());
|
||||
copy(value, request.data());
|
||||
copy(value, request.data(), resolved);
|
||||
value.setCreatedBy(operatorId); value.setCreatedByName(operatorName);
|
||||
value.setUpdatedBy(operatorId); value.setUpdatedByName(operatorName); value.setVersion(0);
|
||||
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()));
|
||||
List<OceanBookingContainerRequirementEntity> requirements = requirements(
|
||||
value.getId(), request.data().containerRequirements());
|
||||
repository.replaceRequirements(value.getId(), requirements);
|
||||
OceanBookingEntity draft = required(value.getId());
|
||||
addHistory(value.getId(), null, OceanBookingStatus.DRAFT.name(), action, null, null, draft, operatorId, operatorName);
|
||||
return complete(value.getId(), operatorId, action, key, detail(draft));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Detail updateDraft(long oceanBookingId, long operatorId, String operatorName, String key,
|
||||
UpdateDraftRequest request) {
|
||||
validateDraft(request.data());
|
||||
public Detail updateDraft(long id, long operatorId, String operatorName, String key, UpdateRequest request) {
|
||||
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);
|
||||
OceanBookingEntity value = new OceanBookingEntity(); value.setId(oceanBookingId); copy(value, request.data());
|
||||
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);
|
||||
validateConfirmation(before, repository.requirements(id));
|
||||
if (repository.transition(id, OceanBookingStatus.SUBMITTED.name(), OceanBookingStatus.CONFIRMED.name(),
|
||||
null, null, request.expectedVersion(), operatorId, name) != 1) throw concurrent();
|
||||
requireStatus(before, OceanBookingStatus.DRAFT);
|
||||
validateDraftStructure(request.data());
|
||||
ResolvedDraft resolved = resolveDraft(request.data());
|
||||
copy(before, request.data(), resolved);
|
||||
if (repository.updateDraft(before, request.expectedVersion(), operatorId, operatorName) != 1) throw concurrent();
|
||||
repository.replaceParties(id, parties(id, request.data().parties()));
|
||||
repository.replaceRequirements(id, requirements(id, request.data().containerRequirements()));
|
||||
OceanBookingEntity after = required(id);
|
||||
generateContainers(after, repository.requirements(id), operatorId, name);
|
||||
addHistory(id, before.getStatus(), after.getStatus(), action, request.reason(), before, after,
|
||||
operatorId, name);
|
||||
addHistory(id, OceanBookingStatus.DRAFT.name(), OceanBookingStatus.DRAFT.name(), action, null, before, after, operatorId, operatorName);
|
||||
return complete(id, operatorId, action, key, detail(after));
|
||||
}
|
||||
@Transactional
|
||||
public void deleteDraft(long id, long operatorId, String operatorName, String key, ActionRequest request) {
|
||||
String action = "DELETE_DRAFT";
|
||||
Detail replay = claimOrReplay(null, operatorId, action, key, hash(action, id, request));
|
||||
if (replay != null) return;
|
||||
OceanBookingEntity before = requiredVersion(id, request.expectedVersion());
|
||||
requireStatus(before, OceanBookingStatus.DRAFT);
|
||||
if (repository.deleteDraft(id, request.expectedVersion()) != 1) throw concurrent();
|
||||
complete(null, operatorId, action, key, new Detail(before, List.of(), List.of(), List.of(), List.of()));
|
||||
}
|
||||
|
||||
@Transactional public Detail release(long id, long operatorId, String name, String key, ReleaseRequest request) {
|
||||
validateRange(request.estimatedSailingAt(), request.estimatedArrivalAt());
|
||||
String action = "RELEASE";
|
||||
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.CONFIRMED);
|
||||
if (repository.release(id, request, operatorId, name) != 1) throw concurrent();
|
||||
repository.syncReleasedTransport(id, request, operatorId, name);
|
||||
requireStatus(before, OceanBookingStatus.DRAFT);
|
||||
validateRelease(before, repository.parties(id), repository.requirements(id));
|
||||
if (repository.transition(id, OceanBookingStatus.DRAFT.name(), OceanBookingStatus.RELEASED.name(),
|
||||
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 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("取消原因不能为空");
|
||||
String action = "CANCEL";
|
||||
Detail replay = claimOrReplay(id, operatorId, action, key, hash(action, id, request));
|
||||
if (replay != null) return replay;
|
||||
OceanBookingEntity before = requiredVersion(id, request.expectedVersion());
|
||||
OceanBookingStatus from = parseStatus(before.getStatus());
|
||||
if (from == OceanBookingStatus.RELEASED || from == OceanBookingStatus.CANCELLED) {
|
||||
throw conflict("订舱已越过不可逆节点或已经取消");
|
||||
}
|
||||
if (repository.transition(id, from.name(), OceanBookingStatus.CANCELLED.name(), null,
|
||||
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);
|
||||
return complete(id, operatorId, action, key, detail(after));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Detail requestChange(long id, long operatorId, String name, String key, ChangeRequest request) {
|
||||
validateRange(request.estimatedSailingAt(), request.estimatedArrivalAt());
|
||||
String action = "REQUEST_CHANGE";
|
||||
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.CONFIRMED);
|
||||
OceanBookingEntity pending = new OceanBookingEntity();
|
||||
pending.setPendingVesselName(request.vesselName().trim());
|
||||
pending.setPendingVoyageNo(request.voyageNo().trim());
|
||||
pending.setPendingEstimatedSailingAt(request.estimatedSailingAt());
|
||||
pending.setPendingEstimatedArrivalAt(request.estimatedArrivalAt());
|
||||
if (repository.requestChange(id, pending, request.reason().trim(), 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 confirmChange(long id, long operatorId, String name, String key, ActionRequest request) {
|
||||
String action = "CONFIRM_CHANGE";
|
||||
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.CHANGE_PENDING);
|
||||
if (repository.confirmChange(id, 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 allocate(long id, long operatorId, String name, String key, AllocationRequest request) {
|
||||
String action = "ALLOCATE_CONTAINER";
|
||||
Detail replay = claimOrReplay(id, operatorId, action, key, hash(action, id, request));
|
||||
if (replay != null) return replay;
|
||||
OceanBookingEntity booking = requiredVersion(id, request.expectedVersion());
|
||||
if (!Set.of(OceanBookingStatus.SUBMITTED.name(), OceanBookingStatus.CONFIRMED.name(),
|
||||
OceanBookingStatus.CHANGE_PENDING.name()).contains(booking.getStatus())) {
|
||||
throw conflict("当前订舱状态不可分配柜次");
|
||||
}
|
||||
OceanBookingContainerRequirementEntity requirement = repository.requirement(id, request.requirementId());
|
||||
if (requirement == null) throw new BizException(ResultCode.NOT_FOUND, "箱型需求不存在");
|
||||
ContainerBookingEntity container = repository.containerBooking(request.containerBookingId());
|
||||
if (container == null) throw new BizException(ResultCode.NOT_FOUND, "柜次不存在");
|
||||
if (!requirement.getContainerType().equals(container.getContainerType())) throw conflict("柜次箱型与订舱需求不一致");
|
||||
requireSameChannel(booking, container);
|
||||
long used = repository.allocations(id).stream().filter(v -> request.requirementId().equals(v.getRequirementId())).count();
|
||||
if (used >= requirement.getQuantity()) throw conflict("该箱型需求已分配完毕");
|
||||
if (repository.touchForAllocation(id, request.expectedVersion(), operatorId, name) != 1) throw concurrent();
|
||||
OceanBookingContainerAllocationEntity allocation = new OceanBookingContainerAllocationEntity();
|
||||
allocation.setId(IdWorker.getId()); allocation.setOceanBookingId(id);
|
||||
allocation.setRequirementId(request.requirementId()); allocation.setContainerBookingId(request.containerBookingId());
|
||||
allocation.setAllocatedBy(operatorId); allocation.setAllocatedByName(name); allocation.setAllocatedAt(LocalDateTime.now());
|
||||
try { repository.insert(allocation); } catch (DuplicateKeyException ex) { throw conflict("柜次已被其他订舱占用"); }
|
||||
return complete(id, operatorId, action, key, detail(id));
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -236,7 +152,7 @@ public class OceanBookingService {
|
||||
catch (JsonProcessingException ex) { throw new IllegalStateException("无法读取订舱幂等结果", ex); }
|
||||
}
|
||||
|
||||
private Detail complete(long id, long operatorId, String action, String key, Detail detail) {
|
||||
private Detail complete(Long id, long operatorId, String action, String key, Detail detail) {
|
||||
try {
|
||||
if (repository.completeOperation(id, operatorId, action, key, objectMapper.writeValueAsString(detail)) != 1) {
|
||||
throw conflict("订舱幂等请求完成状态冲突");
|
||||
@@ -254,105 +170,116 @@ public class OceanBookingService {
|
||||
history.setOperatorId(operatorId); history.setOperatorName(operatorName); repository.insert(history);
|
||||
}
|
||||
|
||||
private Detail detail(OceanBookingEntity value) { return new Detail(value, repository.parties(value.getId()), repository.requirements(value.getId()), repository.allocations(value.getId()), repository.histories(value.getId())); }
|
||||
private Detail detail(OceanBookingEntity value) { return new Detail(value, repository.parties(value.getId()), repository.requirements(value.getId()), repository.containers(value.getId()), repository.histories(value.getId())); }
|
||||
private OceanBookingEntity required(long id) { OceanBookingEntity value = repository.find(id); if (value == null) throw new BizException(ResultCode.NOT_FOUND, "订舱单不存在"); return value; }
|
||||
private OceanBookingEntity requiredVersion(long id, int version) { OceanBookingEntity value = required(id); if (!Integer.valueOf(version).equals(value.getVersion())) throw concurrent(); return value; }
|
||||
private static void requireStatus(OceanBookingEntity value, OceanBookingStatus status) { if (!status.name().equals(value.getStatus())) throw conflict("订舱状态不允许当前操作"); }
|
||||
|
||||
private void validateDraft(DraftData data) {
|
||||
if (data.estimatedSailingAt() != null || data.estimatedArrivalAt() != null
|
||||
|| StringUtils.hasText(data.billOfLadingNo()) || StringUtils.hasText(data.vesselName())
|
||||
|| StringUtils.hasText(data.voyageNo())) {
|
||||
throw badRequest("真实运输数据请在确认放舱时录入");
|
||||
}
|
||||
Set<String> types = new HashSet<>();
|
||||
for (PartyRequest party : data.parties()) {
|
||||
private void validateDraftStructure(DraftData data) {
|
||||
boolean hasCustomerId = data.customerId() != null;
|
||||
boolean hasCustomerName = StringUtils.hasText(data.customerName());
|
||||
if (hasCustomerId != hasCustomerName) throw badRequest("客户ID和客户名称必须同时填写或同时为空");
|
||||
if (data.parties() != null) {
|
||||
Set<String> types = new HashSet<>();
|
||||
for (PartyRequest party : data.parties()) {
|
||||
String type = party.partyType().trim().toUpperCase();
|
||||
if (!PARTY_TYPES.contains(type) || !types.add(type)) throw badRequest("订舱方必须各包含一条SHIPPER、CONSIGNEE和NOTIFY_PARTY");
|
||||
}
|
||||
}
|
||||
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 copy(OceanBookingEntity v, DraftData d) {
|
||||
v.setCustomerId(d.customerId()); v.setCustomerName(d.customerName().trim());
|
||||
v.setChannelCode(d.channelCode().trim()); v.setChannelName(d.channelName().trim());
|
||||
v.setWarehouseId(d.warehouseId()); v.setWarehouseName(d.warehouseName().trim());
|
||||
v.setBillOfLadingNo(null);
|
||||
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.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 void generateContainers(OceanBookingEntity booking,
|
||||
List<OceanBookingContainerRequirementEntity> requirements,
|
||||
long operatorId, String operatorName) {
|
||||
for (OceanBookingContainerRequirementEntity requirement : requirements) {
|
||||
for (int sequence = 1; sequence <= requirement.getQuantity(); sequence++) {
|
||||
ContainerBookingEntity container = new ContainerBookingEntity();
|
||||
container.setId(IdWorker.getId()); container.setBookingNo("CB" + container.getId());
|
||||
container.setCabinetNo("CAB-" + booking.getBookingNo() + '-' + requirement.getSortNo() + '-' + sequence);
|
||||
container.setContainerNo(null); container.setChannelCode(booking.getChannelCode());
|
||||
container.setChannelName(booking.getChannelName()); container.setOceanBookingId(booking.getId());
|
||||
container.setOceanBookingRequirementId(requirement.getId());
|
||||
container.setWarehouseId(booking.getWarehouseId()); container.setWarehouseName(booking.getWarehouseName());
|
||||
container.setContainerType(requirement.getContainerType());
|
||||
container.setCapacityCbm(requirement.getCapacityCbm());
|
||||
container.setMaxWeightKg(requirement.getMaxWeightKg());
|
||||
container.setShippingCompanyId(booking.getShippingCompanyId());
|
||||
container.setShippingCompanyName(booking.getShippingCompanyName());
|
||||
container.setDeparturePortCode(booking.getDeparturePortCode());
|
||||
container.setDeparturePortName(booking.getDeparturePortName());
|
||||
container.setDestinationPortCode(booking.getDestinationPortCode());
|
||||
container.setDestinationPortName(booking.getDestinationPortName());
|
||||
container.setCreatedBy(operatorId); container.setCreatedByName(operatorName);
|
||||
container.setUpdatedBy(operatorId); container.setUpdatedByName(operatorName); container.setVersion(0);
|
||||
repository.insert(container);
|
||||
LoadingPlanEntity plan = new LoadingPlanEntity();
|
||||
plan.setPlanNo("LP-" + container.getId()); plan.setContainerBookingId(container.getId());
|
||||
plan.setPlanStatus("EDITING"); plan.setCreatedBy(operatorId); plan.setCreatedByName(operatorName);
|
||||
plan.setUpdatedBy(operatorId); plan.setUpdatedByName(operatorName); plan.setVersion(0);
|
||||
repository.insert(plan);
|
||||
OceanBookingContainerAllocationEntity allocation = new OceanBookingContainerAllocationEntity();
|
||||
allocation.setId(IdWorker.getId()); allocation.setOceanBookingId(booking.getId());
|
||||
allocation.setRequirementId(requirement.getId()); allocation.setContainerBookingId(container.getId());
|
||||
allocation.setAllocatedBy(operatorId); allocation.setAllocatedByName(operatorName);
|
||||
allocation.setAllocatedAt(LocalDateTime.now()); repository.insert(allocation);
|
||||
if (data.containerRequirements() != null) {
|
||||
for (ContainerRequirementRequest container : data.containerRequirements()) {
|
||||
ContainerSpecificationCatalog.requireMatch(container.containerType(), container.capacityCbm(), container.maxWeightKg());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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("箱型需求缺少容量或最大承重快照,不能确认");
|
||||
}
|
||||
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
|
||||
|| !StringUtils.hasText(booking.getBillOfLadingNo()) || !StringUtils.hasText(booking.getVesselName())
|
||||
|| !StringUtils.hasText(booking.getVoyageNo()) || !StringUtils.hasText(booking.getServiceTerm())
|
||||
|| !StringUtils.hasText(booking.getFreightTerm()) || booking.getEstimatedSailingAt() == null
|
||||
|| booking.getEstimatedArrivalAt() == null || booking.getShippingCompanyId() == null
|
||||
|| booking.getBookingAgentId() == null || parties == null || parties.size() != 3
|
||||
|| requirements == null || requirements.isEmpty()) throw badRequest("订舱资料尚未填写完整,无法发布");
|
||||
validateRange(booking.getEstimatedSailingAt(), booking.getEstimatedArrivalAt());
|
||||
}
|
||||
private ChannelOption requireActiveChannel(Long originWarehouseId, String channelCode) {
|
||||
if (originWarehouseId == null || originWarehouseId <= 0) throw badRequest("请选择有效起运仓");
|
||||
String normalized = normalizeChannel(channelCode);
|
||||
Long versionId = priceBooks.activeVersionId();
|
||||
if (versionId == null) throw conflict("当前没有 ACTIVE 价格版本");
|
||||
ChannelRow row = priceBooks.channels(versionId, originWarehouseId).stream()
|
||||
.filter(value -> normalized.equals(normalizeChannel(value.getChannelCode())))
|
||||
.findFirst().orElseThrow(() -> conflict("所选物流渠道已失效,请重新选择"));
|
||||
if (!StringUtils.hasText(row.getChannelName())) throw conflict("所选物流渠道名称配置无效");
|
||||
return channelOption(row, java.util.Map.of());
|
||||
}
|
||||
|
||||
private static ChannelOption channelOption(ChannelRow row, java.util.Map<String, List<String>> taxModes) {
|
||||
return new ChannelOption(row.getPriceBookVersionId(), row.getChannelCode(), row.getChannelName(),
|
||||
row.getPriceModel(), List.copyOf(taxModes.getOrDefault(row.getChannelCode().trim(), List.of())));
|
||||
}
|
||||
|
||||
private static void requireSameChannel(OceanBookingEntity booking, ContainerBookingEntity container) {
|
||||
if (!StringUtils.hasText(booking.getChannelCode()) || !StringUtils.hasText(container.getChannelCode())) {
|
||||
throw conflict("订舱或柜次缺少渠道快照,请先完成存量数据回填");
|
||||
private static String normalizeChannel(String value) {
|
||||
if (!StringUtils.hasText(value)) throw badRequest("channelCode不能为空");
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private record ResolvedDraft(String warehouseName, String channelName, String bookingAgentName) {}
|
||||
private ResolvedDraft resolveDraft(DraftData d) {
|
||||
String warehouseName = null; String channelName = null; String bookingAgentName = trim(d.bookingAgentName());
|
||||
if (d.warehouseId() != null && StringUtils.hasText(d.channelCode())) {
|
||||
WarehouseMasterDataClient.Warehouse warehouse = requireDomesticTransitWarehouse(d.warehouseId());
|
||||
warehouseName = warehouse.warehouseName();
|
||||
channelName = requireActiveChannel(warehouse.id(), d.channelCode()).channelName();
|
||||
}
|
||||
if (d.bookingAgentId() != null) {
|
||||
var snapshot = suppliers.booking(d.bookingAgentId(), d.bookingAgentName());
|
||||
bookingAgentName = snapshot == null ? null : snapshot.name();
|
||||
} else if (StringUtils.hasText(d.bookingAgentName())) throw badRequest("订舱代理名称不能脱离供应商ID提交");
|
||||
return new ResolvedDraft(warehouseName, channelName, bookingAgentName);
|
||||
}
|
||||
private static void copy(OceanBookingEntity v, DraftData d, ResolvedDraft resolved) {
|
||||
v.setCustomerId(d.customerId()); v.setCustomerName(trim(d.customerName()));
|
||||
v.setChannelCode(trim(d.channelCode())); v.setChannelName(resolved.channelName());
|
||||
v.setWarehouseId(d.warehouseId()); v.setWarehouseName(resolved.warehouseName());
|
||||
v.setBillOfLadingNo(trim(d.billOfLadingNo()));
|
||||
v.setShippingCompanyId(d.shippingCompanyId()); v.setShippingCompanyName(trim(d.shippingCompanyName()));
|
||||
v.setBookingAgentId(d.bookingAgentId()); v.setBookingAgentName(resolved.bookingAgentName());
|
||||
v.setVesselName(trim(d.vesselName())); v.setVoyageNo(trim(d.voyageNo()));
|
||||
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(d.estimatedSailingAt()); v.setEstimatedArrivalAt(d.estimatedArrivalAt());
|
||||
v.setRemark(trim(d.remark()));
|
||||
}
|
||||
|
||||
private static List<OceanBookingPartyEntity> parties(long id, List<PartyRequest> requests) { if (requests == null) return List.of(); 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(); }
|
||||
private static List<OceanBookingContainerRequirementEntity> requirements(long id, List<ContainerRequirementRequest> requests) { if (requests == null) return List.of(); int[] sort = {0}; return requests.stream().map(r -> { var specification = ContainerSpecificationCatalog.requireMatch(r.containerType(), r.capacityCbm(), r.maxWeightKg()); OceanBookingContainerRequirementEntity v = new OceanBookingContainerRequirementEntity(); v.setId(IdWorker.getId()); v.setOceanBookingId(id); v.setContainerType(specification.containerType()); v.setContainerKind(r.containerKind().trim().toUpperCase()); v.setCapacityCbm(specification.capacityCbm()); v.setMaxWeightKg(specification.maxWeightKg()); v.setQuantity(r.quantity()); v.setSortNo(sort[0]++); return v; }).toList(); }
|
||||
private WarehouseMasterDataClient.Warehouse requireDomesticTransitWarehouse(long warehouseId) {
|
||||
try {
|
||||
ApiResponse<WarehouseMasterDataClient.Detail> response = warehouses.active(
|
||||
internalToken, publicWarehouseTenantId, warehouseId);
|
||||
if (response == null || !response.successful() || response.data() == null
|
||||
|| response.data().warehouse() == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "国内中转仓不存在或已停用");
|
||||
}
|
||||
WarehouseMasterDataClient.Warehouse warehouse = response.data().warehouse();
|
||||
if (!Long.valueOf(warehouseId).equals(warehouse.id())
|
||||
|| !Long.valueOf(publicWarehouseTenantId).equals(warehouse.tenantId())
|
||||
|| !"ACTIVE".equals(warehouse.status()) || !"DOMESTIC".equals(warehouse.warehouseType())
|
||||
|| !StringUtils.hasText(warehouse.warehouseName())) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "订舱只能选择启用中的国内中转仓");
|
||||
}
|
||||
return warehouse;
|
||||
} catch (BizException exception) {
|
||||
throw exception;
|
||||
} catch (RuntimeException exception) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "国内中转仓服务暂时不可用");
|
||||
}
|
||||
if (!booking.getChannelCode().equals(container.getChannelCode())) throw conflict("柜次渠道与订舱渠道不一致");
|
||||
}
|
||||
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(); }
|
||||
private static List<OceanBookingContainerRequirementEntity> requirements(long id, List<ContainerRequirementRequest> requests) { int[] sort = {0}; return requests.stream().map(r -> { OceanBookingContainerRequirementEntity v = new OceanBookingContainerRequirementEntity(); v.setId(IdWorker.getId()); v.setOceanBookingId(id); v.setContainerType(r.containerType().trim().toUpperCase()); v.setContainerKind(r.containerKind().trim().toUpperCase()); v.setCapacityCbm(r.capacityCbm()); v.setMaxWeightKg(r.maxWeightKg()); v.setQuantity(r.quantity()); v.setSortNo(sort[0]++); return v; }).toList(); }
|
||||
private String hash(String action, Long id, Object request) { try { return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest((action + '|' + id + '|' + objectMapper.writeValueAsString(request)).getBytes(StandardCharsets.UTF_8))); } catch (Exception ex) { throw new IllegalStateException("无法计算订舱请求摘要", ex); } }
|
||||
private String json(Object value) { if (value == null) return null; try { return objectMapper.writeValueAsString(value); } catch (JsonProcessingException ex) { throw new IllegalStateException("无法保存订舱历史快照", ex); } }
|
||||
private static OceanBookingStatus parseStatus(String status) { try { return OceanBookingStatus.valueOf(status); } catch (RuntimeException ex) { throw badRequest("订舱状态无效"); } }
|
||||
|
||||
+2
-1
@@ -20,7 +20,8 @@ public final class OutboundDispatchDtos {
|
||||
|
||||
public record Aggregate(Long orderId, String orderNo, Long inboundId, String inboundCode, Long tenantId,
|
||||
Long customerId, String customerName, String inboundStatus,
|
||||
Long containerBookingId, String containerNo, Long loadingPlanId, String loadingPlanStatus,
|
||||
Long containerBookingId, String cabinetNo, String containerNo,
|
||||
Long loadingPlanId, String loadingPlanStatus,
|
||||
Long pickTaskId, String pickTaskStatus, Long oceanBookingId, String oceanBookingStatus,
|
||||
Long truckAppointmentId, String truckAppointmentStatus,
|
||||
String dispatchStatus, Integer version, GateResult gate) {}
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ public interface OutboundDispatchReadMapper {
|
||||
SELECT DISTINCT inbound.order_id orderId,inbound.order_no orderNo,inbound.id inboundId,
|
||||
inbound.inbound_code inboundCode,inbound.tenant_id tenantId,inbound.customer_id customerId,
|
||||
inbound.customer_name customerName,inbound.process_status inboundStatus,
|
||||
booking.id containerBookingId,booking.container_no containerNo,
|
||||
booking.id containerBookingId,booking.cabinet_no cabinetNo,booking.container_no containerNo,
|
||||
plan.id loadingPlanId,plan.plan_status loadingPlanStatus
|
||||
FROM fl_loading_plan_item item
|
||||
JOIN fl_loading_plan plan ON plan.id=item.loading_plan_id
|
||||
@@ -100,7 +100,7 @@ public interface OutboundDispatchReadMapper {
|
||||
|
||||
record Binding(Long orderId, String orderNo, Long inboundId, String inboundCode, Long tenantId,
|
||||
Long customerId, String customerName, String inboundStatus, Long containerBookingId,
|
||||
String containerNo, Long loadingPlanId, String loadingPlanStatus) {}
|
||||
String cabinetNo, String containerNo, Long loadingPlanId, String loadingPlanStatus) {}
|
||||
record IdStatus(Long id, String status) {}
|
||||
record Evidence(Integer evidenceCount, Integer validSeal) {}
|
||||
}
|
||||
|
||||
+2
-1
@@ -109,7 +109,8 @@ public class OutboundDispatchService {
|
||||
GateResult gate = gate(binding, pick, ocean, truck, evidence);
|
||||
return new Aggregate(binding.orderId(), binding.orderNo(), binding.inboundId(), binding.inboundCode(),
|
||||
binding.tenantId(), binding.customerId(), binding.customerName(), binding.inboundStatus(),
|
||||
binding.containerBookingId(), binding.containerNo(), binding.loadingPlanId(), binding.loadingPlanStatus(),
|
||||
binding.containerBookingId(), binding.cabinetNo(), binding.containerNo(),
|
||||
binding.loadingPlanId(), binding.loadingPlanStatus(),
|
||||
id(pick), status(pick), id(ocean), status(ocean), id(truck), status(truck),
|
||||
dispatch == null ? OutboundDispatchStatus.READY_FOR_DISPATCH.name() : dispatch.getStatus(),
|
||||
dispatch == null ? 0 : dispatch.getVersion(), gate);
|
||||
|
||||
@@ -73,8 +73,9 @@ public class ActivePriceBookEstimator {
|
||||
}
|
||||
|
||||
private Estimate estimateRegion(long versionId, String channelCode, EstimateRequest request) {
|
||||
if (request.destinationType() != PricingDestinationType.FBA) {
|
||||
throw validation("海派渠道仅支持FBA目的仓参考估价");
|
||||
if (request.destinationType() != PricingDestinationType.FBA
|
||||
&& request.destinationType() != PricingDestinationType.OVERSEAS) {
|
||||
throw validation("海派渠道仅支持平台FBA仓或海外中转仓参考估价");
|
||||
}
|
||||
if (request.pricingTaxMode() == null) {
|
||||
throw validation("请选择包税或不包税报价");
|
||||
@@ -95,7 +96,7 @@ public class ActivePriceBookEstimator {
|
||||
|
||||
private PriceRegion resolveWarehouseRegion(Long warehouseId) {
|
||||
if (warehouseId == null || warehouseId <= 0) {
|
||||
throw validation("请提供目的仓区域或平台FBA仓,以确定海派区域");
|
||||
throw validation("请提供目的仓区域、平台FBA仓或海外中转仓,以确定海派区域");
|
||||
}
|
||||
return warehouseRegionResolver.resolve(warehouseId);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user