feat(lastleg): retain receipt and redispatch history on exceptions

This commit is contained in:
2026-10-04 10:45:05 +08:00
parent 446fad33d0
commit dc993c9713
5 changed files with 194 additions and 4 deletions
@@ -0,0 +1,76 @@
package com.baiyue.lastleg.exceptionorder.service;
import com.baiyue.lastleg.exceptionorder.dto.ExceptionOrderResponses;
import com.baiyue.lastleg.exceptionorder.entity.ExceptionOrder;
import com.baiyue.lastleg.exceptionorder.mapper.ExceptionOrderMapper;
import com.baiyue.lastleg.exceptionorder.mapper.ExceptionReturnItemMapper;
import com.baiyue.lastleg.inventory.mapper.ReturnStockReceiptMapper;
import com.baiyue.lastleg.inventory.mapper.ReturnStockReceiptMapper.ReturnHistoryRow;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class ExceptionReturnHistoryTest {
private final ExceptionOrderService service = new ExceptionOrderService();
private final LocalDateTime time = LocalDateTime.of(2026, 10, 3, 23, 24);
private List<ExceptionOrderResponses.ReturnReceipt> history(List<ReturnHistoryRow> rows) {
return ReflectionTestUtils.invokeMethod(service, "receiptHistory", rows);
}
private ReturnHistoryRow row(long receipt, String dispatch, long item, String delivery, int quantity) {
return new ReturnHistoryRow(receipt, "RSR-" + receipt, 2L, time, item, dispatch, "DEPARTED",
quantity, (long) quantity, delivery, delivery == null ? null : "DELIVERED");
}
@Test void retainsAllBatchesAndMultipleRedispatchesWithoutMultiplyingItemQuantitiesByDeliveryRows() {
var result = history(List.of(row(1, "DS-A", 11, "DL-A", 1), row(1, "DS-A", 11, "DL-B", 1),
row(1, "DS-B", 12, "DL-C", 1), new ReturnHistoryRow(2L, "RSR-2", 1L, time.plusMinutes(1),
null, null, null, null, 0L, null, null)));
assertEquals(2, result.size()); assertEquals(2, result.get(0).redispatches().size());
var first = result.get(0).redispatches().get(0);
assertEquals(1, first.selectedPackages()); assertEquals(1, first.pickedPackages());
assertEquals(List.of("DL-A", "DL-B"), first.deliveries().stream().map(ExceptionOrderResponses.RelatedDelivery::deliveryNo).toList());
assertEquals(1, result.get(1).receivedPackages()); assertTrue(result.get(1).redispatches().isEmpty());
}
@Test void sameDispatchAcrossDistinctReceiptBatchesRemainsSeparate() {
var result = history(List.of(row(1, "DS-A", 11, "DL-A", 1), row(2, "DS-A", 12, "DL-A", 1)));
assertEquals(2, result.size()); assertEquals("RSR-1", result.get(0).receiptNo());
assertEquals("RSR-2", result.get(1).receiptNo());
assertEquals(1, result.get(0).redispatches().get(0).pickedPackages());
}
@Test void currentDetailReadsOnlyResolvedCustomerAndExceptionWithoutStateCommands() {
var orders = mock(ExceptionOrderMapper.class); var returns = mock(ExceptionReturnItemMapper.class);
var receipts = mock(ReturnStockReceiptMapper.class);
ReflectionTestUtils.setField(service, "exceptionOrders", orders);
ReflectionTestUtils.setField(service, "returnItems", returns);
ReflectionTestUtils.setField(service, "returnReceipts", receipts);
var order = new ExceptionOrder(); order.setId(10L); order.setCustomerId("C");
order.setExceptionNo("EX-10"); order.setExceptionType("RETURN"); order.setStatus("COMPLETED");
when(orders.findByNo("C", "EX-10")).thenReturn(order);
when(returns.findByException("C", 10L)).thenReturn(List.of());
when(receipts.history("C", 10L)).thenReturn(List.of(row(1, "DS-A", 11, "DL-A", 2)));
var result = service.detail("C", "EX-10");
assertEquals("COMPLETED", result.order().status()); assertTrue(result.order().allowedActions().isEmpty());
assertEquals(2, result.returnReceipts().get(0).receivedPackages());
verify(receipts).history("C", 10L); verify(orders, never()).updateById(any(ExceptionOrder.class));
assertThrows(ResponseStatusException.class, () -> service.detail("OTHER", "EX-10"));
verify(receipts, never()).history(eq("OTHER"), any());
}
@Test void historyQueryKeepsConsumedReceiptsAndFiltersDeliveryByCustomerAndItemDestination() throws Exception {
String sql = String.join(" ", ReturnStockReceiptMapper.class.getMethod("history", String.class, Long.class)
.getAnnotation(org.apache.ibatis.annotations.Select.class).value());
assertTrue(sql.contains("r.exception_order_id=#{exceptionId}"));
assertTrue(sql.contains("BINARY r.customer_id=BINARY #{customerId}"));
assertTrue(sql.contains("BINARY delivery.delivery_group_key=BINARY CASE"));
assertTrue(sql.contains("WHEN di.destination_type='PRIVATE'"));
assertTrue(sql.contains("a.return_receipt_id=r.id"));
assertFalse(sql.contains("available_packages")); assertFalse(sql.contains("ll_order o"));
}
@Test void receiptBusinessNumberAndPositiveReceivedQuantityCannotBeGuessed() {
assertThrows(ResponseStatusException.class, () -> history(List.of(new ReturnHistoryRow(1L, null, 2L, time,
null, null, null, null, 0L, null, null))));
}
}