优化
This commit is contained in:
+2
-2
@@ -129,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));
|
||||
@@ -149,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));
|
||||
|
||||
+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));}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -100,9 +100,12 @@ public final class ContainerDtos {
|
||||
@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 realContainerBookingId,
|
||||
@NotNull @PositiveOrZero Integer expectedVersion) {}
|
||||
@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) {
|
||||
@Deprecated public OceanBookingLinkRequest(Long legacyRealContainerBookingId, Integer expectedVersion) { this(null, null, null, expectedVersion); }
|
||||
}
|
||||
|
||||
@Schema(name = "ContainerOceanBookingUnlinkRequest", description = "解除空模拟柜的真实订舱关联")
|
||||
public record OceanBookingUnlinkRequest(@NotNull @PositiveOrZero Integer expectedVersion,
|
||||
|
||||
@@ -44,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,
|
||||
@@ -72,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
|
||||
|
||||
@@ -59,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,
|
||||
|
||||
@@ -35,7 +35,6 @@ public record MeasurementBatchCommand(
|
||||
BigDecimal lengthCm,
|
||||
BigDecimal widthCm,
|
||||
BigDecimal heightCm,
|
||||
BigDecimal unitWeightKg,
|
||||
Long intelligentImageFileId,
|
||||
String overLimitWarning,
|
||||
BigDecimal outerLengthCm,
|
||||
|
||||
@@ -31,7 +31,6 @@ 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) {
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -2,7 +2,7 @@ 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 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=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 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){ }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -23,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;
|
||||
|
||||
@@ -11,6 +11,8 @@ 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, p.plan_no AS plan_no,
|
||||
|
||||
+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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,7 +4,8 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.myba
|
||||
@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 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},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);
|
||||
@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);
|
||||
}
|
||||
|
||||
+7
-10
@@ -36,19 +36,16 @@ public class PlatformOceanBookingController {
|
||||
public ApiResponse<Detail> detail(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long oceanBookingId) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.detail(oceanBookingId)); }
|
||||
|
||||
@PostMapping @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:create')")
|
||||
@BusinessAudit(event="OCEAN_BOOKING_CREATE", resourceType="OCEAN_BOOKING", description="登记已确认海运订舱", captureBefore=false)
|
||||
@BusinessAudit(event="OCEAN_BOOKING_CREATE", resourceType="OCEAN_BOOKING", description="创建海运订舱草稿", captureBefore=false)
|
||||
public ApiResponse<Detail> create(@AuthenticationPrincipal AuthenticatedUser user, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody CreateRequest request) { validateOptionalCustomer(user, request.data().customerId()); return ApiResponse.success(service.create(user.userId(), user.username(), key, request)); }
|
||||
@PutMapping("/{oceanBookingId}") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:edit')")
|
||||
@BusinessAudit(event="OCEAN_BOOKING_UPDATE_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}/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)); }
|
||||
private static void validateOptionalCustomer(AuthenticatedUser user, Long customerId) { FirstlegScopeGuard.platform(user); if (customerId != null) FirstlegScopeGuard.platform(user, customerId); }
|
||||
}
|
||||
|
||||
+1
-4
@@ -2,8 +2,5 @@ package com.baiyue.firstleg.oceanbooking.domain;
|
||||
|
||||
public enum OceanBookingStatus {
|
||||
DRAFT,
|
||||
CONFIRMED,
|
||||
RELEASED,
|
||||
CHANGE_PENDING,
|
||||
CANCELLED
|
||||
RELEASED
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.baiyue.firstleg.oceanbooking.dto;
|
||||
|
||||
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;
|
||||
@@ -31,53 +32,67 @@ public final class OceanBookingDtos {
|
||||
@Size(max = 128) String contactName,
|
||||
@Size(max = 64) String contactPhone) {}
|
||||
|
||||
public record ContainerRequest(@NotBlank @Size(max = 64) String containerNo,
|
||||
@NotBlank @Size(max = 32) String containerType,
|
||||
public record ContainerRequirementRequest(@NotBlank @Size(max = 32) String containerType,
|
||||
@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) {}
|
||||
@Deprecated
|
||||
public record ContainerRequest(String containerNo, String containerType, String containerKind,
|
||||
BigDecimal capacityCbm, BigDecimal maxWeightKg) {}
|
||||
|
||||
public record DraftData(@Positive Long customerId,
|
||||
@Size(max = 128) String customerName,
|
||||
@NotBlank @Size(max = 64) String channelCode,
|
||||
@NotNull @Positive Long warehouseId,
|
||||
@NotBlank @Size(max = 128) String billOfLadingNo,
|
||||
@Size(max = 64) String channelCode,
|
||||
@Positive Long warehouseId,
|
||||
@Size(max = 128) String billOfLadingNo,
|
||||
@Positive Long shippingCompanyId,
|
||||
@Size(max = 128) String shippingCompanyName,
|
||||
@Positive Long bookingAgentId,
|
||||
@Size(max = 128) String bookingAgentName,
|
||||
@NotBlank @Size(max = 128) String vesselName,
|
||||
@NotBlank @Size(max = 64) String voyageNo,
|
||||
@Size(max = 128) String vesselName,
|
||||
@Size(max = 64) String voyageNo,
|
||||
@Size(max = 32) String departurePortCode,
|
||||
@Size(max = 128) String departurePortName,
|
||||
@Size(max = 32) String destinationPortCode,
|
||||
@Size(max = 128) String destinationPortName,
|
||||
@Size(max = 128) String placeOfDelivery,
|
||||
@NotBlank @Size(max = 32) String serviceTerm,
|
||||
@NotBlank @Size(max = 32) String freightTerm,
|
||||
@NotNull LocalDateTime estimatedSailingAt,
|
||||
@NotNull LocalDateTime estimatedArrivalAt,
|
||||
@Size(max = 32) String serviceTerm,
|
||||
@Size(max = 32) String freightTerm,
|
||||
LocalDateTime estimatedSailingAt,
|
||||
LocalDateTime estimatedArrivalAt,
|
||||
@Size(max = 1000) String remark,
|
||||
@Valid @NotEmpty @Size(max = 3) List<PartyRequest> parties,
|
||||
@Valid @NotEmpty @Size(max = 20) List<ContainerRequest> containers) {}
|
||||
@Valid @Size(max = 3) List<PartyRequest> parties,
|
||||
@Valid @Size(max = 20) List<ContainerRequirementRequest> containerRequirements) {
|
||||
public DraftData(Long customerId, String customerName, String channelCode, Long warehouseId, String billOfLadingNo,
|
||||
Long shippingCompanyId, String shippingCompanyName, Long bookingAgentId, String bookingAgentName,
|
||||
String vesselName, String voyageNo, String departurePortCode, String departurePortName,
|
||||
String destinationPortCode, String destinationPortName, String placeOfDelivery, String serviceTerm,
|
||||
String freightTerm, LocalDateTime estimatedSailingAt, LocalDateTime estimatedArrivalAt, String remark,
|
||||
List<PartyRequest> parties, java.util.Collection<?> legacyContainers) {
|
||||
this(customerId, customerName, channelCode, warehouseId, billOfLadingNo, shippingCompanyId, shippingCompanyName,
|
||||
bookingAgentId, bookingAgentName, vesselName, voyageNo, departurePortCode, departurePortName, destinationPortCode,
|
||||
destinationPortName, placeOfDelivery, serviceTerm, freightTerm, estimatedSailingAt, estimatedArrivalAt, remark,
|
||||
parties, legacyContainers == null ? List.of() : legacyContainers.stream().map(v -> v instanceof ContainerRequirementRequest x ? x : v instanceof ContainerRequest x ? new ContainerRequirementRequest(x.containerType(), x.containerKind(), 1, x.capacityCbm(), x.maxWeightKg()) : null).filter(java.util.Objects::nonNull).toList());
|
||||
}
|
||||
public List<ContainerRequest> containers() { return containerRequirements.stream().map(v -> new ContainerRequest(null, v.containerType(), v.containerKind(), v.capacityCbm(), v.maxWeightKg())).toList(); }
|
||||
}
|
||||
|
||||
public record CreateRequest(@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) {}
|
||||
@Size(max = 1000) String reason) {
|
||||
public ReleaseRequest(int expectedVersion, String bill, String vessel, String voyage, LocalDateTime etd, LocalDateTime eta, String reason) {
|
||||
this(expectedVersion, reason);
|
||||
}
|
||||
}
|
||||
@Deprecated
|
||||
public record ChangeRequest(Integer expectedVersion, String vesselName, String voyageNo,
|
||||
LocalDateTime estimatedSailingAt, LocalDateTime estimatedArrivalAt, String reason) {}
|
||||
public record RealContainer(Long containerBookingId, String cabinetNo, String containerNo,
|
||||
Long requirementId, Long allocationId, String containerType,
|
||||
String containerKind, BigDecimal capacityCbm, BigDecimal maxWeightKg,
|
||||
@@ -86,6 +101,7 @@ public final class OceanBookingDtos {
|
||||
|
||||
public record Detail(OceanBookingEntity oceanBooking,
|
||||
List<OceanBookingPartyEntity> parties,
|
||||
List<OceanBookingContainerRequirementEntity> containerRequirements,
|
||||
List<RealContainer> containers,
|
||||
List<OceanBookingStatusHistoryEntity> histories) {}
|
||||
|
||||
|
||||
+6
-1
@@ -5,6 +5,7 @@ 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;
|
||||
@@ -13,6 +14,10 @@ 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 booking.*,
|
||||
@@ -25,7 +30,7 @@ public interface OceanBookingMapper extends BaseMapper<OceanBookingEntity> {
|
||||
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 ('CONFIRMED','CHANGE_PENDING','RELEASED','CANCELLED')
|
||||
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},'%')
|
||||
|
||||
+10
-9
@@ -5,7 +5,6 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.Query;
|
||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.RealContainer;
|
||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.ReleaseRequest;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.*;
|
||||
import com.baiyue.firstleg.oceanbooking.mapper.*;
|
||||
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||
@@ -32,15 +31,21 @@ public class OceanBookingRepository {
|
||||
return PageResult.from(bookings.selectPage(new Page<>(query.page(), query.size()), query));
|
||||
}
|
||||
public OceanBookingEntity find(long id) { return bookings.selectById(id); }
|
||||
@Deprecated public int release(long id, Object ignored, long operatorId, String operatorName) { return 0; }
|
||||
public void insert(OceanBookingEntity value) { bookings.insert(value); }
|
||||
public int transition(long id, String from, String to, String cancellationReason,
|
||||
int version, long operatorId, String operatorName) {
|
||||
return bookings.transition(id, from, to, cancellationReason, version, operatorId, operatorName);
|
||||
}
|
||||
public int release(long id, ReleaseRequest request, long operatorId, String operatorName) {
|
||||
return bookings.release(id, request.billOfLadingNo().trim(), request.vesselName().trim(),
|
||||
request.voyageNo().trim(), request.estimatedSailingAt(), request.estimatedArrivalAt(),
|
||||
request.expectedVersion(), operatorId, operatorName);
|
||||
public int updateDraft(OceanBookingEntity value, int version, long operatorId, String operatorName) {
|
||||
return bookings.updateDraft(value.getId(), value, version, operatorId, operatorName);
|
||||
}
|
||||
public int deleteDraft(long id, int version) {
|
||||
allocations.delete(new LambdaQueryWrapper<OceanBookingContainerAllocationEntity>().eq(OceanBookingContainerAllocationEntity::getOceanBookingId, id));
|
||||
requirements.delete(new LambdaQueryWrapper<OceanBookingContainerRequirementEntity>().eq(OceanBookingContainerRequirementEntity::getOceanBookingId, id));
|
||||
parties.delete(new LambdaQueryWrapper<OceanBookingPartyEntity>().eq(OceanBookingPartyEntity::getOceanBookingId, id));
|
||||
histories.delete(new LambdaQueryWrapper<OceanBookingStatusHistoryEntity>().eq(OceanBookingStatusHistoryEntity::getOceanBookingId, id));
|
||||
return bookings.deleteDraft(id, version);
|
||||
}
|
||||
public int requestChange(long id, OceanBookingEntity pending, String reason, int version,
|
||||
long operatorId, String operatorName) {
|
||||
@@ -78,10 +83,6 @@ public class OceanBookingRepository {
|
||||
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 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);
|
||||
}
|
||||
|
||||
+89
-127
@@ -6,7 +6,6 @@ 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.client.WarehouseMasterDataClient;
|
||||
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||
import com.baiyue.firstleg.oceanbooking.domain.OceanBookingStatus;
|
||||
import com.baiyue.firstleg.domain.ContainerSpecificationCatalog;
|
||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.*;
|
||||
@@ -15,14 +14,12 @@ 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.LoadingPickService;
|
||||
import com.baiyue.firstleg.service.WarehouseSupplierSnapshotService;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -39,12 +36,10 @@ import java.util.Set;
|
||||
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.CONFIRMED, OceanBookingStatus.CHANGE_PENDING,
|
||||
OceanBookingStatus.RELEASED, OceanBookingStatus.CANCELLED);
|
||||
OceanBookingStatus.DRAFT, OceanBookingStatus.RELEASED);
|
||||
@Resource private OceanBookingRepository repository;
|
||||
@Resource private PriceBookQueryRepository priceBooks;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
@Resource private LoadingPickService loadingPickService;
|
||||
@Resource private WarehouseSupplierSnapshotService suppliers;
|
||||
@Resource private WarehouseMasterDataClient warehouses;
|
||||
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
|
||||
@@ -53,7 +48,7 @@ public class OceanBookingService {
|
||||
public PageResult<OceanBookingEntity> page(Query query) {
|
||||
String status = trim(query.status());
|
||||
if (status != null && !OPERATIONAL_LIST_STATUSES.contains(parseStatus(status))) {
|
||||
throw badRequest("订舱列表仅支持已确认、变更中、已放舱或已取消状态");
|
||||
throw badRequest("订舱列表仅支持草稿或已放舱状态");
|
||||
}
|
||||
return repository.page(new Query(query.customerId(), status, trim(query.keyword()), query.page(), query.size()));
|
||||
}
|
||||
@@ -78,112 +73,74 @@ public class OceanBookingService {
|
||||
public Detail create(long operatorId, String operatorName, String key, CreateRequest request) {
|
||||
if (request.expectedVersion() != 0) throw concurrent();
|
||||
validateDraftStructure(request.data());
|
||||
validateRange(request.data().estimatedSailingAt(), request.data().estimatedArrivalAt());
|
||||
String action = "CREATE";
|
||||
String hash = hash(action, null, request);
|
||||
Detail replay = claimOrReplay(null, operatorId, action, key, hash);
|
||||
if (replay != null) return replay;
|
||||
WarehouseMasterDataClient.Warehouse warehouse = requireDomesticTransitWarehouse(request.data().warehouseId());
|
||||
ChannelOption channel = requireActiveChannel(warehouse.id(), request.data().channelCode());
|
||||
var bookingAgent = suppliers.booking(request.data().bookingAgentId(), request.data().bookingAgentName());
|
||||
request.data().containers().forEach(container -> {
|
||||
if (repository.containerNoExists(container.containerNo().trim())) {
|
||||
throw conflict("集装箱号已存在:" + container.containerNo().trim());
|
||||
}
|
||||
});
|
||||
ResolvedDraft resolved = resolveDraft(request.data());
|
||||
OceanBookingEntity value = new OceanBookingEntity();
|
||||
value.setId(IdWorker.getId());
|
||||
value.setBookingNo("OB" + value.getId());
|
||||
// Child-row triggers still require DRAFT while the aggregate is assembled.
|
||||
// The status transition is in the same transaction, so callers only observe CONFIRMED.
|
||||
value.setStatus(OceanBookingStatus.DRAFT.name());
|
||||
copy(value, request.data(), warehouse.warehouseName(), channel.channelName(),
|
||||
bookingAgent == null ? null : bookingAgent.name());
|
||||
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()));
|
||||
List<OceanBookingContainerRequirementEntity> requirements = requirements(
|
||||
value.getId(), request.data().containers());
|
||||
value.getId(), request.data().containerRequirements());
|
||||
repository.replaceRequirements(value.getId(), requirements);
|
||||
if (repository.transition(value.getId(), OceanBookingStatus.DRAFT.name(),
|
||||
OceanBookingStatus.CONFIRMED.name(), null, 0, operatorId, operatorName) != 1) {
|
||||
throw concurrent();
|
||||
}
|
||||
createRealContainers(value, requirements, request.data().containers(), operatorId, operatorName);
|
||||
OceanBookingEntity confirmed = required(value.getId());
|
||||
addHistory(value.getId(), OceanBookingStatus.DRAFT.name(), OceanBookingStatus.CONFIRMED.name(), action,
|
||||
null, value, confirmed, operatorId, operatorName);
|
||||
return complete(value.getId(), operatorId, action, key, detail(confirmed));
|
||||
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 id, long operatorId, String operatorName, String key, UpdateRequest request) {
|
||||
String action = "UPDATE_DRAFT";
|
||||
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.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);
|
||||
addHistory(id, OceanBookingStatus.DRAFT.name(), OceanBookingStatus.DRAFT.name(), action, null, before, after, operatorId, operatorName);
|
||||
return complete(id, operatorId, action, key, detail(after));
|
||||
}
|
||||
@Deprecated public Detail cancel(long id, long operatorId, String name, String key, ActionRequest request) { throw conflict("订舱仅支持DRAFT和RELEASED,不能取消"); }
|
||||
@Deprecated public Detail requestChange(long id, long operatorId, String name, String key, ChangeRequest request) { throw conflict("RELEASED订舱不可变更"); }
|
||||
|
||||
@Transactional
|
||||
public void deleteDraft(long id, long operatorId, String operatorName, String key, ActionRequest request) {
|
||||
String action = "DELETE_DRAFT";
|
||||
Detail replay = claimOrReplay(id, 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(id, 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);
|
||||
validateRegisteredTransport(before, request);
|
||||
if (repository.release(id, request, operatorId, name) != 1) throw concurrent();
|
||||
repository.syncReleasedTransport(id, request, operatorId, name);
|
||||
repository.allocations(id).forEach(allocation ->
|
||||
loadingPickService.beginIfReady(allocation.getContainerBookingId(), operatorId, name));
|
||||
requireStatus(before, OceanBookingStatus.DRAFT);
|
||||
validateRelease(before, repository.parties(id), repository.requirements(id));
|
||||
if (repository.transition(id, OceanBookingStatus.DRAFT.name(), OceanBookingStatus.RELEASED.name(),
|
||||
null, request.expectedVersion(), operatorId, name) != 1) throw concurrent();
|
||||
OceanBookingEntity after = required(id);
|
||||
addHistory(id, before.getStatus(), after.getStatus(), action, request.reason(), before, after,
|
||||
operatorId, name);
|
||||
return complete(id, operatorId, action, key, detail(after));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Detail 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(),
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
private Detail claimOrReplay(Long id, long operatorId, String action, String key, String hash) {
|
||||
@@ -214,7 +171,7 @@ 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.containers(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("订舱状态不允许当前操作"); }
|
||||
@@ -223,37 +180,31 @@ public class OceanBookingService {
|
||||
boolean hasCustomerId = data.customerId() != null;
|
||||
boolean hasCustomerName = StringUtils.hasText(data.customerName());
|
||||
if (hasCustomerId != hasCustomerName) throw badRequest("客户ID和客户名称必须同时填写或同时为空");
|
||||
if (!StringUtils.hasText(data.serviceTerm()) || !StringUtils.hasText(data.freightTerm())) {
|
||||
throw badRequest("Service Term和Freight Term不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(data.billOfLadingNo()) || !StringUtils.hasText(data.vesselName())
|
||||
|| !StringUtils.hasText(data.voyageNo()) || data.estimatedSailingAt() == null
|
||||
|| data.estimatedArrivalAt() == null) {
|
||||
throw badRequest("提单号、船名、航次、ETD和ETA不能为空");
|
||||
}
|
||||
Set<String> types = new HashSet<>();
|
||||
for (PartyRequest party : data.parties()) {
|
||||
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");
|
||||
Set<String> containerNos = new HashSet<>();
|
||||
for (ContainerRequest container : data.containers()) {
|
||||
String containerNo = container.containerNo().trim();
|
||||
if (!containerNos.add(containerNo)) throw badRequest("同一订舱内集装箱号不能重复");
|
||||
ContainerSpecificationCatalog.requireMatch(container.containerType(),
|
||||
container.capacityCbm(), container.maxWeightKg());
|
||||
if (data.containerRequirements() != null) {
|
||||
for (ContainerRequirementRequest container : data.containerRequirements()) {
|
||||
ContainerSpecificationCatalog.requireMatch(container.containerType(), container.capacityCbm(), container.maxWeightKg());
|
||||
}
|
||||
}
|
||||
}
|
||||
private static void validateRange(LocalDateTime from, LocalDateTime to) { if (from != null && to != null && to.isBefore(from)) throw badRequest("预计到港时间不能早于预计开船时间"); }
|
||||
private static void validateRegisteredTransport(OceanBookingEntity booking, ReleaseRequest request) {
|
||||
if (!java.util.Objects.equals(trim(booking.getBillOfLadingNo()), trim(request.billOfLadingNo()))
|
||||
|| !java.util.Objects.equals(trim(booking.getVesselName()), trim(request.vesselName()))
|
||||
|| !java.util.Objects.equals(trim(booking.getVoyageNo()), trim(request.voyageNo()))
|
||||
|| !java.util.Objects.equals(booking.getEstimatedSailingAt(), request.estimatedSailingAt())
|
||||
|| !java.util.Objects.equals(booking.getEstimatedArrivalAt(), request.estimatedArrivalAt())) {
|
||||
throw conflict("放舱运输字段必须与已登记的提单、船名、航次、ETD和ETA一致");
|
||||
}
|
||||
private static void 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("请选择有效起运仓");
|
||||
@@ -277,15 +228,28 @@ public class OceanBookingService {
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private static void copy(OceanBookingEntity v, DraftData d, String resolvedWarehouseName,
|
||||
String resolvedChannelName, String resolvedBookingAgentName) {
|
||||
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(d.channelCode().trim()); v.setChannelName(resolvedChannelName);
|
||||
v.setWarehouseId(d.warehouseId()); v.setWarehouseName(resolvedWarehouseName);
|
||||
v.setBillOfLadingNo(d.billOfLadingNo().trim());
|
||||
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(trim(resolvedBookingAgentName));
|
||||
v.setVesselName(d.vesselName().trim()); v.setVoyageNo(d.voyageNo().trim());
|
||||
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()));
|
||||
@@ -293,15 +257,12 @@ public class OceanBookingService {
|
||||
v.setRemark(trim(d.remark()));
|
||||
}
|
||||
|
||||
private static List<OceanBookingPartyEntity> parties(long id, List<PartyRequest> requests) { return requests.stream().map(r -> { OceanBookingPartyEntity v = new OceanBookingPartyEntity(); v.setId(IdWorker.getId()); v.setOceanBookingId(id); v.setPartyType(r.partyType().trim().toUpperCase()); v.setPartyName(r.partyName().trim()); v.setAddress(trim(r.address())); v.setContactName(trim(r.contactName())); v.setContactPhone(trim(r.contactPhone())); return v; }).toList(); }
|
||||
private static List<OceanBookingContainerRequirementEntity> requirements(long id, List<ContainerRequest> requests) { 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(1); v.setSortNo(sort[0]++); return v; }).toList(); }
|
||||
|
||||
private void createRealContainers(OceanBookingEntity booking,
|
||||
List<OceanBookingContainerRequirementEntity> requirements,
|
||||
List<ContainerRequest> requests, long operatorId,
|
||||
String operatorName) {
|
||||
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(); }
|
||||
/* real container rows are created only by direct container linking after release. */
|
||||
/*
|
||||
for (int index = 0; index < requests.size(); index++) {
|
||||
ContainerRequest request = requests.get(index);
|
||||
ContainerRequirementRequest request = requests.get(index);
|
||||
OceanBookingContainerRequirementEntity requirement = requirements.get(index);
|
||||
ContainerBookingEntity container = new ContainerBookingEntity();
|
||||
container.setId(IdWorker.getId()); container.setBookingNo("CB" + container.getId());
|
||||
@@ -340,6 +301,7 @@ public class OceanBookingService {
|
||||
repository.insert(allocation);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
private WarehouseMasterDataClient.Warehouse requireDomesticTransitWarehouse(long warehouseId) {
|
||||
try {
|
||||
|
||||
@@ -47,6 +47,11 @@ public class ContainerRepository {
|
||||
long operatorId, String operatorName) {
|
||||
return mapper.restoreMergedSource(sourceId, targetId, containerNo, operatorId, operatorName);
|
||||
}
|
||||
public int linkReleasedBooking(long id, long oceanBookingId, long requirementId, String containerNo,
|
||||
com.baiyue.firstleg.oceanbooking.entity.OceanBookingEntity booking,
|
||||
int version, long operatorId, String operatorName) {
|
||||
return mapper.linkReleasedBooking(id, oceanBookingId, requirementId, containerNo, booking.getBillOfLadingNo(), booking.getVesselName(), booking.getVoyageNo(), booking.getShippingCompanyId(), booking.getShippingCompanyName(), booking.getDeparturePortCode(), booking.getDeparturePortName(), booking.getDestinationPortCode(), booking.getDestinationPortName(), booking.getChannelCode(), booking.getChannelName(), booking.getCustomerId(), booking.getCustomerName(), booking.getEstimatedSailingAt(), booking.getEstimatedArrivalAt(), version, operatorId, operatorName);
|
||||
}
|
||||
public int updateDraft(ContainerBookingEntity value, int version, long operatorId, String operatorName) {
|
||||
return mapper.updateDraft(value, version, operatorId, operatorName);
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baiyue.common.core.api.PageResult; import com.baiyue.firstleg.dto.TruckAppointmentDtos.Query; import com.baiyue.firstleg.entity.*; import com.baiyue.firstleg.mapper.*; import jakarta.annotation.Resource; import org.springframework.stereotype.Repository;
|
||||
@Repository public class TruckAppointmentRepository { @Resource TruckAppointmentMapper mapper; @Resource TruckAppointmentHistoryMapper history; @Resource TruckAppointmentAttachmentMapper attachments; @Resource TruckAppointmentOperationMapper operations;
|
||||
public PageResult<TruckAppointmentEntity> page(Query q){return PageResult.from(mapper.page(new Page<>(q.page(),q.size()),q));} public TruckAppointmentEntity find(long id){return mapper.selectById(id);} public void insert(TruckAppointmentEntity e){mapper.insert(e);} public int transition(long id,String from,String to,int v){return mapper.transition(id,from,to,v);} public int reschedule(long id,java.time.LocalDateTime at,String reason,int v){return mapper.reschedule(id,at,reason,v);} public void history(TruckAppointmentHistoryEntity e){history.insert(e);} public void operation(TruckAppointmentOperationEntity e){operations.insert(e);} public void attachment(TruckAppointmentAttachmentEntity e){attachments.insert(e);}
|
||||
public PageResult<TruckAppointmentEntity> page(Query q){return PageResult.from(mapper.page(new Page<>(q.page(),q.size()),q));} public TruckAppointmentEntity find(long id){return mapper.selectById(id);} public void insert(TruckAppointmentEntity e){mapper.insert(e);} public int transition(long id,String from,String to,int v){return mapper.transition(id,from,to,v);} public int dispatch(long id,String from,String billOfLadingNo,int v){return mapper.dispatch(id,from,billOfLadingNo,v);} public int reschedule(long id,java.time.LocalDateTime at,String reason,int v){return mapper.reschedule(id,at,reason,v);} public void history(TruckAppointmentHistoryEntity e){history.insert(e);} public void operation(TruckAppointmentOperationEntity e){operations.insert(e);} public void attachment(TruckAppointmentAttachmentEntity e){attachments.insert(e);}
|
||||
public boolean claimOperation(long operationId,long appointmentId,String action,String key,String hash){return operations.claim(operationId,appointmentId,action,key,hash)==1;} public TruckAppointmentOperationEntity operation(String key){return operations.find(key);} public int completeOperation(long appointmentId,String action,String key,String response){return operations.complete(appointmentId,action,key,response);}
|
||||
public int updateEdit(TruckAppointmentEntity e,int v){return mapper.updateEdit(e,v);} public java.util.List<TruckAppointmentHistoryEntity> histories(long id){return history.selectList(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TruckAppointmentHistoryEntity>().eq(TruckAppointmentHistoryEntity::getAppointmentId,id).orderByAsc(TruckAppointmentHistoryEntity::getCreatedAt));} public java.util.List<TruckAppointmentAttachmentEntity> attachments(long id){return attachments.selectList(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TruckAppointmentAttachmentEntity>().eq(TruckAppointmentAttachmentEntity::getAppointmentId,id).orderByAsc(TruckAppointmentAttachmentEntity::getUploadedAt));} public int deleteAttachment(long appointmentId,long attachmentId){return attachments.delete(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TruckAppointmentAttachmentEntity>().eq(TruckAppointmentAttachmentEntity::getAppointmentId,appointmentId).eq(TruckAppointmentAttachmentEntity::getId,attachmentId));}
|
||||
public java.util.List<TruckAppointmentEntity> activeForUpdate(long containerBookingId){return mapper.activeForUpdate(containerBookingId);} public int completeLoadingFromPda(long id,String fromStatus,int version){return mapper.completeLoadingFromPda(id,fromStatus,version);}
|
||||
|
||||
@@ -224,7 +224,6 @@ public class CartonMeasurementService {
|
||||
item.setLengthCm(command.lengthCm());
|
||||
item.setWidthCm(command.widthCm());
|
||||
item.setHeightCm(command.heightCm());
|
||||
item.setUnitWeightKg(command.unitWeightKg());
|
||||
item.setSourceImageFileId(imageFileId);
|
||||
item.setMachineNo(command.machineNo().trim());
|
||||
item.setDeviceMeasuredAt(command.measuredAt());
|
||||
@@ -277,13 +276,9 @@ public class CartonMeasurementService {
|
||||
BigDecimal totalVolume = unitVolume.multiply(count).setScale(4, RoundingMode.UNNECESSARY);
|
||||
item.setGirthCm(item.getLengthCm().add(item.getWidthCm().add(item.getHeightCm())
|
||||
.multiply(BigDecimal.valueOf(2))).setScale(3, RoundingMode.CEILING));
|
||||
item.setUnitWeightKg(item.getUnitWeightKg() == null ? null
|
||||
: item.getUnitWeightKg().setScale(3, RoundingMode.CEILING));
|
||||
item.setUnitVolumeCbm(unitVolume);
|
||||
item.setVolumeRawCbm(totalRaw);
|
||||
item.setVolumeCbm(totalVolume);
|
||||
item.setTotalWeightKg(item.getUnitWeightKg() == null ? null
|
||||
: item.getUnitWeightKg().multiply(count).setScale(3, RoundingMode.CEILING));
|
||||
item.setTotalVolumeCbm(totalVolume);
|
||||
item.setVolumeWeightKg(totalVolume.multiply(VOLUME_WEIGHT_FACTOR).setScale(3, RoundingMode.CEILING));
|
||||
item.setCalculationRuleVersion(weighted.calculationRuleVersion());
|
||||
@@ -307,8 +302,6 @@ public class CartonMeasurementService {
|
||||
.setScale(4, RoundingMode.UNNECESSARY);
|
||||
item.setVolumeRawCbm(totalRaw);
|
||||
item.setVolumeCbm(totalVolume);
|
||||
item.setTotalWeightKg(item.getUnitWeightKg() == null ? null
|
||||
: item.getUnitWeightKg().multiply(count).setScale(3, RoundingMode.CEILING));
|
||||
item.setTotalVolumeCbm(totalVolume);
|
||||
item.setVolumeWeightKg(totalVolume.multiply(VOLUME_WEIGHT_FACTOR).setScale(3, RoundingMode.CEILING));
|
||||
}
|
||||
@@ -416,9 +409,6 @@ public class CartonMeasurementService {
|
||||
if (!positive(command.lengthCm()) || !positive(command.widthCm()) || !positive(command.heightCm())) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "长、宽、高必须大于0");
|
||||
}
|
||||
if (command.unitWeightKg() != null && !positive(command.unitWeightKg())) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "单箱重量必须大于0");
|
||||
}
|
||||
if (!StringUtils.hasText(command.machineNo()) || command.machineNo().trim().length() > 64) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "设备编号不能为空且不能超过64个字符");
|
||||
}
|
||||
@@ -452,7 +442,7 @@ public class CartonMeasurementService {
|
||||
return new CartonMeasurementResult(measurement.getId(), measurement.getVersion(), item.getId(),
|
||||
item.getBoxCode(), item.getScannedShipmentId(), item.getPackageCount(),
|
||||
item.getLengthCm(), item.getWidthCm(),
|
||||
item.getHeightCm(), item.getUnitWeightKg(), item.getSourceImageFileId(), item.getMachineNo(),
|
||||
item.getHeightCm(), item.getSourceImageFileId(), item.getMachineNo(),
|
||||
item.getDeviceMeasuredAt(), created);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.baiyue.firstleg.repository.PriceBookQueryRows.ChannelRow;
|
||||
import com.baiyue.firstleg.oceanbooking.repository.OceanBookingRepository;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingEntity;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingContainerRequirementEntity;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingContainerAllocationEntity;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -79,27 +80,13 @@ public class ContainerService {
|
||||
public ContainerBookingEntity linkOceanBooking(long id, long operatorId, String operatorName, OceanBookingLinkRequest request) {
|
||||
ContainerBookingEntity container = required(id); requireVersion(container, request.expectedVersion()); requireFirstLinkMutable(container);
|
||||
if (container.getOceanBookingId() != null) throw conflict("模拟柜已关联真实订舱柜");
|
||||
ContainerBookingEntity source = repository.findForUpdate(request.realContainerBookingId());
|
||||
if (source == null || !"OCEAN_BOOKING".equals(source.getSourceType())
|
||||
|| !"ACTIVE".equals(source.getRecordStatus()) || source.getMergedIntoContainerBookingId() != null
|
||||
|| source.getContainerNo() == null || source.getOceanBookingId() == null
|
||||
|| source.getOceanBookingRequirementId() == null) {
|
||||
throw conflict("真实订舱柜不存在、已被消费或不可关联");
|
||||
}
|
||||
if (repository.hasLoadingPlan(source.getId()) || repository.hasPickTask(source.getId())
|
||||
|| repository.hasEvidence(source.getId()) || repository.hasDocuments(source.getId())) {
|
||||
throw conflict("真实订舱柜已有装柜计划、PDA、证据或单证事实,不能被模拟柜消费");
|
||||
}
|
||||
OceanBookingEntity booking = oceanBookings.find(source.getOceanBookingId());
|
||||
if (booking == null || !("CONFIRMED".equals(booking.getStatus()) || "RELEASED".equals(booking.getStatus()))) throw conflict("只能关联已确认的真实订舱柜");
|
||||
OceanBookingContainerRequirementEntity requirement = oceanBookings.requirement(
|
||||
booking.getId(), source.getOceanBookingRequirementId());
|
||||
if (requirement == null) throw new BizException(ResultCode.NOT_FOUND, "真实订舱柜箱型分配不存在");
|
||||
var allocation = oceanBookings.allocationByContainer(source.getId());
|
||||
if (allocation == null || !java.util.Objects.equals(allocation.getOceanBookingId(), booking.getId())
|
||||
|| !java.util.Objects.equals(allocation.getRequirementId(), requirement.getId())) {
|
||||
throw conflict("真实订舱柜分配关系不完整");
|
||||
}
|
||||
OceanBookingEntity booking = oceanBookings.find(request.oceanBookingId());
|
||||
if (booking == null || !"RELEASED".equals(booking.getStatus())) throw conflict("只能关联已发布订舱");
|
||||
OceanBookingContainerRequirementEntity requirement = oceanBookings.requirement(request.oceanBookingId(), request.requirementId());
|
||||
if (requirement == null) throw new BizException(ResultCode.NOT_FOUND, "订舱箱型需求不存在");
|
||||
long allocated = oceanBookings.allocations(request.oceanBookingId()).stream().filter(a -> request.requirementId().equals(a.getRequirementId())).count();
|
||||
if (allocated >= requirement.getQuantity()) throw conflict("该订舱箱型需求已达到数量上限");
|
||||
if (repository.containerNoExists(request.containerNo().trim(), id)) throw conflict("集装箱号已存在");
|
||||
if (!java.util.Objects.equals(container.getWarehouseId(), booking.getWarehouseId())
|
||||
|| !java.util.Objects.equals(container.getChannelCode(), booking.getChannelCode())
|
||||
|| !java.util.Objects.equals(container.getContainerType(), requirement.getContainerType())
|
||||
@@ -107,21 +94,12 @@ public class ContainerService {
|
||||
|| container.getMaxWeightKg() == null || requirement.getMaxWeightKg() == null
|
||||
|| container.getCapacityCbm().compareTo(requirement.getCapacityCbm()) != 0
|
||||
|| container.getMaxWeightKg().compareTo(requirement.getMaxWeightKg()) != 0) throw conflict("模拟柜与真实订舱柜仓库、渠道、箱型或容量不一致");
|
||||
if (!java.util.Objects.equals(source.getWarehouseId(), booking.getWarehouseId())
|
||||
|| !java.util.Objects.equals(source.getChannelCode(), booking.getChannelCode())
|
||||
|| !java.util.Objects.equals(source.getContainerType(), requirement.getContainerType())
|
||||
|| source.getCapacityCbm() == null || source.getMaxWeightKg() == null
|
||||
|| source.getCapacityCbm().compareTo(requirement.getCapacityCbm()) != 0
|
||||
|| source.getMaxWeightKg().compareTo(requirement.getMaxWeightKg()) != 0) {
|
||||
throw conflict("真实订舱柜仓库、渠道或规格快照不一致");
|
||||
}
|
||||
if (booking.getCustomerId() != null && repository.hasMismatchedCustomerItems(id, booking.getCustomerId())) throw conflict("模拟柜已排货客户与真实订舱客户范围不一致");
|
||||
if (repository.consumeRealSource(source.getId(), id, operatorId, operatorName) != 1
|
||||
|| repository.mergeRealSource(id, source.getId(), source.getContainerNo(),
|
||||
request.expectedVersion(), operatorId, operatorName) != 1
|
||||
|| oceanBookings.reassignAllocation(source.getId(), id) != 1) {
|
||||
if (repository.linkReleasedBooking(id, request.oceanBookingId(), request.requirementId(), request.containerNo().trim(), booking,
|
||||
request.expectedVersion(), operatorId, operatorName) != 1) {
|
||||
throw conflict("真实订舱柜已被消费或货柜版本发生变化,请刷新后重试");
|
||||
}
|
||||
OceanBookingContainerAllocationEntity allocation = new OceanBookingContainerAllocationEntity(); allocation.setId(IdWorker.getId()); allocation.setOceanBookingId(request.oceanBookingId()); allocation.setRequirementId(request.requirementId()); allocation.setContainerBookingId(id); allocation.setAllocatedBy(operatorId); allocation.setAllocatedByName(operatorName); allocation.setAllocatedAt(LocalDateTime.now()); oceanBookings.insert(allocation);
|
||||
return required(id);
|
||||
}
|
||||
|
||||
|
||||
-4
@@ -259,13 +259,9 @@ public class InboundMeasurementOpenApiService {
|
||||
item.setHeightCm(source.heightCm());
|
||||
item.setGirthCm(source.lengthCm().add(source.widthCm().add(source.heightCm())
|
||||
.multiply(BigDecimal.valueOf(2))).setScale(3, RoundingMode.CEILING));
|
||||
item.setUnitWeightKg(source.unitWeightKg() == null ? null
|
||||
: source.unitWeightKg().setScale(3, RoundingMode.CEILING));
|
||||
item.setUnitVolumeCbm(unitVolume);
|
||||
item.setVolumeRawCbm(totalRaw);
|
||||
item.setVolumeCbm(totalVolume);
|
||||
item.setTotalWeightKg(source.unitWeightKg() == null ? null
|
||||
: source.unitWeightKg().multiply(count).setScale(3, RoundingMode.CEILING));
|
||||
item.setTotalVolumeCbm(totalVolume);
|
||||
item.setVolumeWeightKg(totalVolume.multiply(VOLUME_WEIGHT_FACTOR).setScale(3, RoundingMode.CEILING));
|
||||
item.setCalculationRuleVersion(CALCULATION_RULE_VERSION);
|
||||
|
||||
@@ -108,7 +108,7 @@ public class InboundMeasurementService {
|
||||
actualGoodsName,
|
||||
operatorId, request.items().stream().map(item -> new MeasurementBatchCommand.Item(
|
||||
item.inboundDetailId(), item.itemNo(), item.boxSpecification(), item.packageCount(), item.lengthCm(),
|
||||
item.widthCm(), item.heightCm(), item.unitWeightKg(), item.intelligentImageFileId(),
|
||||
item.widthCm(), item.heightCm(), item.intelligentImageFileId(),
|
||||
item.overLimitWarning(), item.outerLengthCm(), item.outerWidthCm(), item.outerHeightCm(),
|
||||
item.remark())).toList());
|
||||
InboundMeasurementEntity saved = batchService.saveBatch(command);
|
||||
@@ -488,7 +488,7 @@ public class InboundMeasurementService {
|
||||
return new InboundMeasurementResponses.Item(
|
||||
item.getId(), item.getInboundDetailId(), item.getItemNo(), item.getBoxSpecification(), item.getPackageCount(),
|
||||
item.getLengthCm(), item.getWidthCm(), item.getHeightCm(), item.getGirthCm(),
|
||||
item.getUnitWeightKg(), item.getUnitVolumeCbm(), item.getTotalWeightKg(),
|
||||
item.getUnitVolumeCbm(),
|
||||
item.getTotalVolumeCbm(), item.getVolumeWeightKg(), item.getSourceImageFileId(),
|
||||
item.getOverLimitWarning(), item.getOuterLengthCm(), item.getOuterWidthCm(),
|
||||
item.getOuterHeightCm(), item.getRemark(), item.getBoxCode(), item.getScannedShipmentId(),
|
||||
|
||||
@@ -152,13 +152,9 @@ public class MeasurementBatchService {
|
||||
item.setHeightCm(source.heightCm());
|
||||
item.setGirthCm(source.lengthCm().add(source.widthCm().add(source.heightCm())
|
||||
.multiply(BigDecimal.valueOf(2))).setScale(3, RoundingMode.CEILING));
|
||||
item.setUnitWeightKg(source.unitWeightKg() == null ? null
|
||||
: source.unitWeightKg().setScale(3, RoundingMode.CEILING));
|
||||
item.setUnitVolumeCbm(unitVolume);
|
||||
item.setVolumeRawCbm(totalVolumeRaw);
|
||||
item.setVolumeCbm(totalVolume);
|
||||
item.setTotalWeightKg(source.unitWeightKg() == null ? null
|
||||
: source.unitWeightKg().multiply(packageCount).setScale(3, RoundingMode.CEILING));
|
||||
item.setTotalVolumeCbm(totalVolume);
|
||||
item.setVolumeWeightKg(totalVolume.multiply(VOLUME_WEIGHT_FACTOR).setScale(3, RoundingMode.CEILING));
|
||||
item.setCalculationRuleVersion(weighted.calculationRuleVersion());
|
||||
|
||||
+2
-2
@@ -69,8 +69,8 @@ public class MerchantInboundBillingService {
|
||||
var measurementItems = measurement.items().stream()
|
||||
.map(item -> new MerchantInboundBillingResponse.MeasurementItem(
|
||||
item.id(), item.itemNo(), item.boxSpecification(), item.packageCount(),
|
||||
item.lengthCm(), item.widthCm(), item.heightCm(), item.unitWeightKg(),
|
||||
item.totalWeightKg(), item.unitVolumeCbm(), item.totalVolumeCbm(), item.volumeWeightKg()))
|
||||
item.lengthCm(), item.widthCm(), item.heightCm(), item.unitVolumeCbm(),
|
||||
item.totalVolumeCbm(), item.volumeWeightKg()))
|
||||
.toList();
|
||||
var weightResults = measurement.weightResults().stream()
|
||||
.map(weight -> new MerchantInboundBillingResponse.WeightResult(
|
||||
|
||||
+3
-4
@@ -77,7 +77,7 @@ public class MojiaMeasurementOpenApiService {
|
||||
try {
|
||||
result = cartonMeasurements.upsert(
|
||||
app.tenantId(), app.subjectId(), new CartonMeasurementCommand(
|
||||
request.boxCode(), request.length(), request.width(), request.height(), request.weight(),
|
||||
request.boxCode(), request.length(), request.width(), request.height(),
|
||||
request.machineNo(), request.measuredAt() == null ? LocalDateTime.now() : request.measuredAt(),
|
||||
image, optionalTrim(request.shipmentId())));
|
||||
} catch (BizException exception) {
|
||||
@@ -87,10 +87,10 @@ public class MojiaMeasurementOpenApiService {
|
||||
throw exception;
|
||||
}
|
||||
log.info("MOJIA_MEASUREMENT accepted requestId={} traceId={} boxCode={} machineNo={} "
|
||||
+ "lengthCm={} widthCm={} heightCm={} weightKg={} imageBytes={} measurementId={} "
|
||||
+ "lengthCm={} widthCm={} heightCm={} imageBytes={} measurementId={} "
|
||||
+ "itemId={} created={}",
|
||||
correlation("requestId"), correlation("traceId"), safe(request.boxCode()),
|
||||
safe(request.machineNo()), request.length(), request.width(), request.height(), request.weight(),
|
||||
safe(request.machineNo()), request.length(), request.width(), request.height(),
|
||||
image.getSize(), result.measurementId(), result.itemId(), result.created());
|
||||
return new UploadResponse(true, true, null, result.measurementId(), result.itemId(),
|
||||
result.boxCode(), result.imageFileId(), result.created());
|
||||
@@ -121,7 +121,6 @@ public class MojiaMeasurementOpenApiService {
|
||||
receipt.setLengthCm(request.length());
|
||||
receipt.setWidthCm(request.width());
|
||||
receipt.setHeightCm(request.height());
|
||||
receipt.setWeightKg(request.weight());
|
||||
receipt.setMachineNo(request.machineNo().trim());
|
||||
receipt.setMeasuredAt(request.measuredAt() == null ? LocalDateTime.now() : request.measuredAt());
|
||||
receipt.setImageFileId(uploaded.fileId());
|
||||
|
||||
@@ -124,7 +124,8 @@ public class SmartLoadingService {
|
||||
try { repository.insert(item); } catch (DuplicateKeyException e) { throw conflict("货物明细已被并发锁定"); }
|
||||
changed = item;
|
||||
} else {
|
||||
existing.setPackageCount(total); existing.setGrossWeightKg(unit(candidate.unitWeightKg()).multiply(BigDecimal.valueOf(total)));
|
||||
existing.setPackageCount(total); existing.setGrossWeightKg(unit(candidate.unitGrossWeightKg()).multiply(BigDecimal.valueOf(total)));
|
||||
existing.setNetWeightKg(unit(candidate.unitNetWeightKg()).multiply(BigDecimal.valueOf(total)));
|
||||
existing.setVolumeCbm(unit(candidate.unitVolumeCbm()).multiply(BigDecimal.valueOf(total))); applyCargo(existing, cargo, total);
|
||||
existing.setUpdatedBy(operatorId); existing.setUpdatedByName(operatorName);
|
||||
if (repository.update(existing) != 1) throw conflict("装柜草稿已被并发修改");
|
||||
@@ -197,7 +198,8 @@ public class SmartLoadingService {
|
||||
checkRouteAndCapacity(booking, others, candidate, request.packageCount());
|
||||
touch(plan, operatorId, operatorName);
|
||||
item.setPackageCount(request.packageCount());
|
||||
item.setGrossWeightKg(unit(candidate.unitWeightKg()).multiply(BigDecimal.valueOf(request.packageCount())));
|
||||
item.setGrossWeightKg(unit(candidate.unitGrossWeightKg()).multiply(BigDecimal.valueOf(request.packageCount())));
|
||||
item.setNetWeightKg(unit(candidate.unitNetWeightKg()).multiply(BigDecimal.valueOf(request.packageCount())));
|
||||
item.setVolumeCbm(unit(candidate.unitVolumeCbm()).multiply(BigDecimal.valueOf(request.packageCount()))); applyCargo(item, cargo, request.packageCount());
|
||||
item.setUpdatedBy(operatorId); item.setUpdatedByName(operatorName);
|
||||
if (repository.update(item) != 1) throw conflict("装柜明细已被并发修改");
|
||||
@@ -292,7 +294,9 @@ public class SmartLoadingService {
|
||||
i.setInboundId(c.inboundId()); i.setInboundDetailId(c.inboundDetailId()); i.setOrderId(c.orderId()); i.setOrderNo(c.orderNo()); i.setInboundCode(c.inboundCode());
|
||||
i.setDestinationWarehouseId(c.destinationWarehouseId()); i.setDestinationWarehouseCode(c.destinationWarehouseCode());
|
||||
i.setDestinationWarehouseName(c.destinationWarehouseName()); i.setProductNameZh(cargo.cargoName()); i.setPackageCount(count);
|
||||
i.setGrossWeightKg(unit(c.unitWeightKg()).multiply(BigDecimal.valueOf(count))); i.setVolumeCbm(unit(c.unitVolumeCbm()).multiply(BigDecimal.valueOf(count)));
|
||||
i.setGrossWeightKg(unit(c.unitGrossWeightKg()).multiply(BigDecimal.valueOf(count)));
|
||||
i.setNetWeightKg(unit(c.unitNetWeightKg()).multiply(BigDecimal.valueOf(count)));
|
||||
i.setVolumeCbm(unit(c.unitVolumeCbm()).multiply(BigDecimal.valueOf(count)));
|
||||
applyCargo(i, cargo, count); i.setLoadSequence(seq); i.setDeleted(false); i.setCreatedBy(op); i.setCreatedByName(name); i.setUpdatedBy(op); i.setUpdatedByName(name); i.setVersion(0); return i;
|
||||
}
|
||||
private void applyCargo(LoadingPlanItemEntity item, OrderCargoClient.CargoBinding cargo, int boxes) {
|
||||
@@ -309,10 +313,6 @@ public class SmartLoadingService {
|
||||
.multiply(BigDecimal.valueOf(boxes))
|
||||
.divide(BigDecimal.valueOf(original), 0, RoundingMode.HALF_UP).intValueExact());
|
||||
}
|
||||
if (cargo.netWeightKg() != null) {
|
||||
item.setNetWeightKg(cargo.netWeightKg().multiply(BigDecimal.valueOf(boxes))
|
||||
.divide(BigDecimal.valueOf(original), 6, RoundingMode.HALF_UP));
|
||||
}
|
||||
}
|
||||
|
||||
private OrderCargoClient.CargoBinding loadCargo(Candidate candidate) {
|
||||
@@ -344,7 +344,7 @@ public class SmartLoadingService {
|
||||
if (!Objects.equals(b.getWarehouseId(), c.warehouseId())) throw validation("装柜仓库不一致");
|
||||
if (StringUtils.hasText(b.getDestinationWarehouseCode()) && !Objects.equals(b.getDestinationWarehouseCode(), c.destinationWarehouseCode())) throw validation("目的仓/港不一致");
|
||||
BigDecimal v=lines.stream().map(x->unit(x.getVolumeCbm())).reduce(BigDecimal.ZERO,BigDecimal::add).add(unit(c.unitVolumeCbm()).multiply(BigDecimal.valueOf(count)));
|
||||
BigDecimal w=lines.stream().map(x->unit(x.getGrossWeightKg())).reduce(BigDecimal.ZERO,BigDecimal::add).add(unit(c.unitWeightKg()).multiply(BigDecimal.valueOf(count)));
|
||||
BigDecimal w=lines.stream().map(x->unit(x.getGrossWeightKg())).reduce(BigDecimal.ZERO,BigDecimal::add).add(unit(c.unitGrossWeightKg()).multiply(BigDecimal.valueOf(count)));
|
||||
if (b.getCapacityCbm()==null || b.getMaxWeightKg()==null) throw validation("货柜容量和最大承重未配置");
|
||||
if (v.compareTo(b.getCapacityCbm().multiply(CAPACITY_LIMIT_RATIO))>0
|
||||
|| w.compareTo(b.getMaxWeightKg().multiply(CAPACITY_LIMIT_RATIO))>0) {
|
||||
@@ -373,7 +373,7 @@ public class SmartLoadingService {
|
||||
BigDecimal leftV=b.getCapacityCbm().multiply(CAPACITY_LIMIT_RATIO).subtract(usedV);
|
||||
BigDecimal leftW=b.getMaxWeightKg().multiply(CAPACITY_LIMIT_RATIO).subtract(usedW);
|
||||
int byV=unit(c.unitVolumeCbm()).signum()==0?c.availablePackageCount():leftV.max(BigDecimal.ZERO).divideToIntegralValue(c.unitVolumeCbm()).intValue();
|
||||
int byW=unit(c.unitWeightKg()).signum()==0?c.availablePackageCount():leftW.max(BigDecimal.ZERO).divideToIntegralValue(c.unitWeightKg()).intValue();
|
||||
int byW=unit(c.unitGrossWeightKg()).signum()==0?c.availablePackageCount():leftW.max(BigDecimal.ZERO).divideToIntegralValue(c.unitGrossWeightKg()).intValue();
|
||||
return Math.max(0,Math.min(c.availablePackageCount(),Math.min(byV,byW)));
|
||||
}
|
||||
private Plan view(LoadingPlanEntity p,ContainerBookingEntity b){
|
||||
|
||||
@@ -27,10 +27,12 @@ import java.security.MessageDigest;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HexFormat;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Service
|
||||
public class TruckAppointmentService {
|
||||
private static final Set<String> ATTACHMENT_TYPES = Set.of("BILL_OF_LADING", "DISPATCH_ORDER", "DRIVER_DOCUMENT", "VEHICLE_DOCUMENT", "OTHER");
|
||||
@Resource TruckAppointmentRepository repo;
|
||||
@Resource OceanBookingRepository oceanBookings;
|
||||
@Resource SmartLoadingRepository loadingPlans;
|
||||
@@ -42,27 +44,31 @@ public class TruckAppointmentService {
|
||||
@Value("${baiyue.security.internal-service-token}") String internalToken;
|
||||
public PageResult<TruckAppointmentEntity> page(Query q){return repo.page(q);} public TruckAppointmentEntity detail(long id){return enrich(required(id));}
|
||||
public java.util.List<TruckAppointmentHistoryEntity> history(long id){required(id);return repo.histories(id);} public java.util.List<TruckAppointmentAttachmentEntity> attachments(long id){required(id);return repo.attachments(id);}
|
||||
@Transactional public TruckAppointmentAttachmentEntity upload(long id,String type,MultipartFile file,long uid){TruckAppointmentEntity appointment=required(id);if(appointment.getCustomerId()==null||appointment.getCustomerId()<=0)throw bad("预约缺少客户归属,无法上传附件");try{var response=files.upload(internalToken,uid,appointment.getCustomerId(),"TRUCK_APPOINTMENT_ATTACHMENT",Long.toString(id),file);if(response==null||!response.successful()||response.data()==null)throw bad("附件上传失败");var m=response.data();TruckAppointmentAttachmentEntity a=new TruckAppointmentAttachmentEntity();a.setId(IdWorker.getId());a.setAppointmentId(id);a.setFileId(m.fileId());a.setAttachmentType(type);a.setFileName(m.fileName());a.setFileHash(m.sha256());a.setVisibility("PLATFORM");a.setUploadedBy(uid);a.setUploadedAt(LocalDateTime.now());repo.attachment(a);return a;}catch(FeignException ex){throw new BizException(ResultCode.SERVICE_UNAVAILABLE,"文件服务暂不可用");}}
|
||||
@Transactional public TruckAppointmentAttachmentEntity attach(long id,AttachmentRequest r,long uid){TruckAppointmentEntity appointment=required(id); if(appointment.getCustomerId()==null||appointment.getCustomerId()<=0)throw bad("预约缺少客户归属,无法引用附件"); TruckAppointmentFileClient.Metadata metadata;try{var response=files.metadata(internalToken,appointment.getCustomerId(),"TRUCK_APPOINTMENT_ATTACHMENT",Long.toString(id),r.fileId());if(response==null||!response.successful()||response.data()==null)throw bad("附件不存在或归属不匹配");metadata=response.data();}catch(FeignException ex){throw new BizException(ResultCode.SERVICE_UNAVAILABLE,"文件服务暂不可用");} TruckAppointmentAttachmentEntity a=new TruckAppointmentAttachmentEntity();a.setId(IdWorker.getId());a.setAppointmentId(id);a.setFileId(metadata.fileId());a.setAttachmentType(r.attachmentType());a.setFileName(metadata.fileName());a.setFileHash(metadata.sha256());a.setVisibility("PLATFORM");a.setUploadedBy(uid);a.setUploadedAt(LocalDateTime.now());repo.attachment(a);return a;}
|
||||
@Transactional public TruckAppointmentAttachmentEntity upload(long id,String type,MultipartFile file,long uid){String attachmentType=attachmentType(type);TruckAppointmentEntity appointment=required(id);if(appointment.getCustomerId()==null||appointment.getCustomerId()<=0)throw bad("预约缺少客户归属,无法上传附件");try{var response=files.upload(internalToken,uid,appointment.getCustomerId(),"TRUCK_APPOINTMENT_ATTACHMENT",Long.toString(id),file);if(response==null||!response.successful()||response.data()==null)throw bad("附件上传失败");var m=response.data();TruckAppointmentAttachmentEntity a=new TruckAppointmentAttachmentEntity();a.setId(IdWorker.getId());a.setAppointmentId(id);a.setFileId(m.fileId());a.setAttachmentType(attachmentType);a.setFileName(m.fileName());a.setFileHash(m.sha256());a.setVisibility("PLATFORM");a.setUploadedBy(uid);a.setUploadedAt(LocalDateTime.now());repo.attachment(a);return a;}catch(FeignException ex){throw new BizException(ResultCode.SERVICE_UNAVAILABLE,"文件服务暂不可用");}}
|
||||
@Transactional public TruckAppointmentAttachmentEntity attach(long id,AttachmentRequest r,long uid){String attachmentType=attachmentType(r.attachmentType());TruckAppointmentEntity appointment=required(id); if(appointment.getCustomerId()==null||appointment.getCustomerId()<=0)throw bad("预约缺少客户归属,无法引用附件"); TruckAppointmentFileClient.Metadata metadata;try{var response=files.metadata(internalToken,appointment.getCustomerId(),"TRUCK_APPOINTMENT_ATTACHMENT",Long.toString(id),r.fileId());if(response==null||!response.successful()||response.data()==null)throw bad("附件不存在或归属不匹配");metadata=response.data();}catch(FeignException ex){throw new BizException(ResultCode.SERVICE_UNAVAILABLE,"文件服务暂不可用");} TruckAppointmentAttachmentEntity a=new TruckAppointmentAttachmentEntity();a.setId(IdWorker.getId());a.setAppointmentId(id);a.setFileId(metadata.fileId());a.setAttachmentType(attachmentType);a.setFileName(metadata.fileName());a.setFileHash(metadata.sha256());a.setVisibility("PLATFORM");a.setUploadedBy(uid);a.setUploadedAt(LocalDateTime.now());repo.attachment(a);return a;}
|
||||
@Transactional public void deleteAttachment(long id,long attachmentId){required(id);if(repo.deleteAttachment(id,attachmentId)!=1)throw new BizException(ResultCode.NOT_FOUND,"附件引用不存在");}
|
||||
@Transactional public TruckAppointmentEntity create(long uid,String name,SaveRequest r){if(r.expectedVersion()==null||r.expectedVersion()!=0)throw bad("新增 expectedVersion 必须为0");validateRequired(r); var customer=customers.active(r.customerId(),r.customerName()); AssociationContext links=validateAssociations(r,customer.id()); var carrier=suppliers.trucking(r.carrierId(),r.carrierName()); TruckAppointmentEntity e=new TruckAppointmentEntity(); e.setId(IdWorker.getId()); e.setAppointmentNo("TA"+e.getId()); e.setStatus(TruckAppointmentStatus.DRAFT); e.setRevision(0); e.setVersion(0); e.setCreatedAt(LocalDateTime.now()); e.setUpdatedAt(e.getCreatedAt()); copy(e,r,links,customer.tenantName(),carrier.name()); repo.insert(e); record(e,null,e.getStatus().name(),"CREATE",null,uid,name); return e;}
|
||||
@Transactional public TruckAppointmentEntity edit(long id,long uid,String name,SaveRequest r){validateRequired(r);TruckAppointmentEntity e=required(id); checkVersion(e,r.expectedVersion()); if(!(e.getStatus()==TruckAppointmentStatus.DRAFT||e.getStatus()==TruckAppointmentStatus.PENDING_DISPATCH||e.getStatus()==TruckAppointmentStatus.RESCHEDULED))throw conflict("当前状态不可编辑"); var customer=customers.active(r.customerId(),r.customerName()); AssociationContext links=validateAssociations(r,customer.id()); var carrier=suppliers.trucking(r.carrierId(),r.carrierName()); copy(e,r,links,customer.tenantName(),carrier.name()); if(repo.updateEdit(e,r.expectedVersion())!=1)throw conflict("版本已变化,请刷新后重试"); record(e,e.getStatus().name(),e.getStatus().name(),"EDIT",null,uid,name); return required(id);}
|
||||
@Transactional public TruckAppointmentEntity action(long id,String action,ActionRequest r,String idem,String clientHash,long uid,String name){String requestHash=hash(action,id,r);verifyClientHash(clientHash,requestHash);TruckAppointmentEntity replay=claimOrReplay(id,action,idem,requestHash);if(replay!=null)return replay;TruckAppointmentEntity e=required(id); checkVersion(e,r.expectedVersion()); if("RESCHEDULE".equals(action))throw conflict("改约请使用专用接口"); TruckAppointmentStatus from=e.getStatus(),to=next(from,action); if(repo.transition(id,from.name(),to.name(),e.getVersion())!=1)throw conflict("状态或版本已变化,请刷新后重试"); TruckAppointmentEntity after=required(id);record(after,from.name(),to.name(),action,r.reason(),uid,name);return completeOperation(id,action,idem,after);}
|
||||
@Transactional public TruckAppointmentEntity action(long id,String action,ActionRequest r,String idem,String clientHash,long uid,String name){String requestHash=hash(action,id,r);verifyClientHash(clientHash,requestHash);TruckAppointmentEntity replay=claimOrReplay(id,action,idem,requestHash);if(replay!=null)return replay;TruckAppointmentEntity e=required(id); checkVersion(e,r.expectedVersion()); if("RESCHEDULE".equals(action))throw conflict("改约请使用专用接口"); TruckAppointmentStatus from=e.getStatus(),to=next(from,action); String dispatchBill="DISPATCH".equals(action)?requireDispatchReady(e):null; int changed="DISPATCH".equals(action)?repo.dispatch(id,from.name(),dispatchBill,e.getVersion()):repo.transition(id,from.name(),to.name(),e.getVersion()); if(changed!=1)throw conflict("状态或版本已变化,请刷新后重试"); TruckAppointmentEntity after=required(id);record(after,from.name(),to.name(),action,r.reason(),uid,name);return completeOperation(id,action,idem,after);}
|
||||
@Transactional public TruckAppointmentEntity reschedule(long id,LocalDateTime at,ActionRequest r,String idem,String clientHash,long uid,String name){String action="RESCHEDULE";String requestHash=hash(action,id,new ReschedulePayload(at,r));verifyClientHash(clientHash,requestHash);TruckAppointmentEntity replay=claimOrReplay(id,action,idem,requestHash);if(replay!=null)return replay;TruckAppointmentEntity e=required(id);checkVersion(e,r.expectedVersion()); if(e.getStatus()==TruckAppointmentStatus.COMPLETED||e.getStatus()==TruckAppointmentStatus.CANCELLED)throw conflict("当前状态不可改约"); String from=e.getStatus().name(); if(repo.reschedule(id,at,r.reason(),e.getVersion())!=1)throw conflict("状态或版本已变化,请刷新后重试");TruckAppointmentEntity after=required(id);record(after,from,"RESCHEDULED",action,r.reason(),uid,name);return completeOperation(id,action,idem,after);}
|
||||
@Transactional(propagation=Propagation.REQUIRES_NEW) public void completeLoadingFromPda(long containerBookingId,long taskId,long uid,String name){java.util.List<TruckAppointmentEntity> active=repo.activeForUpdate(containerBookingId);if(active.isEmpty())return;if(active.size()!=1)throw conflict("同一柜次存在多条有效拖车预约,跳过自动完成");TruckAppointmentEntity appointment=active.get(0);String from=appointment.getStatus().name();if(repo.completeLoadingFromPda(appointment.getId(),from,appointment.getVersion())!=1)throw conflict("拖车预约已被并发修改,跳过自动完成");TruckAppointmentEntity after=required(appointment.getId());record(after,from,TruckAppointmentStatus.COMPLETED.name(),"PDA_COMPLETE_LOADING","PDA task "+taskId+" 确认装柜完成",uid,name);}
|
||||
private AssociationContext validateAssociations(SaveRequest r,long customerId){OceanBookingEntity booking=null;ContainerBookingEntity container=null;Long warehouseId=r.warehouseId();if(r.oceanBookingId()!=null){booking=oceanBookings.find(r.oceanBookingId());if(booking==null)throw new BizException(ResultCode.NOT_FOUND,"关联订舱不存在");if(booking.getCustomerId()!=null&&!Objects.equals(customerId,booking.getCustomerId()))throw bad("customerId与订舱客户不一致");warehouseId=mergeWarehouse(warehouseId,booking.getWarehouseId(),"订舱");}if(r.containerBookingId()!=null){container=containers.find(r.containerBookingId());if(container==null)throw new BizException(ResultCode.NOT_FOUND,"关联柜次不存在");if(container.getCustomerId()!=null&&!Objects.equals(customerId,container.getCustomerId()))throw bad("customerId与柜次客户不一致");warehouseId=mergeWarehouse(warehouseId,container.getWarehouseId(),"柜次");}if(booking!=null&&container!=null){long containerId=container.getId();boolean allocated=oceanBookings.allocations(booking.getId()).stream().anyMatch(a->Objects.equals(a.getContainerBookingId(),containerId));if(!allocated)throw bad("containerBookingId未分配至该订舱");}if(r.loadingPlanId()!=null){if(container==null)throw bad("选择loadingPlanId时必须同时选择containerBookingId");LoadingPlanEntity plan=loadingPlans.planByBooking(container.getId());if(plan==null||!Objects.equals(r.loadingPlanId(),plan.getId()))throw bad("loadingPlanId与柜次当前装柜计划不一致");}return new AssociationContext(booking,container,warehouseId);}
|
||||
private AssociationContext validateAssociations(SaveRequest r,long customerId){OceanBookingEntity booking=null;ContainerBookingEntity container=null;Long warehouseId=r.warehouseId();String billOfLadingNo=trim(r.billOfLadingNo());if(r.oceanBookingId()!=null){booking=oceanBookings.find(r.oceanBookingId());if(booking==null)throw new BizException(ResultCode.NOT_FOUND,"关联订舱不存在");if(booking.getCustomerId()!=null&&!Objects.equals(customerId,booking.getCustomerId()))throw bad("customerId与订舱客户不一致");warehouseId=mergeWarehouse(warehouseId,booking.getWarehouseId(),"订舱");if("RELEASED".equals(booking.getStatus()))billOfLadingNo=releasedBookingBill(booking,billOfLadingNo);}if(r.containerBookingId()!=null){container=containers.find(r.containerBookingId());if(container==null)throw new BizException(ResultCode.NOT_FOUND,"关联柜次不存在");if(container.getCustomerId()!=null&&!Objects.equals(customerId,container.getCustomerId()))throw bad("customerId与柜次客户不一致");warehouseId=mergeWarehouse(warehouseId,container.getWarehouseId(),"柜次");}if(booking!=null&&container!=null){long containerId=container.getId();boolean allocated=oceanBookings.allocations(booking.getId()).stream().anyMatch(a->Objects.equals(a.getContainerBookingId(),containerId));if(!allocated)throw bad("containerBookingId未分配至该订舱");}if(r.loadingPlanId()!=null){if(container==null)throw bad("选择loadingPlanId时必须同时选择containerBookingId");LoadingPlanEntity plan=loadingPlans.planByBooking(container.getId());if(plan==null||!Objects.equals(r.loadingPlanId(),plan.getId()))throw bad("loadingPlanId与柜次当前装柜计划不一致");}return new AssociationContext(booking,container,warehouseId,billOfLadingNo);}
|
||||
private static void validateRequired(SaveRequest r){if(r.customerId()==null||r.customerId()<=0)throw bad("customerId不能为空");if(r.carrierId()==null||r.carrierId()<=0)throw bad("carrierId不能为空");if(r.plannedLoadingAt()==null)throw bad("plannedLoadingAt不能为空");if(r.expectedVersion()==null||r.expectedVersion()<0)throw bad("expectedVersion不合法");}
|
||||
private static Long mergeWarehouse(Long requested,Long linked,String source){if(requested!=null&&linked!=null&&!Objects.equals(requested,linked))throw bad("warehouseId与"+source+"不一致");return requested!=null?requested:linked;}
|
||||
private TruckAppointmentStatus next(TruckAppointmentStatus s,String a){return switch(a){case"SUBMIT"->s==TruckAppointmentStatus.DRAFT?TruckAppointmentStatus.PENDING_DISPATCH:badStatus();case"DISPATCH"->s==TruckAppointmentStatus.PENDING_DISPATCH||s==TruckAppointmentStatus.RESCHEDULED?TruckAppointmentStatus.DISPATCHED:badStatus();case"PICKUP"->s==TruckAppointmentStatus.DISPATCHED?TruckAppointmentStatus.PICKUP_IN_PROGRESS:badStatus();case"ARRIVE"->s==TruckAppointmentStatus.PICKUP_IN_PROGRESS?TruckAppointmentStatus.ARRIVED_WAREHOUSE:badStatus();case"LOAD"->s==TruckAppointmentStatus.ARRIVED_WAREHOUSE?TruckAppointmentStatus.LOADING:badStatus();case"DEPART"->s==TruckAppointmentStatus.LOADING?TruckAppointmentStatus.DEPARTED:badStatus();case"COMPLETE"->s==TruckAppointmentStatus.DEPARTED?TruckAppointmentStatus.COMPLETED:badStatus();case"EXCEPTION"->s!=TruckAppointmentStatus.COMPLETED&&s!=TruckAppointmentStatus.CANCELLED?TruckAppointmentStatus.EXCEPTION:badStatus();case"CANCEL"->s!=TruckAppointmentStatus.COMPLETED?TruckAppointmentStatus.CANCELLED:badStatus();default->badStatus();};}
|
||||
private TruckAppointmentStatus badStatus(){throw conflict("不允许的状态动作");} private TruckAppointmentEntity required(long id){TruckAppointmentEntity e=repo.find(id);if(e==null)throw new BizException(ResultCode.NOT_FOUND,"拖车预约不存在");return e;} private void checkVersion(TruckAppointmentEntity e,int v){if(e.getVersion()!=v)throw conflict("版本已变化,请刷新后重试");} private static BizException conflict(String m){return new BizException(ResultCode.CONFLICT,m);} private static BizException bad(String m){return new BizException(ResultCode.BAD_REQUEST,m);}
|
||||
private TruckAppointmentEntity enrich(TruckAppointmentEntity value){if(value.getOceanBookingId()!=null){OceanBookingEntity booking=oceanBookings.find(value.getOceanBookingId());if(booking!=null){value.setOceanBookingNo(booking.getBookingNo());value.setWarehouseName(booking.getWarehouseName());value.setShippingCompanyId(booking.getShippingCompanyId());value.setShippingCompanyName(booking.getShippingCompanyName());value.setBookingAgentId(booking.getBookingAgentId());value.setBookingAgentName(booking.getBookingAgentName());}}if(value.getContainerBookingId()!=null){ContainerBookingEntity container=containers.find(value.getContainerBookingId());if(container!=null){value.setCabinetNo(container.getCabinetNo());value.setContainerNo(container.getContainerNo());if(!StringUtils.hasText(value.getWarehouseName()))value.setWarehouseName(container.getWarehouseName());}}value.setAttachmentCount((long)repo.attachments(value.getId()).size());return value;}
|
||||
private void copy(TruckAppointmentEntity e,SaveRequest r,AssociationContext links,String customerName,String carrierName){e.setOutboundOrderId(r.outboundOrderId());e.setOceanBookingId(r.oceanBookingId());e.setContainerBookingId(r.containerBookingId());e.setLoadingPlanId(r.loadingPlanId());e.setWarehouseId(links.warehouseId());e.setCustomerId(r.customerId());e.setCustomerName(customerName);e.setCarrierId(r.carrierId());e.setCarrierName(carrierName);e.setFleetContact(r.fleetContact());e.setFleetPhone(r.fleetPhone());e.setDriverName(r.driverName());e.setDriverPhone(r.driverPhone());e.setDriverIdCard(r.driverIdCard());e.setVehicleNo(r.vehicleNo());e.setStoreName(r.storeName());e.setStoreContact(r.storeContact());e.setStorePhone(r.storePhone());e.setStoreAddress(r.storeAddress());e.setStoreCity(r.storeCity());e.setStoreZipcode(r.storeZipcode());e.setPackageCount(r.packageCount());e.setGrossWeightKg(r.grossWeightKg());e.setVolumeCbm(r.volumeCbm());e.setPalletCount(r.palletCount());e.setPoNo(r.poNo());e.setPlannedLoadingAt(r.plannedLoadingAt());e.setTransitMinutes(r.transitMinutes());e.setPayableAmount(r.payableAmount());e.setReceivableAmount(r.receivableAmount());e.setCurrency(r.currency()==null?"RMB":r.currency());e.setRemark(r.remark());}
|
||||
private void copy(TruckAppointmentEntity e,SaveRequest r,AssociationContext links,String customerName,String carrierName){e.setOutboundOrderId(r.outboundOrderId());e.setOceanBookingId(r.oceanBookingId());e.setContainerBookingId(r.containerBookingId());e.setLoadingPlanId(r.loadingPlanId());e.setWarehouseId(links.warehouseId());e.setCustomerId(r.customerId());e.setCustomerName(customerName);e.setCarrierId(r.carrierId());e.setCarrierName(carrierName);e.setBillOfLadingNo(links.billOfLadingNo());e.setFleetContact(r.fleetContact());e.setFleetPhone(r.fleetPhone());e.setDriverName(r.driverName());e.setDriverPhone(r.driverPhone());e.setDriverIdCard(r.driverIdCard());e.setVehicleNo(r.vehicleNo());e.setStoreName(r.storeName());e.setStoreContact(r.storeContact());e.setStorePhone(r.storePhone());e.setStoreAddress(r.storeAddress());e.setStoreCity(r.storeCity());e.setStoreZipcode(r.storeZipcode());e.setPackageCount(r.packageCount());e.setGrossWeightKg(r.grossWeightKg());e.setVolumeCbm(r.volumeCbm());e.setPalletCount(r.palletCount());e.setPoNo(r.poNo());e.setPlannedLoadingAt(r.plannedLoadingAt());e.setTransitMinutes(r.transitMinutes());e.setPayableAmount(r.payableAmount());e.setReceivableAmount(r.receivableAmount());e.setCurrency(r.currency()==null?"RMB":r.currency());e.setRemark(r.remark());}
|
||||
private TruckAppointmentEntity claimOrReplay(long id,String action,String key,String hash){validateKey(key);String normalized=key.trim();if(repo.claimOperation(IdWorker.getId(),id,action,normalized,hash))return null;TruckAppointmentOperationEntity existing=repo.operation(normalized);if(existing==null||!Objects.equals(existing.getAppointmentId(),id)||!action.equals(existing.getAction())||!hash.equals(existing.getRequestHash()))throw conflict("Idempotency-Key已用于不同请求");if(!StringUtils.hasText(existing.getResponseSnapshot()))throw conflict("相同请求正在处理中,请稍后重试");try{return objectMapper.readValue(existing.getResponseSnapshot(),TruckAppointmentEntity.class);}catch(JsonProcessingException ex){throw new IllegalStateException("无法读取拖车预约幂等结果",ex);}}
|
||||
private TruckAppointmentEntity completeOperation(long id,String action,String key,TruckAppointmentEntity result){try{String normalized=key.trim();if(repo.completeOperation(id,action,normalized,objectMapper.writeValueAsString(result))!=1)throw conflict("拖车预约幂等请求完成状态冲突");return result;}catch(JsonProcessingException ex){throw new IllegalStateException("无法保存拖车预约幂等结果",ex);}}
|
||||
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 static void verifyClientHash(String clientHash,String serverHash){if(StringUtils.hasText(clientHash)&&!serverHash.equalsIgnoreCase(clientHash.trim()))throw bad("X-Request-Hash与请求内容不一致");}
|
||||
private static void validateKey(String key){if(!StringUtils.hasText(key)||key.trim().length()>128)throw bad("Idempotency-Key不能为空且不能超过128个字符");}
|
||||
private record ReschedulePayload(LocalDateTime plannedLoadingAt,ActionRequest request){}
|
||||
private record AssociationContext(OceanBookingEntity booking,ContainerBookingEntity container,Long warehouseId){}
|
||||
private String requireDispatchReady(TruckAppointmentEntity appointment){String bill=trim(appointment.getBillOfLadingNo());if(appointment.getOceanBookingId()!=null){OceanBookingEntity booking=oceanBookings.find(appointment.getOceanBookingId());if(booking==null)throw new BizException(ResultCode.NOT_FOUND,"关联订舱不存在");if("RELEASED".equals(booking.getStatus()))bill=releasedBookingBill(booking,bill);}if(!StringUtils.hasText(bill))throw conflict("派车前必须填写提单号");boolean active=false;java.util.List<TruckAppointmentAttachmentEntity> references=repo.attachments(appointment.getId());if(references!=null)for(TruckAppointmentAttachmentEntity attachment:references){if(!"BILL_OF_LADING".equals(attachment.getAttachmentType()))continue;try{var response=files.metadata(internalToken,appointment.getCustomerId(),"TRUCK_APPOINTMENT_ATTACHMENT",Long.toString(appointment.getId()),attachment.getFileId());if(response!=null&&response.successful()&&response.data()!=null){active=true;break;}}catch(FeignException.NotFound ignored){}catch(FeignException ex){throw new BizException(ResultCode.SERVICE_UNAVAILABLE,"文件服务暂不可用");}}if(!active)throw conflict("派车前必须上传有效提单附件");return bill;}
|
||||
private static String releasedBookingBill(OceanBookingEntity booking,String requested){String authoritative=trim(booking.getBillOfLadingNo());if(!StringUtils.hasText(authoritative))throw conflict("关联已放舱订舱缺少提单号");if(StringUtils.hasText(requested)&&!authoritative.equals(requested))throw bad("billOfLadingNo与关联订舱不一致");return authoritative;}
|
||||
private static String attachmentType(String value){String normalized=trim(value);if(!ATTACHMENT_TYPES.contains(normalized))throw bad("不支持的拖车附件类型");return normalized;}
|
||||
private static String trim(String value){return StringUtils.hasText(value)?value.trim():null;}
|
||||
private record AssociationContext(OceanBookingEntity booking,ContainerBookingEntity container,Long warehouseId,String billOfLadingNo){}
|
||||
private void record(TruckAppointmentEntity e,String from,String to,String action,String reason,long uid,String name){TruckAppointmentHistoryEntity h=new TruckAppointmentHistoryEntity();h.setId(IdWorker.getId());h.setAppointmentId(e.getId());h.setRevision(e.getRevision());h.setAction(action);h.setFromStatus(from);h.setToStatus(to);h.setReason(reason);h.setOperatorId(uid);h.setOperatorName(name);h.setCreatedAt(LocalDateTime.now());repo.history(h);}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user