🐛 配载准入的本单豁免只对正式单生效,合同不属于本项目点名运单与其原因单一来源
This commit is contained in:
+51
-55
@@ -155,9 +155,12 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
|||||||
prepare(loadingManage);
|
prepare(loadingManage);
|
||||||
validate(loadingManage, false);
|
validate(loadingManage, false);
|
||||||
List<Long> oldWaybillIds = new ArrayList<>();
|
List<Long> oldWaybillIds = new ArrayList<>();
|
||||||
|
boolean ownChildrenAdmitted = false;
|
||||||
if (Func.isNotEmpty(loadingManage.getId())) {
|
if (Func.isNotEmpty(loadingManage.getId())) {
|
||||||
LoadingManage oldRecord = loadEditable(loadingManage.getId(), true);
|
LoadingManage oldRecord = loadEditable(loadingManage.getId(), true);
|
||||||
oldWaybillIds = waybillIds(oldRecord.getWaybillIdsJson());
|
oldWaybillIds = waybillIds(oldRecord.getWaybillIdsJson());
|
||||||
|
// 草稿只预占运单号、未过判据,草稿转正式不得沿用本单豁免
|
||||||
|
ownChildrenAdmitted = !STATUS_DRAFT.equals(oldRecord.getBusinessStatus());
|
||||||
loadingManage.setLoadingNo(oldRecord.getLoadingNo());
|
loadingManage.setLoadingNo(oldRecord.getLoadingNo());
|
||||||
loadingManage.setDeptId(oldRecord.getDeptId());
|
loadingManage.setDeptId(oldRecord.getDeptId());
|
||||||
loadingManage.setDeptName(oldRecord.getDeptName());
|
loadingManage.setDeptName(oldRecord.getDeptName());
|
||||||
@@ -168,10 +171,10 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
|||||||
if (Func.isEmpty(loadingManage.getLoadingNo())) {
|
if (Func.isEmpty(loadingManage.getLoadingNo())) {
|
||||||
loadingManage.setLoadingNo(nextCode());
|
loadingManage.setLoadingNo(nextCode());
|
||||||
}
|
}
|
||||||
validateWaybillsAvailable(loadingManage, waybillIds(loadingManage.getWaybillIdsJson()));
|
List<Waybill> waybills = validateWaybillsAvailable(loadingManage, waybillIds(loadingManage.getWaybillIdsJson()), ownChildrenAdmitted);
|
||||||
validateCarrierContract(loadingManage, true);
|
validateCarrierContract(loadingManage, true, waybills);
|
||||||
boolean result = saveOrUpdate(loadingManage);
|
boolean result = saveOrUpdate(loadingManage);
|
||||||
writeWaybillsForDriver(loadingManage, oldWaybillIds);
|
writeWaybillsForDriver(loadingManage, oldWaybillIds, waybills);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,10 +420,10 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
|||||||
loadingManage.setDeptId(oldRecord.getDeptId());
|
loadingManage.setDeptId(oldRecord.getDeptId());
|
||||||
loadingManage.setDeptName(oldRecord.getDeptName());
|
loadingManage.setDeptName(oldRecord.getDeptName());
|
||||||
loadingManage.setBusinessStatus(STATUS_PENDING);
|
loadingManage.setBusinessStatus(STATUS_PENDING);
|
||||||
validateWaybillsAvailable(loadingManage, waybillIds(loadingManage.getWaybillIdsJson()));
|
List<Waybill> waybills = validateWaybillsAvailable(loadingManage, waybillIds(loadingManage.getWaybillIdsJson()), true);
|
||||||
validateCarrierContract(loadingManage, true);
|
validateCarrierContract(loadingManage, true, waybills);
|
||||||
boolean result = updateById(loadingManage);
|
boolean result = updateById(loadingManage);
|
||||||
writeWaybillsForDriver(loadingManage, oldWaybillIds);
|
writeWaybillsForDriver(loadingManage, oldWaybillIds, waybills);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -742,57 +745,46 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
|||||||
TransportBusinessSupport.validateLength(loadingManage.getTaskRemark(), 500, "任务备注不能超过500字");
|
TransportBusinessSupport.validateLength(loadingManage.getTaskRemark(), 500, "任务备注不能超过500字");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateWaybillsAvailable(LoadingManage loadingManage, List<Long> waybillIds) {
|
private List<Waybill> validateWaybillsAvailable(LoadingManage loadingManage, List<Long> waybillIds, boolean ownChildrenAdmitted) {
|
||||||
if (Func.isEmpty(waybillIds)) {
|
if (Func.isEmpty(waybillIds)) {
|
||||||
throw new ServiceException("请选择待配载运单");
|
throw new ServiceException("请选择待配载运单");
|
||||||
}
|
}
|
||||||
List<Waybill> waybills = waybillMapper.selectList(Wrappers.<Waybill>lambdaQuery()
|
List<Waybill> waybills = loadWaybills(waybillIds);
|
||||||
.eq(Waybill::getIsDeleted, 0)
|
|
||||||
.in(Waybill::getId, waybillIds));
|
|
||||||
if (waybills.size() != waybillIds.size()) {
|
if (waybills.size() != waybillIds.size()) {
|
||||||
throw new ServiceException("待配载运单不存在或已删除");
|
throw new ServiceException("待配载运单不存在或已删除");
|
||||||
}
|
}
|
||||||
String ownLoadingNo = loadingManage.getLoadingNo();
|
String ownLoadingNo = loadingManage.getLoadingNo();
|
||||||
for (Waybill waybill : waybills) {
|
for (Waybill waybill : waybills) {
|
||||||
TransportBusinessSupport.assertCurrentDept(waybill.getDeptId(), "运单管理");
|
TransportBusinessSupport.assertCurrentDept(waybill.getDeptId(), "运单管理");
|
||||||
// 本单既有子运单在先前提交时已过准入判据,重新提交/编辑不再判:
|
if (ownChildrenAdmitted && LoadableAdmissionSupport.isOwnChild(waybill, ownLoadingNo)) {
|
||||||
// 不需接单项目的票提交后即为进行中,重复判会让本单再也保存不了
|
|
||||||
if (LoadableAdmissionSupport.isOwnChild(waybill, ownLoadingNo)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!LoadableAdmissionSupport.isLoadable(waybill, ownLoadingNo)) {
|
LoadableAdmissionSupport.UnloadableReason reason =
|
||||||
throw new ServiceException(unloadableReason(waybill));
|
LoadableAdmissionSupport.unloadableReason(waybill, ownLoadingNo);
|
||||||
|
if (reason != null) {
|
||||||
|
throw new ServiceException(unloadableMessage(waybill, reason));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
validateSameProject(waybills);
|
validateSameProject(waybills);
|
||||||
|
return waybills;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 逐张准入失败的可读原因。判据本身在可配载支持类里,这里只把命中的那一条翻成文案。
|
* 逐张准入失败的可读原因:判据只给原因枚举,运单号与文案在这里拼。
|
||||||
*
|
|
||||||
* @param waybill 未通过准入判据的运单
|
|
||||||
* @return 含运单号与原因的提示
|
|
||||||
*/
|
*/
|
||||||
private String unloadableReason(Waybill waybill) {
|
private String unloadableMessage(Waybill waybill, LoadableAdmissionSupport.UnloadableReason reason) {
|
||||||
String prefix = "运单" + waybill.getWaybillNo();
|
String prefix = "运单" + waybill.getWaybillNo();
|
||||||
if (Func.isNotEmpty(waybill.getLoadingNo())) {
|
return prefix + switch (reason) {
|
||||||
return prefix + "已关联其他配载单";
|
case NOT_ROAD -> "运输方式不是公路,不可配载";
|
||||||
}
|
case NOT_PENDING -> "当前状态不是待执行,不可配载";
|
||||||
if (!LoadableAdmissionSupport.isRoadTransport(waybill.getTransportType())) {
|
case ON_OTHER_MANIFEST -> "已关联其他配载单";
|
||||||
return prefix + "运输方式不是公路,不可配载";
|
};
|
||||||
}
|
|
||||||
if (!LoadableAdmissionSupport.LOADABLE_BUSINESS_STATUS.equals(waybill.getBusinessStatus())) {
|
|
||||||
return prefix + "当前状态不是待执行,不可配载";
|
|
||||||
}
|
|
||||||
return prefix + "不可配载";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一张配载单的全部子运单必须属于同一个项目,否则点名与其它子运单项目不同的运单。
|
* 一张配载单的全部子运单必须属于同一个项目,否则点名与其它子运单项目不同的运单。
|
||||||
* <p>
|
* <p>
|
||||||
* 查询条件表达不了跨行约束,只能整批取回后在内存里比。
|
* 查询条件表达不了跨行约束,只能整批取回后在内存里比。
|
||||||
*
|
|
||||||
* @param waybills 本次要写入的子运单全集
|
|
||||||
*/
|
*/
|
||||||
private void validateSameProject(List<Waybill> waybills) {
|
private void validateSameProject(List<Waybill> waybills) {
|
||||||
Long projectId = waybills.get(0).getProjectId();
|
Long projectId = waybills.get(0).getProjectId();
|
||||||
@@ -807,6 +799,10 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void validateCarrierContract(LoadingManage loadingManage, boolean required) {
|
private void validateCarrierContract(LoadingManage loadingManage, boolean required) {
|
||||||
|
validateCarrierContract(loadingManage, required, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateCarrierContract(LoadingManage loadingManage, boolean required, List<Waybill> waybills) {
|
||||||
if (!Objects.equals(loadingManage.getCarrierType(), "承运商")) {
|
if (!Objects.equals(loadingManage.getCarrierType(), "承运商")) {
|
||||||
loadingManage.setCarrierContractId(null);
|
loadingManage.setCarrierContractId(null);
|
||||||
if (!Objects.equals(loadingManage.getCarrierType(), CARRIER_SELF)) {
|
if (!Objects.equals(loadingManage.getCarrierType(), CARRIER_SELF)) {
|
||||||
@@ -825,9 +821,19 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
|||||||
if (carrierContract == null) {
|
if (carrierContract == null) {
|
||||||
throw new ServiceException("所选承运商合同不存在、未审核通过或已失效");
|
throw new ServiceException("所选承运商合同不存在、未审核通过或已失效");
|
||||||
}
|
}
|
||||||
|
List<Waybill> childWaybills = waybills == null
|
||||||
|
? loadWaybills(waybillIds(loadingManage.getWaybillIdsJson()))
|
||||||
|
: waybills;
|
||||||
// 合同自带项目与乙方:合同的项目不属于本单子运单的项目,等于换了项目却没换乙方
|
// 合同自带项目与乙方:合同的项目不属于本单子运单的项目,等于换了项目却没换乙方
|
||||||
if (!projectIdsByWaybills(loadingManage).contains(carrierContract.getProjectId())) {
|
if (!projectIdsOf(childWaybills).contains(carrierContract.getProjectId())) {
|
||||||
throw new ServiceException("所选承运商合同不属于本项目");
|
String waybillNos = childWaybills.stream()
|
||||||
|
.filter(waybill -> !Objects.equals(waybill.getProjectId(), carrierContract.getProjectId()))
|
||||||
|
.map(Waybill::getWaybillNo)
|
||||||
|
.filter(Func::isNotEmpty)
|
||||||
|
.collect(Collectors.joining("、"));
|
||||||
|
throw new ServiceException(Func.isEmpty(waybillNos)
|
||||||
|
? "所选承运商合同不属于本项目"
|
||||||
|
: "所选承运商合同不属于运单" + waybillNos + "的项目");
|
||||||
}
|
}
|
||||||
loadingManage.setCarrierName(carrierContract.getPartyB());
|
loadingManage.setCarrierName(carrierContract.getPartyB());
|
||||||
}
|
}
|
||||||
@@ -845,16 +851,17 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Long> projectIdsByWaybills(LoadingManage loadingManage) {
|
private List<Waybill> loadWaybills(List<Long> waybillIds) {
|
||||||
List<Long> waybillIds = waybillIds(loadingManage.getWaybillIdsJson());
|
|
||||||
if (Func.isEmpty(waybillIds)) {
|
if (Func.isEmpty(waybillIds)) {
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
return waybillMapper.selectList(Wrappers.<Waybill>lambdaQuery()
|
return waybillMapper.selectList(Wrappers.<Waybill>lambdaQuery()
|
||||||
.select(Waybill::getProjectId)
|
|
||||||
.eq(Waybill::getIsDeleted, 0)
|
.eq(Waybill::getIsDeleted, 0)
|
||||||
.in(Waybill::getId, waybillIds))
|
.in(Waybill::getId, waybillIds));
|
||||||
.stream().map(Waybill::getProjectId).filter(Objects::nonNull).distinct().toList();
|
}
|
||||||
|
|
||||||
|
private List<Long> projectIdsOf(List<Waybill> waybills) {
|
||||||
|
return waybills.stream().map(Waybill::getProjectId).filter(Objects::nonNull).distinct().toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ContractManage> availableCarrierContracts(List<Long> projectIds) {
|
private List<ContractManage> availableCarrierContracts(List<Long> projectIds) {
|
||||||
@@ -870,9 +877,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
|||||||
* 按主键取一张可用的承运商合同。
|
* 按主键取一张可用的承运商合同。
|
||||||
* <p>
|
* <p>
|
||||||
* 不按项目过滤,才能把「合同不可用」与「合同不属于本项目」分成两种提示。
|
* 不按项目过滤,才能把「合同不可用」与「合同不属于本项目」分成两种提示。
|
||||||
*
|
* 取不到可用的合同返回 null。
|
||||||
* @param contractId 合同主键
|
|
||||||
* @return 可用的合同;不存在或不可用返回 null
|
|
||||||
*/
|
*/
|
||||||
private ContractManage availableCarrierContractById(Long contractId) {
|
private ContractManage availableCarrierContractById(Long contractId) {
|
||||||
if (Func.isEmpty(contractId)) {
|
if (Func.isEmpty(contractId)) {
|
||||||
@@ -899,9 +904,8 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
|||||||
* <p>
|
* <p>
|
||||||
* 推导按「接单事实已清、尚未接单」的口径取值,故清接单事实放在状态写入之后,两者口径一致。
|
* 推导按「接单事实已清、尚未接单」的口径取值,故清接单事实放在状态写入之后,两者口径一致。
|
||||||
*/
|
*/
|
||||||
private void writeWaybillsForDriver(LoadingManage loadingManage, List<Long> oldWaybillIds) {
|
private void writeWaybillsForDriver(LoadingManage loadingManage, List<Long> oldWaybillIds, List<Waybill> waybills) {
|
||||||
syncAssociatedWaybills(loadingManage,
|
syncAssociatedWaybills(loadingManage, deriveWaybillBusinessStatuses(waybills), oldWaybillIds);
|
||||||
deriveWaybillBusinessStatuses(waybillIds(loadingManage.getWaybillIdsJson())), oldWaybillIds);
|
|
||||||
// 接单事实不清,被拒的票对新司机不可见——司机端一切入口都排除已拒绝的运单
|
// 接单事实不清,被拒的票对新司机不可见——司机端一切入口都排除已拒绝的运单
|
||||||
clearAssociatedWaybillAcceptRecords(loadingManage);
|
clearAssociatedWaybillAcceptRecords(loadingManage);
|
||||||
}
|
}
|
||||||
@@ -913,16 +917,11 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
|||||||
* 快照只作兜底、永不优先,否则项目改配置后写入值与读时的归一化结果会分叉。
|
* 快照只作兜底、永不优先,否则项目改配置后写入值与读时的归一化结果会分叉。
|
||||||
* 项目为空且快照也为空 → 视为不需要接单 → 进行中。
|
* 项目为空且快照也为空 → 视为不需要接单 → 进行中。
|
||||||
*/
|
*/
|
||||||
private Map<Long, String> deriveWaybillBusinessStatuses(List<Long> waybillIds) {
|
private Map<Long, String> deriveWaybillBusinessStatuses(List<Waybill> waybills) {
|
||||||
Map<Long, String> businessStatusByWaybillId = new LinkedHashMap<>();
|
Map<Long, String> businessStatusByWaybillId = new LinkedHashMap<>();
|
||||||
if (Func.isEmpty(waybillIds)) {
|
if (Func.isEmpty(waybills)) {
|
||||||
return businessStatusByWaybillId;
|
return businessStatusByWaybillId;
|
||||||
}
|
}
|
||||||
List<Waybill> waybills = waybillMapper.selectList(Wrappers.<Waybill>lambdaQuery()
|
|
||||||
.select(Waybill::getId, Waybill::getProjectId, Waybill::getProcessJson)
|
|
||||||
.eq(Waybill::getIsDeleted, 0)
|
|
||||||
.in(Waybill::getId, waybillIds));
|
|
||||||
// 一次查全所有项目的生效配置,不逐张回查
|
|
||||||
Map<Long, String> liveConfigByProject = LiveProcessConfigSupport.byProjects(processConfigService,
|
Map<Long, String> liveConfigByProject = LiveProcessConfigSupport.byProjects(processConfigService,
|
||||||
waybills.stream().map(Waybill::getProjectId).toList());
|
waybills.stream().map(Waybill::getProjectId).toList());
|
||||||
for (Waybill waybill : waybills) {
|
for (Waybill waybill : waybills) {
|
||||||
@@ -943,9 +942,6 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
|||||||
syncAssociatedWaybills(loadingManage, statusByWaybillId, oldWaybillIds);
|
syncAssociatedWaybills(loadingManage, statusByWaybillId, oldWaybillIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 逐张写状态:同状态的票合成一条批量更新,配载字段对每张票都写全。
|
|
||||||
*/
|
|
||||||
private void syncAssociatedWaybills(LoadingManage loadingManage, Map<Long, String> statusByWaybillId, List<Long> oldWaybillIds) {
|
private void syncAssociatedWaybills(LoadingManage loadingManage, Map<Long, String> statusByWaybillId, List<Long> oldWaybillIds) {
|
||||||
List<Long> newWaybillIds = waybillIds(loadingManage.getWaybillIdsJson());
|
List<Long> newWaybillIds = waybillIds(loadingManage.getWaybillIdsJson());
|
||||||
if (Func.isNotEmpty(oldWaybillIds)) {
|
if (Func.isNotEmpty(oldWaybillIds)) {
|
||||||
|
|||||||
+36
-16
@@ -55,6 +55,23 @@ public final class LoadableAdmissionSupport {
|
|||||||
private LoadableAdmissionSupport() {
|
private LoadableAdmissionSupport() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不可配载的原因,按判定顺序排列:公路 → 待执行 → 未关联其它配载单。
|
||||||
|
* <p>
|
||||||
|
* 判据只产出原因,中文文案由调用方映射——面向用户的措辞不进这一层。
|
||||||
|
*/
|
||||||
|
public enum UnloadableReason {
|
||||||
|
|
||||||
|
/** 运输方式不是公路 */
|
||||||
|
NOT_ROAD,
|
||||||
|
|
||||||
|
/** 业务状态不是待执行 */
|
||||||
|
NOT_PENDING,
|
||||||
|
|
||||||
|
/** 已关联其它配载单 */
|
||||||
|
ON_OTHER_MANIFEST
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否公路运输。未知或为空一律按非公路处理。
|
* 是否公路运输。未知或为空一律按非公路处理。
|
||||||
*
|
*
|
||||||
@@ -77,16 +94,6 @@ public final class LoadableAdmissionSupport {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 该运单此刻是否可被配载:要求未关联任何配载单。
|
|
||||||
*
|
|
||||||
* @param waybill 运单
|
|
||||||
* @return true-可配载
|
|
||||||
*/
|
|
||||||
public static boolean isLoadable(Waybill waybill) {
|
|
||||||
return isLoadable(waybill, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 该运单此刻是否可被配载。
|
* 该运单此刻是否可被配载。
|
||||||
* <p>
|
* <p>
|
||||||
@@ -98,15 +105,28 @@ public final class LoadableAdmissionSupport {
|
|||||||
* @return true-可配载
|
* @return true-可配载
|
||||||
*/
|
*/
|
||||||
public static boolean isLoadable(Waybill waybill, String ownLoadingNo) {
|
public static boolean isLoadable(Waybill waybill, String ownLoadingNo) {
|
||||||
if (waybill == null) {
|
return unloadableReason(waybill, ownLoadingNo) == null;
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该运单此刻不可配载的原因。
|
||||||
|
*
|
||||||
|
* @param waybill 运单
|
||||||
|
* @param ownLoadingNo 本配载单号;新建时传 null
|
||||||
|
* @return 命中的第一条原因;可配载时返回 null
|
||||||
|
*/
|
||||||
|
public static UnloadableReason unloadableReason(Waybill waybill, String ownLoadingNo) {
|
||||||
|
if (waybill == null || !isRoadTransport(waybill.getTransportType())) {
|
||||||
|
return UnloadableReason.NOT_ROAD;
|
||||||
}
|
}
|
||||||
if (!isRoadTransport(waybill.getTransportType())
|
if (!LOADABLE_BUSINESS_STATUS.equals(text(waybill.getBusinessStatus()))) {
|
||||||
|| !LOADABLE_BUSINESS_STATUS.equals(text(waybill.getBusinessStatus()))) {
|
return UnloadableReason.NOT_PENDING;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
String loadingNo = text(waybill.getLoadingNo());
|
String loadingNo = text(waybill.getLoadingNo());
|
||||||
return loadingNo.isEmpty() || loadingNo.equals(text(ownLoadingNo));
|
if (loadingNo.isEmpty() || loadingNo.equals(text(ownLoadingNo))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return UnloadableReason.ON_OTHER_MANIFEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+67
-2
@@ -39,6 +39,7 @@ import static org.mockito.Mockito.when;
|
|||||||
* <p>
|
* <p>
|
||||||
* 逐张准入用的是读侧候选列表的同一份判据;已挂在本单号上的子运单在先前提交时已过判据,
|
* 逐张准入用的是读侧候选列表的同一份判据;已挂在本单号上的子运单在先前提交时已过判据,
|
||||||
* 重新提交不再重复判——不需接单项目的票提交后即为进行中,重复判会让本单再也保存不了。
|
* 重新提交不再重复判——不需接单项目的票提交后即为进行中,重复判会让本单再也保存不了。
|
||||||
|
* 暂存只预占运单号、未过判据,故草稿转正式时本单号不构成豁免。
|
||||||
* <p>
|
* <p>
|
||||||
* 跨行判据(同项目、合同属于本项目)只对提交/编辑/重新派单生效,暂存草稿不受影响。
|
* 跨行判据(同项目、合同属于本项目)只对提交/编辑/重新派单生效,暂存草稿不受影响。
|
||||||
*/
|
*/
|
||||||
@@ -197,6 +198,60 @@ class LoadingManageAdmissionTest {
|
|||||||
assertDoesNotThrow(() -> service.reassign(payload));
|
assertDoesNotThrow(() -> service.reassign(payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("交叉判据同时命中时按 公路 → 待执行 → 已关联其它配载单 取第一条")
|
||||||
|
void firstFailingConditionWins() {
|
||||||
|
stubWaybills(waybill(1L, WAYBILL_NO_A, "railway", "pending", "PZ20261018-999", PROJECT_A));
|
||||||
|
|
||||||
|
ServiceException exception = assertThrows(ServiceException.class,
|
||||||
|
() -> service.submit(submitPayload(List.of(1L))));
|
||||||
|
|
||||||
|
assertEquals("运单" + WAYBILL_NO_A + "运输方式不是公路,不可配载", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------ 草稿预占
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("暂存只预占运单号:草稿里那张进行中的票在草稿转正式时被拒")
|
||||||
|
void draftReservedRunningWaybillRejected() {
|
||||||
|
stubManifest("draft");
|
||||||
|
stubWaybills(waybill(1L, WAYBILL_NO_A, "road", "running", OWN_LOADING_NO, PROJECT_A));
|
||||||
|
stubContract(CONTRACT_ID, PROJECT_A);
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> service.saveDraft(existingPayload(List.of(1L))));
|
||||||
|
|
||||||
|
ServiceException exception = assertThrows(ServiceException.class,
|
||||||
|
() -> service.submit(existingPayload(List.of(1L))));
|
||||||
|
|
||||||
|
assertEquals("运单" + WAYBILL_NO_A + "当前状态不是待执行,不可配载", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("暂存只预占运单号:草稿里那张非公路的票在草稿转正式时被拒")
|
||||||
|
void draftReservedNonRoadWaybillRejected() {
|
||||||
|
stubManifest("draft");
|
||||||
|
stubWaybills(waybill(1L, WAYBILL_NO_A, "railway", "pending", OWN_LOADING_NO, PROJECT_A));
|
||||||
|
stubContract(CONTRACT_ID, PROJECT_A);
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> service.saveDraft(existingPayload(List.of(1L))));
|
||||||
|
|
||||||
|
ServiceException exception = assertThrows(ServiceException.class,
|
||||||
|
() -> service.submit(existingPayload(List.of(1L))));
|
||||||
|
|
||||||
|
assertEquals("运单" + WAYBILL_NO_A + "运输方式不是公路,不可配载", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("暂存预占的票本身合格时,草稿转正式照常通过")
|
||||||
|
void draftReservationStaysAdmissibleOnSubmit() {
|
||||||
|
stubManifest("draft");
|
||||||
|
stubWaybills(waybill(1L, WAYBILL_NO_A, "road", "pending", OWN_LOADING_NO, PROJECT_A));
|
||||||
|
stubContract(CONTRACT_ID, PROJECT_A);
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> service.saveDraft(existingPayload(List.of(1L))));
|
||||||
|
assertDoesNotThrow(() -> service.submit(existingPayload(List.of(1L))));
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------ 跨行判据
|
// ------------------------------------------------------------ 跨行判据
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -222,7 +277,7 @@ class LoadingManageAdmissionTest {
|
|||||||
ServiceException exception = assertThrows(ServiceException.class,
|
ServiceException exception = assertThrows(ServiceException.class,
|
||||||
() -> service.submit(submitPayload(List.of(1L))));
|
() -> service.submit(submitPayload(List.of(1L))));
|
||||||
|
|
||||||
assertEquals("所选承运商合同不属于本项目", exception.getMessage());
|
assertEquals("所选承运商合同不属于运单" + WAYBILL_NO_A + "的项目", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -296,16 +351,26 @@ class LoadingManageAdmissionTest {
|
|||||||
return loadingManage;
|
return loadingManage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private LoadingManage existingPayload(List<Long> waybillIdList) {
|
||||||
|
LoadingManage loadingManage = submitPayload(waybillIdList);
|
||||||
|
loadingManage.setId(OWN_LOADING_ID);
|
||||||
|
return loadingManage;
|
||||||
|
}
|
||||||
|
|
||||||
private static String waybillIdsJson(List<Long> waybillIdList) {
|
private static String waybillIdsJson(List<Long> waybillIdList) {
|
||||||
return waybillIdList.stream().map(String::valueOf).collect(Collectors.joining(",", "[", "]"));
|
return waybillIdList.stream().map(String::valueOf).collect(Collectors.joining(",", "[", "]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stubExistingManifest() {
|
private void stubExistingManifest() {
|
||||||
|
stubManifest("pending");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stubManifest(String businessStatus) {
|
||||||
LoadingManage existing = new LoadingManage();
|
LoadingManage existing = new LoadingManage();
|
||||||
existing.setId(OWN_LOADING_ID);
|
existing.setId(OWN_LOADING_ID);
|
||||||
existing.setLoadingNo(OWN_LOADING_NO);
|
existing.setLoadingNo(OWN_LOADING_NO);
|
||||||
existing.setDeptId(DEPT_ID);
|
existing.setDeptId(DEPT_ID);
|
||||||
existing.setBusinessStatus("pending");
|
existing.setBusinessStatus(businessStatus);
|
||||||
when(loadingManageMapper.selectById(OWN_LOADING_ID)).thenReturn(existing);
|
when(loadingManageMapper.selectById(OWN_LOADING_ID)).thenReturn(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-6
@@ -166,9 +166,6 @@ class LoadingManageChildStatusWriteTest {
|
|||||||
@Test
|
@Test
|
||||||
@DisplayName("跨项目的子单逐张按各自项目推导,互不牵连,配置只查一次")
|
@DisplayName("跨项目的子单逐张按各自项目推导,互不牵连,配置只查一次")
|
||||||
void deriveEachWaybillStatusFromItsOwnProject() throws Exception {
|
void deriveEachWaybillStatusFromItsOwnProject() throws Exception {
|
||||||
stubWaybills(
|
|
||||||
waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID),
|
|
||||||
waybill(NO_ACCEPT_WAYBILL_ID, NO_ACCEPT_PROJECT_ID));
|
|
||||||
stubConfigs(
|
stubConfigs(
|
||||||
config(NEED_ACCEPT_PROJECT_ID, NEED_ACCEPT_JSON),
|
config(NEED_ACCEPT_PROJECT_ID, NEED_ACCEPT_JSON),
|
||||||
config(NO_ACCEPT_PROJECT_ID, NO_ACCEPT_JSON));
|
config(NO_ACCEPT_PROJECT_ID, NO_ACCEPT_JSON));
|
||||||
@@ -177,7 +174,9 @@ class LoadingManageChildStatusWriteTest {
|
|||||||
assertEquals(Map.of(
|
assertEquals(Map.of(
|
||||||
NEED_ACCEPT_WAYBILL_ID, "pending",
|
NEED_ACCEPT_WAYBILL_ID, "pending",
|
||||||
NO_ACCEPT_WAYBILL_ID, "running"),
|
NO_ACCEPT_WAYBILL_ID, "running"),
|
||||||
deriveBusinessStatuses(NEED_ACCEPT_WAYBILL_ID, NO_ACCEPT_WAYBILL_ID));
|
deriveBusinessStatuses(
|
||||||
|
waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID),
|
||||||
|
waybill(NO_ACCEPT_WAYBILL_ID, NO_ACCEPT_PROJECT_ID)));
|
||||||
verify(processConfigService, times(1)).list(ArgumentMatchers.<Wrapper<ProcessConfig>>any());
|
verify(processConfigService, times(1)).list(ArgumentMatchers.<Wrapper<ProcessConfig>>any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,12 +408,12 @@ class LoadingManageChildStatusWriteTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 逐张推导的结果:提交/重新派单在准入阶段就拦下跨项目子单,跨项目推导只能直接测这个函数 */
|
/** 逐张推导的结果:提交/重新派单在准入阶段就拦下跨项目子单,跨项目推导只能直接测这个函数 */
|
||||||
private Map<Long, String> deriveBusinessStatuses(Long... waybillIds) throws Exception {
|
private Map<Long, String> deriveBusinessStatuses(Waybill... waybills) throws Exception {
|
||||||
Method method = LoadingManageServiceImpl.class
|
Method method = LoadingManageServiceImpl.class
|
||||||
.getDeclaredMethod("deriveWaybillBusinessStatuses", List.class);
|
.getDeclaredMethod("deriveWaybillBusinessStatuses", List.class);
|
||||||
method.setAccessible(true);
|
method.setAccessible(true);
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<Long, String> statusByWaybillId = (Map<Long, String>) method.invoke(service, List.of(waybillIds));
|
Map<Long, String> statusByWaybillId = (Map<Long, String>) method.invoke(service, List.of(waybills));
|
||||||
return statusByWaybillId;
|
return statusByWaybillId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+29
-10
@@ -4,7 +4,9 @@ import org.junit.jupiter.api.DisplayName;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springblade.transport.pojo.entity.Waybill;
|
import org.springblade.transport.pojo.entity.Waybill;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,30 +52,47 @@ class LoadableAdmissionSupportTest {
|
|||||||
@Test
|
@Test
|
||||||
@DisplayName("公路 + 待执行 + 未配载才可配载")
|
@DisplayName("公路 + 待执行 + 未配载才可配载")
|
||||||
void loadableOnlyWhenAllConditionsHold() {
|
void loadableOnlyWhenAllConditionsHold() {
|
||||||
assertTrue(LoadableAdmissionSupport.isLoadable(waybill("road", "pending", null, "pending")));
|
assertTrue(LoadableAdmissionSupport.isLoadable(waybill("road", "pending", null, "pending"), null));
|
||||||
assertTrue(LoadableAdmissionSupport.isLoadable(waybill("公路运输", "pending", "", null)));
|
assertTrue(LoadableAdmissionSupport.isLoadable(waybill("公路运输", "pending", "", null), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("已拒单的票照样可配载:接单事实由配载提交清除,不靠把拒单票挡在候选之外")
|
@DisplayName("已拒单的票照样可配载:接单事实由配载提交清除,不靠把拒单票挡在候选之外")
|
||||||
void rejectedWaybillIsLoadable() {
|
void rejectedWaybillIsLoadable() {
|
||||||
assertTrue(LoadableAdmissionSupport.isLoadable(waybill("road", "pending", null, "rejected")));
|
assertTrue(LoadableAdmissionSupport.isLoadable(waybill("road", "pending", null, "rejected"), null));
|
||||||
assertTrue(LoadableAdmissionSupport.isLoadable(waybill("road", "pending", null, "rejected"), "PZ20260101-001"));
|
assertTrue(LoadableAdmissionSupport.isLoadable(waybill("road", "pending", null, "rejected"), "PZ20260101-001"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("非公路、非待执行、已关联配载单各自单独即可拒绝")
|
@DisplayName("非公路、非待执行、已关联配载单各自单独即可拒绝")
|
||||||
void eachConditionRejectsOnItsOwn() {
|
void eachConditionRejectsOnItsOwn() {
|
||||||
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("railway", "pending", null, "pending")),
|
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("railway", "pending", null, "pending"), null),
|
||||||
"非公路不可配载");
|
"非公路不可配载");
|
||||||
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("road", "running", null, null)),
|
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("road", "running", null, null), null),
|
||||||
"进行中不可配载:不需要接单的项目保存后即为进行中,那是过程配置的产物,不代表可配载");
|
"进行中不可配载:不需要接单的项目保存后即为进行中,那是过程配置的产物,不代表可配载");
|
||||||
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("road", "draft", null, null)));
|
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("road", "draft", null, null), null));
|
||||||
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("road", "completed", null, null)));
|
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("road", "completed", null, null), null));
|
||||||
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("road", "cancelled", null, null)));
|
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("road", "cancelled", null, null), null));
|
||||||
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("road", "pending", "PZ20260101-001", null)),
|
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("road", "pending", "PZ20260101-001", null), null),
|
||||||
"已挂在其它配载单上的票不可再配载");
|
"已挂在其它配载单上的票不可再配载");
|
||||||
assertFalse(LoadableAdmissionSupport.isLoadable(null));
|
assertFalse(LoadableAdmissionSupport.isLoadable(null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("不可配载原因按 公路 → 待执行 → 未关联其它配载单 取第一条")
|
||||||
|
void unloadableReasonFollowsPredicateOrder() {
|
||||||
|
assertEquals(LoadableAdmissionSupport.UnloadableReason.NOT_ROAD,
|
||||||
|
LoadableAdmissionSupport.unloadableReason(
|
||||||
|
waybill("railway", "running", "PZ20260101-002", null), "PZ20260101-001"));
|
||||||
|
assertEquals(LoadableAdmissionSupport.UnloadableReason.NOT_PENDING,
|
||||||
|
LoadableAdmissionSupport.unloadableReason(
|
||||||
|
waybill("road", "running", "PZ20260101-002", null), "PZ20260101-001"));
|
||||||
|
assertEquals(LoadableAdmissionSupport.UnloadableReason.ON_OTHER_MANIFEST,
|
||||||
|
LoadableAdmissionSupport.unloadableReason(
|
||||||
|
waybill("road", "pending", "PZ20260101-002", null), "PZ20260101-001"));
|
||||||
|
assertNull(LoadableAdmissionSupport.unloadableReason(waybill("road", "pending", null, null), null));
|
||||||
|
assertNull(LoadableAdmissionSupport.unloadableReason(
|
||||||
|
waybill("road", "pending", "PZ20260101-001", null), "PZ20260101-001"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user