This commit is contained in:
wujinliang
2026-09-18 11:41:13 +08:00
68 changed files with 819 additions and 383 deletions
@@ -179,7 +179,8 @@ public class LastlegOrderController {
@PutMapping("/api/platform/v1/last-leg/orders/{id}/hold")
@PreAuthorize("hasAuthority('" + OverseasPermissions.LAST_MILE_ORDER_EDIT + "')")
@Operation(summary = "暂扣或解除单票尾程订单",
description = "要求版本与幂等键;暂扣只作用于该票并阻断其出库推进。Required authority: " + OverseasPermissions.LAST_MILE_ORDER_EDIT)
description = "要求版本与幂等键;新增暂扣只允许完成入库且尚未进入出库流程的订单,"
+ "暂扣只作用于该票并仅阻断排车和出库。Required authority: " + OverseasPermissions.LAST_MILE_ORDER_EDIT)
public ApiResponse<LastlegOrderCommandDtos.HoldResponse> hold(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -7,9 +7,11 @@ import com.baiyue.lastleg.dto.LastlegOrderDetailResponse;
import com.baiyue.lastleg.dto.LastlegOrderRequests;
import com.baiyue.lastleg.dto.LastlegOrderSummary;
import com.baiyue.lastleg.service.LastlegOrderService;
import com.baiyue.lastleg.security.OverseasPermissions;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -19,6 +21,7 @@ import org.springframework.web.server.ResponseStatusException;
@RestController
@RequestMapping("/api/merchant/v1/last-leg/orders")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_LAST_MILE_ORDER_LIST + "')")
public class MerchantLastlegOrderController {
@Resource private LastlegOrderService orderService;
@@ -6,9 +6,11 @@ import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.lastleg.discard.dto.CargoDiscardRequests;
import com.baiyue.lastleg.discard.dto.CargoDiscardResponses;
import com.baiyue.lastleg.discard.service.CargoDiscardService;
import com.baiyue.lastleg.security.OverseasPermissions;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
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;
@@ -19,18 +21,23 @@ import java.util.List;
public class MerchantCargoDiscardController {
@Resource private CargoDiscardService service;
@GetMapping("/inventory-candidates")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_DISCARD_LIST + "')")
public ApiResponse<List<CargoDiscardResponses.Candidate>> candidates(@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String inboundCode) { return ApiResponse.success(service.candidates(customer(user), inboundCode)); }
@PostMapping
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_DISCARD_CREATE + "')")
public ApiResponse<CargoDiscardResponses.Detail> create(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody CargoDiscardRequests.Create request) { return ApiResponse.success(service.create(customer(user), operator(user), request)); }
@GetMapping
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_DISCARD_LIST + "')")
public ApiResponse<PageResult<CargoDiscardResponses.Summary>> page(@AuthenticationPrincipal AuthenticatedUser user,
@Valid CargoDiscardRequests.Query query) { return ApiResponse.success(service.page(customer(user), query)); }
@GetMapping("/{discardNo}")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_DISCARD_LIST + "')")
public ApiResponse<CargoDiscardResponses.Detail> detail(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String discardNo) { return ApiResponse.success(service.detail(customer(user), discardNo)); }
@PostMapping("/{discardNo}/cancel")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_DISCARD_CANCEL + "')")
public ApiResponse<CargoDiscardResponses.Detail> cancel(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String discardNo) { return ApiResponse.success(service.cancel(customer(user), discardNo, operator(user), false)); }
private String customer(AuthenticatedUser user) {
@@ -114,12 +114,6 @@ public final class LastlegOrderRequests {
) {
}
public record SetDetained(
@NotNull Boolean detained,
@NotBlank @Size(max = 500) String remark
) {
}
public record SetPalletMergeRequired(
@NotNull Boolean palletMergeRequired
) {
@@ -126,7 +126,7 @@ public interface InboundPlanOrderMapper {
@Update("UPDATE ll_order SET inbound_order_no=#{planNo},current_status='INBOUND_PLANNED'," +
"pallet_merge_flag=CASE WHEN #{mergeRequired}=1 THEN 'MERGE_REQUIRED' ELSE NULL END," +
"pallet_count=#{palletCount},version=version+1,updated_at=#{updatedAt} " +
"WHERE customer_id=#{customerId} AND id=#{orderId} AND version=#{expectedVersion} AND detained=0 " +
"WHERE customer_id=#{customerId} AND id=#{orderId} AND version=#{expectedVersion} " +
"AND current_status IN ('PENDING_ASSIGNMENT','DESTINATION_ASSIGNED') AND inbound_order_no IS NULL")
int markPlannedManual(@Param("customerId") String customerId, @Param("orderId") Long orderId,
@Param("planNo") String planNo, @Param("palletCount") int palletCount,
@@ -2,6 +2,7 @@ package com.baiyue.lastleg.inboundplan.pda.dto;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Positive;
@@ -23,7 +24,8 @@ public final class ReceivingContainerRequests {
public record PalletResolve(@NotBlank String palletCode, @NotBlank String inboundCode) { }
/** packageCount 是该托当前累计实收箱数,不是本次增量。 */
public record BulkReceive(@NotBlank String inboundCode, @NotBlank String palletCode,
@Positive int packageCount, Integer expectedPalletVersion,
@Positive @Max(value = 66, message = "一个托盘最多只能收66箱,请拆分到其他托盘") int packageCount,
Integer expectedPalletVersion,
@NotBlank @Size(max = 255) String idempotencyKey) {
public BulkReceive(String inboundCode, String palletCode, int packageCount, String idempotencyKey) {
this(inboundCode, palletCode, packageCount, null, idempotencyKey);
@@ -73,6 +73,9 @@ public class ReceivingContainerService {
"/api/pda/v1/last-leg/receiving-containers/current/domestic-loading-photos/{type}/content";
private static final String PALLET_MERGE_HINT =
"该入库计划可选择合托:合托时扫描同目的仓已使用的托盘码,独立收货时扫描新托盘码;不强制合托";
private static final int MAX_PACKAGES_PER_PALLET = 66;
private static final String PALLET_CAPACITY_MESSAGE =
"一个托盘最多只能收66箱,请拆分到其他托盘";
@Resource private InboundPlanMapper plans;
@Resource private InboundPlanLineMapper lines;
@@ -364,6 +367,7 @@ public class ReceivingContainerService {
ReceivingContainerRequests.BulkReceive request) {
ReceivingContainerOperation replay = replay(operatorKey, "BULK_RECEIVE", request.idempotencyKey(), request);
if (replay != null) return read(replay, ReceivingContainerResponses.Receipt.class);
requirePalletCapacity(request.packageCount());
ReceivingContainerSession session = requireSigned(operatorKey);
InboundRef ref = requireInbound(session.getContainerNo(), request.inboundCode());
if ("ONE_CLICK_DROPSHIP".equals(ref.order().getOrderType())) {
@@ -463,6 +467,10 @@ public class ReceivingContainerService {
PalletSelection selection = resolveReceivingPallet(ref, request.palletCode(), true);
WarehouseResourceGateway.WarehousePalletReference resource = selection.resource();
InboundPlanPallet pallet = selection.pallet();
long packagesOnPallet = cartons.selectCount(new LambdaQueryWrapper<InboundPlanCarton>()
.eq(InboundPlanCarton::getWarehousePalletId, resource.palletId())
.eq(InboundPlanCarton::getStatus, "ON_PALLET"));
requirePalletCapacity(packagesOnPallet + 1);
workflow.validateCartonResolved(customerId, ref.plan(), ref.line(), pallet, request.cartonMark());
Long resourceLockId = workflow.assignResolvedPallet(customerId, ref.plan(), pallet, resource);
try {
@@ -492,6 +500,8 @@ public class ReceivingContainerService {
if (sourcePalletNo == null) throw notFound("DP or carton is not part of the current container");
requireCompleteManifest(session.getContainerNo(), sourcePalletNo);
List<DropshipPalletRef> matches = dropshipPalletRefs(session.getContainerNo(), sourcePalletNo);
int total = matches.stream().mapToInt(value -> value(value.allocation().getPlannedPackageCount())).sum();
requirePalletCapacity(total);
boolean alreadyReceived = matches.stream().allMatch(match ->
value(match.allocation().getPickedPackageCount()) == value(match.allocation().getPlannedPackageCount()));
int accepted = 0;
@@ -511,7 +521,6 @@ public class ReceivingContainerService {
}
List<InboundRef> group = matches.stream().map(DropshipPalletRef::ref).distinct().toList();
List<String> inboundCodes = group.stream().map(value -> value.line().getInboundCode()).distinct().sorted().toList();
int total = matches.stream().mapToInt(value -> value(value.allocation().getPlannedPackageCount())).sum();
int received = matches.stream().mapToInt(value -> value(value.allocation().getPickedPackageCount())).sum();
InboundRef first = group.get(0);
ReceivingContainerResponses.Inbound inbound = inbound(first);
@@ -885,6 +894,10 @@ public class ReceivingContainerService {
|| "LOCKED".equals(pallet.getAssignmentStatus()));
}
private void requirePalletCapacity(long packageCount) {
if (packageCount > MAX_PACKAGES_PER_PALLET) throw conflict(PALLET_CAPACITY_MESSAGE);
}
private void requireMaterializedPlans(List<InboundPlan> containerPlans) {
for (InboundPlan plan : containerPlans) {
int expected = plan.getPalletCount() == null ? 0 : plan.getPalletCount();
@@ -126,7 +126,6 @@ public class InboundPlanService {
if (order.version() != request.expectedOrderVersion()) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "尾程订单版本已变化,请刷新后重试");
}
if (order.detained()) throw new ResponseStatusException(HttpStatus.CONFLICT, "暂扣尾程订单不能生成入库计划");
if (!Objects.equals(containerWarehouseId, trimToNull(order.overseasWarehouseId()))) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "来源柜接柜仓冻结事实已变化,请刷新后重试");
}
@@ -280,7 +279,6 @@ public class InboundPlanService {
boolean planned = trimToNull(row.planNo()) != null;
if (!planned) {
if (trimToNull(row.inboundCode()) == null) blockers.add("MISSING_INBOUND_CODE");
if (row.detained()) blockers.add("ORDER_DETAINED");
if (!("PENDING_ASSIGNMENT".equals(row.currentStatus())
|| "DESTINATION_ASSIGNED".equals(row.currentStatus()))) blockers.add("ORDER_STATUS_NOT_READY");
if (containerNo == null || !Objects.equals(containerNo, trimToNull(row.containerNo()))) {
@@ -60,6 +60,12 @@ public interface InventoryCommandMapper {
@Param("sourceIdentity") String sourceIdentity,
@Param("skuCode") String skuCode);
@Select("SELECT p.* FROM ll_inventory_position p JOIN ll_inventory_balance b ON b.id=p.balance_id " +
"WHERE p.customer_id=#{customerId} AND b.inbound_order_id=#{inboundOrderId} " +
"AND p.available_packages>0 ORDER BY p.id FOR UPDATE")
List<InventoryPosition> lockInboundAvailablePositions(@Param("customerId") String customerId,
@Param("inboundOrderId") Long inboundOrderId);
@Select("SELECT p.* FROM ll_inventory_position p JOIN ll_inventory_balance b ON b.id=p.balance_id " +
"WHERE p.customer_id=#{customerId} AND b.inbound_order_id=#{inboundOrderId} " +
"AND p.hold_packages>0 ORDER BY p.id FOR UPDATE")
@@ -142,6 +142,17 @@ public class InventoryCommandService {
});
}
@Transactional
public void holdInboundOrderInventory(String customerId, String operatorId, long inboundOrderId,
String businessOrderNo, String remark) {
for (InventoryPosition position : commands.lockInboundAvailablePositions(customerId, inboundOrderId)) {
long packages = value(position.getAvailablePackages());
if (packages == 0) continue;
hold(customerId, operatorId, new InventoryCommandRequests.Move(position.getId(), packages,
businessOrderNo, null, null, "ORDER_HOLD:" + inboundOrderId + ":" + position.getId(), remark));
}
}
@Transactional
public void releaseInboundOrderHold(String customerId, String operatorId, long inboundOrderId,
String businessOrderNo, String remark) {
@@ -23,29 +23,32 @@ import org.springframework.web.server.ResponseStatusException;
@RestController
@RequestMapping("/api/merchant/v1/last-leg/local-looseboard-orders")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_LOOSEBOARD + "')")
public class MerchantLocalLooseboardController {
@Resource private LocalLooseboardService service;
@PostMapping
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_LOOSEBOARD_CREATE + "')")
public ApiResponse<LocalLooseboardResponses.Detail> create(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody LocalLooseboardRequests.Create request) {
return ApiResponse.success(service.create(customer(user), operator(user), request));
}
@GetMapping
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_LOOSEBOARD_LIST + "')")
public ApiResponse<PageResult<LocalLooseboardResponses.Summary>> page(
@AuthenticationPrincipal AuthenticatedUser user, @Valid LocalLooseboardRequests.MerchantQuery query) {
return ApiResponse.success(service.page(customer(user), query));
}
@GetMapping("/{orderNo}")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_LOOSEBOARD_LIST + "')")
public ApiResponse<LocalLooseboardResponses.Detail> detail(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String orderNo) {
return ApiResponse.success(service.detail(customer(user), orderNo));
}
@PutMapping("/{orderNo}")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_LOOSEBOARD_EDIT + "')")
public ApiResponse<LocalLooseboardResponses.Detail> update(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String orderNo, @Valid @RequestBody LocalLooseboardRequests.Update request) {
return ApiResponse.success(service.update(customer(user), orderNo, operator(user), request));
@@ -22,11 +22,11 @@ import org.springframework.web.server.ResponseStatusException;
@RestController
@RequestMapping("/api/merchant/v1/last-leg/overseas-procurements")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_PROCUREMENT + "')")
public class MerchantOverseasProcurementController {
@Resource private OverseasProcurementService service;
@PostMapping
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_PROCUREMENT_CREATE + "')")
public ApiResponse<OverseasProcurementResponses.Detail> create(
@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody OverseasProcurementRequests.Create request) {
@@ -34,12 +34,14 @@ public class MerchantOverseasProcurementController {
}
@GetMapping
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_PROCUREMENT_LIST + "')")
public ApiResponse<PageResult<OverseasProcurementResponses.Summary>> page(
@AuthenticationPrincipal AuthenticatedUser user, @Valid OverseasProcurementRequests.MerchantQuery query) {
return ApiResponse.success(service.page(customer(user), query.scoped()));
}
@GetMapping("/{orderNo}")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_PROCUREMENT_LIST + "')")
public ApiResponse<OverseasProcurementResponses.Detail> detail(
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String orderNo) {
return ApiResponse.success(service.detail(customer(user), orderNo));
@@ -6,9 +6,11 @@ import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.lastleg.relabel.dto.RelabelRequests;
import com.baiyue.lastleg.relabel.dto.RelabelResponses;
import com.baiyue.lastleg.relabel.service.RelabelApplicationService;
import com.baiyue.lastleg.security.OverseasPermissions;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
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;
@@ -24,6 +26,7 @@ import java.util.List;
public class MerchantRelabelApplicationController {
@Resource private RelabelApplicationService service;
@GetMapping("/inventory-candidates")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RELABEL_LIST + "')")
public ApiResponse<List<RelabelResponses.InventoryCandidate>> candidates(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam String operationWarehouseId,
@@ -31,6 +34,7 @@ public class MerchantRelabelApplicationController {
return ApiResponse.success(service.candidates(customer(user), operationWarehouseId, inboundCode));
}
@GetMapping("/historical-orders")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RELABEL_LIST + "')")
public ApiResponse<List<RelabelResponses.HistoricalOrder>> historicalOrders(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam String operationWarehouseId,
@@ -38,20 +42,25 @@ public class MerchantRelabelApplicationController {
return ApiResponse.success(service.historicalOrders(customer(user), operationWarehouseId, keyword));
}
@PostMapping
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RELABEL_CREATE + "')")
public ApiResponse<RelabelResponses.Detail> create(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody RelabelRequests.Create request) { return ApiResponse.success(service.create(customer(user), operator(user), request)); }
@GetMapping
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RELABEL_LIST + "')")
public ApiResponse<PageResult<RelabelResponses.Summary>> page(@AuthenticationPrincipal AuthenticatedUser user,
@Valid RelabelRequests.Query query) { return ApiResponse.success(service.page(customer(user), query)); }
@GetMapping("/{relabelNo}")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RELABEL_LIST + "')")
public ApiResponse<RelabelResponses.Detail> detail(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String relabelNo) { return ApiResponse.success(service.detail(customer(user), relabelNo)); }
@PostMapping(value = "/{relabelNo}/shipping-label", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RELABEL_FILES + "')")
public ApiResponse<RelabelResponses.Detail> uploadShippingLabel(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String relabelNo, @RequestPart("file") MultipartFile file) {
return ApiResponse.success(service.uploadShippingLabel(customer(user), relabelNo, operatorLong(user), file));
}
@PostMapping(value = "/{relabelNo}/files", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RELABEL_FILES + "')")
public ApiResponse<RelabelResponses.Detail> uploadFile(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String relabelNo, @RequestParam String scope, @RequestParam(required = false) Long itemId,
@RequestPart("file") MultipartFile file) {
@@ -60,18 +69,21 @@ public class MerchantRelabelApplicationController {
return ApiResponse.success(service.uploadTotalFile(customer(user), relabelNo, operatorLong(user), file, kind));
}
@GetMapping("/{relabelNo}/shipping-label/{fileId}/download")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RELABEL_LIST + "')")
public ResponseEntity<byte[]> downloadShippingLabel(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String relabelNo, @PathVariable long fileId) {
var file = service.downloadShippingLabel(customer(user), relabelNo, fileId);
return download(file.content(), file.fileName(), file.contentType());
}
@GetMapping("/{relabelNo}/completion-photos/{fileId}/download")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RELABEL_LIST + "')")
public ResponseEntity<byte[]> downloadCompletionPhoto(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String relabelNo, @PathVariable long fileId) {
var file = service.downloadCompletionPhoto(customer(user), relabelNo, fileId);
return download(file.content(), file.fileName(), file.contentType());
}
@PostMapping("/{relabelNo}/confirm-completion")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RELABEL_CONFIRM + "')")
public ApiResponse<RelabelResponses.Detail> confirmCompletion(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String relabelNo, @Valid @RequestBody RelabelRequests.CustomerConfirm request) {
return ApiResponse.success(service.confirmCompletion(customer(user), relabelNo, operator(user), request));
@@ -6,9 +6,11 @@ import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.lastleg.returnreceipt.dto.ReturnOrderRequests;
import com.baiyue.lastleg.returnreceipt.dto.ReturnOrderResponses;
import com.baiyue.lastleg.returnreceipt.service.ReturnReceiptService;
import com.baiyue.lastleg.security.OverseasPermissions;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
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;
@@ -17,17 +19,22 @@ import org.springframework.web.server.ResponseStatusException;
@RequestMapping("/api/merchant/v1/last-leg/return-orders")
public class MerchantReturnOrderController {
@Resource private ReturnReceiptService service;
@PostMapping public ApiResponse<ReturnOrderResponses.Detail> create(@AuthenticationPrincipal AuthenticatedUser user,
@PostMapping @PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RETURN_ORDER_CREATE + "')")
public ApiResponse<ReturnOrderResponses.Detail> create(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody ReturnOrderRequests.Create request) { return ApiResponse.success(service.create(customer(user), operator(user), request)); }
@GetMapping public ApiResponse<PageResult<ReturnOrderResponses.Summary>> page(@AuthenticationPrincipal AuthenticatedUser user,
@GetMapping @PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RETURN_ORDER_LIST + "')")
public ApiResponse<PageResult<ReturnOrderResponses.Summary>> page(@AuthenticationPrincipal AuthenticatedUser user,
@Valid ReturnOrderRequests.Query query) { return ApiResponse.success(service.page(customer(user), query)); }
@GetMapping("/{returnNo}") public ApiResponse<ReturnOrderResponses.Detail> detail(@AuthenticationPrincipal AuthenticatedUser user,
@GetMapping("/{returnNo}") @PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RETURN_ORDER_LIST + "')")
public ApiResponse<ReturnOrderResponses.Detail> detail(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String returnNo) { return ApiResponse.success(service.detail(customer(user), returnNo)); }
@GetMapping("/{returnNo}/relabel-candidates") public ApiResponse<java.util.List<ReturnOrderResponses.RelabelCandidate>> relabelCandidates(
@GetMapping("/{returnNo}/relabel-candidates") @PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RETURN_ORDER_RELABEL + "')")
public ApiResponse<java.util.List<ReturnOrderResponses.RelabelCandidate>> relabelCandidates(
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String returnNo) {
return ApiResponse.success(service.relabelCandidates(customer(user), returnNo));
}
@PostMapping("/{returnNo}/relabel") public ApiResponse<ReturnOrderResponses.RelabelCreated> createRelabel(
@PostMapping("/{returnNo}/relabel") @PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RETURN_ORDER_RELABEL + "')")
public ApiResponse<ReturnOrderResponses.RelabelCreated> createRelabel(
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String returnNo,
@Valid @RequestBody ReturnOrderRequests.RelabelCreate request) {
return ApiResponse.success(service.createRelabel(customer(user), operator(user), returnNo, request));
@@ -45,9 +45,29 @@ public final class OverseasPermissions {
public static final String MERCHANT_INBOUND_LIST = "merchant:warehouse:inbound:list";
public static final String MERCHANT_RETURN_INVENTORY_LIST =
"merchant:overseas-warehouse:return-inventory:list";
public static final String MERCHANT_PROCUREMENT = "merchant:cross-border:procurement";
public static final String MERCHANT_TRUCKING = "merchant:cross-border:domestic-trucking";
public static final String MERCHANT_LOOSEBOARD = "merchant:cross-border:pallet-shipping";
public static final String MERCHANT_LAST_MILE_ORDER_LIST =
"merchant:overseas-warehouse:last-mile-order:list";
public static final String MERCHANT_RELABEL_LIST = "merchant:overseas-warehouse:relabel:list";
public static final String MERCHANT_RELABEL_CREATE = "merchant:overseas-warehouse:relabel:create";
public static final String MERCHANT_RELABEL_FILES = "merchant:overseas-warehouse:relabel:files";
public static final String MERCHANT_RELABEL_CONFIRM = "merchant:overseas-warehouse:relabel:confirm";
public static final String MERCHANT_TRANSFER_LIST = "merchant:overseas-warehouse:transfer:list";
public static final String MERCHANT_TRANSFER_CREATE = "merchant:overseas-warehouse:transfer:create";
public static final String MERCHANT_TRANSFER_CONFIRM = "merchant:overseas-warehouse:transfer:confirm";
public static final String MERCHANT_DISCARD_LIST = "merchant:overseas-warehouse:discard:list";
public static final String MERCHANT_DISCARD_CREATE = "merchant:overseas-warehouse:discard:create";
public static final String MERCHANT_DISCARD_CANCEL = "merchant:overseas-warehouse:discard:cancel";
public static final String MERCHANT_RETURN_ORDER_LIST = "merchant:overseas-warehouse:return-order:list";
public static final String MERCHANT_RETURN_ORDER_CREATE = "merchant:overseas-warehouse:return-order:create";
public static final String MERCHANT_RETURN_ORDER_RELABEL = "merchant:overseas-warehouse:return-order:relabel";
public static final String MERCHANT_PROCUREMENT_LIST = "merchant:cross-border:procurement:list";
public static final String MERCHANT_PROCUREMENT_CREATE = "merchant:cross-border:procurement:create";
public static final String MERCHANT_TRUCKING_LIST = "merchant:cross-border:domestic-trucking:list";
public static final String MERCHANT_TRUCKING_CREATE = "merchant:cross-border:domestic-trucking:create";
public static final String MERCHANT_TRUCKING_EDIT = "merchant:cross-border:domestic-trucking:edit";
public static final String MERCHANT_LOOSEBOARD_LIST = "merchant:cross-border:pallet-shipping:list";
public static final String MERCHANT_LOOSEBOARD_CREATE = "merchant:cross-border:pallet-shipping:create";
public static final String MERCHANT_LOOSEBOARD_EDIT = "merchant:cross-border:pallet-shipping:edit";
public static final String PURCHASE_LIST = "platform:overseas-business:purchase:list";
public static final String PURCHASE_EDIT = "platform:overseas-business:purchase:edit";
public static final String PURCHASE_REVIEW = "platform:overseas-business:purchase:review";
@@ -15,6 +15,7 @@ import com.baiyue.lastleg.mapper.LastlegOrderManualNodeHistoryMapper;
import com.baiyue.lastleg.mapper.LastlegOrderManualNodeMapper;
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
import com.baiyue.lastleg.mapper.LastlegOrderOperationLogMapper;
import com.baiyue.lastleg.inventory.command.service.InventoryCommandService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
@@ -51,6 +52,7 @@ public class LastlegOrderCommandService {
@Resource private LastlegOrderManualNodeHistoryMapper manualNodeHistory;
@Resource private LastlegOrderOperationLogMapper operationLogs;
@Resource private InboundOrderDetainedOutboxService detainedOutbox;
@Resource private InventoryCommandService inventory;
@Resource private ObjectMapper objectMapper;
@Transactional
@@ -69,6 +71,9 @@ public class LastlegOrderCommandService {
if (Boolean.TRUE.equals(order.getDetained()) == held) {
throw conflict(held ? "尾程订单已暂扣" : "尾程订单未处于暂扣状态");
}
if (held && !"INBOUND_COMPLETED".equals(order.getCurrentStatus())) {
throw conflict("货物完成入库后才能暂扣");
}
if (held && (positive(order.getPickedPackageCount()) || positive(order.getShippedPackageCount())
|| text(order.getOutboundOrderNo()) != null)) {
throw conflict("尾程订单已进入出库流程,不能新增暂扣");
@@ -79,6 +84,13 @@ public class LastlegOrderCommandService {
if (orders.updateHold(order.getCustomerId(), orderId, held, chargeable, reason, version, now) != 1) {
throw conflict("尾程订单版本已变化,请刷新后重试");
}
if (held) {
inventory.holdInboundOrderInventory(order.getCustomerId(), operatorId, orderId,
order.getOrderNo(), reason);
} else {
inventory.releaseInboundOrderHold(order.getCustomerId(), operatorId, orderId,
order.getOrderNo(), reason);
}
int nextVersion = version + 1;
LastlegOrderCommandDtos.HoldResponse response = new LastlegOrderCommandDtos.HoldResponse(
orderId, held, chargeable, held ? reason : null, nextVersion);
@@ -49,7 +49,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -80,7 +79,6 @@ public class LastlegOrderService {
@Resource private InboundPlanCartonMapper cartonMapper;
@Resource private FbaWarehouseValidator fbaWarehouseValidator;
@Resource private InventoryCommandService inventory;
@Resource private InboundOrderDetainedOutboxService detainedOutbox;
@Resource private ReceivingWarehouseOperationMapper receivingWarehouseOperations;
@Resource private ContainerArrivalTimeOperationMapper containerArrivalTimeOperations;
@Resource private WarehouseResourceGateway warehouseResourceGateway;
@@ -388,47 +386,6 @@ public class LastlegOrderService {
return fee;
}
@Transactional
public void setDetained(String customerId, long orderId, LastlegOrderRequests.SetDetained request,
String operatorName) {
setDetained(customerId, orderId, request, null, operatorName);
}
@Transactional
public void setDetained(String customerId, long orderId, LastlegOrderRequests.SetDetained request,
String operatorId, String operatorName) {
LastlegOrder current = findOwned(customerId, orderId);
LastlegOrder order = orderMapper.lockById(current.getCustomerId(), orderId);
if (order == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Tail-leg order not found");
boolean detained = request.detained();
if (Boolean.TRUE.equals(order.getDetained()) == detained) {
throw new ResponseStatusException(HttpStatus.CONFLICT, detained ? "Order is already held" : "Order is not held");
}
if (detained && "INBOUND_COMPLETED".equals(order.getCurrentStatus())) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "Completed inbound orders cannot be newly held");
}
LocalDateTime now = LocalDateTime.now();
order.setDetained(detained);
order.setUpdatedAt(now);
orderMapper.updateById(order);
if (!detained && "INBOUND_COMPLETED".equals(order.getCurrentStatus())) {
inventory.releaseInboundOrderHold(order.getCustomerId(), operatorName, orderId, order.getOrderNo(), request.remark().trim());
}
LastlegOrderOperationLog operation = new LastlegOrderOperationLog();
operation.setId(IdWorker.getId());
operation.setCustomerId(order.getCustomerId());
operation.setOrderId(orderId);
operation.setOperationNode(detained ? "暂扣" : "解除暂扣");
operation.setOperatorName(operatorName);
operation.setResult(request.remark().trim());
operation.setCreatedAt(now);
operationLogMapper.insert(operation);
if (StringUtils.hasText(order.getSourceInboundId())) {
detainedOutbox.publish(order, detained, request.remark().trim(), operatorId, operatorName,
now.toInstant(ZoneOffset.UTC), operation.getId());
}
}
@Transactional
public void setPalletMergeRequired(String customerId, long orderId,
LastlegOrderRequests.SetPalletMergeRequired request,
@@ -6,9 +6,11 @@ import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.lastleg.transfer.dto.WarehouseTransferRequests;
import com.baiyue.lastleg.transfer.dto.WarehouseTransferResponses;
import com.baiyue.lastleg.transfer.service.WarehouseTransferService;
import com.baiyue.lastleg.security.OverseasPermissions;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
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;
@@ -27,6 +29,7 @@ public class MerchantWarehouseTransferController {
@Resource private WarehouseTransferService service;
@PostMapping
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_TRANSFER_CREATE + "')")
public ApiResponse<WarehouseTransferResponses.Detail> create(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody WarehouseTransferRequests.Create request) {
return ApiResponse.success(service.create(customer(user), operator(user), request));
@@ -57,12 +60,14 @@ public class MerchantWarehouseTransferController {
}
@GetMapping
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_TRANSFER_LIST + "')")
public ApiResponse<PageResult<WarehouseTransferResponses.Summary>> page(@AuthenticationPrincipal AuthenticatedUser user,
@Valid WarehouseTransferRequests.Query query) {
return ApiResponse.success(service.page(customer(user), query));
}
@GetMapping("/{transferNo}")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_TRANSFER_LIST + "')")
public ApiResponse<WarehouseTransferResponses.Detail> detail(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String transferNo) {
return ApiResponse.success(service.detail(customer(user), transferNo));
@@ -92,6 +97,7 @@ public class MerchantWarehouseTransferController {
}
@PostMapping("/{transferNo}/confirm-measurement")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_TRANSFER_CONFIRM + "')")
public ApiResponse<WarehouseTransferResponses.Detail> confirmMeasurement(
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String transferNo,
@Valid @RequestBody WarehouseTransferRequests.CustomerConfirm request) {
@@ -23,29 +23,32 @@ import org.springframework.web.server.ResponseStatusException;
@RestController
@RequestMapping("/api/merchant/v1/last-leg/us-truck-orders")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_TRUCKING + "')")
public class MerchantUsTruckOrderController {
@Resource private UsTruckOrderService service;
@PostMapping
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_TRUCKING_CREATE + "')")
public ApiResponse<UsTruckOrderResponses.Detail> create(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody UsTruckOrderRequests.Create request) {
return ApiResponse.success(service.create(customer(user), operator(user), request));
}
@GetMapping
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_TRUCKING_LIST + "')")
public ApiResponse<PageResult<UsTruckOrderResponses.Summary>> page(@AuthenticationPrincipal AuthenticatedUser user,
@Valid UsTruckOrderRequests.MerchantQuery query) {
return ApiResponse.success(service.page(customer(user), query.scoped()));
}
@GetMapping("/{orderNo}")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_TRUCKING_LIST + "')")
public ApiResponse<UsTruckOrderResponses.Detail> detail(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String orderNo) {
return ApiResponse.success(service.detail(customer(user), orderNo));
}
@PatchMapping("/{orderNo}")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_TRUCKING_EDIT + "')")
public ApiResponse<UsTruckOrderResponses.Detail> update(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String orderNo, @Valid @RequestBody UsTruckOrderRequests.Update request) {
return ApiResponse.success(service.update(customer(user), orderNo, operator(user), request));