Merge branch 'test' of http://8.163.30.128:10082/java-project/baiyue-firstleg into test
This commit is contained in:
+55
@@ -14,6 +14,14 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/merchant/v1/last-leg/warehouse-transfers")
|
||||
@@ -27,6 +35,30 @@ public class MerchantWarehouseTransferController {
|
||||
return ApiResponse.success(service.create(customer(user), operator(user), request));
|
||||
}
|
||||
|
||||
@PutMapping("/{transferNo}")
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> updateDraft(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String transferNo,
|
||||
@Valid @RequestBody WarehouseTransferRequests.Create request) {
|
||||
return ApiResponse.success(service.updateDraft(customer(user), transferNo, operator(user), request));
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 兼容入口:部分本地网关只放行 POST,草稿保存不能因此阻断提交。
|
||||
* 与 PUT 更新草稿使用完全相同的服务逻辑和租户校验。
|
||||
*/
|
||||
@PostMapping("/{transferNo}/save-draft")
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> saveDraft(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String transferNo,
|
||||
@Valid @RequestBody WarehouseTransferRequests.Create request) {
|
||||
return ApiResponse.success(service.updateDraft(customer(user), transferNo, operator(user), request));
|
||||
}
|
||||
|
||||
@PostMapping("/{transferNo}/submit")
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> submit(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String transferNo) {
|
||||
return ApiResponse.success(service.submit(customer(user), transferNo, operator(user)));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_TRANSFER_LIST + "')")
|
||||
public ApiResponse<PageResult<WarehouseTransferResponses.Summary>> page(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@@ -41,6 +73,29 @@ public class MerchantWarehouseTransferController {
|
||||
return ApiResponse.success(service.detail(customer(user), transferNo));
|
||||
}
|
||||
|
||||
@GetMapping("/{transferNo}/carton-label")
|
||||
public ApiResponse<WarehouseTransferResponses.Label> cartonLabel(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String transferNo) {
|
||||
return ApiResponse.success(service.cartonLabel(customer(user), transferNo));
|
||||
}
|
||||
|
||||
@GetMapping("/{transferNo}/measurement-photos/files")
|
||||
public ApiResponse<List<WarehouseTransferResponses.MeasurementPhoto>> measurementPhotos(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String transferNo) {
|
||||
return ApiResponse.success(service.measurementPhotos(customer(user), transferNo));
|
||||
}
|
||||
|
||||
@GetMapping("/{transferNo}/measurement-photos/files/{fileId}/download")
|
||||
public ResponseEntity<byte[]> downloadMeasurementPhoto(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String transferNo,
|
||||
@PathVariable long fileId) {
|
||||
var file = service.downloadMeasurementPhoto(customer(user), transferNo, fileId);
|
||||
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
ContentDisposition.inline().filename(file.fileName(), StandardCharsets.UTF_8).build().toString())
|
||||
.contentType(MediaType.parseMediaType(file.contentType())).body(file.content());
|
||||
}
|
||||
|
||||
@PostMapping("/{transferNo}/confirm-measurement")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_TRANSFER_CONFIRM + "')")
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> confirmMeasurement(
|
||||
|
||||
+90
@@ -16,7 +16,16 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@@ -32,6 +41,42 @@ public class PdaWarehouseTransferController {
|
||||
return ApiResponse.success(service.pdaCandidates(identity.overseasWarehouseId()));
|
||||
}
|
||||
|
||||
@GetMapping("/{transferNo}")
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> detail(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String transferNo) {
|
||||
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
|
||||
return ApiResponse.success(service.pdaDetail(identity.overseasWarehouseId(), transferNo));
|
||||
}
|
||||
|
||||
@PostMapping(value = "/{transferNo}/measurement-photos/files", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ApiResponse<WarehouseTransferResponses.MeasurementPhoto> uploadMeasurementPhoto(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String transferNo,
|
||||
@RequestParam("file") MultipartFile file) {
|
||||
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
|
||||
long operatorUserId = user.userId() == null ? 0L : user.userId();
|
||||
return ApiResponse.success(service.uploadMeasurementPhoto(identity.operatorKey(), operatorUserId,
|
||||
identity.overseasWarehouseId(), transferNo, file));
|
||||
}
|
||||
|
||||
@GetMapping("/{transferNo}/measurement-photos/files")
|
||||
public ApiResponse<List<WarehouseTransferResponses.MeasurementPhoto>> measurementPhotos(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String transferNo) {
|
||||
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
|
||||
return ApiResponse.success(service.pdaMeasurementPhotos(identity.overseasWarehouseId(), transferNo));
|
||||
}
|
||||
|
||||
@GetMapping("/{transferNo}/measurement-photos/files/{fileId}/download")
|
||||
public ResponseEntity<byte[]> downloadMeasurementPhoto(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String transferNo,
|
||||
@PathVariable long fileId) {
|
||||
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
|
||||
var file = service.downloadPdaMeasurementPhoto(identity.overseasWarehouseId(), transferNo, fileId);
|
||||
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
ContentDisposition.inline().filename(file.fileName(), StandardCharsets.UTF_8).build().toString())
|
||||
.contentType(MediaType.parseMediaType(file.contentType())).body(file.content());
|
||||
}
|
||||
|
||||
@PostMapping("/{transferNo}/palletize")
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> palletize(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String transferNo,
|
||||
@@ -40,4 +85,49 @@ public class PdaWarehouseTransferController {
|
||||
return ApiResponse.success(service.palletize(identity.operatorKey(), identity.overseasWarehouseId(),
|
||||
transferNo, request));
|
||||
}
|
||||
|
||||
@PostMapping("/{transferNo}/pallets/drafts")
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> savePalletDraft(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String transferNo,
|
||||
@Valid @RequestBody WarehouseTransferRequests.PalletDraft request) {
|
||||
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
|
||||
return ApiResponse.success(service.savePalletDraft(identity.overseasWarehouseId(), transferNo, request));
|
||||
}
|
||||
|
||||
@PostMapping("/{transferNo}/pallets/{palletId}/generate")
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> generatePallet(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String transferNo, @PathVariable long palletId,
|
||||
@Valid @RequestBody WarehouseTransferRequests.PalletAction request) {
|
||||
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
|
||||
return ApiResponse.success(service.generatePallet(identity.overseasWarehouseId(), transferNo, palletId, request));
|
||||
}
|
||||
|
||||
@PostMapping("/{transferNo}/pallets/{palletId}/bind")
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> bindPallet(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String transferNo, @PathVariable long palletId,
|
||||
@Valid @RequestBody WarehouseTransferRequests.PalletBind request) {
|
||||
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
|
||||
return ApiResponse.success(service.bindPallet(identity.overseasWarehouseId(), transferNo, palletId, request));
|
||||
}
|
||||
|
||||
@org.springframework.web.bind.annotation.DeleteMapping("/{transferNo}/pallets/{palletId}")
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> deletePalletDraft(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String transferNo, @PathVariable long palletId) {
|
||||
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
|
||||
return ApiResponse.success(service.deletePalletDraft(identity.overseasWarehouseId(), transferNo, palletId));
|
||||
}
|
||||
|
||||
@GetMapping("/{transferNo}/pallets/{warehousePalletId}/label")
|
||||
public ResponseEntity<byte[]> palletLabel(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String transferNo,
|
||||
@PathVariable long warehousePalletId,
|
||||
@RequestHeader(value = "X-Request-Id", required = false) String requestId) {
|
||||
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
|
||||
var file = service.printPdaPalletLabel(identity.overseasWarehouseId(), transferNo,
|
||||
warehousePalletId, requestId);
|
||||
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
ContentDisposition.attachment().filename(file.fileName(), StandardCharsets.UTF_8).build().toString())
|
||||
.contentType(MediaType.parseMediaType(file.contentType())).body(file.content());
|
||||
}
|
||||
}
|
||||
|
||||
+13
-2
@@ -24,14 +24,14 @@ public class WarehouseTransferController {
|
||||
@PostMapping
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> create(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam(required = false) String customerId, @Valid @RequestBody WarehouseTransferRequests.Create request) {
|
||||
return ApiResponse.success(service.create(scope(user, customerId), operator(user), request));
|
||||
return ApiResponse.success(service.createAndSubmit(scope(user, customerId), operator(user), request));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.LAST_MILE_ORDER_LIST + "')")
|
||||
public ApiResponse<PageResult<WarehouseTransferResponses.Summary>> page(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam(required = false) String customerId, @Valid WarehouseTransferRequests.Query query) {
|
||||
return ApiResponse.success(service.page(scope(user, customerId), query));
|
||||
return ApiResponse.success(service.page(scope(user, customerId), query, false));
|
||||
}
|
||||
|
||||
@GetMapping("/{transferNo}")
|
||||
@@ -56,6 +56,17 @@ public class WarehouseTransferController {
|
||||
return ApiResponse.success(service.review(scope(user, customerId), transferNo, operator(user), request));
|
||||
}
|
||||
|
||||
@PostMapping("/bind-destination")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.OUTBOUND_CREATE + "')")
|
||||
public ApiResponse<java.util.List<WarehouseTransferResponses.Detail>> bindDestination(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Valid @RequestBody WarehouseTransferRequests.BindDestination request) {
|
||||
if (user == null || user.tenantId() == null || user.tenantId() != 0L || !"PLATFORM".equals(user.appCode())) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Platform identity is required");
|
||||
}
|
||||
return ApiResponse.success(service.bindDestination(operator(user), request));
|
||||
}
|
||||
|
||||
@PostMapping("/{transferNo}/allocations")
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> allocate(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam(required = false) String customerId, @PathVariable String transferNo,
|
||||
|
||||
+53
-11
@@ -21,7 +21,17 @@ public final class WarehouseTransferRequests {
|
||||
@Size(max = 200) List<@NotNull Long> sourceLastlegOrderIds,
|
||||
@NotBlank @Size(max = 128) String operatingWarehouseId,
|
||||
@Size(max = 128) String fbaWarehouseCode,
|
||||
@Size(max = 16) String destinationType,
|
||||
@Size(max = 128) String destinationWarehouseId,
|
||||
@Size(max = 128) String destinationWarehouseCode,
|
||||
@Size(max = 255) String destinationWarehouseName,
|
||||
@Size(max = 1000) String privateDestinationAddress,
|
||||
@Size(max = 32) String privateDestinationPostalCode,
|
||||
@Size(max = 128) String privateDestinationContactName,
|
||||
@Size(max = 64) String privateDestinationContactPhone,
|
||||
@NotBlank @Size(max = 1000) String deliveryAddress,
|
||||
@Size(max = 128) String pickupContactName,
|
||||
@Size(max = 64) String pickupContactPhone,
|
||||
@Positive int packageCount,
|
||||
@NotNull @DecimalMin(value = "0.0") BigDecimal volumeCbm,
|
||||
@NotBlank @Size(max = 64) String deliveryMethod,
|
||||
@@ -46,12 +56,29 @@ public final class WarehouseTransferRequests {
|
||||
String deliveryMethod, LocalDateTime estimatedArrivalAt, String fbaShipmentNo,
|
||||
String trackingNo, String fbaBoxNos, String remark, boolean needRelabel,
|
||||
String idempotencyKey, List<Item> items) {
|
||||
this(inboundCode, sourceLastlegOrderIds, operatingWarehouseId, fbaWarehouseCode, deliveryAddress,
|
||||
packageCount, volumeCbm, deliveryMethod, estimatedArrivalAt, null, fbaShipmentNo, trackingNo,
|
||||
this(inboundCode, sourceLastlegOrderIds, operatingWarehouseId, fbaWarehouseCode,
|
||||
null, null, null, null, null, null, null, null,
|
||||
deliveryAddress, null, null, packageCount, volumeCbm, deliveryMethod, estimatedArrivalAt, null, fbaShipmentNo, trackingNo,
|
||||
fbaBoxNos, remark, needRelabel, null, null, null, null, null, idempotencyKey, items, null);
|
||||
}
|
||||
}
|
||||
|
||||
public record DestinationSelection(
|
||||
@NotBlank @Size(max = 128) String customerId,
|
||||
@NotBlank @Size(max = 64) String transferNo) {}
|
||||
|
||||
public record BindDestination(
|
||||
@NotEmpty @Size(max = 100) List<@Valid DestinationSelection> transfers,
|
||||
@NotBlank @jakarta.validation.constraints.Pattern(regexp = "FBA|PRIVATE") String destinationType,
|
||||
@Size(max = 128) String destinationWarehouseId,
|
||||
@Size(max = 128) String destinationWarehouseCode,
|
||||
@Size(max = 255) String destinationWarehouseName,
|
||||
@Size(max = 1000) String privateDestinationAddress,
|
||||
@Size(max = 32) String privateDestinationPostalCode,
|
||||
@Size(max = 128) String privateDestinationContactName,
|
||||
@Size(max = 64) String privateDestinationContactPhone,
|
||||
@NotBlank @Size(max = 255) String idempotencyKey) {}
|
||||
|
||||
/** 审核通过备注选填;驳回必须提供非空原因,由服务在任何状态写入前校验。 */
|
||||
public record Review(@NotBlank @Size(max = 32) String decision,
|
||||
@Size(max = 1000) String remark) {}
|
||||
@@ -72,19 +99,34 @@ public final class WarehouseTransferRequests {
|
||||
|
||||
public record Palletize(
|
||||
@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@NotEmpty @Size(max = 100) List<@Valid Pallet> pallets
|
||||
@NotEmpty @Size(max = 20) List<@NotNull Long> photoFileIds
|
||||
) {}
|
||||
|
||||
public record PalletDraft(
|
||||
Long palletId,
|
||||
@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@Positive int cartonCount,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal lengthCm,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal widthCm,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal heightCm,
|
||||
@NotNull @DecimalMin("0.000001") BigDecimal volumeCbm,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal grossWeightKg
|
||||
) {}
|
||||
|
||||
public record PalletAction(@NotBlank @Size(max = 255) String idempotencyKey) {}
|
||||
|
||||
public record PalletBind(@NotBlank @Size(max = 128) String palletCode,
|
||||
@NotBlank @Size(max = 255) String idempotencyKey) {}
|
||||
|
||||
public record Pallet(
|
||||
@Size(max = 128) String palletCode,
|
||||
@Positive int cartonCount,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal lengthCm,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal widthCm,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal heightCm,
|
||||
@NotNull @DecimalMin("0.000001") BigDecimal volumeCbm,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal grossWeightKg,
|
||||
@NotEmpty @Size(max = 500) List<@Valid PalletItem> items
|
||||
@NotNull @DecimalMin("0.01") BigDecimal grossWeightKg
|
||||
) {}
|
||||
|
||||
public record PalletItem(@NotNull Long transferItemId, @Positive int packageCount) {}
|
||||
|
||||
public record CustomerConfirm(@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@Size(max = 1000) String remark) {}
|
||||
|
||||
@@ -106,8 +148,8 @@ public final class WarehouseTransferRequests {
|
||||
) {}
|
||||
|
||||
public record Item(
|
||||
@NotBlank @Size(max = 128) String skuCode,
|
||||
@NotBlank @Size(max = 255) String productNameZh,
|
||||
@Size(max = 128) String skuCode,
|
||||
@Size(max = 255) String productNameZh,
|
||||
@Positive int packageCount,
|
||||
@Size(max = 255) String shippingMark
|
||||
) {}
|
||||
@@ -115,7 +157,7 @@ public final class WarehouseTransferRequests {
|
||||
public record RelabelItem(
|
||||
@NotBlank @Size(max = 32) String operationType,
|
||||
@NotEmpty @Size(max = 500) List<@NotBlank @Size(max = 255) String> customerCartonMarks,
|
||||
@NotBlank @Size(max = 128) String originalSku,
|
||||
@Size(max = 128) String originalSku,
|
||||
@Size(max = 128) String newSku,
|
||||
@Positive int quantity,
|
||||
@Size(max = 255) String fbaCartonMark,
|
||||
|
||||
+17
-4
@@ -9,12 +9,16 @@ public final class WarehouseTransferResponses {
|
||||
public record Item(Long id, String skuCode, String productNameZh, int packageCount, String shippingMark) {}
|
||||
public record Carton(String cartonMark, String customerCartonMark, int cartonIndex, int totalCartons, String status) {}
|
||||
public record Label(String transferNo, List<Carton> cartons, String printableHtml) {}
|
||||
public record DestinationResult(String customerId, String transferNo) {}
|
||||
public record PalletItem(Long transferItemId, String skuCode, int packageCount) {}
|
||||
public record MeasurementPhoto(Long fileId, String fileName, String contentType, long sizeBytes,
|
||||
LocalDateTime uploadedAt) {}
|
||||
public record Pallet(Long id, int sequenceNo, Long warehousePalletId, String palletNo,
|
||||
Long warehouseLocationId, String warehouseLocationCode, int cartonCount,
|
||||
BigDecimal lengthCm, BigDecimal widthCm, BigDecimal heightCm,
|
||||
BigDecimal grossWeightKg, BigDecimal volumeCbm, String status,
|
||||
List<PalletItem> items, LocalDateTime completedAt) {}
|
||||
String fbaWarehouseCode, String labelDownloadPath,
|
||||
List<PalletItem> items, LocalDateTime boundAt, LocalDateTime completedAt) {}
|
||||
public record RelabelBinding(String operationType, String customerCartonMark, String originalSku,
|
||||
String newSku, int quantity, String fbaCartonMark,
|
||||
String destinationWarehouseCode, String remark) {}
|
||||
@@ -24,19 +28,28 @@ public final class WarehouseTransferResponses {
|
||||
Long destinationInventoryPositionId) {}
|
||||
public record Summary(Long id, String transferNo, String inboundCode, Long lastlegOrderId,
|
||||
String lastlegOrderNo, String operatingWarehouseId, String fbaWarehouseCode,
|
||||
String destinationType, String destinationWarehouseCode, String destinationWarehouseName,
|
||||
int packageCount, String deliveryMethod, boolean needRelabel, String relabelNo,
|
||||
String status, LocalDateTime estimatedArrivalAt, LocalDateTime createdAt) {}
|
||||
public record Detail(Long id, String transferNo, Long inboundOrderId, String inboundCode,
|
||||
Long lastlegOrderId, String lastlegOrderNo, String orderType, String orderStatus,
|
||||
String status,
|
||||
boolean needRelabel, Long relabelApplicationId, String relabelNo, String relabelStatus,
|
||||
String operatingWarehouseId, String fbaWarehouseCode, String deliveryAddress,
|
||||
int packageCount, String deliveryMethod, LocalDateTime estimatedArrivalAt,
|
||||
String operatingWarehouseId, String fbaWarehouseCode, String deliveryAddress,
|
||||
String destinationType, String destinationWarehouseId, String destinationWarehouseCode,
|
||||
String destinationWarehouseName, String privateDestinationAddress,
|
||||
String privateDestinationPostalCode,
|
||||
String privateDestinationContactName, String privateDestinationContactPhone,
|
||||
String pickupContactName, String pickupContactPhone,
|
||||
int packageCount, BigDecimal volumeCbm, String deliveryMethod, LocalDateTime estimatedArrivalAt,
|
||||
LocalDateTime estimatedShipAt,
|
||||
String fbaShipmentNo, String trackingNo, String fbaBoxNos, String remark,
|
||||
LocalDateTime labelDeadline, String relabelContactName, String relabelContactPhone,
|
||||
String relabelDeliveryMethod,
|
||||
String reviewDecision, String reviewRemark, String reviewedBy, LocalDateTime reviewedAt,
|
||||
List<SourceOrder> sourceOrders, List<Item> items, List<Carton> cartons,
|
||||
List<Pallet> pallets, List<RelabelBinding> relabelBindings,
|
||||
List<Allocation> allocations, LocalDateTime customerConfirmedAt,
|
||||
List<Allocation> allocations, List<MeasurementPhoto> measurementPhotos,
|
||||
LocalDateTime customerConfirmedAt,
|
||||
LocalDateTime actualShipAt, LocalDateTime createdAt) {}
|
||||
}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.baiyue.lastleg.transfer.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("ll_warehouse_transfer_measurement_photo")
|
||||
public class WarehouseTransferMeasurementPhoto {
|
||||
@TableId(type = IdType.INPUT) private Long id;
|
||||
private Long transferOrderId;
|
||||
private String customerId;
|
||||
private Long fileId;
|
||||
private String fileName;
|
||||
private String contentType;
|
||||
private Long sizeBytes;
|
||||
private String uploadedBy;
|
||||
private LocalDateTime uploadedAt;
|
||||
private LocalDateTime submittedAt;
|
||||
}
|
||||
+12
@@ -18,7 +18,17 @@ public class WarehouseTransferOrder {
|
||||
private String lastlegOrderNo;
|
||||
private String operatingWarehouseId;
|
||||
private String fbaWarehouseCode;
|
||||
private String destinationType;
|
||||
private String destinationWarehouseId;
|
||||
private String destinationWarehouseCode;
|
||||
private String destinationWarehouseName;
|
||||
private String privateDestinationAddress;
|
||||
private String privateDestinationPostalCode;
|
||||
private String privateDestinationContactName;
|
||||
private String privateDestinationContactPhone;
|
||||
private String deliveryAddress;
|
||||
private String pickupContactName;
|
||||
private String pickupContactPhone;
|
||||
private Integer packageCount;
|
||||
private String deliveryMethod;
|
||||
private LocalDateTime estimatedArrivalAt;
|
||||
@@ -50,6 +60,8 @@ public class WarehouseTransferOrder {
|
||||
private String palletizeRequestHash;
|
||||
private String confirmationIdempotencyKey;
|
||||
private String confirmationRequestHash;
|
||||
private String destinationBindingIdempotencyKey;
|
||||
private String destinationBindingRequestHash;
|
||||
private LocalDateTime customerConfirmedAt;
|
||||
private String idempotencyKey;
|
||||
private String requestHash;
|
||||
|
||||
+5
@@ -27,6 +27,11 @@ public class WarehouseTransferPallet {
|
||||
private BigDecimal grossWeightKg;
|
||||
private BigDecimal volumeCbm;
|
||||
private String status;
|
||||
private String draftIdempotencyKey;
|
||||
private String draftRequestHash;
|
||||
private String generateIdempotencyKey;
|
||||
private String bindIdempotencyKey;
|
||||
private LocalDateTime boundAt;
|
||||
private LocalDateTime completedAt;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package com.baiyue.lastleg.transfer.gateway;
|
||||
|
||||
import com.baiyue.lastleg.config.LastlegRestClientFactory;
|
||||
import com.baiyue.lastleg.config.LastlegRestClientProperties;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.http.client.MultipartBodyBuilder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class HttpTransferMeasurementPhotoFileGateway implements TransferMeasurementPhotoFileGateway {
|
||||
private static final String TYPE = "OVERSEAS_TRANSFER_MEASUREMENT_PHOTO";
|
||||
@Resource private RestClient.Builder restClientBuilder;
|
||||
@Resource private LastlegRestClientFactory restClientFactory;
|
||||
@Resource private LastlegRestClientProperties restClientProperties;
|
||||
@Value("${baiyue.clients.file.url:http://127.0.0.1:9206}") private String baseUrl;
|
||||
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
|
||||
private RestClient client;
|
||||
|
||||
@PostConstruct void initializeClient() {
|
||||
client = restClientFactory.build(restClientBuilder, baseUrl, restClientProperties.getRelabelPhotoFile());
|
||||
}
|
||||
|
||||
@Override public UploadedFile upload(String customerId, long transferId, long operatorId, MultipartFile file) {
|
||||
MultipartBodyBuilder body = new MultipartBodyBuilder();
|
||||
try {
|
||||
body.part("file", new ByteArrayResource(file.getBytes()) {
|
||||
@Override public String getFilename() { return file.getOriginalFilename(); }
|
||||
}).contentType(file.getContentType() == null ? MediaType.APPLICATION_OCTET_STREAM
|
||||
: MediaType.parseMediaType(file.getContentType()));
|
||||
} catch (IOException exception) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot read transfer measurement photo", exception);
|
||||
}
|
||||
MetadataEnvelope response = client.post().uri("/internal/file/v1/business-files/{tenant}/{type}/{business}",
|
||||
tenant(customerId), TYPE, transferId)
|
||||
.header("X-Internal-Service-Token", internalToken).header("X-Operator-Id", String.valueOf(operatorId))
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA).body(body.build()).retrieve()
|
||||
.onStatus(HttpStatusCode::isError, (request, result) -> { throw failure(result.getStatusCode(), "Transfer measurement photo upload failed"); })
|
||||
.body(MetadataEnvelope.class);
|
||||
Metadata data = success(response, "Transfer measurement photo upload failed");
|
||||
return new UploadedFile(data.fileId(), data.fileName(), data.contentType(), data.sizeBytes() == null ? 0 : data.sizeBytes());
|
||||
}
|
||||
|
||||
@Override public void validate(String customerId, long transferId, long fileId) {
|
||||
long tenant = tenant(customerId);
|
||||
MetadataEnvelope response = client.get().uri("/internal/file/v1/business-files/{tenant}/{type}/{business}/{file}",
|
||||
tenant, TYPE, transferId, fileId).header("X-Internal-Service-Token", internalToken).retrieve()
|
||||
.onStatus(HttpStatusCode::isError, (request, result) -> { throw failure(result.getStatusCode(), "Transfer measurement photo ownership is invalid"); })
|
||||
.body(MetadataEnvelope.class);
|
||||
Metadata data = success(response, "Transfer measurement photo ownership is invalid");
|
||||
if (data.fileId() != fileId || data.tenantId() == null || data.tenantId() != tenant
|
||||
|| !TYPE.equals(data.businessType()) || !String.valueOf(transferId).equals(data.businessId())) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Transfer measurement photo ownership is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
@Override public DownloadedFile download(String customerId, long transferId, long fileId) {
|
||||
validate(customerId, transferId, fileId);
|
||||
ResponseEntity<byte[]> response = client.get().uri("/internal/file/v1/business-files/{tenant}/{type}/{business}/{file}/content",
|
||||
tenant(customerId), TYPE, transferId, fileId).header("X-Internal-Service-Token", internalToken).retrieve()
|
||||
.onStatus(HttpStatusCode::isError, (request, result) -> { throw failure(result.getStatusCode(), "Transfer measurement photo download failed"); })
|
||||
.toEntity(byte[].class);
|
||||
String name = response.getHeaders().getContentDisposition().getFilename();
|
||||
String type = response.getHeaders().getContentType() == null ? MediaType.APPLICATION_OCTET_STREAM_VALUE
|
||||
: response.getHeaders().getContentType().toString();
|
||||
return new DownloadedFile(response.getBody() == null ? new byte[0] : response.getBody(),
|
||||
name == null ? "transfer-measurement-photo" : name, type);
|
||||
}
|
||||
|
||||
private static long tenant(String value) {
|
||||
try { return Long.parseLong(value); }
|
||||
catch (NumberFormatException exception) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Customer ID must be numeric", exception); }
|
||||
}
|
||||
private static Metadata success(MetadataEnvelope response, String fallback) {
|
||||
if (response == null) throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, fallback);
|
||||
if (!"200".equals(response.code())) {
|
||||
HttpStatus status = switch (String.valueOf(response.code())) {
|
||||
case "A0400", "A0422" -> HttpStatus.BAD_REQUEST;
|
||||
case "A0403" -> HttpStatus.FORBIDDEN;
|
||||
case "A0404" -> HttpStatus.NOT_FOUND;
|
||||
default -> HttpStatus.SERVICE_UNAVAILABLE;
|
||||
};
|
||||
throw new ResponseStatusException(status,
|
||||
response.message() == null || response.message().isBlank() ? fallback : response.message());
|
||||
}
|
||||
if (response.data() == null || response.data().fileId() == null) {
|
||||
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,
|
||||
"File service returned no transfer measurement photo metadata");
|
||||
}
|
||||
return response.data();
|
||||
}
|
||||
private static ResponseStatusException failure(HttpStatusCode status, String message) {
|
||||
return new ResponseStatusException(status.is4xxClientError() ? HttpStatus.BAD_REQUEST : HttpStatus.SERVICE_UNAVAILABLE, message);
|
||||
}
|
||||
private record MetadataEnvelope(String code, String message, Metadata data) {}
|
||||
private record Metadata(Long fileId, Long tenantId, String businessType, String businessId,
|
||||
String fileName, String contentType, Long sizeBytes) {}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.baiyue.lastleg.transfer.gateway;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
public interface TransferMeasurementPhotoFileGateway {
|
||||
UploadedFile upload(String customerId, long transferId, long operatorId, MultipartFile file);
|
||||
void validate(String customerId, long transferId, long fileId);
|
||||
DownloadedFile download(String customerId, long transferId, long fileId);
|
||||
|
||||
record UploadedFile(long fileId, String fileName, String contentType, long sizeBytes) {}
|
||||
record DownloadedFile(byte[] content, String fileName, String contentType) {}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.baiyue.lastleg.transfer.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.lastleg.transfer.entity.WarehouseTransferMeasurementPhoto;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface WarehouseTransferMeasurementPhotoMapper extends BaseMapper<WarehouseTransferMeasurementPhoto> {
|
||||
@Select("SELECT * FROM ll_warehouse_transfer_measurement_photo WHERE customer_id=#{customerId} AND transfer_order_id=#{transferOrderId} ORDER BY uploaded_at,id")
|
||||
List<WarehouseTransferMeasurementPhoto> listOrder(@Param("customerId") String customerId,
|
||||
@Param("transferOrderId") Long transferOrderId);
|
||||
@Select("SELECT * FROM ll_warehouse_transfer_measurement_photo WHERE customer_id=#{customerId} AND transfer_order_id=#{transferOrderId} AND submitted_at IS NOT NULL ORDER BY submitted_at,id")
|
||||
List<WarehouseTransferMeasurementPhoto> listSubmitted(@Param("customerId") String customerId,
|
||||
@Param("transferOrderId") Long transferOrderId);
|
||||
|
||||
@Select("SELECT * FROM ll_warehouse_transfer_measurement_photo WHERE customer_id=#{customerId} AND transfer_order_id=#{transferOrderId} AND file_id=#{fileId} LIMIT 1")
|
||||
WarehouseTransferMeasurementPhoto findFile(@Param("customerId") String customerId,
|
||||
@Param("transferOrderId") Long transferOrderId,
|
||||
@Param("fileId") Long fileId);
|
||||
@Select("SELECT * FROM ll_warehouse_transfer_measurement_photo WHERE customer_id=#{customerId} AND transfer_order_id=#{transferOrderId} AND file_id=#{fileId} AND submitted_at IS NOT NULL LIMIT 1")
|
||||
WarehouseTransferMeasurementPhoto findSubmittedFile(@Param("customerId") String customerId,
|
||||
@Param("transferOrderId") Long transferOrderId,
|
||||
@Param("fileId") Long fileId);
|
||||
@Update("<script>UPDATE ll_warehouse_transfer_measurement_photo SET submitted_at=#{submittedAt} WHERE customer_id=#{customerId} AND transfer_order_id=#{transferOrderId} AND file_id IN <foreach collection='fileIds' item='id' open='(' separator=',' close=')'>#{id}</foreach></script>")
|
||||
int markSubmitted(@Param("customerId") String customerId, @Param("transferOrderId") Long transferOrderId,
|
||||
@Param("fileIds") java.util.Collection<Long> fileIds,
|
||||
@Param("submittedAt") java.time.LocalDateTime submittedAt);
|
||||
}
|
||||
+2
@@ -14,6 +14,8 @@ import org.apache.ibatis.annotations.Update;
|
||||
WarehouseTransferOrder lockNo(@Param("customerId") String customerId, @Param("transferNo") String transferNo);
|
||||
@Select("SELECT * FROM ll_warehouse_transfer_order WHERE transfer_no=#{transferNo} AND operating_warehouse_id=#{warehouseId} LIMIT 1 FOR UPDATE")
|
||||
WarehouseTransferOrder lockForPda(@Param("transferNo") String transferNo, @Param("warehouseId") String warehouseId);
|
||||
@Select("SELECT * FROM ll_warehouse_transfer_order WHERE transfer_no=#{transferNo} AND operating_warehouse_id=#{warehouseId} LIMIT 1")
|
||||
WarehouseTransferOrder findNoByWarehouse(@Param("transferNo") String transferNo, @Param("warehouseId") String warehouseId);
|
||||
@Select("SELECT * FROM ll_warehouse_transfer_order WHERE customer_id=#{customerId} AND relabel_no=#{relabelNo} LIMIT 1 FOR UPDATE")
|
||||
WarehouseTransferOrder lockByRelabelNo(@Param("customerId") String customerId, @Param("relabelNo") String relabelNo);
|
||||
@Select("SELECT * FROM ll_warehouse_transfer_order WHERE operating_warehouse_id=#{warehouseId} AND status='WAIT_PDA_RECEIVING' ORDER BY created_at,id")
|
||||
|
||||
+5
@@ -13,4 +13,9 @@ public interface WarehouseTransferPalletMapper extends BaseMapper<WarehouseTrans
|
||||
@Select("SELECT * FROM ll_warehouse_transfer_pallet WHERE customer_id=#{customerId} AND transfer_order_id=#{transferOrderId} ORDER BY sequence_no")
|
||||
List<WarehouseTransferPallet> listOrder(@Param("customerId") String customerId,
|
||||
@Param("transferOrderId") Long transferOrderId);
|
||||
|
||||
@Select("SELECT * FROM ll_warehouse_transfer_pallet WHERE customer_id=#{customerId} AND transfer_order_id=#{transferOrderId} AND id=#{id} FOR UPDATE")
|
||||
WarehouseTransferPallet lockOne(@Param("customerId") String customerId,
|
||||
@Param("transferOrderId") Long transferOrderId,
|
||||
@Param("id") Long id);
|
||||
}
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.baiyue.lastleg.transfer.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/** 仓储转运单状态。状态编码持久化到数据库,中文名称用于接口和前端展示。 */
|
||||
public enum WarehouseTransferStatus {
|
||||
DRAFT("草稿"),
|
||||
PENDING_REVIEW("待审核"),
|
||||
APPROVED("审核通过"),
|
||||
REJECTED("审核驳回"),
|
||||
WAIT_PDA_RECEIVING("待PDA收货"),
|
||||
INBOUND_MEASURING("入库测量中"),
|
||||
WAIT_CUSTOMER_CONFIRMATION("待客户确认实测"),
|
||||
WAIT_RELABEL("待换标"),
|
||||
WAIT_FBA_DESTINATION("待确认目的仓"),
|
||||
READY_FOR_DISPATCH("待排车"),
|
||||
WAIT_OUTBOUND("待出库"),
|
||||
PENDING_ASSIGNMENT("待分配"),
|
||||
ALLOCATED("已分配库存"),
|
||||
OUTBOUNDED("已出库"),
|
||||
PARTIALLY_RECEIVED("部分收货"),
|
||||
RECEIVED("已收货"),
|
||||
INBOUND_COMPLETED("入库完成"),
|
||||
COMPLETED("已完成");
|
||||
|
||||
private final String label;
|
||||
|
||||
WarehouseTransferStatus(String label) { this.label = label; }
|
||||
|
||||
public String code() { return name(); }
|
||||
public String label() { return label; }
|
||||
|
||||
public static WarehouseTransferStatus fromCode(String code) {
|
||||
return Arrays.stream(values()).filter(value -> value.name().equals(code)).findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unsupported warehouse transfer status: " + code));
|
||||
}
|
||||
}
|
||||
+876
-161
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user