diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java index e5958c76..0446ece9 100644 --- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java @@ -207,6 +207,12 @@ public class Dept extends TenantEntity { @Schema(description = "是否平台公司:0否,1是") private Integer isPlatformCompany; + /** + * 是否业务部门:0否,1是 + */ + @Schema(description = "是否业务部门:0否,1是") + private Integer isBusinessDept; + /** * OA主键,仅同步记录,不对外展示 */ diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractManage.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractManage.java index 04f6a1b4..1d3ab279 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractManage.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractManage.java @@ -199,6 +199,10 @@ public class ContractManage extends TenantEntity { @TableField(updateStrategy = FieldStrategy.ALWAYS) private String changeContent; + @TableField(exist = false) + @Schema(description = "变更类型") + private String changeType; + @Schema(description = "变更原因") @TableField(updateStrategy = FieldStrategy.ALWAYS) private String changeReason; diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java index 47ef9a82..eafb7b30 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java @@ -260,6 +260,17 @@ public class DeptController extends BladeController { return R.data(deptService.listPlatformCompany()); } + /** + * 业务部门下拉(是否业务部门=是) + */ + @PreAuth(AuthConstant.PERMIT_ALL) + @GetMapping("/business-dept-select") + @ApiOperationSupport(order = 12) + @Operation(summary = "业务部门下拉", description = "返回是否业务部门=是的部门列表") + public R> businessDeptSelect() { + return R.data(deptService.listBusinessDept()); + } + /** * 获取部门的主管信息 */ diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml index bfbaf61f..1f38b0fd 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml @@ -20,6 +20,7 @@ + @@ -43,6 +44,7 @@ + diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java index d9094bf0..39ccb769 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java @@ -106,6 +106,13 @@ public interface IDeptService extends IService { */ List listPlatformCompany(); + /** + * 业务部门下拉(是否业务部门=是) + * + * @return 业务部门列表 + */ + List listBusinessDept(); + /** * 根据部门名称精确匹配获取部门ID集合 * diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java index fb539f67..551918ed 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java @@ -337,6 +337,18 @@ public class DeptServiceImpl extends ServiceImpl implements ID return list(queryWrapper); } + @Override + public List listBusinessDept() { + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery() + .eq(Dept::getIsBusinessDept, 1) + .orderByAsc(Dept::getSort) + .orderByAsc(Dept::getId); + if (!AuthUtil.isAdministrator()) { + queryWrapper.eq(Dept::getTenantId, AuthUtil.getTenantId()); + } + return list(queryWrapper); + } + @Override public String getDeptIds(String tenantId, String deptNames) { List deptList = baseMapper.selectList(Wrappers.query().lambda().eq(Dept::getTenantId, tenantId).in(Dept::getDeptName, Func.toStrList(deptNames))); @@ -443,6 +455,7 @@ public class DeptServiceImpl extends ServiceImpl implements ID rootDept.setIsDeleted(BladeConstant.DB_NOT_DELETED); rootDept.setIsOa(0); rootDept.setIsPlatformCompany(0); + rootDept.setIsBusinessDept(0); rootDept.setSyncTime(new Date()); this.save(rootDept); log.info("已创建 OA 同步集团顶级 id={} name={} code={}", @@ -536,6 +549,7 @@ public class DeptServiceImpl extends ServiceImpl implements ID newRoot.setIsDeleted(BladeConstant.DB_NOT_DELETED); newRoot.setIsOa(0); newRoot.setIsPlatformCompany(0); + newRoot.setIsBusinessDept(0); newRoot.setSyncTime(new Date()); this.save(newRoot); log.info("迁移创建新集团顶级 id={}(来自历史 id={})", newRootId, legacyRootId); @@ -598,6 +612,7 @@ public class DeptServiceImpl extends ServiceImpl implements ID externalDept.setIsDeleted(BladeConstant.DB_NOT_DELETED); externalDept.setIsOa(0); externalDept.setIsPlatformCompany(0); + externalDept.setIsBusinessDept(0); externalDept.setSyncTime(new Date()); this.save(externalDept); log.info("已创建外部组织顶级 id={}", externalDept.getId()); @@ -678,6 +693,9 @@ public class DeptServiceImpl extends ServiceImpl implements ID if (dept.getIsPlatformCompany() == null) { dept.setIsPlatformCompany(0); } + if (dept.getIsBusinessDept() == null) { + dept.setIsBusinessDept(0); + } // 排序未填时默认 1 if (dept.getSort() == null) { dept.setSort(1); @@ -804,6 +822,7 @@ public class DeptServiceImpl extends ServiceImpl implements ID dept.setIsDeleted(BladeConstant.DB_NOT_DELETED); dept.setIsOa(1); dept.setIsPlatformCompany(0); + dept.setIsBusinessDept(0); dept.setSyncTime(new Date()); boolean saved = save(dept); if (saved) { diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java index 65049d14..44e5e746 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java @@ -924,7 +924,8 @@ public class OASyncServiceImpl implements IOASyncService { .select(Dept::getId, Dept::getDeptCode, Dept::getDeptName, Dept::getFullName, Dept::getOaModified, Dept::getParentId, Dept::getOaId, Dept::getBelongCompanyCode, Dept::getParentCode, Dept::getOaSupSubComId, - Dept::getContractCodePrefix, Dept::getContractInitialSerial)); + Dept::getContractCodePrefix, Dept::getContractInitialSerial, + Dept::getIsPlatformCompany, Dept::getIsBusinessDept)); Map byOaId = new HashMap<>(); for (Dept dept : deptList) { if (StringUtils.isNotBlank(dept.getOaId())) { @@ -989,6 +990,8 @@ public class OASyncServiceImpl implements IOASyncService { } // OA 不同步合同本地配置:保留已有值,并把 -1 空占位规范为 null,避免 ALWAYS 策略写回 -1/清空 preserveLocalContractFields(dept, existing); + // OA 不同步「是否平台公司 / 是否业务部门」:更新时强制保留本地已配置值,新增默认否 + preserveLocalBizFlags(dept, existing); normalizeContractInitialSerial(dept); if (StringUtils.isNotBlank(dept.getDeptCode())) { deptIdByCode.put(dept.getDeptCode(), dept.getId()); @@ -1066,6 +1069,26 @@ public class OASyncServiceImpl implements IOASyncService { } } + /** + * OA 同步不携带本地业务标志:更新时保留已配置的「是否平台公司 / 是否业务部门」,新增默认否。 + */ + private void preserveLocalBizFlags(Dept incoming, Dept existing) { + if (incoming == null) { + return; + } + if (existing != null) { + incoming.setIsPlatformCompany(existing.getIsPlatformCompany() != null ? existing.getIsPlatformCompany() : 0); + incoming.setIsBusinessDept(existing.getIsBusinessDept() != null ? existing.getIsBusinessDept() : 0); + return; + } + if (incoming.getIsPlatformCompany() == null) { + incoming.setIsPlatformCompany(0); + } + if (incoming.getIsBusinessDept() == null) { + incoming.setIsBusinessDept(0); + } + } + /** * 合同初始流水号 -1 为空占位,同步落库前规范为 null。 */ diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/BillLedgerServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/BillLedgerServiceImpl.java index a20a4e0e..6528a411 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/BillLedgerServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/BillLedgerServiceImpl.java @@ -107,7 +107,7 @@ public class BillLedgerServiceImpl extends BaseServiceImpl availableList(String keyword, Long deptId, Long selectedId, String payerName, String payeeName) { LocalDate today = LocalDate.now(); - // 未到期且有余额;收票且部门可用,或出票/收票单位与结算收付款方一致(开票、收票都可) + // 可选汇票:未到期且可用余额 > 0(已选中的可回显);不再按部门、开票/收票类型、结算收付款方过滤 return list(Wrappers.lambdaQuery() .and(Func.isNotEmpty(keyword), wrapper -> wrapper.like(BillLedger::getBillNo, keyword) .or().like(BillLedger::getIssuerName, keyword) @@ -119,35 +119,10 @@ public class BillLedgerServiceImpl extends BaseServiceImpl child.eq(BillLedger::getId, selectedId))) .orderByAsc(BillLedger::getMaturityDate) .orderByDesc(BillLedger::getCreateTime)).stream() - .filter(item -> selectedId != null && Objects.equals(item.getId(), selectedId) - || selectableBill(item, deptId, payerName, payeeName)) .map(item -> BillLedgerWrapper.build().entityVO(item)) .toList(); } - /** - * 收票且当前部门在可用范围内,或出票单位、收票单位与结算付款方、收款方一致(顺序不限)。 - */ - private boolean selectableBill(BillLedger ledger, Long deptId, String payerName, String payeeName) { - boolean receivedAndDept = "received".equals(ledger.getBillType()) && departmentAvailable(ledger, deptId); - return receivedAndDept || partiesMatch(ledger.getIssuerName(), ledger.getReceiverName(), payerName, payeeName); - } - - private boolean partiesMatch(String issuerName, String receiverName, String payerName, String payeeName) { - String issuer = trimName(issuerName); - String receiver = trimName(receiverName); - String payer = trimName(payerName); - String payee = trimName(payeeName); - if (issuer.isEmpty() || receiver.isEmpty() || payer.isEmpty() || payee.isEmpty()) { - return false; - } - return (issuer.equals(payer) && receiver.equals(payee)) || (issuer.equals(payee) && receiver.equals(payer)); - } - - private String trimName(String value) { - return value == null ? "" : value.trim(); - } - @Override @Transactional(rollbackFor = Exception.class) public Long submit(BillLedgerSaveRequest request) { @@ -292,12 +267,6 @@ public class BillLedgerServiceImpl extends BaseServiceImpl values = parseArray(ledger.getAvailableDeptIdsJson(), "可用部门"); - if (values.stream().anyMatch(value -> "all".equals(String.valueOf(value)))) return true; - return deptId == null || values.stream().anyMatch(value -> String.valueOf(deptId).equals(String.valueOf(value))); - } - private List parseArray(String value, String name) { if (Func.isEmpty(value)) return List.of(); try { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/BillPaymentServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/BillPaymentServiceImpl.java index ec93da0a..b2b3e6f9 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/BillPaymentServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/BillPaymentServiceImpl.java @@ -32,7 +32,6 @@ import lombok.RequiredArgsConstructor; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.base.BaseServiceImpl; import org.springblade.core.secure.utils.AuthUtil; -import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.utils.Func; import org.springblade.system.cache.SysCache; import org.springblade.transport.constant.TransportBusiConstant; @@ -112,7 +111,7 @@ public class BillPaymentServiceImpl extends BaseServiceImpllambdaQuery() .eq(BillLedgerUsage::getBillPaymentId, entity.getId()) .eq(BillLedgerUsage::getUsageStatus, TransportBusiConstant.APPROVED) @@ -245,13 +244,13 @@ public class BillPaymentServiceImpl extends BaseServiceImpl 0) { throw new ServiceException("本次使用不能超过汇票可用余额"); } - if (!departmentAvailable(ledger, deptId)) { - throw new ServiceException("当前使用部门不在汇票可用部门范围内"); - } - } - - private boolean departmentAvailable(BillLedger ledger, Long deptId) { - if (Func.isEmpty(ledger.getAvailableDeptIdsJson())) return false; - try { - Object parsed = JsonUtil.parse(ledger.getAvailableDeptIdsJson(), List.class); - if (!(parsed instanceof List values)) return false; - if (values.stream().anyMatch(item -> "all".equals(String.valueOf(item)))) return true; - return deptId != null && values.stream().anyMatch(item -> String.valueOf(deptId).equals(String.valueOf(item))); - } catch (Exception exception) { - throw new ServiceException("汇票可用部门配置不正确"); - } } private BillLedger lockedBill(Long id) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java index 9d36d811..aab31d8b 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java @@ -83,6 +83,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl beforeData = buildContractChangeSnapshot(source); - applyContractNo(source, request.getContractNo(), false); - source.setContractName(request.getContractName()); source.setPartyB(request.getPartyB()); - source.setStartDate(request.getStartDate()); source.setEndDate(request.getEndDate()); source.setContractFormat(request.getContractFormat()); - source.setSettlementMode(request.getSettlementMode()); source.setLegalSealFlag(request.getLegalSealFlag()); source.setCopyCount(request.getCopyCount()); - source.setSettlementCurrency(TransportBusinessSupport.trimToNull(request.getSettlementCurrency())); source.setInvoiceCycle(request.getInvoiceCycle()); - source.setPaymentDays(request.getPaymentDays()); source.setRemark(request.getRemark()); source.setBillingEnabled(request.getBillingEnabled()); source.setFeeGenerationMode(request.getFeeGenerationMode()); - source.setContractAmount(request.getContractAmount()); source.setTemplateFlag(request.getTemplateFlag()); - source.setOriginalContractNo(TransportBusinessSupport.trimToNull(request.getOriginalContractNo())); - source.setElectronicSealFlag(request.getElectronicSealFlag()); - normalizeOptionalIntegerFields(source); - validateContractAmountRequired(source); - validateAdditionalFields(source); - source.setBillingPlanJson(request.getBillingPlanJson()); source.setSettlementRuleJson(request.getSettlementRuleJson()); source.setPreSettlementConfigJson(request.getPreSettlementConfigJson()); source.setFormalSettlementConfigJson(request.getFormalSettlementConfigJson()); source.setPaymentRatioJson(request.getPaymentRatioJson()); - source.setContractFileJson(request.getContractFileJson()); source.setAttachmentsJson(request.getAttachmentsJson()); - source.setChangeAttachmentsJson(TransportBusinessSupport.trimToNull(request.getChangeAttachmentsJson())); + if (settlementRuleOnly) { + applySettlementRuleChange(source, request); + } else { + applyContractInfoChange(source, request); + } Map afterData = buildContractChangeSnapshot(source); retainChangedSnapshotFields(beforeData, afterData); source.setApprovalStatus(STATUS_CHANGE_REVIEWING); source.setChangeContent(TransportBusinessSupport.trimToNull(request.getChangeContent())); source.setChangeReason(changeReason); + source.setChangeAttachmentsJson(TransportBusinessSupport.trimToNull(request.getChangeAttachmentsJson())); source.setCurrentNode("合同变更审批"); source.setCurrentProcessor("待处理"); - appendChangeRecord(source, "合同信息变更", changeReason, STATUS_CHANGE_REVIEWING, "审核中", beforeData, afterData); + appendChangeRecord(source, changeType, changeReason, STATUS_CHANGE_REVIEWING, "审核中", beforeData, afterData); return updateById(source); } + /** + * 解析并校验合同变更类型,兼容历史未传类型的请求。 + */ + private String resolveChangeType(String changeType) { + String normalized = TransportBusinessSupport.trimToNull(changeType); + if (Func.isEmpty(normalized)) { + return CHANGE_TYPE_INFO; + } + if (!List.of(CHANGE_TYPE_INFO, CHANGE_TYPE_SETTLEMENT).contains(normalized)) { + throw new ServiceException("请选择变更类型"); + } + return normalized; + } + + /** + * 结算规则变更:仅允许更新计费信息与结算单规则。 + */ + private void applySettlementRuleChange(ContractManage source, ContractManage request) { + source.setBillingEnabled(request.getBillingEnabled()); + source.setFeeGenerationMode(request.getFeeGenerationMode()); + source.setBillingPlanJson(request.getBillingPlanJson()); + source.setSettlementRuleJson(request.getSettlementRuleJson()); + source.setPreSettlementConfigJson(request.getPreSettlementConfigJson()); + source.setFormalSettlementConfigJson(request.getFormalSettlementConfigJson()); + } + + /** + * 合同信息变更:更新合同基本信息、计费/结算规则、付款比例及附件。 + */ + private void applyContractInfoChange(ContractManage source, ContractManage request) { + TransportBusinessSupport.validateRequired(request.getSettlementCurrency(), "请选择结算币种"); + ContractManage candidate = new ContractManage(); + BeanUtil.copyProperties(source, candidate); + candidate.setPartyB(TransportBusinessSupport.trimToNull(request.getPartyB())); + validateContractPartyCombinationUnique(candidate); + validatePaymentRatios(request.getPaymentRatioJson()); + applyContractNo(source, request.getContractNo(), false); + source.setContractName(request.getContractName()); + source.setPartyB(request.getPartyB()); + source.setStartDate(request.getStartDate()); + source.setEndDate(request.getEndDate()); + source.setContractFormat(request.getContractFormat()); + source.setSettlementMode(request.getSettlementMode()); + source.setLegalSealFlag(request.getLegalSealFlag()); + source.setCopyCount(request.getCopyCount()); + source.setSettlementCurrency(TransportBusinessSupport.trimToNull(request.getSettlementCurrency())); + source.setInvoiceCycle(request.getInvoiceCycle()); + source.setPaymentDays(request.getPaymentDays()); + source.setRemark(request.getRemark()); + source.setBillingEnabled(request.getBillingEnabled()); + source.setFeeGenerationMode(request.getFeeGenerationMode()); + source.setContractAmount(request.getContractAmount()); + source.setTemplateFlag(request.getTemplateFlag()); + source.setOriginalContractNo(TransportBusinessSupport.trimToNull(request.getOriginalContractNo())); + source.setElectronicSealFlag(request.getElectronicSealFlag()); + normalizeOptionalIntegerFields(source); + validateContractAmountRequired(source); + validateAdditionalFields(source); + source.setBillingPlanJson(request.getBillingPlanJson()); + source.setSettlementRuleJson(request.getSettlementRuleJson()); + source.setPreSettlementConfigJson(request.getPreSettlementConfigJson()); + source.setFormalSettlementConfigJson(request.getFormalSettlementConfigJson()); + source.setPaymentRatioJson(request.getPaymentRatioJson()); + source.setContractFileJson(request.getContractFileJson()); + source.setAttachmentsJson(request.getAttachmentsJson()); + } + @Override @Transactional(rollbackFor = Exception.class) public boolean updateAttachments(ContractManage contractManage) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java index ae825828..079dfcf8 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java @@ -107,6 +107,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { private static final String STATUS_WAITING_DISPATCH = "waiting_dispatch"; private static final String STATUS_DISPATCHING = "dispatching"; private static final String STATUS_RUNNING = "running"; + private static final String STATUS_PENDING_RETURN = WaybillProcessSupport.STATUS_PENDING_RETURN; private static final String STATUS_COMPLETED = "completed"; private static final String STATUS_CANCELLED = "cancelled"; @@ -118,9 +119,10 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { private static final List PENDING_STATUSES = Arrays.asList( STATUS_PENDING, STATUS_WAITING_DISPATCH, STATUS_DISPATCHING ); - /** Tab「全部」统计口径:待接 + 运输中 + 已完成(不含已取消) */ + /** Tab「全部」统计口径:待接 + 运输中 + 待确认回单 + 已完成(不含已取消) */ private static final List TAB_ALL_STATUSES = Arrays.asList( - STATUS_PENDING, STATUS_WAITING_DISPATCH, STATUS_DISPATCHING, STATUS_RUNNING, STATUS_COMPLETED + STATUS_PENDING, STATUS_WAITING_DISPATCH, STATUS_DISPATCHING, STATUS_RUNNING, + STATUS_PENDING_RETURN, STATUS_COMPLETED ); /** 允许完成运单的业务状态 */ private static final List COMPLETABLE_STATUSES = Arrays.asList( @@ -520,22 +522,35 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { nodePunchMapper.insert(punch); advanceCurrentProcessNode(waybill, nodeCfg); - // 末位打卡节点是到货/卸货/签收、且回单无需确认完成时,本次打卡一并把运单置为完成; + // 末位打卡节点是到货/卸货/签收时: + // - 回单无需确认 → 直接完成; + // - 回单需确认 → 进入「待确认回单」,等运管员批量/单条完成后才变已完成。 // isLastDriverPunchNode 与 returnNeedsNoConfirmation 传的就是上面那份 processJson,未再调 resolveProcessJson 取过程配置。 String nodeKey = WaybillProcessSupport.nodeKey(nodeCfg); String nodeName = WaybillProcessSupport.nodeName(nodeCfg); boolean completed = false; if (WaybillProcessSupport.isLastDriverPunchNode(processJson, nodeKey) && WaybillProcessSupport.isDeliveryNode(nodeKey, nodeName) - && WaybillProcessSupport.returnNeedsNoConfirmation(processJson) && canAutoCompleteWaybill(waybill)) { - completed = waybillService.completeWithoutDeptCheck(waybill.getId()); + if (WaybillProcessSupport.returnNeedsNoConfirmation(processJson)) { + completed = waybillService.completeWithoutDeptCheck(waybill.getId()); + } else { + markPendingReturn(waybill); + } } DriverNodePunchVO vo = toNodePunchVO(punch); vo.setWaybillCompleted(completed); return vo; } + /** + * 司机末位打卡完成且回单需内部确认时,将运单置为待确认回单。 + */ + private void markPendingReturn(Waybill waybill) { + waybill.setBusinessStatus(STATUS_PENDING_RETURN); + waybillService.updateById(waybill); + } + @Override @Transactional(rollbackFor = Exception.class) public boolean accept(Long id) { @@ -844,7 +859,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { switch (status) { case 0 -> wrapper.in(Waybill::getBusinessStatus, PENDING_STATUSES); case 1 -> wrapper.in(Waybill::getBusinessStatus, COMPLETABLE_STATUSES); - case 2 -> wrapper.eq(Waybill::getBusinessStatus, STATUS_COMPLETED); + case 2 -> wrapper.in(Waybill::getBusinessStatus, STATUS_COMPLETED, STATUS_PENDING_RETURN); case 3 -> wrapper.eq(Waybill::getBusinessStatus, STATUS_CANCELLED); default -> wrapper.in(Waybill::getBusinessStatus, TAB_ALL_STATUSES); } @@ -2004,7 +2019,8 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { return switch (businessStatus) { case STATUS_PENDING, STATUS_WAITING_DISPATCH, STATUS_DISPATCHING -> 0; case STATUS_RUNNING -> 1; - case STATUS_COMPLETED -> 2; + // 待确认回单:司机侧节点已打完,对司机按已完成展示 + case STATUS_PENDING_RETURN, STATUS_COMPLETED -> 2; case STATUS_CANCELLED -> 3; default -> null; }; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java index ed88df34..ccd9dee9 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java @@ -20,9 +20,11 @@ import org.springblade.system.pojo.entity.Dept; import org.springblade.transport.excel.LoadingManageExcel; import org.springblade.transport.mapper.LoadingManageMapper; import org.springblade.transport.mapper.WaybillMapper; +import org.springblade.transport.mapper.WaybillNodePunchMapper; import org.springblade.transport.pojo.entity.ContractManage; import org.springblade.transport.pojo.entity.LoadingManage; import org.springblade.transport.pojo.entity.Waybill; +import org.springblade.transport.pojo.entity.WaybillNodePunch; import org.springblade.transport.pojo.vo.BusinessRemoveResultVO; import org.springblade.transport.pojo.vo.LoadingCarrierContractVO; import org.springblade.transport.pojo.vo.LoadingManageVO; @@ -74,6 +76,9 @@ public class LoadingManageServiceImpl extends BaseServiceImpl nextIds = new HashSet<>(waybillIds(loadingManage.getWaybillIdsJson())); + for (Long oldId : oldWaybillIds) { + if (oldId != null && !nextIds.contains(oldId) && hasLoadOrUnloadPunch(oldId)) { + throw new ServiceException("已打卡的子运单不可从进行中配载单移除"); + } + } + } + } boolean result = saveOrUpdate(loadingManage); // 草稿仅预占运单号,不把运单业务状态改成 draft;正式确认时再完整同步为待执行 if (STATUS_DRAFT.equals(businessStatus)) { syncDraftWaybillReservation(loadingManage, oldWaybillIds); + } else if (STATUS_RUNNING.equals(businessStatus)) { + // 进行中暂存:不重置原有子单状态/接单,仅同步任务信息与新增子单 + List waybills = loadWaybills(waybillIds(loadingManage.getWaybillIdsJson())); + writeWaybillsForRunningEdit(loadingManage, oldWaybillIds, waybills); } else { syncAssociatedWaybills(loadingManage, businessStatus, oldWaybillIds); } @@ -156,28 +177,103 @@ public class LoadingManageServiceImpl extends BaseServiceImpl oldWaybillIds = new ArrayList<>(); boolean ownChildrenAdmitted = false; + boolean runningEdit = false; + LoadingManage oldRecord = null; if (Func.isNotEmpty(loadingManage.getId())) { - LoadingManage oldRecord = loadEditable(loadingManage.getId(), true); + oldRecord = loadEditable(loadingManage.getId(), true); oldWaybillIds = waybillIds(oldRecord.getWaybillIdsJson()); // 草稿只预占运单号、未过判据,草稿转正式不得沿用本单豁免 ownChildrenAdmitted = !STATUS_DRAFT.equals(oldRecord.getBusinessStatus()); + runningEdit = STATUS_RUNNING.equals(oldRecord.getBusinessStatus()); loadingManage.setLoadingNo(oldRecord.getLoadingNo()); loadingManage.setDeptId(oldRecord.getDeptId()); loadingManage.setDeptName(oldRecord.getDeptName()); } prepareCreateOrUpdate(loadingManage); - // 确认提交:草稿/新建统一生成正式配载单(待执行) - loadingManage.setBusinessStatus(STATUS_PENDING); + // 进行中编辑:保持 running;草稿/新建确认后统一为待执行 + loadingManage.setBusinessStatus(runningEdit ? STATUS_RUNNING : STATUS_PENDING); if (Func.isEmpty(loadingManage.getLoadingNo())) { loadingManage.setLoadingNo(nextCode()); } List waybills = validateWaybillsAvailable(loadingManage, waybillIds(loadingManage.getWaybillIdsJson()), ownChildrenAdmitted); validateCarrierContract(loadingManage, true, waybills); + if (runningEdit && oldRecord != null) { + validateRunningEdit(oldRecord, loadingManage, waybills, oldWaybillIds); + } boolean result = saveOrUpdate(loadingManage); - writeWaybillsForDriver(loadingManage, oldWaybillIds, waybills); + // 进行中编辑:保留原有子单状态与接单事实,仅对新加入子单做派车同步 + if (runningEdit) { + writeWaybillsForRunningEdit(loadingManage, oldWaybillIds, waybills); + } else { + writeWaybillsForDriver(loadingManage, oldWaybillIds, waybills); + } return result; } + /** + * 进行中配载编辑校验:必填任务字段不可改;已装/卸打卡的子运单不可移除。 + */ + private void validateRunningEdit(LoadingManage oldRecord, LoadingManage incoming, + List waybills, List oldWaybillIds) { + validateRunningImmutableTaskFields(oldRecord, incoming); + Set nextIds = waybills.stream().map(Waybill::getId).filter(Objects::nonNull).collect(Collectors.toSet()); + for (Long oldId : oldWaybillIds) { + if (oldId == null || nextIds.contains(oldId)) { + continue; + } + if (hasLoadOrUnloadPunch(oldId)) { + throw new ServiceException("已打卡的子运单不可从进行中配载单移除"); + } + } + } + + private void validateRunningImmutableTaskFields(LoadingManage oldRecord, LoadingManage incoming) { + if (!Objects.equals(trimText(oldRecord.getCarrierType()), trimText(incoming.getCarrierType()))) { + throw new ServiceException("进行中配载单不允许修改承运类型"); + } + if (Objects.equals(oldRecord.getCarrierType(), "承运商") + && !Objects.equals(oldRecord.getCarrierContractId(), incoming.getCarrierContractId())) { + throw new ServiceException("进行中配载单不允许修改承运商"); + } + if (!Objects.equals(trimText(oldRecord.getVehicleNo()), trimText(incoming.getVehicleNo()))) { + throw new ServiceException("进行中配载单不允许修改车牌号"); + } + if (Objects.equals(oldRecord.getCarrierType(), CARRIER_SELF)) { + if (!Objects.equals(trimText(oldRecord.getDriverName()), trimText(incoming.getDriverName()))) { + throw new ServiceException("进行中配载单不允许修改司机"); + } + if (!Objects.equals(trimText(oldRecord.getDriverPhone()), trimText(incoming.getDriverPhone()))) { + throw new ServiceException("进行中配载单不允许修改手机号"); + } + } + } + + private boolean hasLoadOrUnloadPunch(Long waybillId) { + if (waybillId == null) { + return false; + } + List punches = waybillNodePunchMapper.selectList(Wrappers.lambdaQuery() + .eq(WaybillNodePunch::getWaybillId, waybillId) + .eq(WaybillNodePunch::getIsDeleted, 0)); + return punches.stream().anyMatch(this::isLoadOrUnloadPunch); + } + + private boolean isLoadOrUnloadPunch(WaybillNodePunch punch) { + if (punch == null) { + return false; + } + String code = Func.toStr(punch.getNodeCode(), "").trim().toLowerCase(); + String name = Func.toStr(punch.getNodeName(), "").trim(); + return "load".equals(code) || name.contains("装货") + || "unload".equals(code) || name.contains("卸货") + || "arrival".equals(code) || name.contains("到货") + || "sign".equals(code) || name.contains("签收"); + } + + private String trimText(String value) { + return Func.toStr(value, "").trim(); + } + @Override @Transactional(rollbackFor = Exception.class) public void createFromImportedWaybills(String loadingNo, List waybills) { @@ -910,6 +1006,87 @@ public class LoadingManageServiceImpl extends BaseServiceImpl oldWaybillIds, List waybills) { + List newWaybillIds = waybillIds(loadingManage.getWaybillIdsJson()); + Set oldIdSet = new HashSet<>(oldWaybillIds); + List removedIds = oldWaybillIds.stream() + .filter(id -> id != null && !newWaybillIds.contains(id)) + .toList(); + releaseAssociatedWaybills(removedIds, STATUS_PENDING); + List keptIds = newWaybillIds.stream() + .filter(oldIdSet::contains) + .toList(); + if (Func.isNotEmpty(keptIds)) { + updateAssociatedWaybillTaskFields(loadingManage, keptIds, null); + } + List addedIds = newWaybillIds.stream() + .filter(id -> id != null && !oldIdSet.contains(id)) + .toList(); + if (Func.isEmpty(addedIds)) { + return; + } + List addedWaybills = waybills.stream() + .filter(waybill -> waybill.getId() != null && addedIds.contains(waybill.getId())) + .toList(); + Map statusByWaybillId = deriveWaybillBusinessStatuses(addedWaybills); + Map> waybillIdsByStatus = new LinkedHashMap<>(); + for (Long waybillId : addedIds) { + waybillIdsByStatus + .computeIfAbsent(statusByWaybillId.getOrDefault(waybillId, STATUS_PENDING), key -> new ArrayList<>()) + .add(waybillId); + } + waybillIdsByStatus.forEach((status, ids) -> updateAssociatedWaybillFields(loadingManage, ids, status)); + clearWaybillAcceptRecords(addedIds); + } + + private void updateAssociatedWaybillTaskFields(LoadingManage loadingManage, List waybillIds, String status) { + if (Func.isEmpty(waybillIds)) { + return; + } + var update = Wrappers.lambdaUpdate() + .in(Waybill::getId, waybillIds) + .set(Waybill::getLoadingNo, loadingManage.getLoadingNo()) + .set(Waybill::getCarrierType, loadingManage.getCarrierType()) + .set(Waybill::getCarrierContractId, loadingManage.getCarrierContractId()) + .set(Waybill::getCarrierName, loadingManage.getCarrierName()) + .set(Waybill::getDriverName, loadingManage.getDriverName()) + .set(Waybill::getDriverPhone, loadingManage.getDriverPhone()) + .set(Waybill::getVehicleNo, loadingManage.getVehicleNo()) + .set(Waybill::getTrailerVehicleNo, loadingManage.getTrailerVehicleNo()) + .set(Waybill::getEscortName, loadingManage.getEscortName()) + .set(Waybill::getEscortPhone, loadingManage.getEscortPhone()) + .set(Waybill::getMileage, loadingManage.getMileage()) + .set(Waybill::getEstimatedStartTime, toDateTime(loadingManage.getEstimatedStartDate())) + .set(Waybill::getEstimatedEndTime, toDateTime(loadingManage.getEstimatedEndDate())) + .set(Waybill::getTaskRemark, loadingManage.getTaskRemark()) + .set(Waybill::getTaskInfoJson, loadingManage.getTaskInfoJson()) + .set(Waybill::getCurrentProcessNode, loadingManage.getCurrentProcessNode()); + if (Func.isNotEmpty(status)) { + update.set(Waybill::getBusinessStatus, status); + if (STATUS_COMPLETED.equals(status)) { + update.set(Waybill::getEndDate, LocalDate.now()); + } + } + waybillMapper.update(null, update); + } + + private void clearWaybillAcceptRecords(List waybillIds) { + if (Func.isEmpty(waybillIds)) { + return; + } + waybillMapper.update(null, Wrappers.lambdaUpdate() + .in(Waybill::getId, waybillIds) + .set(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_PENDING) + .set(Waybill::getDriverAcceptTime, null) + .set(Waybill::getDriverAcceptDriverId, null) + .set(Waybill::getDriverRejectTime, null) + .set(Waybill::getDriverRejectReason, null)); + } + /** * 子运单业务状态逐张按各自项目的实时过程配置推导:需要接单 → 待执行,不需要 → 进行中。 *

diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java index 1ad8e2e0..065e2920 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java @@ -1069,7 +1069,7 @@ public class ManageWaybillServiceImpl implements IManageWaybillService { return switch (businessStatus) { case STATUS_PENDING, "waiting_dispatch", "dispatching" -> 0; case STATUS_RUNNING -> 1; - case STATUS_COMPLETED -> 2; + case WaybillProcessSupport.STATUS_PENDING_RETURN, STATUS_COMPLETED -> 2; case STATUS_CANCELLED -> 3; default -> null; }; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java index 9668c955..227a73c7 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java @@ -2563,44 +2563,11 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl 0) throw new ServiceException("申请付款金额超过汇票可用余额"); - if (!partyMatched && !departmentAvailable(ledger, deptId)) throw new ServiceException("当前使用部门不在汇票可用部门范围内"); - } - - /** 出票单位、收票单位与结算付款方、收款方一致(顺序不限) */ - private boolean settlementPartiesMatch(BillLedger ledger, Long settlementId, Long preSettlementId) { - String payerName = null; - String payeeName = null; - if (settlementId != null) { - FormalSettlement settlement = formalSettlementMapper.selectById(settlementId); - if (settlement != null && !Objects.equals(settlement.getIsDeleted(), 1)) { - payerName = settlement.getPayerName(); - payeeName = settlement.getPayeeName(); - } - } - if ((payerName == null || payeeName == null) && preSettlementId != null) { - PreSettlement settlement = preSettlementMapper.selectById(preSettlementId); - if (settlement != null && !Objects.equals(settlement.getIsDeleted(), 1)) { - payerName = settlement.getPayerName(); - payeeName = settlement.getPayeeName(); - } - } - String issuer = ledger.getIssuerName() == null ? "" : ledger.getIssuerName().trim(); - String receiver = ledger.getReceiverName() == null ? "" : ledger.getReceiverName().trim(); - String payer = payerName == null ? "" : payerName.trim(); - String payee = payeeName == null ? "" : payeeName.trim(); - if (issuer.isEmpty() || receiver.isEmpty() || payer.isEmpty() || payee.isEmpty()) { - return false; - } - return (issuer.equals(payer) && receiver.equals(payee)) || (issuer.equals(payee) && receiver.equals(payer)); } private void useBillBalance(PaymentApplication entity) { @@ -2756,18 +2723,6 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl values)) return false; - if (values.stream().anyMatch(value -> "all".equals(String.valueOf(value)))) return true; - return deptId != null && values.stream().anyMatch(value -> String.valueOf(deptId).equals(String.valueOf(value))); - } catch (Exception exception) { - throw new ServiceException("汇票可用部门配置不正确"); - } - } - @Override public boolean isBillPayment(String paymentMethod) { if (Func.isEmpty(paymentMethod)) return false; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java index 40ddbb6b..d72428c5 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java @@ -34,6 +34,7 @@ import org.springblade.core.tenant.annotation.TenantIgnore; import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.Func; +import org.springblade.system.cache.SysCache; import org.springblade.system.cache.UserCache; import org.springblade.system.pojo.entity.Dept; import org.springblade.transport.excel.ProjectApplyExcel; @@ -488,8 +489,7 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl deptIds = Func.toLongList(AuthUtil.getDeptId()); + if (Func.isEmpty(deptIds)) { + return; + } + for (Long deptId : deptIds) { + if (Func.isEmpty(deptId) || deptId <= 0) { + continue; + } + Dept dept = SysCache.getDept(deptId); + if (dept != null && Objects.equals(dept.getIsBusinessDept(), 1)) { + projectApply.setBusinessDeptId(dept.getId()); + projectApply.setBusinessDeptName(dept.getDeptName()); + return; + } + } + } + + /** + * 校验业务部门必须是组织中标记为「是否业务部门=是」的部门。 + */ + private void validateBusinessDept(ProjectApply projectApply) { + Long businessDeptId = projectApply.getBusinessDeptId(); + if (Func.isEmpty(businessDeptId)) { + throw new ServiceException("请选择业务部门"); + } + Dept dept = SysCache.getDept(businessDeptId); + if (dept == null || Objects.equals(dept.getIsDeleted(), 1)) { + throw new ServiceException("业务部门不存在"); + } + if (!Objects.equals(dept.getIsBusinessDept(), 1)) { + throw new ServiceException("请选择组织中标记为业务部门的部门"); + } + if (Func.isNotEmpty(dept.getDeptName())) { + projectApply.setBusinessDeptName(dept.getDeptName()); + } + TransportBusinessSupport.validateRequired(projectApply.getBusinessDeptName(), "请选择业务部门"); + } + private void normalizeUndertakeDept(ProjectApply projectApply) { if (projectApply == null) { return; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java index 09a3b020..b43a54df 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java @@ -3295,6 +3295,7 @@ public class ReceivablePayableDetailServiceImpl private String waybillStatusName(String status) { return switch (status == null ? "" : status) { case "completed" -> "已完成"; + case "pending_return" -> "待确认回单"; case "processing", "running", "in_progress", "inProgress" -> "进行中"; case "pending", "created" -> "待执行"; case "cancelled", "canceled" -> "已取消"; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java index 9f40356f..fa90287b 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java @@ -967,6 +967,7 @@ public class WaybillServiceImpl extends BaseServiceImpl */ private static boolean passedAcceptStage(String businessStatus) { return "running".equals(businessStatus) + || WaybillProcessSupport.STATUS_PENDING_RETURN.equals(businessStatus) || "completed".equals(businessStatus) || "cancelled".equals(businessStatus); } @@ -1507,6 +1508,13 @@ public class WaybillServiceImpl extends BaseServiceImpl @Transactional(rollbackFor = Exception.class) public boolean confirmReturnReceipt(Long id) { Waybill waybill = loadEditable(id, true); + String status = waybill.getBusinessStatus(); + // pending_return 为正规待确认态;running/processing 兼容历史「打完卡仍停在进行中」的数据 + if (!WaybillProcessSupport.STATUS_PENDING_RETURN.equals(status) + && !"running".equals(status) + && !"processing".equals(status)) { + throw new ServiceException("当前状态不允许确认回单"); + } String processJson = LiveProcessConfigSupport.resolve( LiveProcessConfigSupport.byProject(processConfigService, waybill.getProjectId()), waybill.getProcessJson()); @@ -1585,7 +1593,12 @@ public class WaybillServiceImpl extends BaseServiceImpl BusinessRemoveResultVO result = new BusinessRemoveResultVO(); for (Waybill waybill : listByIds(idList)) { try { - complete(waybill.getId()); + // 待确认回单走确认回单校验(打卡节点 + 未了结异常);其它状态仍走普通完成 + if (WaybillProcessSupport.STATUS_PENDING_RETURN.equals(waybill.getBusinessStatus())) { + confirmReturnReceipt(waybill.getId()); + } else { + complete(waybill.getId()); + } result.setSuccessCount(result.getSuccessCount() + 1); } catch (Exception exception) { log.error("批量完成运单失败,waybillId:{}, waybillNo:{}, failureReason:{}", diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java index 4462c3a3..800a5c10 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java @@ -49,6 +49,8 @@ public final class WaybillProcessSupport { public static final String STATUS_PENDING = "pending"; public static final String STATUS_RUNNING = "running"; + /** 司机打卡已完成、待运管员确认回单 */ + public static final String STATUS_PENDING_RETURN = "pending_return"; public static final String ACCEPT_PENDING = "pending"; public static final String ACCEPT_ACCEPTED = "accepted"; public static final String ACCEPT_REJECTED = "rejected"; @@ -134,14 +136,17 @@ public final class WaybillProcessSupport { } /** - * 运单已结束:已完成或已取消。此后不再接受司机打卡。 + * 运单已结束:已完成、已取消,或待确认回单。此后不再接受司机打卡。 *

- * 只认这两个状态,大小写不敏感。草稿、待执行、待调度、调度中的运单司机仍要能打卡, + * 待确认回单表示司机侧节点已打完,等待运管确认,不能再打卡。 + * 草稿、待执行、待调度、调度中的运单司机仍要能打卡, * 所以不能按 {@link #isTerminalBusinessStatus} 的「终态或调度中间态」口径判定。 */ public static boolean isPunchEnded(String businessStatus) { String status = stringVal(businessStatus); - return "completed".equalsIgnoreCase(status) || "cancelled".equalsIgnoreCase(status); + return "completed".equalsIgnoreCase(status) + || "cancelled".equalsIgnoreCase(status) + || STATUS_PENDING_RETURN.equalsIgnoreCase(status); } /** @@ -155,6 +160,9 @@ public final class WaybillProcessSupport { if ("cancelled".equalsIgnoreCase(status)) { return "运单已取消"; } + if (STATUS_PENDING_RETURN.equalsIgnoreCase(status)) { + return "运单待确认回单"; + } return null; } @@ -213,7 +221,8 @@ public final class WaybillProcessSupport { || "completed".equals(businessStatus) || "cancelled".equals(businessStatus) || "waiting_dispatch".equals(businessStatus) - || "dispatching".equals(businessStatus); + || "dispatching".equals(businessStatus) + || STATUS_PENDING_RETURN.equals(businessStatus); } /** @@ -221,7 +230,7 @@ public final class WaybillProcessSupport { *

* 需要接单(接单节点 confirmMode=yes)且尚未接单(未响应 / 已拒绝)→ pending(待执行); * 已接单 → running(进行中);不需要接单 → running(仅当当前为空或 pending 时提升)。 - * draft / completed / cancelled 等终态或调度中间态不改动。 + * draft / completed / cancelled / pending_return 等终态或调度中间态不改动。 */ public static String normalizeBusinessStatus(String businessStatus, String processJson) { return normalizeBusinessStatus(businessStatus, processJson, null); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/WaybillWrapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/WaybillWrapper.java index 1dab5a5b..5bd0bd98 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/WaybillWrapper.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/WaybillWrapper.java @@ -122,6 +122,7 @@ public class WaybillWrapper extends BaseEntityWrapper { case "waiting_dispatch" -> "待调度"; case "dispatching" -> "调度中"; case "running", "processing", "in_progress", "inProgress" -> "进行中"; + case "pending_return" -> "待确认回单"; case "completed" -> "已完成"; case "cancelled" -> "已取消"; case "closed" -> "已关闭"; diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java index b09c82c4..c08e4ea2 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java @@ -337,12 +337,14 @@ class WaybillProcessSupportTest { // ------------------------------------------------------------ 结束门禁 @Test - @DisplayName("结束门禁:只认已完成与已取消,大小写不敏感") + @DisplayName("结束门禁:已完成、已取消、待确认回单,大小写不敏感") void punchEndedOnlyForCompletedAndCancelled() { assertTrue(WaybillProcessSupport.isPunchEnded("completed")); assertTrue(WaybillProcessSupport.isPunchEnded("COMPLETED")); assertTrue(WaybillProcessSupport.isPunchEnded("cancelled")); assertTrue(WaybillProcessSupport.isPunchEnded("Cancelled")); + assertTrue(WaybillProcessSupport.isPunchEnded("pending_return")); + assertTrue(WaybillProcessSupport.isPunchEnded("PENDING_RETURN")); } @Test @@ -358,11 +360,12 @@ class WaybillProcessSupportTest { } @Test - @DisplayName("结束门禁文案:完成与取消各一句,非结束态没有文案") + @DisplayName("结束门禁文案:完成、取消、待确认回单各一句,非结束态没有文案") void punchEndedReasonOnlyForEndedStatuses() { assertEquals("运单已完成", WaybillProcessSupport.punchEndedReason("completed")); assertEquals("运单已完成", WaybillProcessSupport.punchEndedReason(" COMPLETED ")); assertEquals("运单已取消", WaybillProcessSupport.punchEndedReason("cancelled")); + assertEquals("运单待确认回单", WaybillProcessSupport.punchEndedReason("pending_return")); assertNull(WaybillProcessSupport.punchEndedReason(null)); assertNull(WaybillProcessSupport.punchEndedReason("")); assertNull(WaybillProcessSupport.punchEndedReason(" ")); diff --git a/doc/sql/bladex/bladex.mysql.all.create.sql b/doc/sql/bladex/bladex.mysql.all.create.sql index fed3b1b5..0d8125ed 100644 --- a/doc/sql/bladex/bladex.mysql.all.create.sql +++ b/doc/sql/bladex/bladex.mysql.all.create.sql @@ -311,6 +311,7 @@ CREATE TABLE `blade_dept` ( `mnemonic_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '助记码', `carrier_customer_id` bigint NULL DEFAULT NULL COMMENT '承运商客商档案主键', `is_platform_company` tinyint NOT NULL DEFAULT 0 COMMENT '是否平台公司:0否,1是', + `is_business_dept` tinyint NOT NULL DEFAULT 0 COMMENT '是否业务部门:0否,1是', `oa_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'OA主键', `oa_sup_sub_com_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'OA上级主键', `sort` int NULL DEFAULT NULL COMMENT '排序', diff --git a/doc/sql/transport/blade_dept_is_business_dept_20261022.sql b/doc/sql/transport/blade_dept_is_business_dept_20261022.sql new file mode 100644 index 00000000..da6793f8 --- /dev/null +++ b/doc/sql/transport/blade_dept_is_business_dept_20261022.sql @@ -0,0 +1,5 @@ +-- 机构表:新增「是否业务部门」字段,默认否 +-- 全量 OA 同步时须保留本地已配置值,不得覆盖 + +ALTER TABLE `blade_dept` + ADD COLUMN `is_business_dept` tinyint NOT NULL DEFAULT 0 COMMENT '是否业务部门:0否,1是' AFTER `is_platform_company`; diff --git a/doc/sql/transport/stat_account_module_create_count.sql b/doc/sql/transport/stat_account_module_create_count.sql index 6cc9a5b2..a0233b35 100644 --- a/doc/sql/transport/stat_account_module_create_count.sql +++ b/doc/sql/transport/stat_account_module_create_count.sql @@ -7,8 +7,12 @@ -- 3. 只统计各功能列表对应的主表。随主单保存的明细、联系人、附件、变更流水不重复计数。 -- 4. 含已逻辑删除的数据,因为这些记录确实被创建过。 -- 5. 客商档案菜单挂在「车/船务管理」下,这里按统计口径单独计入「客商管理」。 --- 6. 未纳入:结算管理、首付款管理、在途管理,以及系统管理下的开票项目。 +-- 6. 未纳入:在途管理,以及系统管理下的开票项目。 -- 7. 导入运单产生的运单计入「业务模块-运单管理」,不另计导入批次。 +-- 导入结算单产生的正式结算单计入「结算管理」的付款结算单或收款结算单,不另计导入批次。 +-- 8. 付款结算单、收款结算单共用 blade_formal_settlement,按 settlement_type 区分(payable/receivable)。 +-- 应收、应付共用 blade_receivable_payable_detail,同样按 settlement_type 区分。 +-- 9. 菜单名称为「收付款管理」,统计模块名与菜单一致。 SET NAMES utf8mb4; @@ -396,7 +400,124 @@ SELECT t.create_user, 8, '运力模块', 4, '车辆调度', 'blade_transport_veh FROM blade_transport_vehicle_dispatch t INNER JOIN tmp_stat_user u ON u.id = t.create_user WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive -GROUP BY t.create_user; +GROUP BY t.create_user +; + +INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt) +SELECT t.create_user, 9, '结算管理', 1, '应收', 'blade_receivable_payable_detail', COUNT(*) +FROM blade_receivable_payable_detail t +INNER JOIN tmp_stat_user u ON u.id = t.create_user +WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive + AND t.settlement_type = 'receivable' +GROUP BY t.create_user +; + +INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt) +SELECT t.create_user, 9, '结算管理', 2, '应付', 'blade_receivable_payable_detail', COUNT(*) +FROM blade_receivable_payable_detail t +INNER JOIN tmp_stat_user u ON u.id = t.create_user +WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive + AND t.settlement_type = 'payable' +GROUP BY t.create_user +; + +INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt) +SELECT t.create_user, 9, '结算管理', 3, '预付结算单', 'blade_pre_settlement', COUNT(*) +FROM blade_pre_settlement t +INNER JOIN tmp_stat_user u ON u.id = t.create_user +WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive +GROUP BY t.create_user +; + +INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt) +SELECT t.create_user, 9, '结算管理', 4, '付款结算单', 'blade_formal_settlement', COUNT(*) +FROM blade_formal_settlement t +INNER JOIN tmp_stat_user u ON u.id = t.create_user +WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive + AND t.settlement_type = 'payable' +GROUP BY t.create_user +; + +INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt) +SELECT t.create_user, 9, '结算管理', 5, '收款结算单', 'blade_formal_settlement', COUNT(*) +FROM blade_formal_settlement t +INNER JOIN tmp_stat_user u ON u.id = t.create_user +WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive + AND t.settlement_type = 'receivable' +GROUP BY t.create_user +; + +INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt) +SELECT t.create_user, 9, '结算管理', 6, '运输对账', 'blade_transport_reconciliation', COUNT(*) +FROM blade_transport_reconciliation t +INNER JOIN tmp_stat_user u ON u.id = t.create_user +WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive +GROUP BY t.create_user +; + +INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt) +SELECT t.create_user, 9, '结算管理', 7, '结算调整单', 'blade_settlement_adjustment', COUNT(*) +FROM blade_settlement_adjustment t +INNER JOIN tmp_stat_user u ON u.id = t.create_user +WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive +GROUP BY t.create_user +; + +INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt) +SELECT t.create_user, 10, '收付款管理', 1, '付款管理', 'blade_payment_application', COUNT(*) +FROM blade_payment_application t +INNER JOIN tmp_stat_user u ON u.id = t.create_user +WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive +GROUP BY t.create_user +; + +INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt) +SELECT t.create_user, 10, '收付款管理', 2, '开票管理', 'blade_invoice_application', COUNT(*) +FROM blade_invoice_application t +INNER JOIN tmp_stat_user u ON u.id = t.create_user +WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive +GROUP BY t.create_user +; + +INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt) +SELECT t.create_user, 10, '收付款管理', 3, '收票管理', 'blade_invoice_receipt', COUNT(*) +FROM blade_invoice_receipt t +INNER JOIN tmp_stat_user u ON u.id = t.create_user +WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive +GROUP BY t.create_user +; + +INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt) +SELECT t.create_user, 10, '收付款管理', 4, '收款流水', 'blade_kingdee_receipt_flow', COUNT(*) +FROM blade_kingdee_receipt_flow t +INNER JOIN tmp_stat_user u ON u.id = t.create_user +WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive +GROUP BY t.create_user +; + +INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt) +SELECT t.create_user, 10, '收付款管理', 5, '认领记录', 'blade_receipt_claim', COUNT(*) +FROM blade_receipt_claim t +INNER JOIN tmp_stat_user u ON u.id = t.create_user +WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive +GROUP BY t.create_user +; + +INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt) +SELECT t.create_user, 10, '收付款管理', 6, '汇票台账', 'blade_bill_ledger', COUNT(*) +FROM blade_bill_ledger t +INNER JOIN tmp_stat_user u ON u.id = t.create_user +WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive +GROUP BY t.create_user +; + +INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt) +SELECT t.create_user, 10, '收付款管理', 7, '汇票付款', 'blade_bill_payment', COUNT(*) +FROM blade_bill_payment t +INNER JOIN tmp_stat_user u ON u.id = t.create_user +WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive +GROUP BY t.create_user +; -- 结果 1:账号匹配情况。用户 ID 为空表示库中没有该账号。 SELECT @@ -422,6 +543,8 @@ SELECT IFNULL(SUM(CASE WHEN d.module_name = '业务模块' THEN d.cnt ELSE 0 END), 0) AS `业务模块`, IFNULL(SUM(CASE WHEN d.module_name = '车/船务模块' THEN d.cnt ELSE 0 END), 0) AS `车/船务模块`, IFNULL(SUM(CASE WHEN d.module_name = '运力模块' THEN d.cnt ELSE 0 END), 0) AS `运力模块`, + IFNULL(SUM(CASE WHEN d.module_name = '结算管理' THEN d.cnt ELSE 0 END), 0) AS `结算管理`, + IFNULL(SUM(CASE WHEN d.module_name = '收付款管理' THEN d.cnt ELSE 0 END), 0) AS `收付款管理`, IFNULL(SUM(d.cnt), 0) AS `合计` FROM tmp_stat_account a LEFT JOIN tmp_stat_user u ON u.account = a.account