✨ 运单过程判定补充到货卸货签收、末位打卡与回单无需确认完成三项

This commit is contained in:
2026-10-08 09:09:17 +08:00
parent f3ae784f20
commit 9f8ea5412d
2 changed files with 186 additions and 0 deletions
@@ -53,6 +53,7 @@ public final class WaybillProcessSupport {
public static final String ACCEPT_REJECTED = "rejected";
private static final String CONFIRM_YES = "yes";
private static final String CONFIRM_NO_ACCEPT = "no_confirm_accept";
private static final String CONFIRM_NO_COMPLETE = "no_confirm_complete";
private static final String NODE_TRANSIT = "transit";
private static final String NODE_TRANSIT_NAME = "在途";
/** 异常处置终态:completed=已完成,revoked=已撤销。只有终态解除打卡门禁 */
@@ -244,6 +245,21 @@ public final class WaybillProcessSupport {
return result;
}
/**
* 本次打卡的节点是否是司机可打卡节点的末位。
* <p>
* 末位取「司机可打卡节点」序列:接单与回单不打卡,不参与排位——
* 回单只要启用就必然排在配置最末,按全部启用节点取末位会永远取到回单。
*/
public static boolean isLastDriverPunchNode(String processJson, String nodeCode) {
List<Map<String, Object>> nodes = listDriverPunchNodes(processJson);
if (nodes.isEmpty()) {
return false;
}
String code = stringVal(nodeCode);
return !code.isEmpty() && code.equals(nodeKey(nodes.get(nodes.size() - 1)));
}
/**
* 启用中的过程节点(按配置顺序,含接单/回单)。
*/
@@ -591,6 +607,36 @@ public final class WaybillProcessSupport {
return isAcceptNode(stringVal(node.get("key")), stringVal(node.get("name")));
}
/**
* 是否到货 / 卸货 / 签收节点:key 或中文名任一命中即算。
*/
public static boolean isDeliveryNode(String nodeCode, String nodeName) {
String key = stringVal(nodeCode);
String name = stringVal(nodeName);
return "arrival".equals(key) || "到货".equals(name)
|| "unload".equals(key) || "卸货".equals(name)
|| "sign".equals(key) || "签收".equals(name);
}
/**
* 回单是否无需确认完成:配置里没有回单节点、回单未启用,或启用且配置为「无需确认完成」。
* <p>
* 未启用的回单节点不看 confirmMode——没有回单环节就不会有人来确认;
* 启用但 confirmMode 缺失按「需确认完成」算。
*/
public static boolean returnNeedsNoConfirmation(String processJson) {
for (Map<String, Object> node : parseProcessNodes(processJson)) {
if (!isReturnNode(node)) {
continue;
}
if (!isEnabled(node)) {
return true;
}
return CONFIRM_NO_COMPLETE.equals(stringVal(node.get("confirmMode")));
}
return true;
}
private static boolean isTransitNode(Map<String, Object> node) {
String key = stringVal(node.get("key"));
String name = stringVal(node.get("name"));
@@ -172,4 +172,144 @@ class WaybillProcessSupportTest {
assertFalse(WaybillProcessSupport.sameNodeCode(null, null));
}
// ------------------------------------------------------------ 到货/卸货/签收
@Test
@DisplayName("到货/卸货/签收:key 或中文名任一命中即可,两者都为空不算")
void deliveryNodeMatchesKeyOrName() {
assertTrue(WaybillProcessSupport.isDeliveryNode("arrival", "到货"));
assertTrue(WaybillProcessSupport.isDeliveryNode("unload", "卸货"));
assertTrue(WaybillProcessSupport.isDeliveryNode("sign", "签收"));
assertTrue(WaybillProcessSupport.isDeliveryNode("arrival", ""), "只有 key 也要认,别把中文名当必要条件");
assertTrue(WaybillProcessSupport.isDeliveryNode("", "签收"), "只有中文名也要认——key 可能被改成自定义编码");
assertFalse(WaybillProcessSupport.isDeliveryNode("dispatch", "发货"));
assertFalse(WaybillProcessSupport.isDeliveryNode("transit", "在途"));
assertFalse(WaybillProcessSupport.isDeliveryNode("", ""));
assertFalse(WaybillProcessSupport.isDeliveryNode(null, null));
}
// ------------------------------------------------------------ 末位司机打卡节点
/** 末位是「到货」,其后没有打卡节点 */
private static final String ARRIVAL_LAST_JSON = """
[{"key":"load","name":"装货","enabled":true,"punch":true},
{"key":"arrival","name":"到货","enabled":true,"punch":true}]
""";
/** 末位是「卸货」 */
private static final String UNLOAD_LAST_JSON = """
[{"key":"load","name":"装货","enabled":true,"punch":true},
{"key":"unload","name":"卸货","enabled":true,"punch":true}]
""";
/** 末位是「在途」 */
private static final String TRANSIT_LAST_JSON = """
[{"key":"load","name":"装货","enabled":true,"punch":true},
{"key":"transit","name":"在途","type":"transit","enabled":true,"punch":true}]
""";
/** 末位是「发货」 */
private static final String DISPATCH_LAST_JSON = """
[{"key":"load","name":"装货","enabled":true,"punch":true},
{"key":"dispatch","name":"发货","enabled":true,"punch":true}]
""";
@Test
@DisplayName("末位打卡节点取「司机可打卡节点」的末位:接单与回单就算启用也不算")
void lastDriverPunchNodeSkipsAcceptAndReturn() {
assertTrue(WaybillProcessSupport.isLastDriverPunchNode(TRANSPORT_JSON, "sign"),
"回单启用在配置里排最末,但回单不打卡,末位打卡节点仍是签收");
assertFalse(WaybillProcessSupport.isLastDriverPunchNode(TRANSPORT_JSON, "unload"));
assertFalse(WaybillProcessSupport.isLastDriverPunchNode(TRANSPORT_JSON, "arrival"));
assertFalse(WaybillProcessSupport.isLastDriverPunchNode(TRANSPORT_JSON, "return"));
}
@Test
@DisplayName("末位是签收 / 到货 / 卸货才同时成立「末位」与「到货卸货签收」;末位是在途 / 发货只满足前者")
void lastNodeIsDeliveryOnlyForDeliveryTypes() {
assertTrue(WaybillProcessSupport.isLastDriverPunchNode(TRANSPORT_JSON, "sign")
&& WaybillProcessSupport.isDeliveryNode("sign", "签收"));
assertTrue(WaybillProcessSupport.isLastDriverPunchNode(ARRIVAL_LAST_JSON, "arrival")
&& WaybillProcessSupport.isDeliveryNode("arrival", "到货"));
assertTrue(WaybillProcessSupport.isLastDriverPunchNode(UNLOAD_LAST_JSON, "unload")
&& WaybillProcessSupport.isDeliveryNode("unload", "卸货"));
assertTrue(WaybillProcessSupport.isLastDriverPunchNode(TRANSIT_LAST_JSON, "transit"),
"在途排在末位时它确实是末位打卡节点");
assertFalse(WaybillProcessSupport.isDeliveryNode("transit", "在途"), "在途不是可完成节点");
assertTrue(WaybillProcessSupport.isLastDriverPunchNode(DISPATCH_LAST_JSON, "dispatch"));
assertFalse(WaybillProcessSupport.isDeliveryNode("dispatch", "发货"), "发货不是可完成节点");
assertFalse(WaybillProcessSupport.isLastDriverPunchNode(DISPATCH_LAST_JSON, "unload"),
"该配置里没有卸货节点,不能因为编码眼熟就认下");
}
@Test
@DisplayName("没有可打卡节点(或没有过程配置)时,谁都不是末位")
void lastDriverPunchNodeWithoutPunchNodes() {
String onlyAcceptAndReturn = """
[{"key":"accept","name":"接单","enabled":true,"punch":true},
{"key":"return","name":"回单","type":"confirm","enabled":true,"punch":true}]
""";
assertFalse(WaybillProcessSupport.isLastDriverPunchNode(onlyAcceptAndReturn, "accept"));
assertFalse(WaybillProcessSupport.isLastDriverPunchNode(onlyAcceptAndReturn, "return"));
assertFalse(WaybillProcessSupport.isLastDriverPunchNode("", "sign"));
assertFalse(WaybillProcessSupport.isLastDriverPunchNode(null, "sign"));
assertFalse(WaybillProcessSupport.isLastDriverPunchNode(TRANSPORT_JSON, null));
assertFalse(WaybillProcessSupport.isLastDriverPunchNode(TRANSPORT_JSON, ""));
}
// ------------------------------------------------------------ 回单是否无需确认完成
/** 回单启用且配置为「无需确认完成」 */
private static final String RETURN_NO_CONFIRM_JSON = """
[{"key":"load","name":"装货","enabled":true,"punch":true},
{"key":"sign","name":"签收","enabled":true,"punch":true},
{"key":"return","name":"回单","type":"confirm","enabled":true,"punch":true,"confirmMode":"no_confirm_complete"}]
""";
/** 回单启用且配置为「需确认完成」 */
private static final String RETURN_NEED_CONFIRM_JSON = """
[{"key":"load","name":"装货","enabled":true,"punch":true},
{"key":"sign","name":"签收","enabled":true,"punch":true},
{"key":"return","name":"回单","type":"confirm","enabled":true,"punch":true,"confirmMode":"yes"}]
""";
/** 回单未启用,但 confirmMode 写着「需确认完成」 */
private static final String RETURN_DISABLED_JSON = """
[{"key":"load","name":"装货","enabled":true,"punch":true},
{"key":"sign","name":"签收","enabled":true,"punch":true},
{"key":"return","name":"回单","type":"confirm","enabled":false,"punch":true,"confirmMode":"yes"}]
""";
/** 配置里根本没有回单节点 */
private static final String NO_RETURN_JSON = """
[{"key":"load","name":"装货","enabled":true,"punch":true},
{"key":"sign","name":"签收","enabled":true,"punch":true}]
""";
@Test
@DisplayName("回单启用时要看 confirmMode:只有「无需确认完成」才成立")
void enabledReturnNeedsConfirmMode() {
assertTrue(WaybillProcessSupport.returnNeedsNoConfirmation(RETURN_NO_CONFIRM_JSON));
assertFalse(WaybillProcessSupport.returnNeedsNoConfirmation(RETURN_NEED_CONFIRM_JSON),
"回单需要确认完成时,打完签收不能算整单完成");
}
@Test
@DisplayName("回单未启用、或配置里没有回单节点,一律视为无需确认完成")
void absentOrDisabledReturnNeedsNoConfirmation() {
assertTrue(WaybillProcessSupport.returnNeedsNoConfirmation(RETURN_DISABLED_JSON),
"回单未启用时 confirmMode 写着需确认也不算数:没启用的环节不会有人来确认");
assertTrue(WaybillProcessSupport.returnNeedsNoConfirmation(NO_RETURN_JSON));
assertTrue(WaybillProcessSupport.returnNeedsNoConfirmation(""));
assertTrue(WaybillProcessSupport.returnNeedsNoConfirmation(null));
}
@Test
@DisplayName("回单启用但 confirmMode 缺失时按「需确认完成」算")
void enabledReturnWithoutConfirmModeNeedsConfirmation() {
assertFalse(WaybillProcessSupport.returnNeedsNoConfirmation(TRANSPORT_JSON),
"TRANSPORT_JSON 的回单启用且没有 confirmMode:缺字段当没配「无需确认完成」,否则会跳过本该人工确认的回单");
}
}