fix overseas return inventory flow

This commit is contained in:
2026-09-12 13:55:48 +08:00
parent 2191bacbfe
commit d33922ad35
5 changed files with 150 additions and 6 deletions
@@ -24,7 +24,7 @@ import java.util.stream.Collectors;
public class SecurityEventOutboxService {
private static final List<String> OVERSEAS_AUTHORITY_PREFIXES = List.of(
"platform:overseas-warehouse:", "platform:overseas-business:",
"platform:overseas-master-data:", "merchant:warehouse:",
"platform:overseas-master-data:", "merchant:warehouse:", "merchant:overseas-warehouse:",
"merchant:cross-border:", "platform:finance:", "lastleg:");
@Resource private JdbcTemplate jdbc;
@Resource private ObjectMapper objectMapper;
@@ -84,7 +84,7 @@ class SecurityEventOutboxServiceTest {
.thenReturn(List.of("PLATFORM_ADMIN"));
when(authorizationQueries.findPermissionsByIdentity(101L, 0L, "ADMIN"))
.thenReturn(List.of("platform:overseas-warehouse:orders:view", "platform:iam:users:view",
"lastleg:orders:view"));
"merchant:overseas-warehouse:return-inventory:list", "lastleg:orders:view"));
service.permissionChanged(101L, 0L, "ADMIN", "ROLE_CHANGED");
@@ -96,7 +96,8 @@ class SecurityEventOutboxServiceTest {
envelope.payload(), IamSecurityEventsV1.PermissionVersionChanged.class);
assertThat(payload.roles()).containsExactly("PLATFORM_ADMIN");
assertThat(payload.authorities()).containsExactlyInAnyOrder(
"platform:overseas-warehouse:orders:view", "lastleg:orders:view");
"platform:overseas-warehouse:orders:view",
"merchant:overseas-warehouse:return-inventory:list", "lastleg:orders:view");
assertThat(payload.authorities()).doesNotContain("platform:iam:users:view");
}
}
@@ -6,6 +6,7 @@ import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
@@ -20,6 +21,12 @@ public interface ReturnInventoryBalanceMapper extends BaseMapper<ReturnInventory
@Param("totalQuantity") long totalQuantity, @Param("availableQuantity") long availableQuantity,
@Param("cartons") long cartons);
@Update("UPDATE ll_return_inventory_balance SET carton_quantity=carton_quantity+#{cartons},updated_at=NOW(3) " +
"WHERE customer_id=#{customerId} AND overseas_warehouse_id=#{warehouseId} AND sku_code=#{skuCode} " +
"AND carton_quantity+#{cartons}>=0")
int adjustCartonQuantity(@Param("customerId") String customerId, @Param("warehouseId") String warehouseId,
@Param("skuCode") String skuCode, @Param("cartons") long cartons);
@Select("<script>SELECT COUNT(*) FROM ll_return_inventory_balance WHERE customer_id=#{customerId} " +
"<if test='warehouseId != null and warehouseId != \"\"'>AND overseas_warehouse_id=#{warehouseId} </if>" +
"<if test='skuCode != null and skuCode != \"\"'>AND sku_code LIKE CONCAT('%',#{skuCode},'%') </if></script>")
@@ -65,7 +65,8 @@ public class ReturnInventoryService {
long total = lines.stream().mapToLong(ReceiptLine::quantity).sum();
ReturnInventoryCarton carton = new ReturnInventoryCarton();
carton.setId(cartonId); carton.setCustomerId(order.getCustomerId()); carton.setOverseasWarehouseId(order.getOverseasWarehouseId());
carton.setSourceReceiptId(receipt.getId()); carton.setSourceBatchNo(receipt.getReceiptBatchNo()); carton.setCartonMark(cartonMark);
carton.setSourceReceiptId(receipt.getId()); carton.setSourceBatchNo(receipt.getReceiptBatchNo());
carton.setSourceReturnNo(order.getReturnNo()); carton.setCartonMark(cartonMark);
carton.setStatus(AVAILABLE); carton.setSkuTypes(lines.size()); carton.setTotalQuantity(total); carton.setCreatedAt(now); carton.setUpdatedAt(now);
cartons.insert(carton);
for (ReceiptLine line : lines) {
@@ -264,8 +265,9 @@ public class ReturnInventoryService {
// A SKU's carton count is the number of available physical cartons containing it:
// remove fully emptied source cartons, then add the one merged target carton.
long cartonDelta = 1L - emptiedBySku.getOrDefault(entry.getKey(), 0);
balances.adjust(IdWorker.getId(), customerId, target.getOverseasWarehouseId(), entry.getKey(), productNames.get(entry.getKey()), 0,
-entry.getValue(), cartonDelta);
if (balances.adjustCartonQuantity(customerId, target.getOverseasWarehouseId(), entry.getKey(), cartonDelta) != 1) {
throw new IllegalStateException("退货合箱库存箱数更新失败: " + entry.getKey());
}
}
operation.setResultCartonId(targetId); operation.setUpdatedAt(now); operations.updateById(operation);
return new ReturnInventoryResponses.MergeResult(target.getCartonMark(), requested.keySet().iterator().next(), total, label(target));
@@ -0,0 +1,134 @@
package com.baiyue.lastleg.returnreceipt.service;
import com.baiyue.lastleg.pda.security.PdaWarehouseIdentity;
import com.baiyue.lastleg.returnreceipt.dto.ReturnInventoryRequests;
import com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCarton;
import com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCartonItem;
import com.baiyue.lastleg.returnreceipt.entity.ReturnOrder;
import com.baiyue.lastleg.returnreceipt.entity.ReturnReceipt;
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryBalanceMapper;
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonItemMapper;
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper;
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryMovementMapper;
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryOperationMapper;
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryQueryMapper;
import com.baiyue.lastleg.returnreceipt.mapper.ReturnOrderMapper;
import com.baiyue.lastleg.returnreceipt.mapper.ReturnReceiptMapper;
import com.baiyue.lastleg.service.LastlegOrderNoGenerator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import java.math.BigDecimal;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class ReturnInventoryServiceTest {
@Mock ReturnInventoryCartonMapper cartons;
@Mock ReturnInventoryCartonItemMapper cartonItems;
@Mock ReturnInventoryBalanceMapper balances;
@Mock ReturnInventoryOperationMapper operations;
@Mock ReturnInventoryQueryMapper queries;
@Mock ReturnInventoryMovementMapper movements;
@Mock LastlegOrderNoGenerator numbers;
@Mock ReturnOrderMapper orders;
@Mock ReturnReceiptMapper receipts;
private ReturnInventoryService service;
@BeforeEach
void setUp() {
service = new ReturnInventoryService();
ReflectionTestUtils.setField(service, "cartons", cartons);
ReflectionTestUtils.setField(service, "cartonItems", cartonItems);
ReflectionTestUtils.setField(service, "balances", balances);
ReflectionTestUtils.setField(service, "operations", operations);
ReflectionTestUtils.setField(service, "queries", queries);
ReflectionTestUtils.setField(service, "movements", movements);
ReflectionTestUtils.setField(service, "numbers", numbers);
ReflectionTestUtils.setField(service, "orders", orders);
ReflectionTestUtils.setField(service, "receiptMapper", receipts);
}
@Test
void receiptCartonKeepsTheSourceReturnNumber() {
ReturnOrder order = completedOrder();
ReturnReceipt receipt = new ReturnReceipt();
receipt.setId(21L);
receipt.setReceiptBatchNo("RB-1");
when(numbers.next(anyString(), anyString(), anyLong())).thenReturn("RC-1");
service.createReceiptCarton(order, receipt, null,
List.of(new ReturnInventoryService.ReceiptLine("SKU-A", "商品A", 2)));
ArgumentCaptor<ReturnInventoryCarton> captor = ArgumentCaptor.forClass(ReturnInventoryCarton.class);
verify(cartons).insert(captor.capture());
assertThat(captor.getValue().getSourceReturnNo()).isEqualTo("RT-1");
}
@Test
void mergeMovesItemsBetweenCartonsWithoutReducingAvailableQuantity() {
ReturnOrder order = completedOrder();
ReturnReceipt receipt = new ReturnReceipt();
receipt.setId(21L);
ReturnInventoryCarton source = new ReturnInventoryCarton();
source.setId(31L);
source.setCustomerId("10001");
source.setOverseasWarehouseId("42");
source.setCartonMark("RC-SOURCE");
source.setStatus(ReturnInventoryService.AVAILABLE);
source.setSkuTypes(2);
source.setTotalQuantity(4L);
ReturnInventoryCartonItem skuA = item(41L, "SKU-A", 2L);
ReturnInventoryCartonItem skuB = item(42L, "SKU-B", 2L);
when(orders.findByNos("42", List.of("RT-1"))).thenReturn(List.of(order));
when(receipts.listOrder(11L)).thenReturn(List.of(receipt));
when(cartons.findReceipt(21L)).thenReturn(source);
when(cartons.lockMark("RC-SOURCE")).thenReturn(source);
when(cartonItems.listCarton(31L)).thenReturn(List.of(skuA, skuB));
when(numbers.next(anyString(), anyString(), anyLong())).thenReturn("RC-MERGED");
when(balances.adjustCartonQuantity(anyString(), anyString(), anyString(), anyLong())).thenReturn(1);
service.merge(new PdaWarehouseIdentity("operator-1", "操作员", 42L),
new ReturnInventoryRequests.Merge(List.of("RT-1"), List.of(
new ReturnInventoryRequests.ValidItem("SKU-A", 1),
new ReturnInventoryRequests.ValidItem("SKU-B", 1)),
BigDecimal.ONE, BigDecimal.ONE, BigDecimal.ONE, BigDecimal.ONE, "merge-key"));
verify(balances).adjustCartonQuantity("10001", "42", "SKU-A", 1L);
verify(balances).adjustCartonQuantity("10001", "42", "SKU-B", 1L);
assertThat(source.getTotalQuantity()).isEqualTo(2L);
assertThat(source.getStatus()).isEqualTo(ReturnInventoryService.AVAILABLE);
}
private ReturnOrder completedOrder() {
ReturnOrder order = new ReturnOrder();
order.setId(11L);
order.setCustomerId("10001");
order.setReturnNo("RT-1");
order.setOverseasWarehouseId("42");
order.setStatus("COMPLETED");
return order;
}
private ReturnInventoryCartonItem item(long id, String sku, long quantity) {
ReturnInventoryCartonItem item = new ReturnInventoryCartonItem();
item.setId(id);
item.setCartonId(31L);
item.setCustomerId("10001");
item.setSkuCode(sku);
item.setProductNameZh(sku);
item.setQuantity(quantity);
return item;
}
}