提交
This commit is contained in:
@@ -3270,7 +3270,7 @@ CREATE TABLE ll_warehouse_transfer_carton (
|
||||
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',
|
||||
transfer_item_id bigint DEFAULT NULL COMMENT '转运申报明细ID',
|
||||
customer_id varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户租户ID',
|
||||
carton_mark varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '系统生成的一箱一码箱唛',
|
||||
customer_carton_mark varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '客户原箱唛,用于换标现场绑定',
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.baiyue.lastleg.relabel.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.relabel.entity.RelabelApplication;
|
||||
import com.baiyue.lastleg.relabel.mapper.RelabelApplicationMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.entity.ReturnOrder;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnOrderMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.util.StringUtils;
|
||||
import java.util.List;
|
||||
|
||||
/** 封装换标状态命令的客户隔离查询与持久化。 */
|
||||
@Repository
|
||||
public class RelabelStatusRepository {
|
||||
@Resource private RelabelApplicationMapper applications;
|
||||
@Resource private LastlegOrderMapper orders;
|
||||
@Resource private ReturnOrderMapper returnOrders;
|
||||
@Resource private ReturnInventoryCartonMapper returnCartons;
|
||||
|
||||
/** 锁定申请,平台入口从单据解析客户。 */
|
||||
public RelabelApplication lockApplication(String customer, String no) {
|
||||
return StringUtils.hasText(customer) ? applications.lockNo(customer, no) : applications.lockAnyByRelabelNo(no);
|
||||
}
|
||||
/** 读取申请主订单。 */
|
||||
public LastlegOrder findPrimaryOrder(Long id) { return orders.selectById(id); }
|
||||
/** 保存申请状态及资金状态。 */
|
||||
public void saveApplication(RelabelApplication application) { applications.updateById(application); }
|
||||
/** 保存关联订单状态。 */
|
||||
public void saveOrder(LastlegOrder order) { orders.updateById(order); }
|
||||
/** 读取同一客户下关联的退货单。 */
|
||||
public List<ReturnOrder> linkedReturnOrders(String customer, Long id) {
|
||||
return returnOrders.selectList(new LambdaQueryWrapper<ReturnOrder>()
|
||||
.eq(ReturnOrder::getRelabelApplicationId, id).eq(ReturnOrder::getCustomerId, customer));
|
||||
}
|
||||
/** 保存退货单换标状态。 */
|
||||
public void saveReturnOrder(ReturnOrder order) { returnOrders.updateById(order); }
|
||||
/** 核对退货箱实际已换标数量。 */
|
||||
public long relabelledQuantity(Long id) { return returnCartons.totalRelabelledQuantity(id); }
|
||||
}
|
||||
+26
-21
@@ -82,6 +82,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
|
||||
@Resource private com.baiyue.lastleg.service.CompactBusinessNumberService compactNumbers;
|
||||
@Resource private RelabelApplicationMapper applications;
|
||||
@Resource private com.baiyue.lastleg.relabel.repository.RelabelStatusRepository statusRepository;
|
||||
@Resource private RelabelItemMapper items;
|
||||
@Resource private RelabelStatusHistoryMapper histories;
|
||||
@Resource private LastlegOrderMapper orders;
|
||||
@@ -621,6 +622,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
Objects.requireNonNullElse(value.getPackageCount(), 0), value.getCurrentStatus())).toList(), evidence);
|
||||
}
|
||||
|
||||
/** 统一执行换标单审核、作业状态流转和作废。 */
|
||||
@Transactional
|
||||
public RelabelResponses.Detail changeStatus(String customerId, String relabelNo, String operatorId,
|
||||
RelabelRequests.ChangeStatus request) {
|
||||
@@ -639,12 +641,11 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
return changeStatus(customerId, order.getRelabelOrderNo(), operatorId, request, true);
|
||||
}
|
||||
|
||||
/** 在申请行锁内执行审核及作废,保留实际作业与状态历史。 */
|
||||
private RelabelResponses.Detail changeStatus(String customerId, String relabelNo, String operatorId,
|
||||
RelabelRequests.ChangeStatus request,
|
||||
boolean directTailOrderReview) {
|
||||
RelabelApplication application = StringUtils.hasText(customerId)
|
||||
? applications.lockNo(customerId, relabelNo)
|
||||
: applications.lockAnyByRelabelNo(relabelNo);
|
||||
RelabelApplication application = statusRepository.lockApplication(customerId, relabelNo);
|
||||
if (application == null) throw notFound("Relabel application not found");
|
||||
customerId = application.getCustomerId();
|
||||
String next = request.status().trim().toUpperCase();
|
||||
@@ -652,19 +653,20 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
if ("REJECTED".equals(next) && !StringUtils.hasText(request.remark())) {
|
||||
throw badRequest("审核驳回必须填写原因");
|
||||
}
|
||||
LastlegOrder primaryOrder = orders.selectById(application.getLastlegOrderId());
|
||||
LastlegOrder primaryOrder = statusRepository.findPrimaryOrder(application.getLastlegOrderId());
|
||||
boolean directApplication = primaryOrder != null && "RELABEL_APPLICATION".equals(primaryOrder.getOrderType());
|
||||
if (PENDING_REVIEW.equals(application.getStatus()) && directApplication != directTailOrderReview) {
|
||||
if (!"CANCELLED".equals(next) && PENDING_REVIEW.equals(application.getStatus()) && directApplication != directTailOrderReview) {
|
||||
throw conflict(directApplication
|
||||
? "直接创建的贴标单请在尾程订单审核"
|
||||
: "关联业务产生的换标单请在换标单审核");
|
||||
}
|
||||
if (next.equals(application.getStatus())) return detail(customerId, relabelNo);
|
||||
validateTransition(application.getStatus(), next);
|
||||
if (Set.of("REJECTED", "CANCELLED").contains(next) && itemList(application.getId()).stream()
|
||||
// 作废仅释放申请占用,已经发生的换标事实继续保留;驳回仍禁止回退实际作业。
|
||||
if ("REJECTED".equals(next) && itemList(application.getId()).stream()
|
||||
.filter(item -> item.getReturnCartonId() != null)
|
||||
.anyMatch(item -> returnCartons.totalRelabelledQuantity(item.getReturnCartonId()) > 0))
|
||||
throw conflict("该退货任务已有实际商品换标事实,不能取消并恢复为未换标库存;请按异常完工或退回重做处理");
|
||||
.anyMatch(item -> statusRepository.relabelledQuantity(item.getReturnCartonId()) > 0))
|
||||
throw conflict("该退货任务已有实际商品换标事实,不能驳回并恢复为未换标库存;请按异常完工或退回重做处理");
|
||||
if (PENDING_REVIEW.equals(application.getStatus()) && PENDING_PROCESSING.equals(next)
|
||||
&& itemList(application.getId()).stream().anyMatch(item -> "RETURN_BULK_SKU_LABEL".equals(operationType(item)))) {
|
||||
boolean hasCartonFile = StringUtils.hasText(application.getCartonTotalFileId());
|
||||
@@ -679,20 +681,20 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
application.setStatus(next);
|
||||
application.setUpdatedAt(now);
|
||||
applications.updateById(application);
|
||||
statusRepository.saveApplication(application);
|
||||
List<LastlegOrder> linkedOrders = tailOrders(application);
|
||||
if (linkedOrders.isEmpty()) throw conflict("Linked tail-leg order is missing");
|
||||
if (PENDING_PROCESSING.equals(next)) {
|
||||
application.setFulfillmentStatus("WAIT_RELABEL");
|
||||
applications.updateById(application);
|
||||
statusRepository.saveApplication(application);
|
||||
}
|
||||
if (CUSTOMER_CONFIRM_PENDING.equals(next)) {
|
||||
application.setFulfillmentStatus("WAIT_CUSTOMER_CONFIRMATION");
|
||||
applications.updateById(application);
|
||||
statusRepository.saveApplication(application);
|
||||
} else if ("COMPLETED".equals(next)) {
|
||||
boolean preDispatched = "PRE_DISPATCH_CREATED".equals(application.getFulfillmentStatus());
|
||||
application.setFulfillmentStatus("OUTBOUNDED");
|
||||
applications.updateById(application);
|
||||
statusRepository.saveApplication(application);
|
||||
if (!StringUtils.hasText(application.getOutboundOrderNo())
|
||||
&& !preDispatched) {
|
||||
releaseInventory(customerId, operatorId, application);
|
||||
@@ -703,7 +705,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
if ("FROZEN".equals(application.getWalletStatus())) {
|
||||
walletCommands.publish("RELEASE", Long.parseLong(customerId), "RELABEL_ORDER", relabelNo, null,
|
||||
"换标取消,释放冻结", 2L);
|
||||
application.setWalletStatus("RELEASE_PENDING"); applications.updateById(application);
|
||||
application.setWalletStatus("RELEASE_PENDING"); statusRepository.saveApplication(application);
|
||||
}
|
||||
}
|
||||
for (LastlegOrder order : linkedOrders) {
|
||||
@@ -719,27 +721,25 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
case "CANCELLED" -> "CANCELLED";
|
||||
default -> mainStatus;
|
||||
};
|
||||
// Reviewing/processing a return relabel must not move the already
|
||||
// completed inbound order back to assignment or relabel waiting.
|
||||
if ("RETURN_RELABEL".equals(order.getOrderType())) {
|
||||
// 退货不回退已完成入库;转运作废只终止换标,不取消原转运主单。
|
||||
if ("RETURN_RELABEL".equals(order.getOrderType())
|
||||
|| "WAREHOUSE_TRANSFER".equals(order.getOrderType()) && "CANCELLED".equals(next)) {
|
||||
orderStatus = orderBefore;
|
||||
}
|
||||
order.setCurrentStatus(orderStatus);
|
||||
if (!"RETURN_RELABEL".equals(order.getOrderType())) order.setSubOrderStatus("RELABEL:" + next);
|
||||
order.setRelabelStatus(businessRelabelStatus(next));
|
||||
order.setUpdatedAt(now);
|
||||
orders.updateById(order);
|
||||
statusRepository.saveOrder(order);
|
||||
if (!Objects.equals(orderBefore, orderStatus)) {
|
||||
insertOrderHistory(customerId, order.getId(), orderBefore, orderStatus, request.remark(), now);
|
||||
}
|
||||
}
|
||||
List<ReturnOrder> linkedReturnOrders = returnOrders.selectList(new LambdaQueryWrapper<ReturnOrder>()
|
||||
.eq(ReturnOrder::getRelabelApplicationId, application.getId())
|
||||
.eq(ReturnOrder::getCustomerId, customerId));
|
||||
List<ReturnOrder> linkedReturnOrders = statusRepository.linkedReturnOrders(customerId, application.getId());
|
||||
for (ReturnOrder returnOrder : linkedReturnOrders) {
|
||||
returnOrder.setRelabelStatus(next);
|
||||
returnOrder.setUpdatedAt(now);
|
||||
returnOrders.updateById(returnOrder);
|
||||
statusRepository.saveReturnOrder(returnOrder);
|
||||
}
|
||||
insertHistory(customerId, application.getId(), before, next, operatorId, request.remark(), now);
|
||||
return detail(customerId, relabelNo);
|
||||
@@ -1535,7 +1535,12 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
value.setToStatus(to); value.setEventId("RELABEL_APPLICATION"); value.setRemark(trim(remark)); value.setCreatedAt(now);
|
||||
orderHistories.insert(value);
|
||||
}
|
||||
/** 客户确认换标完成后禁止作废,其余状态允许终止申请。 */
|
||||
private static void validateTransition(String from, String to) {
|
||||
if ("CANCELLED".equals(to)) {
|
||||
if (Set.of(READY_FOR_OUTBOUND, "COMPLETED").contains(from)) throw conflict("换标完成的单据不能作废");
|
||||
return;
|
||||
}
|
||||
boolean allowed = PENDING_REVIEW.equals(from) && Set.of(PENDING_PROCESSING, "REJECTED", "CANCELLED").contains(to)
|
||||
|| PENDING_PROCESSING.equals(from) && Set.of("PROCESSING", "REJECTED", "CANCELLED").contains(to)
|
||||
|| "PROCESSING".equals(from) && Set.of(CUSTOMER_CONFIRM_PENDING, "REJECTED", "CANCELLED").contains(to)
|
||||
|
||||
+5
-1
@@ -187,8 +187,12 @@ public final class WarehouseTransferRequests {
|
||||
@Size(max = 255) String productNameZh,
|
||||
@Positive int packageCount,
|
||||
@Size(max = 255) String shippingMark,
|
||||
@Positive Long quantity
|
||||
@Positive Long quantity,
|
||||
@Size(max = 5000) List<@NotBlank @jakarta.validation.constraints.Pattern(regexp = "BX-[A-Z0-9]{1,40}") String> cartonMarks
|
||||
) {
|
||||
public Item(String skuCode, String productNameZh, int packageCount, String shippingMark, Long quantity) {
|
||||
this(skuCode, productNameZh, packageCount, shippingMark, quantity, null);
|
||||
}
|
||||
public Item(String skuCode, String productNameZh, int packageCount, String shippingMark) {
|
||||
this(skuCode, productNameZh, packageCount, shippingMark, null);
|
||||
}
|
||||
|
||||
+19
-4
@@ -199,6 +199,7 @@ public class WarehouseTransferService {
|
||||
if (itemPackages != request.packageCount()) {
|
||||
throw badRequest("Cargo item package total must equal transfer package count");
|
||||
}
|
||||
validatePrintedCartonMarks(request);
|
||||
validateRelabelRequest(customerId, request);
|
||||
validateDestination(request.destinationType(), request.destinationWarehouseCode(), request.fbaWarehouseCode(),
|
||||
request.privateDestinationAddress(), request.privateDestinationPostalCode(),
|
||||
@@ -321,7 +322,8 @@ public class WarehouseTransferService {
|
||||
for (int itemCarton = 0; itemCarton < source.packageCount(); itemCarton++) {
|
||||
cartonIndex++;
|
||||
insertTransferCarton(customerId, transferId, itemId, transferNo,
|
||||
cartonIndex, request.packageCount(), itemCarton < customerMarks.size() ? customerMarks.get(itemCarton) : null, now);
|
||||
cartonIndex, request.packageCount(), itemCarton < customerMarks.size() ? customerMarks.get(itemCarton) : null, now,
|
||||
source.cartonMarks() == null ? null : source.cartonMarks().get(itemCarton));
|
||||
}
|
||||
insertOrderDetail(customerId, order.getId(), transferNo, index, source, request, now);
|
||||
}
|
||||
@@ -352,6 +354,7 @@ public class WarehouseTransferService {
|
||||
if (itemPackages != request.packageCount()) {
|
||||
throw badRequest("Cargo item package total must equal transfer package count");
|
||||
}
|
||||
validatePrintedCartonMarks(request);
|
||||
validateRelabelRequest(customerId, request);
|
||||
validateDestination(request.destinationType(), request.destinationWarehouseCode(), request.fbaWarehouseCode(),
|
||||
request.privateDestinationAddress(), request.privateDestinationPostalCode(),
|
||||
@@ -439,8 +442,9 @@ public class WarehouseTransferService {
|
||||
String customerMark = itemCarton < customerMarks.size() ? customerMarks.get(itemCarton) : null;
|
||||
if (existingCarton == null) {
|
||||
insertTransferCarton(customerId, transfer.getId(), itemId, transferNo, cartonIndex,
|
||||
request.packageCount(), customerMark, now);
|
||||
request.packageCount(), customerMark, now, source.cartonMarks() == null ? null : source.cartonMarks().get(itemCarton));
|
||||
} else {
|
||||
if (source.cartonMarks() != null && !existingCarton.getCartonMark().equals(source.cartonMarks().get(itemCarton))) throw conflict("已保存箱唛不能替换,请重新读取草稿");
|
||||
existingCarton.setTransferItemId(itemId); existingCarton.setTotalCartons(request.packageCount());
|
||||
existingCarton.setCustomerCartonMark(customerMark); existingCarton.setUpdatedAt(now);
|
||||
transferCartons.updateById(existingCarton);
|
||||
@@ -2134,6 +2138,17 @@ public class WarehouseTransferService {
|
||||
return order;
|
||||
}
|
||||
|
||||
private void validatePrintedCartonMarks(WarehouseTransferRequests.Create request) {
|
||||
Set<String> marks = new java.util.HashSet<>();
|
||||
for (var item : request.items()) {
|
||||
if (item.cartonMarks() == null) continue;
|
||||
if (item.cartonMarks().size() != item.packageCount()) throw badRequest("已生成箱唛数量必须等于预报箱数,请重新打印");
|
||||
for (String mark : item.cartonMarks()) {
|
||||
if (mark == null || !mark.matches("BX-[A-Z0-9]{1,40}") || !marks.add(mark)) throw badRequest("已生成箱唛格式错误或重复,请重新打印");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long insertTransferItem(String customerId, long transferId, WarehouseTransferRequests.Item source,
|
||||
LocalDateTime now) {
|
||||
WarehouseTransferItem item = new WarehouseTransferItem();
|
||||
@@ -2148,10 +2163,10 @@ public class WarehouseTransferService {
|
||||
|
||||
private void insertTransferCarton(String customerId, long transferId, long transferItemId,
|
||||
String transferNo, int cartonIndex, int totalCartons,
|
||||
String customerCartonMark, LocalDateTime now) {
|
||||
String customerCartonMark, LocalDateTime now, String printedMark) {
|
||||
WarehouseTransferCarton carton = new WarehouseTransferCarton();
|
||||
carton.setId(IdWorker.getId()); carton.setTransferOrderId(transferId); carton.setTransferItemId(transferItemId);
|
||||
carton.setCustomerId(customerId); carton.setCartonMark(LastlegOrderNoGenerator.compactCartonMark(carton.getId()));
|
||||
carton.setCustomerId(customerId); carton.setCartonMark(printedMark == null ? LastlegOrderNoGenerator.compactCartonMark(carton.getId()) : printedMark);
|
||||
carton.setCustomerCartonMark(trim(customerCartonMark));
|
||||
carton.setCartonIndex(cartonIndex); carton.setTotalCartons(totalCartons); carton.setStatus("EXPECTED");
|
||||
carton.setCreatedAt(now); carton.setUpdatedAt(now); transferCartons.insert(carton);
|
||||
|
||||
+57
@@ -230,6 +230,11 @@ class RelabelApplicationServiceTest {
|
||||
});
|
||||
lenient().when(pdaTasks.updateWithVersion(any(), any())).thenReturn(1);
|
||||
ReflectionTestUtils.setField(service, "applications", applications);
|
||||
var statusRepository = new com.baiyue.lastleg.relabel.repository.RelabelStatusRepository();
|
||||
ReflectionTestUtils.setField(statusRepository, "applications", applications);
|
||||
ReflectionTestUtils.setField(statusRepository, "orders", orders);
|
||||
ReflectionTestUtils.setField(statusRepository, "returnOrders", returnOrders);
|
||||
ReflectionTestUtils.setField(service, "statusRepository", statusRepository);
|
||||
ReflectionTestUtils.setField(service, "transferRelabelCompletion", transferRelabelCompletion);
|
||||
ReflectionTestUtils.setField(service, "items", items);
|
||||
ReflectionTestUtils.setField(service, "histories", histories);
|
||||
@@ -1305,6 +1310,58 @@ class RelabelApplicationServiceTest {
|
||||
verify(applications, never()).insert(any(RelabelApplication.class));
|
||||
}
|
||||
|
||||
@org.junit.jupiter.params.ParameterizedTest
|
||||
@org.junit.jupiter.params.provider.ValueSource(strings = {"PENDING_REVIEW", "PENDING_PROCESSING", "PROCESSING", "CUSTOMER_CONFIRM_PENDING", "REJECTED"})
|
||||
void unfinishedApplicationsCanBeVoidedAndRetainTransferMainStatus(String status) {
|
||||
var app = application(11L, "RL-VOID", "10001", "void-key", "hash", status);
|
||||
app.setLastlegOrderId(21L); app.setWalletStatus("FROZEN");
|
||||
when(applications.lockNo("10001", "RL-VOID")).thenReturn(app);
|
||||
stubDetail(app);
|
||||
var tail = order(21L, "10001", "WT-1", "INBOUND_COMPLETED"); tail.setOrderType("WAREHOUSE_TRANSFER");
|
||||
when(orders.selectById(21L)).thenReturn(tail);
|
||||
service.changeStatus("10001", "RL-VOID", "operator", new RelabelRequests.ChangeStatus("CANCELLED", "废弃预填换标要求"));
|
||||
assertEquals("CANCELLED", app.getStatus());
|
||||
assertEquals("INBOUND_COMPLETED", tail.getCurrentStatus());
|
||||
assertEquals("CANCELLED", tail.getRelabelStatus());
|
||||
assertEquals("RELEASE_PENDING", app.getWalletStatus());
|
||||
verify(walletCommands).publish(eq("RELEASE"), eq(10001L), eq("RELABEL_ORDER"), eq("RL-VOID"), isNull(), anyString(), eq(2L));
|
||||
verify(histories).insert(any(RelabelStatusHistory.class));
|
||||
}
|
||||
|
||||
@Test void repeatedVoidReturnsExistingRecordWithoutReleasingAgain() {
|
||||
var app = application(11L, "RL-VOID-AGAIN", "10001", "key", "hash", "CANCELLED");
|
||||
app.setLastlegOrderId(21L);
|
||||
when(applications.lockNo("10001", app.getRelabelNo())).thenReturn(app); stubDetail(app);
|
||||
var result = service.changeStatus("10001", app.getRelabelNo(), "operator", new RelabelRequests.ChangeStatus("CANCELLED", "作废"));
|
||||
assertEquals("CANCELLED", result.application().status());
|
||||
verify(applications, never()).updateById(any(RelabelApplication.class));
|
||||
verify(inventoryCommands, never()).release(anyString(), anyString(), any());
|
||||
verify(histories, never()).insert(any(RelabelStatusHistory.class));
|
||||
}
|
||||
|
||||
@Test void directPendingReviewApplicationCanBeVoidedOutsideTailOrderReview() {
|
||||
var app = application(11L, "RL-DIRECT-VOID", "10001", "void-key", "hash", "PENDING_REVIEW");
|
||||
app.setLastlegOrderId(21L);
|
||||
when(applications.lockNo("10001", app.getRelabelNo())).thenReturn(app); stubDetail(app);
|
||||
var tail = order(21L, "10001", "RL-ORDER", "WAIT_RELABEL"); tail.setOrderType("RELABEL_APPLICATION");
|
||||
when(orders.selectById(21L)).thenReturn(tail);
|
||||
service.changeStatus("10001", app.getRelabelNo(), "operator", new RelabelRequests.ChangeStatus("CANCELLED", "作废"));
|
||||
assertEquals("CANCELLED", tail.getCurrentStatus());
|
||||
}
|
||||
|
||||
@org.junit.jupiter.params.ParameterizedTest
|
||||
@org.junit.jupiter.params.provider.ValueSource(strings = {"READY_FOR_OUTBOUND", "COMPLETED"})
|
||||
void completedRelabelCannotBeVoided(String status) {
|
||||
var app = application(11L, "RL-DONE", "10001", "key", "hash", status);
|
||||
app.setLastlegOrderId(21L);
|
||||
when(applications.lockNo("10001", "RL-DONE")).thenReturn(app);
|
||||
when(orders.selectById(21L)).thenReturn(order(21L, "10001", "ORDER", "COMPLETED"));
|
||||
var failure = assertThrows(ResponseStatusException.class, () -> service.changeStatus("10001", "RL-DONE", "operator", new RelabelRequests.ChangeStatus("CANCELLED", "作废")));
|
||||
assertTrue(failure.getReason().contains("不能作废"));
|
||||
verify(applications, never()).updateById(any(RelabelApplication.class));
|
||||
verify(walletCommands, never()).publish(anyString(), anyLong(), anyString(), anyString(), any(), any(), anyLong());
|
||||
}
|
||||
|
||||
private void stubDetail(RelabelApplication application) {
|
||||
when(applications.findNo(application.getCustomerId(), application.getRelabelNo())).thenReturn(application);
|
||||
when(items.selectList(any())).thenReturn(List.of(item(31L, application.getId(), "SKU-A", 10, 6)));
|
||||
|
||||
+17
-2
@@ -619,7 +619,7 @@ class WarehouseTransferServiceTest {
|
||||
new BigDecimal("0.2500"), "WAREHOUSE_PICKUP", LocalDateTime.of(2026, 9, 12, 10, 0),
|
||||
null, null, "TRACK-MUST-NOT-BE-SAVED", null, "Incoming transit cargo", true,
|
||||
null, null, null, null, null, "key-incoming",
|
||||
List.of(new WarehouseTransferRequests.Item("SKU-1", "Product A", 2, null),
|
||||
List.of(new WarehouseTransferRequests.Item("SKU-1", "Product A", 2, null, null, List.of("BX-PRINTED1", "BX-PRINTED2")),
|
||||
new WarehouseTransferRequests.Item("SKU-2", "Product B", 3, null)), null);
|
||||
when(transfers.findNo(eq("TENANT-A"), anyString())).thenAnswer(invocation -> transfer(WarehouseTransferService.DRAFT));
|
||||
when(transferItems.selectList(any())).thenReturn(List.of());
|
||||
@@ -647,6 +647,8 @@ class WarehouseTransferServiceTest {
|
||||
assertEquals("PENDING_BINDING", tailOrder.getValue().getRelabelStatus());
|
||||
assertEquals(List.of(1, 2, 3, 4, 5), cartons.getAllValues().stream()
|
||||
.map(WarehouseTransferCarton::getCartonIndex).toList());
|
||||
assertEquals(List.of("BX-PRINTED1", "BX-PRINTED2"), cartons.getAllValues().subList(0, 2).stream()
|
||||
.map(WarehouseTransferCarton::getCartonMark).toList());
|
||||
assertTrue(cartons.getAllValues().stream().allMatch(value -> value.getCartonMark()
|
||||
.matches("BX-[0-9A-Z]+")));
|
||||
when(transfers.findKey("TENANT-A", request.idempotencyKey())).thenReturn(transfer.getValue());
|
||||
@@ -814,7 +816,20 @@ class WarehouseTransferServiceTest {
|
||||
verify(transfers, never()).updateById(any(WarehouseTransferOrder.class));
|
||||
}
|
||||
|
||||
/** 草稿预先打印与后续正式提交使用同一服务端箱唛。 */
|
||||
/** 未建单时生成的箱唛必须完整且不重复。 */
|
||||
@Test
|
||||
void duplicateOrMismatchedPreprintedMarksRejectBeforeCreatingOrder() throws Exception {
|
||||
var mapper = new ObjectMapper().findAndRegisterModules();
|
||||
for (var marks : List.of(List.of("BX-ONE"), List.of("BX-ONE", "BX-ONE", "BX-3", "BX-4", "BX-5"))) {
|
||||
com.fasterxml.jackson.databind.node.ObjectNode json = mapper.valueToTree(request(false));
|
||||
json.remove("inboundModeValid");
|
||||
((com.fasterxml.jackson.databind.node.ObjectNode) json.get("items").get(0)).set("cartonMarks", mapper.valueToTree(marks));
|
||||
var input = mapper.treeToValue(json, WarehouseTransferRequests.Create.class);
|
||||
assertThrows(ResponseStatusException.class, () -> service.create("TENANT-A", "USER-1", input));
|
||||
}
|
||||
verify(transfers, never()).insert(any(WarehouseTransferOrder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void draftCanPrintPersistedCartonLabels() {
|
||||
WarehouseTransferOrder transfer = persistedTransfer(); transfer.setStatus(WarehouseTransferService.DRAFT);
|
||||
|
||||
Reference in New Issue
Block a user