diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillConfirmReturnTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillConfirmReturnTest.java
index 3cfe106f..b753a03b 100644
--- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillConfirmReturnTest.java
+++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillConfirmReturnTest.java
@@ -23,6 +23,7 @@ import org.springblade.transport.pojo.entity.WaybillNodePunch;
import org.springblade.transport.service.ILoadingManageService;
import org.springblade.transport.service.IProcessConfigService;
import org.springblade.transport.service.IReceivablePayableDetailService;
+import org.springblade.transport.support.WaybillProcessSupport;
import java.lang.reflect.Field;
import java.util.List;
@@ -37,15 +38,17 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
- * 确认回单:回单节点要求业务员确认时,内部人员确认后把运单置为已完成。
+ * 确认回单:停在待回单确认的运单,内部人员确认后把运单置为已完成。
*
- * 完整入口不校验节点打卡,故这里必须自己挡住两件事——该运单的司机打卡节点(在途除外)全部已打卡、
- * 无未了结异常;不挡住就成了绕过打卡门禁与异常门禁的通道。回单不要求确认时不能走这个入口。
+ * 完整入口不校验节点打卡,故这里必须自己挡住三件事——运单确实停在待回单确认、该运单的司机打卡节点
+ * (在途除外)全部已打卡、无未了结异常;不挡住就成了绕过打卡门禁与异常门禁的通道。
+ * 判据只看运单状态,不看回单配置当前值。
*/
class WaybillConfirmReturnTest {
private static final Long WAYBILL_ID = 5701L;
private static final String DEPT_ID = "1123598813738675201";
+ private static final String PENDING_RETURN_CONFIRM = WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM;
private static final String RETURN_NEED_CONFIRM_JSON = """
[{"key":"load","name":"装货","type":"cargo","enabled":true,"punch":true},
@@ -118,9 +121,9 @@ class WaybillConfirmReturnTest {
}
@Test
- @DisplayName("回单要求确认且节点都打完:置为已完成,并照常生成应收应付、尝试完成配载单")
+ @DisplayName("待回单确认且节点都打完:置为已完成,并照常生成应收应付、尝试完成配载单")
void confirmsReturnAndCompletesWaybill() {
- stubWaybill(RETURN_NEED_CONFIRM_JSON);
+ stubWaybill(RETURN_NEED_CONFIRM_JSON, PENDING_RETURN_CONFIRM);
stubPunches("load", "sign");
boolean result = service.confirmReturnReceipt(WAYBILL_ID);
@@ -135,16 +138,57 @@ class WaybillConfirmReturnTest {
@Test
@DisplayName("在途节点没打卡也放行:它不是必经一步")
void transitNodeDoesNotBlockConfirmation() {
- stubWaybill(WITH_TRANSIT_JSON);
+ stubWaybill(WITH_TRANSIT_JSON, PENDING_RETURN_CONFIRM);
stubPunches("load", "sign");
assertEquals(true, service.confirmReturnReceipt(WAYBILL_ID));
}
+ @Test
+ @DisplayName("进行中的运单不能确认回单:状态校验先于打卡校验")
+ void runningWaybillCannotConfirmReturn() {
+ stubWaybill(RETURN_NEED_CONFIRM_JSON, "running");
+ // 故意一条打卡都不补:若状态校验排在打卡校验之后,这里报的会是缺哪个节点
+ stubPunches();
+
+ ServiceException exception = assertThrows(ServiceException.class,
+ () -> service.confirmReturnReceipt(WAYBILL_ID));
+
+ assertEquals("当前状态不允许确认回单", exception.getMessage());
+ verify(waybillMapper, never()).updateById(any(Waybill.class));
+ verify(receivablePayableDetailService, never()).generateForCompletedWaybills(any());
+ }
+
+ @Test
+ @DisplayName("已完成的运单不能确认回单")
+ void completedWaybillCannotConfirmReturn() {
+ stubWaybill(RETURN_NEED_CONFIRM_JSON, "completed");
+ stubPunches("load", "sign");
+
+ ServiceException exception = assertThrows(ServiceException.class,
+ () -> service.confirmReturnReceipt(WAYBILL_ID));
+
+ assertEquals("当前状态不允许确认回单", exception.getMessage());
+ verify(waybillMapper, never()).updateById(any(Waybill.class));
+ }
+
+ @Test
+ @DisplayName("待执行的运单不能确认回单")
+ void pendingWaybillCannotConfirmReturn() {
+ stubWaybill(RETURN_NEED_CONFIRM_JSON, "pending");
+ stubPunches("load", "sign");
+
+ ServiceException exception = assertThrows(ServiceException.class,
+ () -> service.confirmReturnReceipt(WAYBILL_ID));
+
+ assertEquals("当前状态不允许确认回单", exception.getMessage());
+ verify(waybillMapper, never()).updateById(any(Waybill.class));
+ }
+
@Test
@DisplayName("还有节点没打卡:拒绝并点名缺哪个节点,不改状态")
void unfinishedPunchNodeBlocksConfirmation() {
- stubWaybill(RETURN_NEED_CONFIRM_JSON);
+ stubWaybill(RETURN_NEED_CONFIRM_JSON, PENDING_RETURN_CONFIRM);
stubPunches("load");
ServiceException exception = assertThrows(ServiceException.class,
@@ -157,7 +201,7 @@ class WaybillConfirmReturnTest {
@Test
@DisplayName("有未了结异常:拒绝,不改状态")
void unresolvedExceptionBlocksConfirmation() {
- stubWaybill(RETURN_NEED_CONFIRM_JSON);
+ stubWaybill(RETURN_NEED_CONFIRM_JSON, PENDING_RETURN_CONFIRM);
stubPunches("load", "sign");
when(exceptionDisposalMapper.selectCount(any())).thenReturn(1L);
@@ -169,31 +213,59 @@ class WaybillConfirmReturnTest {
}
@Test
- @DisplayName("回单不要求业务员确认:这个入口不适用,说清而不是静默完成")
- void returnWithoutConfirmationIsRejected() {
- stubWaybill(RETURN_NO_CONFIRM_JSON);
+ @DisplayName("回单配置已改成无需确认、运单停在待回单确认:仍能确认回单")
+ void confirmStillWorksAfterConfigSwitchedToNoConfirm() {
+ stubWaybill(RETURN_NO_CONFIRM_JSON, PENDING_RETURN_CONFIRM);
+ stubPunches("load", "sign");
+
+ assertEquals(true, service.confirmReturnReceipt(WAYBILL_ID));
+
+ verify(waybillMapper).updateById(org.mockito.ArgumentMatchers.argThat(waybill ->
+ "completed".equals(waybill.getBusinessStatus())));
+ verify(receivablePayableDetailService).generateForCompletedWaybills(List.of(WAYBILL_ID));
+ }
+
+ @Test
+ @DisplayName("回单配置无需确认且运单还在进行中:这个入口不适用,报状态不符")
+ void noConfirmConfigRunningWaybillCannotConfirmReturn() {
+ stubWaybill(RETURN_NO_CONFIRM_JSON, "running");
stubPunches("load", "sign");
ServiceException exception = assertThrows(ServiceException.class,
() -> service.confirmReturnReceipt(WAYBILL_ID));
- assertEquals("该运单的回单不需要业务员确认", exception.getMessage());
+ assertEquals("当前状态不允许确认回单", exception.getMessage());
verify(waybillMapper, never()).updateById(any(Waybill.class));
}
+ @Test
+ @DisplayName("待回单确认可以取消:置为已取消,不生成应收应付")
+ void cancelPendingReturnConfirmWaybill() {
+ // 取消接口不允许已配载的运单
+ stubWaybill(RETURN_NEED_CONFIRM_JSON, PENDING_RETURN_CONFIRM).setLoadingNo(null);
+
+ assertEquals(true, service.cancel(WAYBILL_ID));
+
+ verify(waybillMapper).updateById(org.mockito.ArgumentMatchers.argThat(waybill ->
+ "cancelled".equals(waybill.getBusinessStatus()) && WAYBILL_ID.equals(waybill.getId())));
+ verify(receivablePayableDetailService, never()).generateForCompletedWaybills(any());
+ verify(loadingManageService, never()).completeIfAllWaybillsCompleted(any());
+ }
+
// ------------------------------------------------------------ 夹具
- private void stubWaybill(String processJson) {
+ private Waybill stubWaybill(String processJson, String businessStatus) {
Waybill waybill = new Waybill();
waybill.setId(WAYBILL_ID);
waybill.setWaybillNo("YD202610180002");
waybill.setDeptId(Long.valueOf(DEPT_ID));
waybill.setProjectId(7001L);
waybill.setProcessJson(processJson);
- waybill.setBusinessStatus("running");
+ waybill.setBusinessStatus(businessStatus);
waybill.setLoadingNo("PZ-20261018-001");
waybill.setIsDeleted(0);
when(waybillMapper.selectById(WAYBILL_ID)).thenReturn(waybill);
+ return waybill;
}
private void stubPunches(String... nodeCodes) {
diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordListTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordListTest.java
index c61dd1ec..6f055f13 100644
--- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordListTest.java
+++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordListTest.java
@@ -14,6 +14,7 @@ import org.springblade.transport.pojo.entity.Waybill;
import org.springblade.transport.pojo.entity.WaybillEnroutePunch;
import org.springblade.transport.pojo.entity.WaybillNodePunch;
import org.springblade.transport.pojo.vo.WaybillPunchRecordItemVO;
+import org.springblade.transport.support.WaybillProcessSupport;
import java.lang.reflect.Field;
import java.text.ParseException;
@@ -126,6 +127,19 @@ class WaybillPunchRecordListTest {
assertEquals("", accept.getPunchTime(), "没有接单动作就没有完成时刻,不得拿别的字段编一个");
}
+ @Test
+ @DisplayName("待回单确认的无需确认接单运单:接单节点已打卡,但没有完成时刻")
+ void acceptNodePunchedWhenPendingReturnConfirm() {
+ stubWaybill(waybill(NO_CONFIRM_ACCEPT_JSON,
+ WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, null));
+
+ WaybillPunchRecordItemVO accept = acceptRecord();
+
+ assertTrue(accept.getPunched());
+ assertEquals("已打卡", accept.getStatusName());
+ assertEquals("", accept.getPunchTime());
+ }
+
@Test
@DisplayName("无需确认接单但仍是草稿:接单节点未打卡")
void acceptNodeUnpunchedWhenDraft() {
diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordSaveTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordSaveTest.java
index 1778a850..fc7b0f38 100644
--- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordSaveTest.java
+++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordSaveTest.java
@@ -68,6 +68,7 @@ class WaybillPunchRecordSaveTest {
assistant.setCurrentNamespace("test");
TableInfoHelper.initTableInfo(assistant, WaybillNodePunch.class);
TableInfoHelper.initTableInfo(assistant, WaybillEnroutePunch.class);
+ TableInfoHelper.initTableInfo(assistant, Waybill.class);
}
@BeforeEach
@@ -79,6 +80,7 @@ class WaybillPunchRecordSaveTest {
inject("baseMapper", waybillMapper);
inject("waybillNodePunchMapper", nodePunchMapper);
inject("waybillEnroutePunchMapper", enroutePunchMapper);
+ when(waybillMapper.updateById(any(Waybill.class))).thenReturn(1);
}
// ------------------------------------------------------------ 过程节点打卡
@@ -202,6 +204,61 @@ class WaybillPunchRecordSaveTest {
assertEquals(Set.of(7001L), new HashSet<>(captor.getValue()));
}
+ @Test
+ @DisplayName("待回单确认:取消打卡后退回进行中——进入该状态的前提已被撤掉")
+ void revertToRunningWhenCancelPunchOnPendingReturnConfirm() {
+ Waybill waybill = waybill();
+ waybill.setBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM);
+ when(waybillMapper.selectById(WAYBILL_ID)).thenReturn(waybill);
+ when(nodePunchMapper.selectList(any())).thenReturn(List.of(nodePunch(7001L, "load", "装货")));
+
+ WaybillPunchRecordSaveRequest request = request("node", "load", "装货");
+ request.setPunched(false);
+
+ assertTrue(service.savePunchRecord(request));
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Waybill.class);
+ verify(waybillMapper).updateById(captor.capture());
+ assertEquals(WaybillProcessSupport.STATUS_RUNNING, captor.getValue().getBusinessStatus());
+ assertEquals(WAYBILL_ID, captor.getValue().getId());
+ }
+
+ @Test
+ @DisplayName("进行中:取消打卡不动运单状态")
+ void keepStatusWhenCancelPunchOnRunningWaybill() {
+ Waybill waybill = waybill();
+ waybill.setBusinessStatus(WaybillProcessSupport.STATUS_RUNNING);
+ when(waybillMapper.selectById(WAYBILL_ID)).thenReturn(waybill);
+ when(nodePunchMapper.selectList(any())).thenReturn(List.of(nodePunch(7001L, "load", "装货")));
+
+ WaybillPunchRecordSaveRequest request = request("node", "load", "装货");
+ request.setPunched(false);
+
+ assertTrue(service.savePunchRecord(request));
+
+ verify(waybillMapper, never()).updateById(any(Waybill.class));
+ }
+
+ @Test
+ @DisplayName("待回单确认:取消在途打卡同样退回进行中")
+ void revertToRunningWhenCancelEnroutePunchOnPendingReturnConfirm() {
+ Waybill waybill = waybill();
+ waybill.setBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM);
+ when(waybillMapper.selectById(WAYBILL_ID)).thenReturn(waybill);
+ WaybillEnroutePunch existed = new WaybillEnroutePunch();
+ existed.setId(8001L);
+ when(enroutePunchMapper.selectList(any())).thenReturn(List.of(existed));
+
+ WaybillPunchRecordSaveRequest request = request("enroute", "transit", "在途");
+ request.setPunched(false);
+
+ assertTrue(service.savePunchRecord(request));
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Waybill.class);
+ verify(waybillMapper).updateById(captor.capture());
+ assertEquals(WaybillProcessSupport.STATUS_RUNNING, captor.getValue().getBusinessStatus());
+ }
+
@Test
@DisplayName("已完成运单仍可补录——门禁只对待回单确认生效")
void keepBackfillOnCompletedWaybill() {