diff --git a/firstleg/src/main/java/com/baiyue/firstleg/dto/OrderApprovedInboundCommand.java b/firstleg/src/main/java/com/baiyue/firstleg/dto/OrderApprovedInboundCommand.java index ece4cea5e..2f39754c9 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/dto/OrderApprovedInboundCommand.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/dto/OrderApprovedInboundCommand.java @@ -34,7 +34,7 @@ public record OrderApprovedInboundCommand( Integer totalPackageCount, String pickupMethod, LocalDateTime requestedPickupAt, - String fulfillmentType + String fulfillmentType, String lxCodes ) { public OrderApprovedInboundCommand( long tenantId, long orderId, String orderNo, String inboundCode, Long customerId, String customerName, @@ -49,7 +49,7 @@ public record OrderApprovedInboundCommand( deliveryWarehouseId, deliveryWarehouseName, expectedArrivalAt, businessType, channelCode, channelName, carrierId, carrierName, taxMode, pricingTaxMode, containerMode, customsInspection, destinationType, destinationWarehouseId, destinationCode, destinationName, publicRemark, expressCompanyName, - expressTrackingNo, totalPackageCount, null, null, "STANDARD"); + expressTrackingNo, totalPackageCount, null, null, "STANDARD", null); } /** 兼容尚未携带订单总箱数的历史事件。 */ public OrderApprovedInboundCommand( @@ -64,7 +64,7 @@ public record OrderApprovedInboundCommand( deliveryWarehouseId, deliveryWarehouseName, expectedArrivalAt, businessType, channelCode, channelName, carrierId, carrierName, taxMode, pricingTaxMode, containerMode, customsInspection, destinationType, destinationWarehouseId, destinationCode, destinationName, publicRemark, expressCompanyName, - expressTrackingNo, null, null, null, "STANDARD"); + expressTrackingNo, null, null, null, "STANDARD", null); } /** 兼容未携带报价包税维度的历史调用方。 */ @@ -80,6 +80,6 @@ public record OrderApprovedInboundCommand( deliveryWarehouseId, deliveryWarehouseName, expectedArrivalAt, businessType, channelCode, channelName, carrierId, carrierName, taxMode, null, containerMode, customsInspection, destinationType, destinationWarehouseId, destinationCode, destinationName, publicRemark, expressCompanyName, - expressTrackingNo, null, null, null, "STANDARD"); + expressTrackingNo, null, null, null, "STANDARD", null); } } diff --git a/firstleg/src/main/java/com/baiyue/firstleg/entity/InboundOrderEntity.java b/firstleg/src/main/java/com/baiyue/firstleg/entity/InboundOrderEntity.java index 816a28e11..f02add72e 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/entity/InboundOrderEntity.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/entity/InboundOrderEntity.java @@ -39,6 +39,7 @@ public class InboundOrderEntity extends BaseEntity { private String deliveryWarehouseName; private String businessType; private String fulfillmentType; + private String lxCodes; private String channelCode; private String channelName; private Long carrierId; diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/InboundOrderCreationService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/InboundOrderCreationService.java index ac82be20b..4cd372692 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/InboundOrderCreationService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/InboundOrderCreationService.java @@ -69,6 +69,7 @@ public class InboundOrderCreationService { inbound.setDeliveryWarehouseName(command.deliveryWarehouseName()); inbound.setBusinessType(command.businessType()); inbound.setFulfillmentType(fulfillmentType(command.fulfillmentType())); + inbound.setLxCodes("ONE_CLICK_DROPSHIP".equals(inbound.getFulfillmentType()) ? command.lxCodes() : null); inbound.setChannelCode(command.channelCode()); inbound.setChannelName(command.channelName()); inbound.setCarrierId(command.carrierId()); @@ -221,6 +222,7 @@ public class InboundOrderCreationService { inbound.setDeliveryWarehouseName(command.deliveryWarehouseName()); inbound.setBusinessType(command.businessType()); inbound.setFulfillmentType(fulfillmentType(command.fulfillmentType())); + inbound.setLxCodes("ONE_CLICK_DROPSHIP".equals(inbound.getFulfillmentType()) ? command.lxCodes() : null); inbound.setChannelCode(command.channelCode()); inbound.setChannelName(command.channelName()); inbound.setCarrierId(command.carrierId()); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/dto/RelabelRequests.java b/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/dto/RelabelRequests.java index 6dda7ce0c..52173eb37 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/dto/RelabelRequests.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/dto/RelabelRequests.java @@ -19,7 +19,7 @@ public final class RelabelRequests { public record Create( @NotBlank @Size(max = 128) String customerOrderNo, - @NotBlank @Size(max = 128) String overseasWarehouseId, + @Size(max = 128) String overseasWarehouseId, @NotNull LocalDateTime labelDeadline, @NotBlank @Size(max = 128) String contactName, @NotBlank @Size(max = 64) String contactPhone, @@ -41,10 +41,23 @@ public final class RelabelRequests { @Size(max = 255) String trackingCode, @NotBlank @Size(max = 128) String newSkuCode, @Size(max = 1000) String labelRequirement, + @Size(max = 1000) String remark, @Size(max = 128) String relabelFileId, @Size(max = 255) String relabelFileName, - @Size(max = 1000) String relabelFileUrl - ) {} + @Size(max = 1000) String relabelFileUrl, + @NotBlank @Size(max = 255) String cartonMark, + @NotBlank @Size(max = 255) String fbaCartonMark, + @Size(max = 128) String destinationWarehouseId + ) { + public Item(String skuCode, String productImageFileId, String productImageFileName, + String productImageUrl, String productNameZh, int totalPackageCount, + int notifiedPackageCount, String shipmentNo, String trackingCode, String newSkuCode, + String labelRequirement, String relabelFileId, String relabelFileName, String relabelFileUrl) { + this(skuCode, productImageFileId, productImageFileName, productImageUrl, productNameZh, + totalPackageCount, notifiedPackageCount, shipmentNo, trackingCode, newSkuCode, + labelRequirement, null, relabelFileId, relabelFileName, relabelFileUrl, skuCode, shipmentNo, null); + } + } public record Query(@Min(1) long page, @Min(1) @Max(200) long size, String relabelNo, String customerOrderNo, String skuCode, String status, diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/dto/RelabelResponses.java b/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/dto/RelabelResponses.java index 249130cad..8929d7d0b 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/dto/RelabelResponses.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/dto/RelabelResponses.java @@ -17,9 +17,13 @@ public final class RelabelResponses { public record Item(Long id, String skuCode, String productImageFileId, String productImageFileName, String productImageUrl, String productNameZh, int totalPackageCount, int notifiedPackageCount, String shipmentNo, String trackingCode, - String newSkuCode, String labelRequirement, String relabelFileId, - String relabelFileName, String relabelFileUrl) {} + String newSkuCode, String labelRequirement, String remark, String relabelFileId, + String relabelFileName, String relabelFileUrl, + String cartonMark, String fbaCartonMark, String destinationWarehouseId) {} + public record TailOrder(Long id, String orderNo, String overseasWarehouseId, int packageCount, + String status) {} public record History(String fromStatus, String toStatus, String operatorId, String remark, LocalDateTime createdAt) {} - public record Detail(Summary application, String remark, List items, List history) {} + public record Detail(Summary application, String remark, List items, List history, + List tailOrders) {} } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelItem.java b/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelItem.java index 5dde51a26..638eb7a7d 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelItem.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelItem.java @@ -24,8 +24,12 @@ public class RelabelItem { private String trackingCode; private String newSkuCode; private String labelRequirement; + private String remark; private String relabelFileId; private String relabelFileName; private String relabelFileUrl; + private String cartonMark; + private String fbaCartonMark; + private String destinationWarehouseId; private LocalDateTime createdAt; } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java index cf9b0250b..5ec1d07f2 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java @@ -33,7 +33,9 @@ import java.security.MessageDigest; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.HexFormat; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -56,12 +58,15 @@ public class RelabelApplicationService implements WalletResultHandler { public RelabelResponses.Detail create(String customerId, String operatorId, RelabelRequests.Create request) { int total = 0; int notified = 0; + Map packagesByWarehouse = new LinkedHashMap<>(); for (RelabelRequests.Item item : request.items()) { if (item.notifiedPackageCount() > item.totalPackageCount()) { throw badRequest("Notified relocation packages cannot exceed total packages for SKU " + item.skuCode()); } total = Math.addExact(total, item.totalPackageCount()); notified = Math.addExact(notified, item.notifiedPackageCount()); + String warehouseId = destination(item, request.overseasWarehouseId()); + packagesByWarehouse.merge(warehouseId, item.totalPackageCount(), Math::addExact); } String requestHash = hash(request); RelabelApplication replay = applications.findKey(customerId, request.idempotencyKey().trim()); @@ -71,40 +76,20 @@ public class RelabelApplicationService implements WalletResultHandler { } LocalDateTime now = LocalDateTime.now(); long applicationId = IdWorker.getId(); - String relabelNo = orderNoGenerator.next("RELABEL", request.overseasWarehouseId(), applicationId); - LastlegOrder order = new LastlegOrder(); - order.setId(IdWorker.getId()); - order.setOrderNo(orderNoGenerator.next("RELABEL", request.overseasWarehouseId(), order.getId())); - order.setCustomerId(customerId); - order.setOrderType("RELABEL_APPLICATION"); - order.setInboundCode(relabelNo); - order.setSourceRegion("CUSTOMER"); - order.setSourceInboundId(relabelNo); - order.setSourceOrderNo(request.customerOrderNo().trim()); - order.setOverseasWarehouseId(request.overseasWarehouseId().trim()); - order.setPalletCount(0); - order.setPackageCount(total); - order.setPickedPackageCount(0); - order.setShippedPackageCount(0); - order.setCurrentStatus("PENDING_ASSIGNMENT"); - order.setSubOrderStatus("RELABEL:" + PENDING_PROCESSING); - order.setRelabelOrderNo(relabelNo); - order.setRelabelStatus(PENDING_PROCESSING); - order.setCustomerRemark(trim(request.remark())); - order.setSourceEventId("CUSTOMER_RELABEL:" + relabelNo); - order.setSourceLoadingPlanId("RELABEL_APPLICATION"); - order.setSourcePayloadHash("sha256:" + requestHash); - order.setCreatedAt(now); - order.setUpdatedAt(now); - orders.insert(order); + String primaryWarehouseId = packagesByWarehouse.keySet().iterator().next(); + String relabelNo = orderNoGenerator.next("RELABEL", primaryWarehouseId, applicationId); + List tailOrders = packagesByWarehouse.entrySet().stream() + .map(entry -> insertTailOrder(customerId, request, requestHash, relabelNo, + entry.getKey(), entry.getValue(), now)) + .toList(); RelabelApplication application = new RelabelApplication(); application.setId(applicationId); application.setCustomerId(customerId); application.setRelabelNo(relabelNo); - application.setLastlegOrderId(order.getId()); + application.setLastlegOrderId(tailOrders.get(0).getId()); application.setCustomerOrderNo(request.customerOrderNo().trim()); - application.setOverseasWarehouseId(request.overseasWarehouseId().trim()); + application.setOverseasWarehouseId(primaryWarehouseId); application.setLabelDeadline(request.labelDeadline()); application.setContactName(request.contactName().trim()); application.setContactPhone(request.contactPhone().trim()); @@ -128,10 +113,12 @@ public class RelabelApplicationService implements WalletResultHandler { application.setWalletCommandEventId(walletEventId); applications.updateById(application); - for (RelabelRequests.Item source : request.items()) insertItem(customerId, applicationId, source, now); + for (RelabelRequests.Item source : request.items()) { + insertItem(customerId, applicationId, source, request.overseasWarehouseId(), now); + } insertHistory(customerId, applicationId, null, PENDING_PROCESSING, operatorId, "Relabel application submitted", now); - insertOrderHistory(customerId, order.getId(), null, order.getCurrentStatus(), - "Relabel application submitted", now); + tailOrders.forEach(order -> insertOrderHistory(customerId, order.getId(), null, + order.getCurrentStatus(), "Relabel application submitted", now)); return detail(customerId, relabelNo); } @@ -145,7 +132,9 @@ public class RelabelApplicationService implements WalletResultHandler { .like(StringUtils.hasText(query.relabelNo()), RelabelApplication::getRelabelNo, query.relabelNo()) .like(StringUtils.hasText(query.customerOrderNo()), RelabelApplication::getCustomerOrderNo, query.customerOrderNo()) .eq(StringUtils.hasText(query.status()), RelabelApplication::getStatus, query.status()) - .eq(StringUtils.hasText(query.overseasWarehouseId()), RelabelApplication::getOverseasWarehouseId, query.overseasWarehouseId()) + .apply(StringUtils.hasText(query.overseasWarehouseId()), + "EXISTS (SELECT 1 FROM ll_relabel_item rw WHERE rw.application_id=ll_relabel_application.id AND rw.customer_id=ll_relabel_application.customer_id AND rw.destination_warehouse_id={0})", + query.overseasWarehouseId()) .ge(query.createdFrom() != null, RelabelApplication::getCreatedAt, query.createdFrom()) .le(query.createdTo() != null, RelabelApplication::getCreatedAt, query.createdTo()) .apply(StringUtils.hasText(query.skuCode()), @@ -166,10 +155,13 @@ public class RelabelApplicationService implements WalletResultHandler { .eq(RelabelStatusHistory::getCustomerId, recordCustomerId) .eq(RelabelStatusHistory::getApplicationId, application.getId()) .orderByAsc(RelabelStatusHistory::getCreatedAt)); + List tailOrders = tailOrders(application); return new RelabelResponses.Detail(summary(application, detailItems), application.getRemark(), detailItems.stream().map(this::item).toList(), statusHistory.stream().map(value -> new RelabelResponses.History(value.getFromStatus(), value.getToStatus(), value.getOperatorId(), - value.getRemark(), value.getCreatedAt())).toList()); + value.getRemark(), value.getCreatedAt())).toList(), tailOrders.stream().map(value -> + new RelabelResponses.TailOrder(value.getId(), value.getOrderNo(), value.getOverseasWarehouseId(), + Objects.requireNonNullElse(value.getPackageCount(), 0), value.getCurrentStatus())).toList()); } @Transactional @@ -201,30 +193,32 @@ public class RelabelApplicationService implements WalletResultHandler { "换标取消,释放冻结", 2L); application.setWalletStatus("RELEASE_PENDING"); applications.updateById(application); } - LastlegOrder order = orders.selectById(application.getLastlegOrderId()); - if (order == null || !customerId.equals(order.getCustomerId())) throw conflict("Linked tail-leg order is missing"); - String orderBefore = order.getCurrentStatus(); - String mainStatus = STATUSES.contains(orderBefore) ? "PENDING_ASSIGNMENT" : orderBefore; - // A return-relabel tail order joins the normal inbound queue after labels are finished. - String orderStatus = switch (next) { - case "COMPLETED" -> "RETURN_RELABEL".equals(order.getOrderType()) - ? "PENDING_ASSIGNMENT" : "COMPLETED"; - case "REJECTED", "CANCELLED" -> "CANCELLED"; - default -> mainStatus; - }; - order.setCurrentStatus(orderStatus); - order.setSubOrderStatus("RELABEL:" + next); - order.setRelabelStatus(next); - order.setUpdatedAt(now); - orders.updateById(order); - insertHistory(customerId, application.getId(), before, next, operatorId, request.remark(), now); - if (!Objects.equals(orderBefore, orderStatus)) { - insertOrderHistory(customerId, order.getId(), orderBefore, orderStatus, request.remark(), now); + List linkedOrders = tailOrders(application); + if (linkedOrders.isEmpty()) throw conflict("Linked tail-leg order is missing"); + for (LastlegOrder order : linkedOrders) { + String orderBefore = order.getCurrentStatus(); + String mainStatus = STATUSES.contains(orderBefore) ? "PENDING_ASSIGNMENT" : orderBefore; + String orderStatus = switch (next) { + case "COMPLETED" -> "RETURN_RELABEL".equals(order.getOrderType()) + ? "PENDING_ASSIGNMENT" : "COMPLETED"; + case "REJECTED", "CANCELLED" -> "CANCELLED"; + default -> mainStatus; + }; + order.setCurrentStatus(orderStatus); + order.setSubOrderStatus("RELABEL:" + next); + order.setRelabelStatus(next); + order.setUpdatedAt(now); + orders.updateById(order); + if (!Objects.equals(orderBefore, orderStatus)) { + insertOrderHistory(customerId, order.getId(), orderBefore, orderStatus, request.remark(), now); + } } + insertHistory(customerId, application.getId(), before, next, operatorId, request.remark(), now); return detail(customerId, relabelNo); } - private void insertItem(String customerId, long applicationId, RelabelRequests.Item source, LocalDateTime now) { + private void insertItem(String customerId, long applicationId, RelabelRequests.Item source, + String defaultWarehouseId, LocalDateTime now) { RelabelItem item = new RelabelItem(); item.setId(IdWorker.getId()); item.setApplicationId(applicationId); item.setCustomerId(customerId); item.setSkuCode(source.skuCode().trim()); item.setProductImageFileId(trim(source.productImageFileId())); @@ -232,16 +226,21 @@ public class RelabelApplicationService implements WalletResultHandler { item.setProductNameZh(source.productNameZh().trim()); item.setTotalPackageCount(source.totalPackageCount()); item.setNotifiedPackageCount(source.notifiedPackageCount()); item.setShipmentNo(source.shipmentNo().trim()); item.setTrackingCode(trim(source.trackingCode())); item.setNewSkuCode(source.newSkuCode().trim()); - item.setLabelRequirement(trim(source.labelRequirement())); item.setRelabelFileId(trim(source.relabelFileId())); + item.setLabelRequirement(trim(source.labelRequirement())); item.setRemark(trim(source.remark())); item.setRelabelFileId(trim(source.relabelFileId())); item.setRelabelFileName(trim(source.relabelFileName())); item.setRelabelFileUrl(trim(source.relabelFileUrl())); + item.setCartonMark(source.cartonMark().trim()); item.setFbaCartonMark(source.fbaCartonMark().trim()); + item.setDestinationWarehouseId(destination(source, defaultWarehouseId)); item.setCreatedAt(now); items.insert(item); } private RelabelResponses.Summary summary(RelabelApplication value, List detailItems) { LastlegOrder order = orders.selectById(value.getLastlegOrderId()); String skuCodes = detailItems.stream().map(RelabelItem::getSkuCode).filter(Objects::nonNull).distinct().collect(Collectors.joining(",")); + String warehouses = detailItems.stream().map(RelabelItem::getDestinationWarehouseId) + .filter(StringUtils::hasText).distinct().collect(Collectors.joining(",")); return new RelabelResponses.Summary(value.getId(), value.getRelabelNo(), value.getLastlegOrderId(), - order == null ? null : order.getOrderNo(), value.getCustomerOrderNo(), value.getOverseasWarehouseId(), + order == null ? null : order.getOrderNo(), value.getCustomerOrderNo(), + StringUtils.hasText(warehouses) ? warehouses : value.getOverseasWarehouseId(), value.getLabelDeadline(), value.getContactName(), value.getContactPhone(), value.getStatus(), Objects.requireNonNullElse(value.getItemCount(), detailItems.size()), Objects.requireNonNullElse(value.getTotalPackageCount(), 0), @@ -276,8 +275,47 @@ public class RelabelApplicationService implements WalletResultHandler { return new RelabelResponses.Item(value.getId(), value.getSkuCode(), value.getProductImageFileId(), value.getProductImageFileName(), value.getProductImageUrl(), value.getProductNameZh(), value.getTotalPackageCount(), value.getNotifiedPackageCount(), value.getShipmentNo(), - value.getTrackingCode(), value.getNewSkuCode(), value.getLabelRequirement(), value.getRelabelFileId(), - value.getRelabelFileName(), value.getRelabelFileUrl()); + value.getTrackingCode(), value.getNewSkuCode(), value.getLabelRequirement(), value.getRemark(), value.getRelabelFileId(), + value.getRelabelFileName(), value.getRelabelFileUrl(), value.getCartonMark(), value.getFbaCartonMark(), + value.getDestinationWarehouseId()); + } + + private LastlegOrder insertTailOrder(String customerId, RelabelRequests.Create request, String requestHash, + String relabelNo, String warehouseId, int packageCount, + LocalDateTime now) { + LastlegOrder order = new LastlegOrder(); + order.setId(IdWorker.getId()); + order.setOrderNo(orderNoGenerator.next("RELABEL", warehouseId, order.getId())); + order.setCustomerId(customerId); order.setOrderType("RELABEL_APPLICATION"); + order.setInboundCode(relabelNo); order.setSourceRegion("CUSTOMER"); + order.setSourceInboundId(relabelNo + ":" + warehouseId); + order.setSourceOrderNo(request.customerOrderNo().trim()); order.setOverseasWarehouseId(warehouseId); + order.setPalletCount(0); order.setPackageCount(packageCount); order.setPickedPackageCount(0); + order.setShippedPackageCount(0); order.setCurrentStatus("PENDING_ASSIGNMENT"); + order.setSubOrderStatus("RELABEL:" + PENDING_PROCESSING); order.setRelabelOrderNo(relabelNo); + order.setRelabelStatus(PENDING_PROCESSING); order.setCustomerRemark(trim(request.remark())); + order.setSourceEventId("CUSTOMER_RELABEL:" + relabelNo + ":" + warehouseId); + order.setSourceLoadingPlanId("RELABEL_APPLICATION"); order.setSourcePayloadHash("sha256:" + requestHash); + order.setCreatedAt(now); order.setUpdatedAt(now); orders.insert(order); + return order; + } + + private List tailOrders(RelabelApplication application) { + List linked = orders.selectList(new LambdaQueryWrapper() + .eq(LastlegOrder::getCustomerId, application.getCustomerId()) + .eq(LastlegOrder::getRelabelOrderNo, application.getRelabelNo()) + .orderByAsc(LastlegOrder::getId)); + if (!linked.isEmpty()) return linked; + LastlegOrder legacy = orders.selectById(application.getLastlegOrderId()); + return legacy == null ? List.of() : List.of(legacy); + } + + private static String destination(RelabelRequests.Item item, String fallback) { + String value = StringUtils.hasText(item.destinationWarehouseId()) + ? item.destinationWarehouseId().trim() + : trim(fallback); + if (!StringUtils.hasText(value)) throw badRequest("Destination warehouse is required for every carton"); + return value; } private List itemList(Long applicationId) { diff --git a/lastleg-service/src/main/resources/db/lastleg.sql b/lastleg-service/src/main/resources/db/lastleg.sql index eac660912..3c2a44006 100644 --- a/lastleg-service/src/main/resources/db/lastleg.sql +++ b/lastleg-service/src/main/resources/db/lastleg.sql @@ -1456,9 +1456,13 @@ CREATE TABLE IF NOT EXISTS ll_relabel_item ( tracking_code VARCHAR(255) NULL COMMENT '物流跟踪编码', new_sku_code VARCHAR(128) NOT NULL COMMENT '换标后SKU编码', label_requirement VARCHAR(1000) NULL COMMENT '换标要求', + remark VARCHAR(1000) NULL COMMENT '货物明细备注', relabel_file_id VARCHAR(128) NULL COMMENT '换标文件ID', relabel_file_name VARCHAR(255) NULL COMMENT '换标文件名', relabel_file_url VARCHAR(1000) NULL COMMENT '换标文件地址', + carton_mark VARCHAR(255) NULL COMMENT '客户自有箱唛', + fba_carton_mark VARCHAR(255) NULL COMMENT '关联FBA箱唛', + destination_warehouse_id VARCHAR(128) NOT NULL COMMENT '该箱目的海外仓ID', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY (id), KEY idx_ll_relabel_item_application (customer_id,application_id), diff --git a/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml b/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml index 33efcbff2..049d35466 100644 --- a/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml +++ b/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml @@ -678,27 +678,6 @@ paths: required: true content: { application/json: { schema: { $ref: '#/components/schemas/ProcurementReview' } } } responses: { '200': { description: 统一 ApiResponse 包装的采购订单详情 } } - /api/platform/v1/last-leg/overseas-procurements/{orderNo}/files/{category}: - post: - summary: 上传采购签收文件或费用凭证 - description: 权限 platform:overseas-business:purchase:edit;category 仅允许 RECEIPT 或 VOUCHER。 - security: [{ bearerAuth: [] }] - parameters: - - { name: orderNo, in: path, required: true, schema: { type: string } } - - { name: category, in: path, required: true, schema: { type: string, enum: [RECEIPT, VOUCHER] } } - - { name: customerId, in: query, required: true, schema: { type: string, pattern: '^[1-9][0-9]*$' } } - requestBody: { required: true, content: { multipart/form-data: { schema: { type: object, required: [file], properties: { file: { type: string, format: binary } } } } } } - responses: { '200': { description: 已保存文件元数据 } } - /api/platform/v1/last-leg/overseas-procurements/{orderNo}/fees: - post: - summary: 登记采购费用并发起资金冻结 - description: 权限 platform:overseas-business:purchase:edit;订单必须已审核通过,费用凭证必须属于本订单。 - security: [{ bearerAuth: [] }] - parameters: - - { name: orderNo, in: path, required: true, schema: { type: string } } - - { name: customerId, in: query, required: true, schema: { type: string, pattern: '^[1-9][0-9]*$' } } - requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ProcurementFeeCreate' } } } } - responses: { '200': { description: 已创建费用并进入冻结流程 }, '409': { description: 订单状态或凭证不允许 } } /api/merchant/v1/last-leg/delivery-orders: get: summary: 客户分页查询自己的派送单 @@ -1662,20 +1641,20 @@ components: RelabelStatus: { type: string, enum: [PENDING_PROCESSING, PROCESSING, COMPLETED, REJECTED, CANCELLED] } RelabelCreate: type: object - required: [customerOrderNo, overseasWarehouseId, labelDeadline, contactName, contactPhone, chargeAmount, idempotencyKey, items] + required: [customerOrderNo, labelDeadline, contactName, contactPhone, chargeAmount, idempotencyKey, items] properties: customerOrderNo: { type: string, maxLength: 128 } - overseasWarehouseId: { type: string, maxLength: 128 } + overseasWarehouseId: { type: string, nullable: true, maxLength: 128, description: 统一目的海外仓;明细未填写目的仓时使用 } labelDeadline: { type: string, format: date-time } contactName: { type: string, maxLength: 128 } contactPhone: { type: string, maxLength: 64 } - chargeAmount: { type: number, minimum: 0.01, description: 换标费用,币种固定 CNY } + chargeAmount: { type: number, minimum: 0.01, description: 本次贴标费用(CNY) } remark: { type: string, nullable: true, maxLength: 1000 } idempotencyKey: { type: string, maxLength: 255 } items: { type: array, minItems: 1, maxItems: 500, items: { $ref: '#/components/schemas/RelabelItem' } } RelabelItem: type: object - required: [skuCode, productNameZh, totalPackageCount, notifiedPackageCount, shipmentNo, newSkuCode] + required: [skuCode, productNameZh, totalPackageCount, notifiedPackageCount, shipmentNo, newSkuCode, cartonMark, fbaCartonMark] properties: skuCode: { type: string, maxLength: 128 } productImageFileId: { type: string, nullable: true, maxLength: 128 } @@ -1691,6 +1670,9 @@ components: relabelFileId: { type: string, nullable: true, maxLength: 128 } relabelFileName: { type: string, nullable: true, maxLength: 255 } relabelFileUrl: { type: string, nullable: true, maxLength: 1000 } + cartonMark: { type: string, maxLength: 255, description: 客户自有箱唛(不在编辑列表展示) } + fbaCartonMark: { type: string, maxLength: 255, description: 关联 FBA 箱唛 } + destinationWarehouseId: { type: string, nullable: true, maxLength: 128, description: 该箱目的海外仓;为空时使用申请统一目的仓 } RelabelStatusChange: type: object required: [status] @@ -1786,7 +1768,7 @@ components: updatedAt: { type: string, format: date-time } ProcurementDetail: type: object - required: [summary, histories, outboundDetails, files, fees] + required: [summary, histories, outboundDetails] properties: summary: { $ref: '#/components/schemas/ProcurementSummary' } productUrl: { type: string, nullable: true } @@ -1805,42 +1787,6 @@ components: reviewedAt: { type: string, format: date-time, nullable: true } histories: { type: array, items: { $ref: '#/components/schemas/BusinessHistory' } } outboundDetails: { type: array, items: { $ref: '#/components/schemas/ProcurementOutboundDetail' } } - files: { type: array, items: { $ref: '#/components/schemas/ProcurementFile' } } - fees: { type: array, items: { $ref: '#/components/schemas/ProcurementFee' } } - ProcurementFeeCreate: - type: object - required: [feeItemCode, feeItemName, amount, voucherFileId] - properties: - feeItemCode: { type: string, maxLength: 64 } - feeItemName: { type: string, maxLength: 128 } - amount: { type: number, minimum: 0.01 } - voucherFileId: { type: string, pattern: '^[0-9]+$' } - remark: { type: string, nullable: true, maxLength: 500 } - ProcurementFile: - type: object - properties: - id: { type: string } - category: { type: string, enum: [RECEIPT, VOUCHER] } - fileId: { type: string } - fileName: { type: string } - contentType: { type: string, nullable: true } - sizeBytes: { type: integer, format: int64 } - createdAt: { type: string, format: date-time } - ProcurementFee: - type: object - properties: - id: { type: string } - feeNo: { type: string } - feeItemCode: { type: string } - feeItemName: { type: string } - amount: { type: number } - currency: { type: string, enum: [CNY] } - voucherFileId: { type: string } - voucherFileName: { type: string } - remark: { type: string, nullable: true } - walletStatus: { type: string } - walletErrorMessage: { type: string, nullable: true } - createdAt: { type: string, format: date-time } ProcurementOutboundDetail: type: object properties: diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/relabel/service/RelabelApplicationServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/relabel/service/RelabelApplicationServiceTest.java index 039d0bf63..ec87da880 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/relabel/service/RelabelApplicationServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/relabel/service/RelabelApplicationServiceTest.java @@ -40,6 +40,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.times; @ExtendWith(MockitoExtension.class) class RelabelApplicationServiceTest { @@ -98,7 +99,7 @@ class RelabelApplicationServiceTest { assertEquals("RELABEL:PENDING_PROCESSING", tailOrder.getValue().getSubOrderStatus()); assertEquals("CUSTOMER", tailOrder.getValue().getSourceRegion()); assertTrue(tailOrder.getValue().getOrderNo().matches("RL-WH-\\d{8}-\\d{6}")); - assertEquals(tailOrder.getValue().getInboundCode(), tailOrder.getValue().getSourceInboundId()); + assertEquals(tailOrder.getValue().getInboundCode() + ":WH-1", tailOrder.getValue().getSourceInboundId()); assertEquals(tailOrder.getValue().getInboundCode(), application.getValue().getRelabelNo()); assertTrue(tailOrder.getValue().getInboundCode().startsWith("RL-")); assertEquals(10, tailOrder.getValue().getPackageCount()); @@ -120,6 +121,71 @@ class RelabelApplicationServiceTest { verify(items, never()).insert(any(RelabelItem.class)); } + @Test + void createPersistsCartonMarkBinding() { + RelabelRequests.Item attachedItem = new RelabelRequests.Item("SKU-A", null, null, null, + "Chinese name", 2, 2, "SHP-1", null, "SKU-NEW", null, null, null, null, null, + "BOX-1", "FBA-BOX-1", "WH-1"); + RelabelRequests.Create request = new RelabelRequests.Create("CUSTOMER-1", "WH-1", + LocalDateTime.of(2026, 8, 20, 10, 0), "Li", "13800000000", BigDecimal.ONE, null, "key-marks", + List.of(attachedItem)); + RelabelApplication persisted = application(11L, "FBR-1", "100", "key-files", null, + RelabelApplicationService.PENDING_PROCESSING); + persisted.setLastlegOrderId(21L); persisted.setItemCount(1); persisted.setTotalPackageCount(2); + persisted.setNotifiedPackageCount(2); + when(applications.findNo(eq("100"), any())).thenReturn(persisted); + when(items.selectList(any())).thenReturn(List.of(item(31L, 11L, "SKU-A", 2, 2))); + when(histories.selectList(any())).thenReturn(List.of()); + when(orders.selectById(21L)).thenReturn(order(21L, "100", "LMO-RELABEL-1", + RelabelApplicationService.PENDING_PROCESSING)); + + service.create("100", "operator-1", request); + + ArgumentCaptor saved = ArgumentCaptor.forClass(RelabelItem.class); + verify(items).insert(saved.capture()); + assertEquals("BOX-1", saved.getValue().getCartonMark()); + assertEquals("FBA-BOX-1", saved.getValue().getFbaCartonMark()); + assertEquals("WH-1", saved.getValue().getDestinationWarehouseId()); + } + + @Test + void createGroupsCartonsIntoOneTailOrderPerDestinationWarehouse() { + RelabelRequests.Item west = carton("SKU-A", "BOX-1", "FBA-1", "ONT8"); + RelabelRequests.Item east = carton("SKU-B", "BOX-2", "FBA-2", "ABE8"); + RelabelRequests.Create request = new RelabelRequests.Create("CUSTOMER-1", null, + LocalDateTime.of(2026, 8, 20, 10, 0), "Li", "13800000000", BigDecimal.ONE, null, "key-multi", + List.of(west, east)); + RelabelApplication persisted = application(11L, "FBR-1", "10001", "key-multi", null, + RelabelApplicationService.PENDING_PROCESSING); + persisted.setLastlegOrderId(21L); + when(applications.findNo(eq("10001"), any())).thenReturn(persisted); + when(items.selectList(any())).thenReturn(List.of(item(31L, 11L, "SKU-A", 1, 1))); + when(histories.selectList(any())).thenReturn(List.of()); + when(orders.selectById(21L)).thenReturn(order(21L, "10001", "LMO-RELABEL-1", + RelabelApplicationService.PENDING_PROCESSING)); + + service.create("10001", "operator-1", request); + + ArgumentCaptor created = ArgumentCaptor.forClass(LastlegOrder.class); + verify(orders, times(2)).insert(created.capture()); + assertEquals(List.of("ONT8", "ABE8"), created.getAllValues().stream() + .map(LastlegOrder::getOverseasWarehouseId).toList()); + assertTrue(created.getAllValues().stream().allMatch(value -> value.getPackageCount() == 1)); + } + + @Test + void createRejectsCartonWithoutDetailOrDefaultDestinationWarehouse() { + RelabelRequests.Create request = new RelabelRequests.Create("CUSTOMER-1", null, + LocalDateTime.of(2026, 8, 20, 10, 0), "Li", "13800000000", BigDecimal.ONE, null, "key-no-destination", + List.of(carton("SKU-A", "BOX-1", "FBA-1", null))); + + ResponseStatusException exception = assertThrows(ResponseStatusException.class, + () -> service.create("10001", "operator-1", request)); + + assertEquals(400, exception.getStatusCode().value()); + verify(orders, never()).insert(any(LastlegOrder.class)); + } + @Test void sameIdempotencyKeyAndPayloadReturnsExistingApplicationWithoutWrites() { RelabelRequests.Create request = request("key-1", 10, 6); @@ -224,6 +290,13 @@ class RelabelApplicationServiceTest { "SKU-NEW", "Label on carton", "file-1", "relabel.pdf", "https://files/relabel.pdf"))); } + private static RelabelRequests.Item carton(String sku, String cartonMark, String fbaCartonMark, + String destinationWarehouseId) { + return new RelabelRequests.Item(sku, null, null, null, "Chinese name", 1, 1, + "SHP-1", null, sku + "-NEW", null, null, null, null, null, + cartonMark, fbaCartonMark, destinationWarehouseId); + } + private static RelabelApplication application(long id, String no, String customerId, String key, String requestHash, String status) { RelabelApplication value = new RelabelApplication(); diff --git a/order/src/main/java/com/baiyue/order/client/FirstlegInboundClient.java b/order/src/main/java/com/baiyue/order/client/FirstlegInboundClient.java index 140947486..73d3ffd86 100644 --- a/order/src/main/java/com/baiyue/order/client/FirstlegInboundClient.java +++ b/order/src/main/java/com/baiyue/order/client/FirstlegInboundClient.java @@ -60,7 +60,7 @@ public interface FirstlegInboundClient { Boolean customsInspection, String destinationType, Long destinationWarehouseId, String destinationCode, String destinationName, String publicRemark, String expressCompanyName, String expressTrackingNo, Integer totalPackageCount, - String pickupMethod, LocalDateTime requestedPickupAt, String fulfillmentType + String pickupMethod, LocalDateTime requestedPickupAt, String fulfillmentType, String lxCodes ) { } } diff --git a/order/src/main/java/com/baiyue/order/dto/OrderRequests.java b/order/src/main/java/com/baiyue/order/dto/OrderRequests.java index e46d526c0..e76c6f9e1 100644 --- a/order/src/main/java/com/baiyue/order/dto/OrderRequests.java +++ b/order/src/main/java/com/baiyue/order/dto/OrderRequests.java @@ -76,6 +76,8 @@ public final class OrderRequests { @Size(max = 40) @Pattern(regexp = "[A-Za-z0-9._:-]+") String inboundCode, @Schema(description = "Fulfillment type; omitted means STANDARD / 履约类型;省略时为普通头程", allowableValues = {"STANDARD", "ONE_CLICK_DROPSHIP"}, defaultValue = "STANDARD") @Pattern(regexp = "STANDARD|ONE_CLICK_DROPSHIP") String fulfillmentType, + @Schema(description = "Lingxing inbound codes for one-click dropship; multiple values separated by comma, space or newline", maxLength = 1000) + @Size(max = 1000) String lxCodes, @Schema(description = "Selected customer pickup address ID; required for PICKUP / 已选客户取货地址 ID;PICKUP 时必填") @Min(1) Long senderAddressId, @Schema(description = "Selected private destination address ID; required for PRIVATE / 已选私人目的仓地址 ID;PRIVATE 时必填") @@ -96,7 +98,7 @@ public final class OrderRequests { sender, receiver, cargoes, pickupMethod, requestedPickupAt, receivingWarehouseId, receivingWarehouseName, channelCode, channelName, carrierId, carrierName, taxMode, pricingTaxMode, containerMode, customsInspection, destinationType, amazonWarehouseCode, - shipmentId, referenceId, inboundCode, fulfillmentType, null, null); + shipmentId, referenceId, inboundCode, fulfillmentType, null, null, null); } public SaveRequest(Long customerId, String customerName, String transportMode, String tradeTerm, @@ -114,7 +116,7 @@ public final class OrderRequests { sender, receiver, cargoes, pickupMethod, requestedPickupAt, receivingWarehouseId, receivingWarehouseName, channelCode, channelName, carrierId, carrierName, taxMode, pricingTaxMode, containerMode, customsInspection, destinationType, amazonWarehouseCode, - shipmentId, referenceId, inboundCode, null, null, null); + shipmentId, referenceId, inboundCode, null, null, null, null); } public SaveRequest(Long customerId, String customerName, String transportMode, String tradeTerm, @@ -131,7 +133,7 @@ public final class OrderRequests { sender, receiver, cargoes, pickupMethod, requestedPickupAt, receivingWarehouseId, receivingWarehouseName, channelCode, channelName, carrierId, carrierName, taxMode, pricingTaxMode, containerMode, customsInspection, destinationType, amazonWarehouseCode, - shipmentId, referenceId, null, null, null, null); + shipmentId, referenceId, null, null, null, null, null); } public SaveRequest(Long customerId, String customerName, String transportMode, String tradeTerm, @@ -148,7 +150,7 @@ public final class OrderRequests { sender, receiver, cargoes, pickupMethod, null, receivingWarehouseId, receivingWarehouseName, channelCode, channelName, carrierId, carrierName, taxMode, pricingTaxMode, containerMode, customsInspection, destinationType, amazonWarehouseCode, - shipmentId, referenceId, null, null, null, null); + shipmentId, referenceId, null, null, null, null, null); } @AssertTrue(message = "承运商 ID 和名称必须同时填写或同时留空") diff --git a/order/src/main/java/com/baiyue/order/entity/OrderEntity.java b/order/src/main/java/com/baiyue/order/entity/OrderEntity.java index 8f59e7bbc..9f5863513 100644 --- a/order/src/main/java/com/baiyue/order/entity/OrderEntity.java +++ b/order/src/main/java/com/baiyue/order/entity/OrderEntity.java @@ -52,6 +52,7 @@ public class OrderEntity extends BaseEntity { private Boolean customsInspection; private String destinationType; private String fulfillmentType; + private String lxCodes; private String amazonWarehouseCode; private String shipmentId; private String referenceId; diff --git a/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java b/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java index 92a842d62..fc8b941cd 100644 --- a/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java +++ b/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java @@ -488,6 +488,7 @@ public class OrderApplicationService { order.setContainerMode(request.containerMode()); order.setCustomsInspection(request.customsInspection()); order.setDestinationType(request.destinationType()); order.setFulfillmentType(fulfillmentType(request.fulfillmentType())); + order.setLxCodes("ONE_CLICK_DROPSHIP".equals(order.getFulfillmentType()) ? normalizeLxCodes(request.lxCodes()) : null); order.setAmazonWarehouseCode("FBA".equals(request.destinationType()) && snapshots.destination() != null ? snapshots.destination().warehouse().warehouseCode() : request.amazonWarehouseCode()); order.setShipmentId(request.shipmentId()); order.setReferenceId(request.referenceId()); @@ -519,6 +520,11 @@ public class OrderApplicationService { throw new BizException(ResultCode.VALIDATION_FAILED, "履约类型必须为 STANDARD 或 ONE_CLICK_DROPSHIP"); } + private static String normalizeLxCodes(String value) { + if (value == null || value.isBlank()) return null; + return java.util.Arrays.stream(value.trim().split("[,,\\s]+")) + .map(String::trim).filter(item -> !item.isEmpty()).distinct().collect(java.util.stream.Collectors.joining(",")); + } private boolean legacyManualMode() { return inboundCodeModeProvider.legacyManual(); } @@ -683,7 +689,7 @@ public class OrderApplicationService { order.getDestinationWarehouseId(), order.getAmazonWarehouseCode(), order.getDestinationWarehouseName(), order.getCustomerRemark(), order.getExpressCompany(), order.getExpressTrackingNo(), totalPackageCount, order.getPickupMethod(), order.getRequestedPickupAt(), - order.getFulfillmentType() == null ? "STANDARD" : order.getFulfillmentType()); + order.getFulfillmentType() == null ? "STANDARD" : order.getFulfillmentType(), order.getLxCodes()); } private void prepareDraftInbound(OrderEntity order, OrderRequests.SaveRequest request) { FirstlegInboundClient client = firstlegProvider == null ? null : firstlegProvider.getIfAvailable(); diff --git a/scripts/db/incremental.sql b/scripts/db/incremental.sql index 05e7418c8..854dae67b 100644 --- a/scripts/db/incremental.sql +++ b/scripts/db/incremental.sql @@ -1049,8 +1049,9 @@ CREATE TABLE IF NOT EXISTS ll_relabel_item ( product_image_file_name VARCHAR(255) NULL COMMENT '产品图片文件名', product_image_url VARCHAR(1000) NULL COMMENT '产品图片地址', product_name_zh VARCHAR(255) NOT NULL COMMENT '产品中文名称', total_package_count INT NOT NULL COMMENT '总箱数', notified_package_count INT NOT NULL COMMENT '已通知箱数', shipment_no VARCHAR(128) NOT NULL COMMENT '货件编号', tracking_code VARCHAR(255) NULL COMMENT '物流跟踪编码', new_sku_code VARCHAR(128) NOT NULL COMMENT '换标后SKU编码', - label_requirement VARCHAR(1000) NULL COMMENT '换标要求', relabel_file_id VARCHAR(128) NULL COMMENT '换标文件ID', + label_requirement VARCHAR(1000) NULL COMMENT '换标要求', remark VARCHAR(1000) NULL COMMENT '货物明细备注', relabel_file_id VARCHAR(128) NULL COMMENT '换标文件ID', relabel_file_name VARCHAR(255) NULL COMMENT '换标文件名', relabel_file_url VARCHAR(1000) NULL COMMENT '换标文件地址', + carton_mark VARCHAR(255) NULL COMMENT '客户自有箱唛', fba_carton_mark VARCHAR(255) NULL COMMENT '关联FBA箱唛', destination_warehouse_id VARCHAR(128) NULL COMMENT '该箱目的海外仓ID', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY (id) ) ENGINE=InnoDB COMMENT='换标货物冻结明细'; @@ -2911,6 +2912,16 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab PREPARE schema_comments_025 FROM @ddl; EXECUTE schema_comments_025; DEALLOCATE PREPARE schema_comments_025; SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='label_requirement' AND COALESCE(column_comment,'') <> '换标要求'), 'ALTER TABLE ll_relabel_item MODIFY COLUMN label_requirement VARCHAR(1000) NULL COMMENT ''换标要求''', 'SELECT 1')); PREPARE schema_comments_025 FROM @ddl; EXECUTE schema_comments_025; DEALLOCATE PREPARE schema_comments_025; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='remark'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN remark VARCHAR(1000) NULL COMMENT ''货物明细备注'' AFTER label_requirement')); +PREPARE schema_comments_025 FROM @ddl; EXECUTE schema_comments_025; DEALLOCATE PREPARE schema_comments_025; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='carton_mark'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN carton_mark VARCHAR(255) NULL COMMENT ''客户自有箱唛'' AFTER relabel_file_url')); +PREPARE schema_comments_025 FROM @ddl; EXECUTE schema_comments_025; DEALLOCATE PREPARE schema_comments_025; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='fba_carton_mark'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN fba_carton_mark VARCHAR(255) NULL COMMENT ''关联FBA箱唛'' AFTER carton_mark')); +PREPARE schema_comments_025 FROM @ddl; EXECUTE schema_comments_025; DEALLOCATE PREPARE schema_comments_025; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='destination_warehouse_id'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN destination_warehouse_id VARCHAR(128) NULL COMMENT ''该箱目的海外仓ID'' AFTER fba_carton_mark')); +PREPARE schema_comments_025 FROM @ddl; EXECUTE schema_comments_025; DEALLOCATE PREPARE schema_comments_025; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND index_name='idx_ll_relabel_item_destination'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD KEY idx_ll_relabel_item_destination (customer_id,destination_warehouse_id,application_id)')); +PREPARE schema_comments_025 FROM @ddl; EXECUTE schema_comments_025; DEALLOCATE PREPARE schema_comments_025; SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='relabel_file_id' AND COALESCE(column_comment,'') <> '换标文件ID'), 'ALTER TABLE ll_relabel_item MODIFY COLUMN relabel_file_id VARCHAR(128) NULL COMMENT ''换标文件ID''', 'SELECT 1')); PREPARE schema_comments_025 FROM @ddl; EXECUTE schema_comments_025; DEALLOCATE PREPARE schema_comments_025; SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='relabel_file_name' AND COALESCE(column_comment,'') <> '换标文件名'), 'ALTER TABLE ll_relabel_item MODIFY COLUMN relabel_file_name VARCHAR(255) NULL COMMENT ''换标文件名''', 'SELECT 1'));