✨ 项目申请平台公司多选,运单导入承运商筛选与复制草稿优化

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-14 12:59:31 +08:00
parent 19c5bc64f1
commit 6f4812db75
7 changed files with 171 additions and 32 deletions
@@ -70,10 +70,10 @@ public class ProjectApply extends TenantEntity {
@Schema(description = "业务部门")
private String businessDeptName;
@Schema(description = "承办部门ID")
private Long undertakeDeptId;
@Schema(description = "平台公司ID,多个逗号分隔")
private String undertakeDeptId;
@Schema(description = "承办部门")
@Schema(description = "平台公司,多个顿号分隔")
private String undertakeDeptName;
@Schema(description = "项目由来")
@@ -338,19 +338,23 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl<ProjectApplyMapper,
TransportBusinessSupport.validateAllDept(projectApply.getAllDept(), "项目立项");
LambdaQueryWrapper<ProjectApply> queryWrapper = Wrappers.<ProjectApply>lambdaQuery().eq(ProjectApply::getIsDeleted, 0);
if (TransportBusinessSupport.isAdministrator()) {
// 超管默认可查看全部未删除数据;仅在明确指定承办组织时按组织精确筛选。
// 超管默认可查看全部未删除数据;仅在明确指定平台公司时按包含匹配筛选。
if (Objects.equals(projectApply.getAllDept(), 1) && Func.isNotEmpty(projectApply.getUndertakeDeptId())) {
queryWrapper.eq(ProjectApply::getUndertakeDeptId, projectApply.getUndertakeDeptId());
applyUndertakeDeptContains(queryWrapper, projectApply.getUndertakeDeptId());
}
} else if (!Objects.equals(projectApply.getAllDept(), 1)) {
Long currentDeptId = TransportBusinessSupport.currentDeptId("项目立项");
Long currentUserId = AuthUtil.getUserId();
queryWrapper.and(wrapper -> wrapper.eq(ProjectApply::getUndertakeDeptId, currentDeptId)
.or().eq(ProjectApply::getCreateUser, currentUserId)
.or().eq(ProjectApply::getHandlerUserId, currentUserId)
.or().eq(ProjectApply::getPrincipalUserId, currentUserId));
queryWrapper.and(wrapper -> {
if (currentDeptId != null) {
wrapper.apply("FIND_IN_SET({0}, IFNULL(undertake_dept_id,''))", String.valueOf(currentDeptId));
}
wrapper.or().eq(ProjectApply::getCreateUser, currentUserId)
.or().eq(ProjectApply::getHandlerUserId, currentUserId)
.or().eq(ProjectApply::getPrincipalUserId, currentUserId);
});
} else if (Func.isNotEmpty(projectApply.getUndertakeDeptId())) {
queryWrapper.eq(ProjectApply::getUndertakeDeptId, projectApply.getUndertakeDeptId());
applyUndertakeDeptContains(queryWrapper, projectApply.getUndertakeDeptId());
}
queryWrapper.like(Func.isNotEmpty(projectApply.getApplyNo()), ProjectApply::getApplyNo, projectApply.getApplyNo())
.like(Func.isNotEmpty(projectApply.getProjectCode()), ProjectApply::getProjectCode, projectApply.getProjectCode())
@@ -464,6 +468,7 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl<ProjectApplyMapper,
}
private void prepareCreateOrUpdate(ProjectApply projectApply) {
normalizeUndertakeDept(projectApply);
if (Func.isEmpty(projectApply.getId())) {
// 新增项目不产生变更记录,避免客户端误传变更字段导致记录回显。
projectApply.setChangeContent(null);
@@ -476,12 +481,12 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl<ProjectApplyMapper,
}
if (Func.isEmpty(projectApply.getUndertakeDeptId())) {
Dept dept = TransportBusinessSupport.currentDept("项目立项");
projectApply.setUndertakeDeptId(dept.getId());
projectApply.setUndertakeDeptId(String.valueOf(dept.getId()));
projectApply.setUndertakeDeptName(dept.getDeptName());
}
if (Func.isEmpty(projectApply.getBusinessDeptId())) {
projectApply.setBusinessDeptId(projectApply.getUndertakeDeptId());
projectApply.setBusinessDeptName(projectApply.getUndertakeDeptName());
projectApply.setBusinessDeptId(firstUndertakeDeptId(projectApply.getUndertakeDeptId()));
projectApply.setBusinessDeptName(firstUndertakeDeptName(projectApply.getUndertakeDeptName()));
}
if (Func.isEmpty(projectApply.getHandlerUserId())) {
projectApply.setHandlerUserId(AuthUtil.getUserId());
@@ -495,8 +500,11 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl<ProjectApplyMapper,
ProjectApply oldRecord = loadEditable(projectApply.getId(), true);
projectApply.setApplyNo(oldRecord.getApplyNo());
projectApply.setProjectCode(oldRecord.getProjectCode());
projectApply.setUndertakeDeptId(oldRecord.getUndertakeDeptId());
projectApply.setUndertakeDeptName(oldRecord.getUndertakeDeptName());
// 平台公司支持编辑多选保存,不再强制写回旧值
if (Func.isEmpty(projectApply.getUndertakeDeptId())) {
projectApply.setUndertakeDeptId(oldRecord.getUndertakeDeptId());
projectApply.setUndertakeDeptName(oldRecord.getUndertakeDeptName());
}
}
if (Func.isEmpty(projectApply.getEffectiveType())) {
projectApply.setEffectiveType(EFFECTIVE_TEMPORARY);
@@ -981,10 +989,86 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl<ProjectApplyMapper,
}
Long currentDeptId = TransportBusinessSupport.currentDeptId("项目立项");
Long currentUserId = AuthUtil.getUserId();
return Objects.equals(projectApply.getUndertakeDeptId(), currentDeptId)
return containsUndertakeDept(projectApply.getUndertakeDeptId(), currentDeptId)
|| Objects.equals(projectApply.getCreateUser(), currentUserId)
|| Objects.equals(projectApply.getHandlerUserId(), currentUserId)
|| Objects.equals(projectApply.getPrincipalUserId(), currentUserId);
}
/**
* 平台公司多值筛选:历史单 ID 与逗号分隔多 ID 均按包含匹配。
*/
private void applyUndertakeDeptContains(LambdaQueryWrapper<ProjectApply> queryWrapper, String undertakeDeptId) {
if (Func.isBlank(undertakeDeptId) || queryWrapper == null) {
return;
}
List<String> deptIds = Arrays.stream(undertakeDeptId.split("[,,]"))
.map(String::trim)
.filter(Func::isNotEmpty)
.distinct()
.toList();
if (deptIds.isEmpty()) {
return;
}
queryWrapper.and(wrapper -> {
boolean first = true;
for (String deptId : deptIds) {
if (first) {
wrapper.apply("FIND_IN_SET({0}, IFNULL(undertake_dept_id,''))", deptId);
first = false;
} else {
wrapper.or().apply("FIND_IN_SET({0}, IFNULL(undertake_dept_id,''))", deptId);
}
}
});
}
private void normalizeUndertakeDept(ProjectApply projectApply) {
if (projectApply == null) {
return;
}
List<String> deptIds = Arrays.stream(Func.toStr(projectApply.getUndertakeDeptId()).split("[,,]"))
.map(String::trim)
.filter(Func::isNotEmpty)
.filter(id -> !"-1".equals(id))
.distinct()
.toList();
projectApply.setUndertakeDeptId(deptIds.isEmpty() ? null : String.join(",", deptIds));
List<String> deptNames = Arrays.stream(Func.toStr(projectApply.getUndertakeDeptName()).split("[,,、]"))
.map(String::trim)
.filter(Func::isNotEmpty)
.distinct()
.toList();
projectApply.setUndertakeDeptName(deptNames.isEmpty() ? null : String.join("、", deptNames));
}
private Long firstUndertakeDeptId(String undertakeDeptId) {
return Arrays.stream(Func.toStr(undertakeDeptId).split("[,,]"))
.map(String::trim)
.filter(Func::isNotEmpty)
.filter(id -> !"-1".equals(id))
.map(Func::toLong)
.filter(id -> id != null && id > 0L)
.findFirst()
.orElse(null);
}
private String firstUndertakeDeptName(String undertakeDeptName) {
return Arrays.stream(Func.toStr(undertakeDeptName).split("[,,、]"))
.map(String::trim)
.filter(Func::isNotEmpty)
.findFirst()
.orElse(null);
}
private boolean containsUndertakeDept(String undertakeDeptId, Long deptId) {
if (deptId == null || Func.isBlank(undertakeDeptId)) {
return false;
}
String target = String.valueOf(deptId);
return Arrays.stream(undertakeDeptId.split("[,,]"))
.map(String::trim)
.anyMatch(target::equals);
}
}
@@ -3037,6 +3037,11 @@ public class ReceivablePayableDetailServiceImpl
}
private String formatQuantity(BigDecimal quantity, String unit) {
// 无有效运输量(空/0/-1)时不展示数值与单位
if (quantity == null || quantity.compareTo(BigDecimal.ZERO) == 0
|| quantity.compareTo(BigDecimal.valueOf(-1)) == 0) {
return "-";
}
return money(quantity).stripTrailingZeros().toPlainString() + (unit == null ? "" : " " + unit);
}
@@ -342,10 +342,11 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl<WaybillImport
.eq(WaybillImportBatch::getIsDeleted, 0)
.eq(request.getId() != null, WaybillImportBatch::getId, request.getId())
.like(Func.isNotEmpty(request.getBatchNo()), WaybillImportBatch::getBatchNo, request.getBatchNo())
.apply(Func.isNotEmpty(request.getCarrierId()), "FIND_IN_SET({0}, carrier_ids)", request.getCarrierId())
.eq(Func.isNotEmpty(request.getCarrierName()), WaybillImportBatch::getCarrierName, request.getCarrierName())
.eq(Func.isNotEmpty(request.getCreateUser()), WaybillImportBatch::getCreateUser, request.getCreateUser())
.orderByDesc(WaybillImportBatch::getCreateTime);
// 历史批次可能只落了承运商名称、carrier_ids 为空,筛选时需同时按 ID 与名称兼容匹配
applyCarrierFilter(queryWrapper, request.getCarrierId());
if (Func.isNotEmpty(request.getCreateTimeStart())) {
queryWrapper.ge(WaybillImportBatch::getCreateTime,
DateUtil.parse(request.getCreateTimeStart(), DateUtil.PATTERN_DATETIME));
@@ -361,6 +362,24 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl<WaybillImport
return resultPage;
}
/**
* 承运商筛选:优先 FIND_IN_SET(carrier_ids),同时兼容仅有 carrier_name 的历史数据。
*/
private void applyCarrierFilter(LambdaQueryWrapper<WaybillImportBatch> queryWrapper, Long carrierId) {
Long normalizedCarrierId = normalizeOptionalId(carrierId);
if (normalizedCarrierId == null) {
return;
}
CustomerArchive carrier = customerArchiveService.getById(normalizedCarrierId);
String carrierName = carrier == null ? null : Func.toStr(carrier.getFullName()).trim();
queryWrapper.and(wrapper -> {
wrapper.apply("FIND_IN_SET({0}, IFNULL(carrier_ids,''))", String.valueOf(normalizedCarrierId));
if (Func.isNotEmpty(carrierName)) {
wrapper.or().eq(WaybillImportBatch::getCarrierName, carrierName);
}
});
}
@Override
@Transactional(rollbackFor = Exception.class)
public BusinessRemoveResultVO removeBatches(String ids) {
@@ -405,8 +424,9 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl<WaybillImport
WaybillImportBatch batch = new WaybillImportBatch();
BeanUtil.copyProperties(request, batch);
batch.setBatchNo(Func.isEmpty(request.getId()) ? nextCode() : request.getBatchNo());
batch.setCarrierIds(joinIds(request.getCarrierIds()));
batch.setCarrierName(resolveCarrierNames(request.getCarrierIds(), request.getCarrierName()));
List<Long> carrierIds = resolveCarrierIds(request);
batch.setCarrierIds(joinIds(carrierIds));
batch.setCarrierName(resolveCarrierNames(carrierIds, request.getCarrierName()));
batch.setImportStatus(importStatus);
batch.setImportType(IMPORT_TYPE_SETTLEMENT.equals(request.getImportType()) ? IMPORT_TYPE_SETTLEMENT : IMPORT_TYPE_WAYBILL);
ProjectApply project = projectApplyService.getById(request.getProjectId());
@@ -586,6 +606,38 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl<WaybillImport
return Func.toLongList(carrierIds).stream().findFirst().orElse(null);
}
/**
* 解析承运商 ID 列表:优先 carrierIds,其次 carrierId,最后按承运商名称回查客商档案。
*/
private List<Long> resolveCarrierIds(WaybillImportBatchRequest request) {
if (request == null) {
return List.of();
}
List<Long> carrierIds = Func.isEmpty(request.getCarrierIds())
? new ArrayList<>()
: request.getCarrierIds().stream()
.map(this::normalizeOptionalId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toCollection(ArrayList::new));
if (Func.isNotEmpty(carrierIds)) {
return carrierIds;
}
Long carrierId = normalizeOptionalId(request.getCarrierId());
if (carrierId != null) {
return List.of(carrierId);
}
String carrierName = Func.toStr(request.getCarrierName()).trim();
if (Func.isEmpty(carrierName)) {
return List.of();
}
CustomerArchive carrier = customerArchiveService.getOne(Wrappers.<CustomerArchive>lambdaQuery()
.eq(CustomerArchive::getFullName, carrierName)
.eq(CustomerArchive::getIsDeleted, 0)
.last("limit 1"));
return carrier == null || carrier.getId() == null ? List.of() : List.of(carrier.getId());
}
private String resolveCarrierNames(List<Long> carrierIds, String carrierName) {
if (Func.isNotEmpty(carrierName)) return carrierName;
if (Func.isEmpty(carrierIds)) return null;
@@ -1344,21 +1344,15 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
target.setFreightJson(source.getFreightJson());
target.setAttachmentsJson(source.getAttachmentsJson());
target.setRemark(source.getRemark());
// 复制后业务状态与原运单保持一致
String sourceBusinessStatus = source.getBusinessStatus();
target.setBusinessStatus(sourceBusinessStatus);
// 复制新建固定为草稿,不沿用原运单业务状态
target.setBusinessStatus(STATUS_DRAFT);
target.setWaybillNo(nextCode());
clearDriverAcceptRecord(target);
fillProjectProcessConfig(target);
prepare(target);
applyDriverAcceptBusinessStatus(target);
if (Func.isNotEmpty(sourceBusinessStatus)) {
target.setBusinessStatus(sourceBusinessStatus);
}
target.setBusinessStatus(STATUS_DRAFT);
validate(target);
save(target);
// 原运单已完成时,复制后按合同系统计费规则生成应收应付明细
generateReceivablePayableIfCompleted(target);
return detail(target.getId());
}
@@ -0,0 +1,4 @@
-- 平台公司支持多选:ID 逗号分隔,名称顿号分隔
ALTER TABLE `blade_project_apply`
MODIFY COLUMN `undertake_dept_id` varchar(500) DEFAULT NULL COMMENT '平台公司ID,多个逗号分隔',
MODIFY COLUMN `undertake_dept_name` varchar(500) DEFAULT NULL COMMENT '平台公司,多个顿号分隔';
@@ -18,8 +18,8 @@ CREATE TABLE `blade_project_apply` (
`project_type` varchar(50) DEFAULT NULL COMMENT '项目类型',
`business_dept_id` bigint(20) DEFAULT NULL COMMENT '业务部门ID',
`business_dept_name` varchar(100) DEFAULT NULL COMMENT '业务部门',
`undertake_dept_id` bigint(20) DEFAULT NULL COMMENT '承办部门ID',
`undertake_dept_name` varchar(100) DEFAULT NULL COMMENT '承办部门',
`undertake_dept_id` varchar(500) DEFAULT NULL COMMENT '平台公司ID,多个逗号分隔',
`undertake_dept_name` varchar(500) DEFAULT NULL COMMENT '平台公司,多个顿号分隔',
`project_source` varchar(50) DEFAULT NULL COMMENT '项目由来',
`source_remark` varchar(500) DEFAULT NULL COMMENT '项目由来说明',
`fund_limit` decimal(18,2) DEFAULT NULL COMMENT '项目资金使用额度(万元)',
@@ -80,8 +80,8 @@ CREATE TABLE `blade_temporary_credit_limit` (
`project_id` bigint(20) DEFAULT NULL COMMENT '项目ID',
`project_code` varchar(100) DEFAULT NULL COMMENT '项目编号',
`project_name` varchar(100) DEFAULT NULL COMMENT '项目名称',
`undertake_dept_id` bigint(20) DEFAULT NULL COMMENT '承办部门ID',
`undertake_dept_name` varchar(100) DEFAULT NULL COMMENT '承办部门',
`undertake_dept_id` varchar(500) DEFAULT NULL COMMENT '平台公司ID,多个逗号分隔',
`undertake_dept_name` varchar(500) DEFAULT NULL COMMENT '平台公司,多个顿号分隔',
`project_fund_limit` decimal(18,2) DEFAULT NULL COMMENT '项目资金使用额度(万元)',
`used_fund_limit` decimal(18,2) DEFAULT NULL COMMENT '已使用项目资金额度(万元)',
`remaining_fund_limit` decimal(18,2) DEFAULT NULL COMMENT '剩余项目资金使用额度(万元)',