Merge branch 'feat/pending-return-confirm' into feat/rc-10

This commit is contained in:
2026-10-22 13:42:45 +08:00
2 changed files with 52 additions and 9 deletions
@@ -49,6 +49,8 @@ public final class WaybillProcessSupport {
public static final String STATUS_PENDING = "pending"; public static final String STATUS_PENDING = "pending";
public static final String STATUS_RUNNING = "running"; public static final String STATUS_RUNNING = "running";
/** 司机已打完末位卡、回单需业务员确认,尚未生成应收应付 */
public static final String STATUS_PENDING_RETURN_CONFIRM = "pending_return_confirm";
public static final String ACCEPT_PENDING = "pending"; public static final String ACCEPT_PENDING = "pending";
public static final String ACCEPT_ACCEPTED = "accepted"; public static final String ACCEPT_ACCEPTED = "accepted";
public static final String ACCEPT_REJECTED = "rejected"; public static final String ACCEPT_REJECTED = "rejected";
@@ -134,14 +136,16 @@ public final class WaybillProcessSupport {
} }
/** /**
* 运单已结束:已完成或已取消。此后不再接受司机打卡。 * 运单已结束:已完成、已取消,或已打完末位卡停在待回单确认。此后不再接受司机打卡。
* <p> * <p>
* 只认这两个状态,大小写不敏感。草稿、待执行、待调度、调度中的运单司机仍要能打卡, * 只认这三个状态,大小写不敏感。草稿、待执行、待调度、调度中的运单司机仍要能打卡,
* 所以不能按 {@link #isTerminalBusinessStatus} 的「终态或调度中间态」口径判定。 * 所以不能按 {@link #isTerminalBusinessStatus} 的「终态或调度中间态」口径判定。
*/ */
public static boolean isPunchEnded(String businessStatus) { public static boolean isPunchEnded(String businessStatus) {
String status = stringVal(businessStatus); String status = stringVal(businessStatus);
return "completed".equalsIgnoreCase(status) || "cancelled".equalsIgnoreCase(status); return "completed".equalsIgnoreCase(status)
|| "cancelled".equalsIgnoreCase(status)
|| STATUS_PENDING_RETURN_CONFIRM.equalsIgnoreCase(status);
} }
/** /**
@@ -155,6 +159,9 @@ public final class WaybillProcessSupport {
if ("cancelled".equalsIgnoreCase(status)) { if ("cancelled".equalsIgnoreCase(status)) {
return "运单已取消"; return "运单已取消";
} }
if (STATUS_PENDING_RETURN_CONFIRM.equalsIgnoreCase(status)) {
return "运单待业务员确认回单";
}
return null; return null;
} }
@@ -195,7 +202,7 @@ public final class WaybillProcessSupport {
* 上报异常前的运单状态门禁原因;可上报时返回 null。 * 上报异常前的运单状态门禁原因;可上报时返回 null。
* <p> * <p>
* 与打卡共用「已开始运输」判据,但文案说的是能不能报,不是能不能打。 * 与打卡共用「已开始运输」判据,但文案说的是能不能报,不是能不能打。
* 结束优先于未开始:已完成/已取消的运单先说结束,不再提「等你上路」。 * 结束优先于未开始:已结束的运单先说结束,不再提「等你上路」。
*/ */
public static String reportBlockedReason(String businessStatus) { public static String reportBlockedReason(String businessStatus) {
String endedReason = punchEndedReason(businessStatus); String endedReason = punchEndedReason(businessStatus);
@@ -213,7 +220,8 @@ public final class WaybillProcessSupport {
|| "completed".equals(businessStatus) || "completed".equals(businessStatus)
|| "cancelled".equals(businessStatus) || "cancelled".equals(businessStatus)
|| "waiting_dispatch".equals(businessStatus) || "waiting_dispatch".equals(businessStatus)
|| "dispatching".equals(businessStatus); || "dispatching".equals(businessStatus)
|| STATUS_PENDING_RETURN_CONFIRM.equals(businessStatus);
} }
/** /**
@@ -334,15 +334,31 @@ class WaybillProcessSupportTest {
"TRANSPORT_JSON 的回单启用且没有 confirmMode:缺字段当没配「无需确认完成」,否则会跳过本该人工确认的回单"); "TRANSPORT_JSON 的回单启用且没有 confirmMode:缺字段当没配「无需确认完成」,否则会跳过本该人工确认的回单");
} }
// ------------------------------------------------------------ 待回单确认
@Test
@DisplayName("待回单确认落库字面量为 pending_return_confirm,且接单归一不得改写它")
void pendingReturnConfirmIsTerminalStatus() {
assertEquals("pending_return_confirm", WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM,
"库里存的是这个字面量,web 与小程序也按它判断,不能改名");
assertEquals(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM,
WaybillProcessSupport.normalizeBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, TRANSPORT_JSON),
"TRANSPORT_JSON 要求确认接单,未接单时归一结果本是 pending;运单已跑完全程,不能被改回待执行");
assertEquals(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM,
WaybillProcessSupport.normalizeBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, TRANSPORT_JSON, "accepted"));
}
// ------------------------------------------------------------ 结束门禁 // ------------------------------------------------------------ 结束门禁
@Test @Test
@DisplayName("结束门禁:只认已完成与已取消,大小写不敏感") @DisplayName("结束门禁:已完成、已取消与待回单确认都算结束,大小写不敏感")
void punchEndedOnlyForCompletedAndCancelled() { void punchEndedForCompletedCancelledAndPendingReturnConfirm() {
assertTrue(WaybillProcessSupport.isPunchEnded("completed")); assertTrue(WaybillProcessSupport.isPunchEnded("completed"));
assertTrue(WaybillProcessSupport.isPunchEnded("COMPLETED")); assertTrue(WaybillProcessSupport.isPunchEnded("COMPLETED"));
assertTrue(WaybillProcessSupport.isPunchEnded("cancelled")); assertTrue(WaybillProcessSupport.isPunchEnded("cancelled"));
assertTrue(WaybillProcessSupport.isPunchEnded("Cancelled")); assertTrue(WaybillProcessSupport.isPunchEnded("Cancelled"));
assertTrue(WaybillProcessSupport.isPunchEnded("pending_return_confirm"),
"司机已打完末位卡,不能再打卡");
} }
@Test @Test
@@ -358,11 +374,12 @@ class WaybillProcessSupportTest {
} }
@Test @Test
@DisplayName("结束门禁文案:完成与取消各一句,非结束态没有文案") @DisplayName("结束门禁文案:完成、取消与待回单确认各一句,非结束态没有文案")
void punchEndedReasonOnlyForEndedStatuses() { void punchEndedReasonOnlyForEndedStatuses() {
assertEquals("运单已完成", WaybillProcessSupport.punchEndedReason("completed")); assertEquals("运单已完成", WaybillProcessSupport.punchEndedReason("completed"));
assertEquals("运单已完成", WaybillProcessSupport.punchEndedReason(" COMPLETED ")); assertEquals("运单已完成", WaybillProcessSupport.punchEndedReason(" COMPLETED "));
assertEquals("运单已取消", WaybillProcessSupport.punchEndedReason("cancelled")); assertEquals("运单已取消", WaybillProcessSupport.punchEndedReason("cancelled"));
assertEquals("运单待业务员确认回单", WaybillProcessSupport.punchEndedReason("pending_return_confirm"));
assertNull(WaybillProcessSupport.punchEndedReason(null)); assertNull(WaybillProcessSupport.punchEndedReason(null));
assertNull(WaybillProcessSupport.punchEndedReason("")); assertNull(WaybillProcessSupport.punchEndedReason(""));
assertNull(WaybillProcessSupport.punchEndedReason(" ")); assertNull(WaybillProcessSupport.punchEndedReason(" "));
@@ -421,6 +438,22 @@ class WaybillProcessSupportTest {
// 结束态由结束文案负责,两句话不能抢 // 结束态由结束文案负责,两句话不能抢
assertNull(WaybillProcessSupport.punchNotStartedReason("completed")); assertNull(WaybillProcessSupport.punchNotStartedReason("completed"));
assertNull(WaybillProcessSupport.punchNotStartedReason("cancelled")); assertNull(WaybillProcessSupport.punchNotStartedReason("cancelled"));
assertNull(WaybillProcessSupport.punchNotStartedReason("pending_return_confirm"),
"司机已打完末位卡,不能再被说成「尚未开始运输」");
}
@Test
@DisplayName("在途卡:待回单确认的运单与已完成同样不可打卡")
void transitCardOnPendingReturnConfirmWaybillIsVisibleButNotPunchable() {
LocalDateTime now = LocalDateTime.of(2026, 10, 20, 12, 0);
WaybillProcessSupport.TransitCheckinDecision decision =
WaybillProcessSupport.evaluateTransitCheckin(TRANSPORT_JSON, "pending_return_confirm", null, now);
assertTrue(decision.visible(), "卡片仍要下发,司机能回看");
assertFalse(decision.punchEnabled(), "末位卡已打完,在途卡不能再提交");
assertFalse(decision.doneToday());
assertFalse(decision.dueToday());
} }
@Test @Test
@@ -455,9 +488,11 @@ class WaybillProcessSupportTest {
assertNull(WaybillProcessSupport.reportBlockedReason("running")); assertNull(WaybillProcessSupport.reportBlockedReason("running"));
assertEquals("运单尚未开始运输,暂不可上报异常", WaybillProcessSupport.reportBlockedReason("pending")); assertEquals("运单尚未开始运输,暂不可上报异常", WaybillProcessSupport.reportBlockedReason("pending"));
assertEquals("运单尚未开始运输,暂不可上报异常", WaybillProcessSupport.reportBlockedReason("dispatching")); assertEquals("运单尚未开始运输,暂不可上报异常", WaybillProcessSupport.reportBlockedReason("dispatching"));
// 结束优先:已完成/已取消先说结束,不提「等你上路」 // 结束优先:已结束态先说结束,不提「等你上路」
assertEquals("运单已完成,不能上报异常", WaybillProcessSupport.reportBlockedReason("completed")); assertEquals("运单已完成,不能上报异常", WaybillProcessSupport.reportBlockedReason("completed"));
assertEquals("运单已取消,不能上报异常", WaybillProcessSupport.reportBlockedReason("cancelled")); assertEquals("运单已取消,不能上报异常", WaybillProcessSupport.reportBlockedReason("cancelled"));
assertEquals("运单待业务员确认回单,不能上报异常",
WaybillProcessSupport.reportBlockedReason("pending_return_confirm"));
} }
} }