fix(lastleg): route all relabel pickups directly to outbound and verify final schemas
This commit is contained in:
+2
-2
@@ -244,7 +244,7 @@ public interface DispatchCandidateMapper {
|
||||
"LEFT JOIN ll_warehouse_transfer_order wto ON wto.relabel_no=a.relabel_no AND wto.customer_id=a.customer_id " +
|
||||
"LEFT JOIN ll_warehouse_transfer_pallet wtp ON wtp.transfer_order_id=wto.id AND wtp.customer_id=a.customer_id " +
|
||||
"AND wtp.warehouse_pallet_id=pos.pallet_id " +
|
||||
"WHERE a.delivery_method!='EXPRESS' AND a.status='READY_FOR_OUTBOUND' AND a.fulfillment_status='WAIT_PRE_DISPATCH' " +
|
||||
"WHERE a.delivery_method='TRUCK' AND a.status='READY_FOR_OUTBOUND' AND a.fulfillment_status='WAIT_PRE_DISPATCH' " +
|
||||
"<if test='customerId != null and customerId != \"\"'>AND a.customer_id=#{customerId} </if>" +
|
||||
"<if test='relabelNo != null and relabelNo != \"\"'>AND a.relabel_no=#{relabelNo} </if>" +
|
||||
"<if test='orderNo != null and orderNo != \"\"'>AND (a.relabel_no LIKE CONCAT('%',#{orderNo},'%') OR ri.source_inbound_code LIKE CONCAT('%',#{orderNo},'%') OR ri.source_inbound_order_no LIKE CONCAT('%',#{orderNo},'%')) </if>" +
|
||||
@@ -271,7 +271,7 @@ public interface DispatchCandidateMapper {
|
||||
"FROM ll_relabel_application a JOIN ll_relabel_item ri ON ri.application_id=a.id AND ri.customer_id=a.customer_id " +
|
||||
"JOIN ll_return_inventory_carton c ON c.id=ri.return_carton_id AND c.customer_id=a.customer_id " +
|
||||
"AND c.relabel_application_id=a.id AND c.overseas_warehouse_id=a.overseas_warehouse_id " +
|
||||
"WHERE a.delivery_method!='EXPRESS' AND a.status='READY_FOR_OUTBOUND' AND a.fulfillment_status='WAIT_PRE_DISPATCH' " +
|
||||
"WHERE a.delivery_method='TRUCK' AND a.status='READY_FOR_OUTBOUND' AND a.fulfillment_status='WAIT_PRE_DISPATCH' " +
|
||||
"AND c.status='RELABEL_PENDING' AND c.pallet_code IS NOT NULL " +
|
||||
"<if test='customerId != null and customerId != \"\"'>AND a.customer_id=#{customerId} </if>" +
|
||||
"<if test='relabelNo != null and relabelNo != \"\"'>AND a.relabel_no=#{relabelNo} </if>" +
|
||||
|
||||
+5
-4
@@ -116,8 +116,8 @@ public class DispatchService {
|
||||
if (!RelabelApplicationService.READY_FOR_OUTBOUND.equals(application.getStatus())) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "换标尚未完成或客户尚未确认,无法进行排车");
|
||||
}
|
||||
if ("EXPRESS".equals(application.getDeliveryMethod())) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "该换标单为快递派送,无需进行排车");
|
||||
if (!"TRUCK".equals(application.getDeliveryMethod())) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "只有平台卡车派送的换标单可以排车");
|
||||
}
|
||||
return application.getCustomerId();
|
||||
}
|
||||
@@ -607,10 +607,11 @@ public class DispatchService {
|
||||
if (application == null || !RelabelApplicationService.READY_FOR_OUTBOUND.equals(application.getStatus())) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "换标尚未完成或客户尚未确认,无法进行排车");
|
||||
}
|
||||
if (!"TRUCK".equals(application.getDeliveryMethod())
|
||||
|| !"WAIT_PRE_DISPATCH".equals(application.getFulfillmentStatus()))
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "换标单不是待排车状态");
|
||||
relabelBindingGuard.requireBound(application);
|
||||
relabelMeasurements.requireMeasured(application);
|
||||
if ("EXPRESS".equals(application.getDeliveryMethod()) || !"WAIT_PRE_DISPATCH".equals(application.getFulfillmentStatus()))
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "换标单不是待排车状态");
|
||||
List<DispatchPalletCandidate> selected = java.util.stream.Stream.concat(
|
||||
measuredRelabelCandidates(candidates.relabelAvailable(customerId, null, request.relabelNo())).stream(),
|
||||
returnRelabelStock.candidates(customerId, null, request.relabelNo()).stream()).toList();
|
||||
|
||||
+10
-2
@@ -512,7 +512,7 @@ public class PdaRelabelService {
|
||||
updateTask(task, request.expectedVersion());
|
||||
insertHistory(task, "COMPLETE", before, WAIT_REVIEW, operatorKey, operatorName,
|
||||
Map.of("photoFileIds", photoIds, "remark", Objects.toString(trim(request.remark()), ""),
|
||||
"manualCompletion", request.manualCompletion()));
|
||||
"manualCompletion", request.manualCompletion(), "roundKey", mixedRound(task)));
|
||||
applicationService.changeStatus(application.getCustomerId(), relabelNo, operatorKey,
|
||||
new RelabelRequests.ChangeStatus(RelabelApplicationService.CUSTOMER_CONFIRM_PENDING, request.remark()));
|
||||
applicationService.settlePdaCompletion(application.getCustomerId(), relabelNo, operatorKey);
|
||||
@@ -816,7 +816,15 @@ public class PdaRelabelService {
|
||||
task == null ? null : task.getCompletionRemark(), task == null ? null : task.getReviewDecision(),
|
||||
task == null ? null : task.getReviewRemark(), task == null ? null : task.getClaimedAt(),
|
||||
task == null ? null : task.getSubmittedAt(), task == null ? null : task.getReviewedAt(),
|
||||
itemResponses, factResponses, historyResponses, application.getDeliveryMethod(), !"EXPRESS".equals(application.getDeliveryMethod()));
|
||||
itemResponses, factResponses, historyResponses, application.getDeliveryMethod(),
|
||||
detailItems.stream().anyMatch(item -> {
|
||||
String operation = operationType(item);
|
||||
if (item.getReturnCartonId() == null)
|
||||
return Set.of("CARTON_LABEL", "CARTON_AND_SKU", "RETURN_BULK_SKU_LABEL").contains(operation);
|
||||
if ("SKU_LABEL".equals(operation) && application.getDeliveryMethod() == null) return false;
|
||||
return !"EXPRESS".equals(application.getDeliveryMethod()) && !"PICKUP".equals(application.getDeliveryMethod())
|
||||
&& !"AMAZON_PICKUP".equals(application.getDeliveryMethod()) && !"CUSTOMER_PICKUP".equals(application.getDeliveryMethod());
|
||||
}));
|
||||
}
|
||||
|
||||
private String effectiveTaskStatus(RelabelApplication application, PdaRelabelTask task) {
|
||||
|
||||
+15
-5
@@ -94,6 +94,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
@Resource private ReturnInventoryCartonMapper returnCartons;
|
||||
@Resource private ReturnInventoryCartonItemMapper returnCartonItems;
|
||||
@Resource private com.baiyue.lastleg.relabel.service.ReturnSkuConversionService skuConversions;
|
||||
@Resource private RelabelBindingGuard bindingGuard;
|
||||
@Resource private com.baiyue.lastleg.returnreceipt.mapper.ReturnSkuConversionMapper skuConversionRows;
|
||||
@Resource private ReturnOrderMapper returnOrders;
|
||||
@Resource private com.baiyue.lastleg.returnreceipt.mapper.ReturnPackingTraceMapper returnPackingTraces;
|
||||
@@ -368,7 +369,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
|| request.items().stream().anyMatch(item -> !"SKU_LABEL".equals(operationType(item)));
|
||||
String deliveryMethod = trim(request.deliveryMethod());
|
||||
if (requiresOutbound && (!StringUtils.hasText(deliveryMethod) || !DELIVERY_METHODS.contains(deliveryMethod))) {
|
||||
throw badRequest("Delivery method must be TRUCK, PICKUP or EXPRESS when carton labels are changed");
|
||||
throw badRequest("箱唛换标请选择平台卡车、快递、亚马逊自提、客户自提或自提");
|
||||
}
|
||||
if (!requiresOutbound && StringUtils.hasText(deliveryMethod)) {
|
||||
throw badRequest("SKU-only relabel does not create an outbound fulfillment");
|
||||
@@ -933,6 +934,8 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
throw conflict("Upload the shipping label before confirming relabel completion");
|
||||
}
|
||||
List<RelabelItem> relabelItems = itemList(application.getId());
|
||||
// Manual warehouse completion does not waive physical binding at customer confirmation.
|
||||
bindingGuard.requireBound(application);
|
||||
skuConversions.confirm(application);
|
||||
WarehouseTransferOrder linkedTransfer = warehouseTransfers == null ? null
|
||||
: warehouseTransfers.lockByRelabelNo(customerId, relabelNo);
|
||||
@@ -940,6 +943,10 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
&& StringUtils.hasText(linkedTransfer.getAllocationIdempotencyKey());
|
||||
boolean requiresOutbound = relabelItems.stream()
|
||||
.anyMatch(item -> Set.of("CARTON_LABEL", "CARTON_AND_SKU").contains(operationType(item)));
|
||||
boolean directOutbound = "EXPRESS".equals(application.getDeliveryMethod())
|
||||
|| "PICKUP".equals(application.getDeliveryMethod())
|
||||
|| "AMAZON_PICKUP".equals(application.getDeliveryMethod())
|
||||
|| "CUSTOMER_PICKUP".equals(application.getDeliveryMethod());
|
||||
reviewTask.setStatus("COMPLETED"); reviewTask.setReviewDecision("CONFIRMED");
|
||||
reviewTask.setReviewerKey(operatorId); reviewTask.setReviewerName("客户确认");
|
||||
reviewTask.setReviewRemark(request.remark()); reviewTask.setReviewedAt(LocalDateTime.now());
|
||||
@@ -951,9 +958,9 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
application.setCustomerConfirmationKey(key);
|
||||
application.setCustomerConfirmedAt(now);
|
||||
application.setFulfillmentStatus(requiresOutbound
|
||||
? ("EXPRESS".equals(application.getDeliveryMethod()) ? "WAIT_OUTBOUND_CREATION" : "WAIT_PRE_DISPATCH")
|
||||
? (directOutbound ? "WAIT_OUTBOUND_CREATION" : "WAIT_PRE_DISPATCH")
|
||||
: "NOT_REQUIRED");
|
||||
if (requiresOutbound && !existingStockTransfer && "EXPRESS".equals(application.getDeliveryMethod())) {
|
||||
if (requiresOutbound && !existingStockTransfer && directOutbound) {
|
||||
createDirectOutbound(application, operatorId, now);
|
||||
application.setFulfillmentStatus("OUTBOUND_CREATED");
|
||||
}
|
||||
@@ -993,7 +1000,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
carton.setRelabelStatus("COMPLETED"); carton.setRelabelCompletedAt(now);
|
||||
// 换标已完成后释放“当前申请占用”关系,但保留 COMPLETED 作为历史状态。
|
||||
// 否则退货箱虽显示 AVAILABLE,下一次贴标申请仍会被旧 relabel_application_id 拦截。
|
||||
boolean awaitingTruck = requiresOutbound && !"EXPRESS".equals(application.getDeliveryMethod());
|
||||
boolean awaitingTruck = requiresOutbound && !directOutbound;
|
||||
if (!StringUtils.hasText(application.getOutboundOrderNo()) && !awaitingTruck) {
|
||||
carton.setRelabelApplicationId(null); carton.setRelabelNo(null);
|
||||
}
|
||||
@@ -1050,6 +1057,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
boolean relabelHasDestination = !relabelItems.isEmpty() && relabelItems.stream()
|
||||
.allMatch(item -> StringUtils.hasText(item.getDestinationFbaWarehouseCode()));
|
||||
application.setFulfillmentStatus(existingStockTransfer ? "WAIT_TRANSFER_OUTBOUND"
|
||||
: StringUtils.hasText(application.getOutboundOrderNo()) ? "OUTBOUND_CREATED"
|
||||
: "READY_FOR_DISPATCH".equals(transferStatus) || relabelHasDestination
|
||||
? "WAIT_PRE_DISPATCH" : transferStatus);
|
||||
applications.updateById(application);
|
||||
@@ -1085,6 +1093,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
if (source.getReturnCartonId() == null) continue;
|
||||
ReturnInventoryCarton carton = returnCartons.lockId(source.getReturnCartonId());
|
||||
if (carton == null || !application.getCustomerId().equals(carton.getCustomerId())
|
||||
|| !Objects.equals(application.getOverseasWarehouseId(), carton.getOverseasWarehouseId())
|
||||
|| !application.getId().equals(carton.getRelabelApplicationId())
|
||||
|| !"RELABEL_PENDING".equals(carton.getStatus())
|
||||
|| intValue(source.getNotifiedPackageCount()) != 1)
|
||||
@@ -1103,7 +1112,8 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
this::outboundSourceKey, LinkedHashMap::new, Collectors.toList()));
|
||||
for (Map.Entry<Long, List<RelabelItem>> entry : byPosition.entrySet()) {
|
||||
InventoryPosition position = entry.getKey() > 0 ? inventoryPositions.selectById(entry.getKey()) : null;
|
||||
if (entry.getKey() > 0 && (position == null || !application.getCustomerId().equals(position.getCustomerId()))) {
|
||||
if (entry.getKey() > 0 && (position == null || !application.getCustomerId().equals(position.getCustomerId())
|
||||
|| !Objects.equals(application.getOverseasWarehouseId(), position.getOverseasWarehouseId()))) {
|
||||
throw conflict("Relabel source inventory position no longer exists");
|
||||
}
|
||||
List<RelabelItem> group = entry.getValue();
|
||||
|
||||
+54
-8
@@ -22,6 +22,7 @@ public class RelabelBindingGuard {
|
||||
@Resource private PdaRelabelHistoryMapper histories;
|
||||
@Resource private InventoryPositionMapper positions;
|
||||
@Resource private ReturnInventoryCartonMapper returnCartons;
|
||||
@Resource private com.baiyue.lastleg.returnreceipt.mapper.ReturnSkuConversionMapper conversions;
|
||||
@Resource private com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonItemMapper returnLines;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
|
||||
@@ -32,10 +33,15 @@ public class RelabelBindingGuard {
|
||||
.contains(item.getOperationType())).toList();
|
||||
if (cartons.isEmpty()) return;
|
||||
Map<String, Set<String>> bindings = new HashMap<>();
|
||||
String currentRound = null;
|
||||
for (PdaRelabelHistory history : histories.selectList(new QueryWrapper<PdaRelabelHistory>()
|
||||
.eq("application_id", app.getId()).eq("customer_id", app.getCustomerId())
|
||||
.orderByAsc("created_at", "id"))) {
|
||||
if ("REWORK_ARCHIVE".equals(history.getAction())) bindings.clear();
|
||||
if ("REWORK_ARCHIVE".equals(history.getAction())) { bindings.clear(); currentRound = null; }
|
||||
if ("COMPLETE".equals(history.getAction())) {
|
||||
try { currentRound = objectMapper.readTree(history.getDetailJson()).path("roundKey").asText(null); }
|
||||
catch (Exception failure) { throw conflict("换标完工轮次记录无法读取"); }
|
||||
}
|
||||
if (!"PALLET_BIND".equals(history.getAction())) continue;
|
||||
try {
|
||||
var data = objectMapper.readTree(history.getDetailJson());
|
||||
@@ -45,20 +51,26 @@ public class RelabelBindingGuard {
|
||||
} catch (Exception e) { throw conflict("组托记录无法读取,请核对后重试"); }
|
||||
}
|
||||
for (RelabelItem item : cartons) {
|
||||
var sourceCarton = item.getReturnCartonId() == null ? null : returnCartons.selectById(item.getReturnCartonId());
|
||||
if (item.getReturnCartonId() != null && (sourceCarton == null
|
||||
|| !Objects.equals(app.getCustomerId(), sourceCarton.getCustomerId())
|
||||
|| !Objects.equals(app.getOverseasWarehouseId(), sourceCarton.getOverseasWarehouseId())))
|
||||
throw conflict("退货箱来源缺失或仓库不一致");
|
||||
if (item.getReturnCartonId() != null && !Set.of("RETURN_BULK_SKU_LABEL", "SKU_LABEL").contains(item.getOperationType())) {
|
||||
var carton = returnCartons.selectById(item.getReturnCartonId());
|
||||
if (carton == null || !app.getCustomerId().equals(carton.getCustomerId())) throw conflict("退货箱来源缺失");
|
||||
var carton = sourceCarton;
|
||||
if ("CARTON_AND_SKU".equals(item.getOperationType())) {
|
||||
var expected = new HashMap<String, Long>();
|
||||
try {
|
||||
for (var sku : objectMapper.readTree(item.getSkuRelabelItemsJson()))
|
||||
expected.merge(sku.path("skuCode").asText().toUpperCase(Locale.ROOT), sku.path("quantity").asLong(), Long::sum);
|
||||
} catch (Exception failure) { throw conflict("混贴 SKU 明细无法读取"); }
|
||||
projectCurrentContents(app, List.of(item), expected, currentRound);
|
||||
var actual = new HashMap<String, Long>();
|
||||
returnLines.lockCarton(item.getReturnCartonId()).forEach(line -> actual.merge(line.getSkuCode().toUpperCase(Locale.ROOT), line.getQuantity(), Long::sum));
|
||||
if (!expected.equals(actual)) throw conflict("包裹/箱混贴必须包含实物全部 SKU 和件数;部分数量请先隔离为独立新箱");
|
||||
}
|
||||
if ("EXPRESS".equals(app.getDeliveryMethod())) continue;
|
||||
if ("EXPRESS".equals(app.getDeliveryMethod()) || "PICKUP".equals(app.getDeliveryMethod())
|
||||
|| "AMAZON_PICKUP".equals(app.getDeliveryMethod()) || "CUSTOMER_PICKUP".equals(app.getDeliveryMethod())) continue;
|
||||
if (carton.getPalletCode() == null || !bindings.getOrDefault(item.getCartonMark(), Set.of()).contains(carton.getPalletCode()))
|
||||
throw conflict("退货箱尚未完成排车托盘绑定");
|
||||
continue;
|
||||
@@ -75,20 +87,54 @@ public class RelabelBindingGuard {
|
||||
}
|
||||
var actual = new HashMap<String, Long>();
|
||||
stock.forEach(i -> actual.merge(i.getSkuCode().toUpperCase(Locale.ROOT), i.getQuantity(), Long::sum));
|
||||
projectCurrentContents(app, sameCarton, selected, currentRound);
|
||||
if (!selected.equals(actual)) throw conflict("部分换标货物必须先合箱生成独立新箱,再提交完工");
|
||||
if ("EXPRESS".equals(app.getDeliveryMethod())) continue;
|
||||
var carton = item.getReturnCartonId() == null ? null : returnCartons.selectById(item.getReturnCartonId());
|
||||
if (carton == null || carton.getPalletCode() == null || !bindings.getOrDefault(item.getCartonMark(), Set.of()).contains(carton.getPalletCode())
|
||||
// SKU-only work has no outbound fulfillment, but must still cover the isolated carton.
|
||||
if ("SKU_LABEL".equals(item.getOperationType()) && app.getDeliveryMethod() == null) continue;
|
||||
if ("EXPRESS".equals(app.getDeliveryMethod()) || "PICKUP".equals(app.getDeliveryMethod())
|
||||
|| "AMAZON_PICKUP".equals(app.getDeliveryMethod()) || "CUSTOMER_PICKUP".equals(app.getDeliveryMethod())) continue;
|
||||
var carton = sourceCarton;
|
||||
if (carton.getPalletCode() == null || !bindings.getOrDefault(item.getCartonMark(), Set.of()).contains(carton.getPalletCode())
|
||||
|| !"PROCESSING".equals(carton.getRelabelStatus()))
|
||||
throw conflict("退货箱 " + item.getCartonMark() + " 尚未绑定带目的仓的新托盘,不能完成换标");
|
||||
continue;
|
||||
}
|
||||
var position = item.getSourceInventoryPositionId() == null ? null
|
||||
: positions.findTenantPosition(app.getCustomerId(), item.getSourceInventoryPositionId());
|
||||
if (position == null || position.getPalletNo() == null || !bindings.getOrDefault(item.getCartonMark(), Set.of()).contains(position.getPalletNo())
|
||||
if (position == null || !Objects.equals(app.getOverseasWarehouseId(), position.getOverseasWarehouseId())
|
||||
|| position.getPalletNo() == null || !bindings.getOrDefault(item.getCartonMark(), Set.of()).contains(position.getPalletNo())
|
||||
|| position.getHoldPackages() == null || position.getHoldPackages() < item.getNotifiedPackageCount())
|
||||
throw conflict("箱唛 " + item.getCartonMark() + " 尚未完成托盘绑定,请先在 PDA 确认组托,不能完成或排车出库");
|
||||
}
|
||||
}
|
||||
|
||||
private void projectCurrentContents(RelabelApplication app, List<RelabelItem> selectedItems,
|
||||
Map<String, Long> expected, String currentRound) {
|
||||
if (!"CUSTOMER_CONFIRM_PENDING".equals(app.getStatus()) && !"READY_FOR_OUTBOUND".equals(app.getStatus())
|
||||
&& !"COMPLETED".equals(app.getStatus())) return;
|
||||
if (currentRound == null || currentRound.isBlank()) throw conflict("换标完工轮次缺失,不能核对当前 SKU 库存");
|
||||
// Warehouse completion has already applied SKU conversions. Confirmation and dispatch
|
||||
// compare against that round's persisted changes, never assume that every new SKU exists.
|
||||
Map<String, Long> outgoing = new HashMap<>(), incoming = new HashMap<>();
|
||||
for (var row : conversions.lockApplication(app.getCustomerId(), app.getId())) {
|
||||
if (!Objects.equals(row.getCustomerId(), app.getCustomerId()) || !Objects.equals(row.getApplicationId(), app.getId())
|
||||
|| !Objects.equals(row.getRoundKey(), currentRound)
|
||||
|| !("APPLIED".equals(row.getStatus()) || "CONFIRMED".equals(row.getStatus()))
|
||||
|| selectedItems.stream().noneMatch(item -> Objects.equals(item.getId(), row.getItemId())
|
||||
&& Objects.equals(item.getReturnCartonId(), row.getCartonId()))) continue;
|
||||
String oldSku = row.getOldSkuCode().toUpperCase(Locale.ROOT);
|
||||
String newSku = row.getNewSkuCode().toUpperCase(Locale.ROOT);
|
||||
long quantity = Objects.requireNonNullElse(row.getQuantity(), 0L);
|
||||
if (quantity <= 0) throw conflict("换标转换数量与批准明细不一致");
|
||||
outgoing.merge(oldSku, quantity, Math::addExact);
|
||||
incoming.merge(newSku, quantity, Math::addExact);
|
||||
}
|
||||
for (var source : outgoing.entrySet()) {
|
||||
long remaining = expected.getOrDefault(source.getKey(), 0L) - source.getValue();
|
||||
if (remaining < 0) throw conflict("换标转换数量与批准明细不一致");
|
||||
if (remaining == 0) expected.remove(source.getKey()); else expected.put(source.getKey(), remaining);
|
||||
}
|
||||
incoming.forEach((sku, quantity) -> expected.merge(sku, quantity, Math::addExact));
|
||||
}
|
||||
private ResponseStatusException conflict(String message) { return new ResponseStatusException(HttpStatus.CONFLICT, message); }
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
-- DDL proposal only. The coordinator must integrate the approved baseline; do not execute from this task.
|
||||
CREATE TABLE IF NOT EXISTS ll_dispatch_appointment_allocation (
|
||||
dispatch_id BIGINT NOT NULL,
|
||||
dispatch_item_id BIGINT NOT NULL,
|
||||
plan_pallet_id BIGINT NOT NULL,
|
||||
batch_key VARCHAR(96) NOT NULL,
|
||||
customer_id VARCHAR(64) NOT NULL,
|
||||
source_key VARCHAR(160) NOT NULL,
|
||||
destination_key VARCHAR(160) NOT NULL,
|
||||
package_count INT NOT NULL,
|
||||
dispatch_id BIGINT NOT NULL COMMENT '排车单标识',
|
||||
dispatch_item_id BIGINT NOT NULL COMMENT '排车明细标识',
|
||||
plan_pallet_id BIGINT NOT NULL COMMENT '入仓计划托盘标识',
|
||||
batch_key VARCHAR(96) NOT NULL COMMENT '预约稳定批次键',
|
||||
customer_id VARCHAR(64) NOT NULL COMMENT '所属客户标识',
|
||||
source_key VARCHAR(160) NOT NULL COMMENT '货物来源稳定键',
|
||||
destination_key VARCHAR(160) NOT NULL COMMENT '目的地稳定键',
|
||||
package_count INT NOT NULL COMMENT '本票分配实物箱数',
|
||||
PRIMARY KEY(dispatch_item_id,batch_key,source_key,destination_key),
|
||||
KEY idx_dispatch_appointment_batch(batch_key,customer_id),
|
||||
KEY idx_dispatch_appointment_dispatch(dispatch_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='排车实物与预约批次逐票分配,原执行域事实';
|
||||
CREATE TABLE IF NOT EXISTS ll_appointment_work_commitment (
|
||||
entity_key VARCHAR(160) NOT NULL,
|
||||
entity_key VARCHAR(160) NOT NULL COMMENT '原业务实体稳定键',
|
||||
state_json LONGTEXT NOT NULL COMMENT '原业务命令产生的只读语义影响基线,不是第二份可编辑计划',
|
||||
impact_version BIGINT NOT NULL DEFAULT 0,
|
||||
impact_version BIGINT NOT NULL DEFAULT 0 COMMENT '承诺影响单调递增版本',
|
||||
PRIMARY KEY (entity_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作事项承诺影响审计基线';
|
||||
CREATE TABLE IF NOT EXISTS ll_appointment_work_guard (
|
||||
|
||||
@@ -2210,6 +2210,7 @@ CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_source_order (
|
||||
) ENGINE=InnoDB COMMENT='转仓来源历史订单绑定';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_item (
|
||||
declared_quantity BIGINT NULL COMMENT 'SKU申报件数,非预计或实收箱数',
|
||||
id BIGINT NOT NULL COMMENT '主键ID', transfer_order_id BIGINT NOT NULL COMMENT '转仓单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID',
|
||||
sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码', product_name_zh VARCHAR(255) NOT NULL COMMENT '产品中文名称',
|
||||
package_count INT NOT NULL COMMENT '箱数', shipping_mark VARCHAR(255) NULL COMMENT '运输唛头',
|
||||
@@ -2218,8 +2219,21 @@ CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_item (
|
||||
) ENGINE=InnoDB COMMENT='转仓货物冻结明细';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_carton (
|
||||
length_cm DECIMAL(12,2) NULL COMMENT '箱子实测长度厘米',
|
||||
width_cm DECIMAL(12,2) NULL COMMENT '箱子实测宽度厘米',
|
||||
height_cm DECIMAL(12,2) NULL COMMENT '箱子实测高度厘米',
|
||||
gross_weight_kg DECIMAL(12,2) NULL COMMENT '箱子实测重量千克',
|
||||
counted_quantity BIGINT NULL COMMENT '箱内SKU实点件数',
|
||||
count_idempotency_key VARCHAR(255) NULL COMMENT '箱内清点幂等键',
|
||||
count_request_hash VARCHAR(64) NULL COMMENT '箱内清点请求摘要',
|
||||
counted_by VARCHAR(128) NULL COMMENT '箱内清点操作人',
|
||||
counted_at DATETIME(3) NULL COMMENT '箱内清点完成时间',
|
||||
discrepancy_reason VARCHAR(1000) NULL COMMENT '实收与预报箱数差异原因',
|
||||
transfer_pallet_id BIGINT NULL COMMENT '签收箱所属转运托盘事实ID',
|
||||
sku_items_json JSON NULL COMMENT '实收箱内SKU及逐项件数明细',
|
||||
id BIGINT NOT NULL COMMENT '预报箱主键', transfer_order_id BIGINT NOT NULL COMMENT '转运单ID',
|
||||
transfer_item_id BIGINT NOT NULL COMMENT '转运申报明细ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID',
|
||||
customer_carton_mark VARCHAR(255) NULL COMMENT '客户原箱唛,用于换标现场绑定',
|
||||
carton_mark VARCHAR(255) NOT NULL COMMENT '系统生成的一箱一码箱唛', carton_index INT NOT NULL COMMENT '全单箱序号',
|
||||
total_cartons INT NOT NULL COMMENT '首次生成时全单总箱数', status VARCHAR(32) NOT NULL DEFAULT 'EXPECTED' COMMENT '箱状态:EXPECTED待实收',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||
@@ -2370,7 +2384,7 @@ CREATE TABLE IF NOT EXISTS ll_relabel_application (
|
||||
label_deadline DATETIME(3) NOT NULL COMMENT '换标截止时间',
|
||||
contact_name VARCHAR(128) NOT NULL COMMENT '联系人姓名',
|
||||
contact_phone VARCHAR(64) NOT NULL COMMENT '联系人电话',
|
||||
delivery_method VARCHAR(32) NULL COMMENT '派送方式:TRUCK卡车、PICKUP自提、EXPRESS快递;仅涉及箱唛换标时必填',
|
||||
delivery_method VARCHAR(32) NULL COMMENT '派送方式:TRUCK平台卡车、EXPRESS快递、PICKUP自提、AMAZON_PICKUP亚马逊自提、CUSTOMER_PICKUP客户自提;仅平台卡车进入预排车',
|
||||
print_shipping_label TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否需要打印面单',
|
||||
shipping_label_file_id VARCHAR(128) NULL COMMENT '客户上传面单文件ID',
|
||||
shipping_label_file_name VARCHAR(255) NULL COMMENT '客户上传面单文件名',
|
||||
@@ -2407,7 +2421,7 @@ CREATE TABLE IF NOT EXISTS ll_relabel_application (
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_application' AND column_name='total_file_id'), 'SELECT 1', 'ALTER TABLE ll_relabel_application ADD COLUMN total_file_id VARCHAR(128) NULL COMMENT ''总换标PDF文件ID'' AFTER remark')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_application' AND column_name='total_file_name'), 'SELECT 1', 'ALTER TABLE ll_relabel_application ADD COLUMN total_file_name VARCHAR(255) NULL COMMENT ''总换标PDF文件名'' AFTER total_file_id')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_application' AND column_name='total_file_url'), 'SELECT 1', 'ALTER TABLE ll_relabel_application ADD COLUMN total_file_url VARCHAR(1000) NULL COMMENT ''总换标PDF预览地址'' AFTER total_file_name')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_application' AND column_name='delivery_method'), 'SELECT 1', 'ALTER TABLE ll_relabel_application ADD COLUMN delivery_method VARCHAR(32) NULL COMMENT ''派送方式:TRUCK卡车、PICKUP自提、EXPRESS快递;仅涉及箱唛换标时必填'' AFTER contact_phone')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_application' AND column_name='delivery_method'), 'SELECT 1', 'ALTER TABLE ll_relabel_application ADD COLUMN delivery_method VARCHAR(32) NULL COMMENT ''派送方式:TRUCK平台卡车、EXPRESS快递、PICKUP自提、AMAZON_PICKUP亚马逊自提、CUSTOMER_PICKUP客户自提;仅平台卡车进入预排车'' AFTER contact_phone')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_application' AND column_name='print_shipping_label'), 'SELECT 1', 'ALTER TABLE ll_relabel_application ADD COLUMN print_shipping_label TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否需要打印面单'' AFTER delivery_method')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_application' AND column_name='shipping_label_file_id'), 'SELECT 1', 'ALTER TABLE ll_relabel_application ADD COLUMN shipping_label_file_id VARCHAR(128) NULL COMMENT ''客户上传面单文件ID'' AFTER print_shipping_label')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_application' AND column_name='shipping_label_file_name'), 'SELECT 1', 'ALTER TABLE ll_relabel_application ADD COLUMN shipping_label_file_name VARCHAR(255) NULL COMMENT ''客户上传面单文件名'' AFTER shipping_label_file_id')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
@@ -2662,6 +2676,8 @@ CREATE TABLE IF NOT EXISTS ll_cargo_discard_item (
|
||||
UNIQUE KEY uk_ll_discard_position_sku(application_id,inventory_position_id,sku_code),
|
||||
KEY idx_ll_cargo_discard_item_list(customer_id,application_id),
|
||||
KEY idx_ll_cargo_discard_item_inbound(customer_id,inbound_code,sku_code)
|
||||
,
|
||||
KEY idx_ll_cargo_discard_return_carton(customer_id,return_carton_id)
|
||||
) ENGINE=InnoDB COMMENT='货物废弃SKU明细';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ll_cargo_discard_carton (
|
||||
@@ -3981,21 +3997,21 @@ CREATE TABLE ll_selfop_sku_profile (
|
||||
|
||||
-- PDA cargo discard photo evidence
|
||||
CREATE TABLE IF NOT EXISTS ll_cargo_discard_photo (
|
||||
id BIGINT NOT NULL,
|
||||
customer_id VARCHAR(64) NOT NULL,
|
||||
application_id BIGINT NOT NULL,
|
||||
pallet_no VARCHAR(128) NOT NULL,
|
||||
file_id BIGINT NOT NULL,
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
content_type VARCHAR(128) NOT NULL,
|
||||
size_bytes BIGINT NOT NULL,
|
||||
operator_id VARCHAR(64) NOT NULL,
|
||||
created_at DATETIME(6) NOT NULL,
|
||||
submitted_at DATETIME(6) NULL,
|
||||
id BIGINT NOT NULL COMMENT '照片记录标识',
|
||||
customer_id VARCHAR(64) NOT NULL COMMENT '所属客户标识',
|
||||
application_id BIGINT NOT NULL COMMENT '废弃申请标识',
|
||||
pallet_no VARCHAR(128) NOT NULL COMMENT '托盘业务码',
|
||||
file_id BIGINT NOT NULL COMMENT '照片文件标识',
|
||||
file_name VARCHAR(255) NOT NULL COMMENT '原始文件名',
|
||||
content_type VARCHAR(128) NOT NULL COMMENT '文件媒体类型',
|
||||
size_bytes BIGINT NOT NULL COMMENT '文件字节数',
|
||||
operator_id VARCHAR(64) NOT NULL COMMENT '上传操作人标识',
|
||||
created_at DATETIME(6) NOT NULL COMMENT '照片创建时间',
|
||||
submitted_at DATETIME(6) NULL COMMENT '照片提交时间',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_cargo_discard_photo_file (file_id),
|
||||
KEY idx_cargo_discard_photo_application (customer_id, application_id, pallet_no)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='海外PDA货物废弃照片凭证';
|
||||
|
||||
CREATE TABLE ll_selfop_owner (
|
||||
id BIGINT NOT NULL COMMENT '主键',
|
||||
@@ -4154,22 +4170,22 @@ PREPARE transfer_sku_stmt FROM @ddl; EXECUTE transfer_sku_stmt; DEALLOCATE PREPA
|
||||
-- Appointment work scope, audit and physical batch allocation.
|
||||
USE baiyue_lastleg;
|
||||
CREATE TABLE IF NOT EXISTS ll_dispatch_appointment_allocation (
|
||||
dispatch_id BIGINT NOT NULL,
|
||||
dispatch_item_id BIGINT NOT NULL,
|
||||
plan_pallet_id BIGINT NOT NULL,
|
||||
batch_key VARCHAR(96) NOT NULL,
|
||||
customer_id VARCHAR(64) NOT NULL,
|
||||
source_key VARCHAR(160) NOT NULL,
|
||||
destination_key VARCHAR(160) NOT NULL,
|
||||
package_count INT NOT NULL,
|
||||
dispatch_id BIGINT NOT NULL COMMENT '排车单标识',
|
||||
dispatch_item_id BIGINT NOT NULL COMMENT '排车明细标识',
|
||||
plan_pallet_id BIGINT NOT NULL COMMENT '入仓计划托盘标识',
|
||||
batch_key VARCHAR(96) NOT NULL COMMENT '预约稳定批次键',
|
||||
customer_id VARCHAR(64) NOT NULL COMMENT '所属客户标识',
|
||||
source_key VARCHAR(160) NOT NULL COMMENT '货物来源稳定键',
|
||||
destination_key VARCHAR(160) NOT NULL COMMENT '目的地稳定键',
|
||||
package_count INT NOT NULL COMMENT '本票分配实物箱数',
|
||||
PRIMARY KEY(dispatch_item_id,batch_key,source_key,destination_key),
|
||||
KEY idx_dispatch_appointment_batch(batch_key,customer_id),
|
||||
KEY idx_dispatch_appointment_dispatch(dispatch_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='排车实物与预约批次逐票分配,原执行域事实';
|
||||
CREATE TABLE IF NOT EXISTS ll_appointment_work_commitment (
|
||||
entity_key VARCHAR(160) NOT NULL,
|
||||
entity_key VARCHAR(160) NOT NULL COMMENT '原业务实体稳定键',
|
||||
state_json LONGTEXT NOT NULL COMMENT '原业务命令产生的只读语义影响基线,不是第二份可编辑计划',
|
||||
impact_version BIGINT NOT NULL DEFAULT 0,
|
||||
impact_version BIGINT NOT NULL DEFAULT 0 COMMENT '承诺影响单调递增版本',
|
||||
PRIMARY KEY (entity_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作事项承诺影响审计基线';
|
||||
CREATE TABLE IF NOT EXISTS ll_appointment_work_guard (
|
||||
|
||||
@@ -1710,7 +1710,7 @@ paths:
|
||||
'200': { description: 客户换标申请分页, content: { application/json: { schema: { $ref: '#/components/schemas/RelabelMerchantPageResponse' } } } }
|
||||
post:
|
||||
summary: 创建客户换标申请
|
||||
description: 权限 merchant:overseas-warehouse:relabel:create;申请创建即冻结对应库存。一张申请只能选择一种换标类型,所有 items.operationType 必须一致;不同类型混合提交返回业务参数错误且不冻结库存。SKU_LABEL 不要求派送方式;CARTON_LABEL/CARTON_AND_SKU 必须指定 TRUCK、PICKUP 或 EXPRESS。
|
||||
description: 权限 merchant:overseas-warehouse:relabel:create;申请创建即冻结对应库存。一张申请只能选择一种换标类型,所有 items.operationType 必须一致;不同类型混合提交返回业务参数错误且不冻结库存。SKU_LABEL 不要求派送方式;CARTON_LABEL/CARTON_AND_SKU 必须指定 TRUCK、EXPRESS、PICKUP、AMAZON_PICKUP 或 CUSTOMER_PICKUP。
|
||||
security: [{ bearerAuth: [] }]
|
||||
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/RelabelCreate' } } } }
|
||||
responses:
|
||||
@@ -1791,7 +1791,7 @@ paths:
|
||||
/api/merchant/v1/last-leg/relabel-applications/{relabelNo}/confirm-completion:
|
||||
post:
|
||||
summary: 客户确认换标完成
|
||||
description: 权限 merchant:overseas-warehouse:relabel:confirm;仅 CUSTOMER_CONFIRM_PENDING 可确认。TRUCK 进入 WAIT_PRE_DISPATCH,PICKUP/EXPRESS 幂等生成 RELABEL_OUTBOUND 出库单;EXPRESS 且要求打印面单时必须先上传面单。
|
||||
description: 权限 merchant:overseas-warehouse:relabel:confirm;仅 CUSTOMER_CONFIRM_PENDING 可确认,确认前复验实际托盘绑定,仓库手动完工不豁免该门禁。普通库存箱唛换标必须绑定实际托盘;退货整箱 EXPRESS、PICKUP、AMAZON_PICKUP、CUSTOMER_PICKUP 可不组托但仍校验全箱 SKU 数量。只有平台卡车 TRUCK 进入 WAIT_PRE_DISPATCH;EXPRESS 与全部自提方式幂等生成 RELABEL_OUTBOUND 出库单和 PDA 出库任务,不进入预排车候选,直接预排请求也拒绝。已分配仓储转运保持原转运出库链,不生成独立出库。EXPRESS 且要求打印面单时必须先上传面单。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: relabelNo, in: path, required: true, schema: { type: string, maxLength: 64 } }]
|
||||
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/RelabelCustomerConfirm' } } } }
|
||||
@@ -3154,7 +3154,7 @@ components:
|
||||
notifiedPackageCount: { type: integer }
|
||||
productNameZh: { type: string, description: 明细非空中文品名按原顺序去重并以顿号连接 }
|
||||
labelRequirement: { type: string, description: 明细非空贴标要求按原顺序去重并以中文分号连接 }
|
||||
deliveryMethod: { type: string, nullable: true, enum: [TRUCK, PICKUP, EXPRESS] }
|
||||
deliveryMethod: { type: string, nullable: true, enum: [TRUCK, EXPRESS, PICKUP, AMAZON_PICKUP, CUSTOMER_PICKUP], description: 仅TRUCK进入平台预排车;快递及全部自提直接创建出库任务 }
|
||||
printShippingLabel: { type: boolean }
|
||||
shippingLabelFileId: { type: string, nullable: true }
|
||||
fulfillmentStatus: { type: string, nullable: true, enum: [WAIT_RELABEL, WAIT_PRE_DISPATCH, WAIT_TRANSFER_OUTBOUND, READY_FOR_OUTBOUND, OUTBOUND_CREATED, OUTBOUNDED, COMPLETED] }
|
||||
@@ -3293,7 +3293,7 @@ components:
|
||||
chargeAmount: { type: number, nullable: true, minimum: 0.01, description: 本次贴标费用(CNY);未传时不冻结或结算资金 }
|
||||
remark: { type: string, nullable: true, maxLength: 1000 }
|
||||
idempotencyKey: { type: string, maxLength: 255 }
|
||||
deliveryMethod: { type: string, nullable: true, enum: [TRUCK, EXPRESS, AMAZON_PICKUP, CUSTOMER_PICKUP, PICKUP], description: 只换箱唛或SKU与箱唛同时换时必填;只换SKU时不传 }
|
||||
deliveryMethod: { type: string, nullable: true, enum: [TRUCK, EXPRESS, AMAZON_PICKUP, CUSTOMER_PICKUP, PICKUP], description: 只换箱唛或SKU与箱唛同时换时必填;只换SKU时不传;仅TRUCK进入平台预排车,其余方式直接创建出库任务 }
|
||||
printShippingLabel: { type: boolean, default: false, description: 仅快递派送可选,客户确认前必须已上传面单 }
|
||||
items: { type: array, minItems: 1, maxItems: 500, items: { $ref: '#/components/schemas/RelabelItem' } }
|
||||
RelabelItem:
|
||||
|
||||
@@ -858,8 +858,8 @@ components:
|
||||
type: object
|
||||
required: [relabelNo, applicationStatus, operationType, taskStatus, expectedPackages, relabelledPackages, skippedPackages, exceptionPackages, version, completionPhotoFileIds, items, unitFacts, history]
|
||||
properties:
|
||||
deliveryMethod: { type: string, nullable: true, description: 申请真实运输方式,留仓为空 }
|
||||
requiresPalletBinding: { type: boolean, description: 退货任务仅TRUCK为true,留仓及快递为false }
|
||||
deliveryMethod: { type: string, nullable: true, enum: [TRUCK, EXPRESS, PICKUP, AMAZON_PICKUP, CUSTOMER_PICKUP], description: 申请真实运输方式,纯SKU留仓为空;只有平台卡车TRUCK进入预排车,快递和所有自提直接创建出库任务 }
|
||||
requiresPalletBinding: { type: boolean, description: 普通库存箱唛或混合换标须实际托盘绑定;退货整箱EXPRESS、PICKUP、AMAZON_PICKUP、CUSTOMER_PICKUP不要求排车组托,TRUCK要求;纯SKU留仓为空派送方式时不要求组托 }
|
||||
relabelNo: { type: string }
|
||||
customerOrderNo: { type: string, nullable: true }
|
||||
labelDeadline: { type: string, format: date-time, nullable: true }
|
||||
|
||||
Reference in New Issue
Block a user