diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/cache/SysCache.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/cache/SysCache.java index 75ce8ed9..70d5d881 100644 --- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/cache/SysCache.java +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/cache/SysCache.java @@ -65,6 +65,7 @@ public class SysCache { private static final String ROLE_NAMES_ID = "roleNames:id:"; private static final String ROLE_ALIAS_ID = "roleAlias:id:"; private static final String ROLE_ALIASES_ID = "roleAliases:id:"; + private static final String BUSINESS_DEPT_CONFIG = "businessDeptConfig:platformDept:"; public static final String TENANT_ID = "tenant:id:"; public static final String TENANT_TENANT_ID = "tenant:tenantId:"; public static final String TENANT_PACKAGE_ID = "tenant:packageId:"; @@ -381,4 +382,21 @@ public class SysCache { }, Boolean.FALSE); } + /** + * 按平台公司 + 业务部门获取业务部门配置 + * + * @param platformCompanyId 平台公司ID + * @param businessDeptId 业务部门ID + * @return 业务部门配置 + */ + public static BusinessDeptConfig getBusinessDeptConfig(Long platformCompanyId, Long businessDeptId) { + if (platformCompanyId == null || businessDeptId == null) { + return null; + } + return CacheUtil.get(SYS_CACHE, BUSINESS_DEPT_CONFIG, platformCompanyId + StringPool.DASH + businessDeptId, () -> { + R result = getSysClient().getBusinessDeptConfig(platformCompanyId, businessDeptId); + return result.getData(); + }); + } + } diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClient.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClient.java index b55af34f..06c2f08a 100644 --- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClient.java +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClient.java @@ -76,6 +76,7 @@ public interface ISysClient { String MEASUREMENT_UNITS = API_PREFIX + "/measurement-units"; String PERMISSIONS = API_PREFIX + "/permissions"; String INVOICE_ITEMS = API_PREFIX + "/invoice-items"; + String BUSINESS_DEPT_CONFIG = API_PREFIX + "/business-dept-config"; /** * 获取菜单 @@ -345,4 +346,15 @@ public interface ISysClient { @GetMapping(INVOICE_ITEMS) R> getInvoiceItems(); + /** + * 按平台公司 + 业务部门获取业务部门配置 + * + * @param platformCompanyId 平台公司ID + * @param businessDeptId 业务部门ID + * @return 业务部门配置 + */ + @GetMapping(BUSINESS_DEPT_CONFIG) + R getBusinessDeptConfig(@RequestParam("platformCompanyId") Long platformCompanyId, + @RequestParam("businessDeptId") Long businessDeptId); + } diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClientFallback.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClientFallback.java index bfb7d123..1579cab4 100644 --- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClientFallback.java +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClientFallback.java @@ -189,4 +189,9 @@ public class ISysClientFallback implements ISysClient { return R.fail("获取数据失败"); } + @Override + public R getBusinessDeptConfig(Long platformCompanyId, Long businessDeptId) { + return R.fail("获取数据失败"); + } + } diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/BusinessDeptConfig.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/BusinessDeptConfig.java new file mode 100644 index 00000000..18d08659 --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/BusinessDeptConfig.java @@ -0,0 +1,87 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.pojo.entity; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springblade.core.tenant.mp.TenantEntity; + +import java.io.Serial; + +/** + * 业务部门配置实体类 + * + * @author Chill + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("blade_business_dept_config") +@Schema(description = "业务部门配置") +public class BusinessDeptConfig extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "平台公司ID") + private Long platformCompanyId; + + @Schema(description = "平台公司名称") + private String platformCompanyName; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "业务部门ID") + private Long businessDeptId; + + @Schema(description = "业务部门名称") + private String businessDeptName; + + @Schema(description = "合同编码前缀") + private String contractCodePrefix; + + @Schema(description = "初始流水号") + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private Integer initialSerial; + + @Schema(description = "金蝶公司代码") + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String kingdeeCompanyCode; + + @Schema(description = "金蝶部门代码") + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String kingdeeDeptCode; + + @Schema(description = "备注") + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String remark; + +} 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 0446ece9..a024a6f8 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 @@ -203,14 +203,18 @@ public class Dept extends TenantEntity { /** * 是否平台公司:0否,1是 + * ALWAYS:保证「否(0)」也能更新落库,避免被字段策略跳过 */ @Schema(description = "是否平台公司:0否,1是") + @TableField(updateStrategy = FieldStrategy.ALWAYS) private Integer isPlatformCompany; /** * 是否业务部门:0否,1是 + * ALWAYS:保证「否(0)」也能更新落库,避免被字段策略跳过 */ @Schema(description = "是否业务部门:0否,1是") + @TableField(updateStrategy = FieldStrategy.ALWAYS) private Integer isBusinessDept; /** diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/BusinessDeptConfigVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/BusinessDeptConfigVO.java new file mode 100644 index 00000000..a81cbdea --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/BusinessDeptConfigVO.java @@ -0,0 +1,57 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.pojo.vo; + +import com.baomidou.mybatisplus.annotation.TableField; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springblade.system.pojo.entity.BusinessDeptConfig; + +import java.io.Serial; + +/** + * 业务部门配置视图实体类 + * + * @author Chill + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Schema(description = "业务部门配置") +public class BusinessDeptConfigVO extends BusinessDeptConfig { + + @Serial + private static final long serialVersionUID = 1L; + + @TableField(exist = false) + @Schema(description = "创建人姓名") + private String createUserName; + + @TableField(exist = false) + @Schema(description = "更新人姓名") + private String updateUserName; + +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleDataScopeVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleDataScopeVO.java index 1656f7ae..78eb5307 100644 --- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleDataScopeVO.java +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleDataScopeVO.java @@ -67,6 +67,18 @@ public class RoleDataScopeVO implements Serializable { return scope; } + /** + * 无组织可见(未配置数据权限时的默认结果) + */ + public static RoleDataScopeVO none() { + RoleDataScopeVO scope = new RoleDataScopeVO(); + scope.setScopeType(RoleDataScopeType.CUSTOM.getCode()); + scope.setAllDept(false); + scope.setOwnCreate(false); + scope.setDeptIds(Collections.emptyList()); + return scope; + } + public static RoleDataScopeVO ownCreate() { RoleDataScopeVO scope = new RoleDataScopeVO(); scope.setScopeType(RoleDataScopeType.OWN_CREATE.getCode()); diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/CertAuditRequest.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/CertAuditRequest.java new file mode 100644 index 00000000..00a69ce8 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/CertAuditRequest.java @@ -0,0 +1,62 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 管理端认证审核请求 + *

+ * 同一字段 body 与 query 都可传,两处都给时以 body 为准。 + * + * @author Chill + */ +@Data +@Schema(description = "管理端认证审核请求") +public class CertAuditRequest implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "认证记录主键") + private Long id; + @Schema(description = "true 通过,false 驳回") + private Boolean pass; + @Schema(description = "驳回原因;驳回时必填,≤200 字") + private String rejectReason; + /** + * 审核员修订后的全量认证内容(JSON);空或空白表示不修订 + *

+ * 通过时才生效:它既是写回档案的依据,也会原样留痕到认证记录上。 + */ + @Schema(description = "审核员修订后的全量认证内容JSON;空表示不修订") + private String auditedSnapshotJson; + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/ExceptionMaintenanceRecordRequest.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/ExceptionMaintenanceRecordRequest.java new file mode 100644 index 00000000..bae3f139 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/ExceptionMaintenanceRecordRequest.java @@ -0,0 +1,90 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 异常处置 → 录入维修记录请求 + *

+ * 车牌号与车船类型不接收前端传值:车牌取自异常处置,车船类型固定「车辆」, + * 避免维修记录挂到与异常无关的车上。 + * + * @author Chill + */ +@Data +@Schema(description = "异常处置录入维修记录请求") +public class ExceptionMaintenanceRecordRequest implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "异常处置ID") + private Long id; + + @Schema(description = "维修时间") + private LocalDateTime maintenanceTime; + + @Schema(description = "维修位置") + private String location; + + @Schema(description = "维修人") + private String maintainer; + + @Schema(description = "维修单位") + private String company; + + @Schema(description = "联系方式") + private String contact; + + @Schema(description = "更换零件") + private String replacedPart; + + @Schema(description = "费用") + private BigDecimal cost; + + @Schema(description = "里程数(公里)") + private BigDecimal mileage; + + @Schema(description = "出厂时间") + private LocalDateTime factoryTime; + + @Schema(description = "详细地址") + private String address; + + @Schema(description = "备注说明") + private String remark; + + @Schema(description = "附件JSON,[{\"name\":\"...\",\"url\":\"...\"}]") + private String attachments; + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractChange.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractChange.java index dacae817..f87dd1c3 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractChange.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractChange.java @@ -103,6 +103,18 @@ public class ContractChange extends TenantEntity { @Schema(description = "乙方") private String partyB; + @Schema(description = "甲方客商明细JSON") + private String partyAJson; + + @Schema(description = "乙方客商明细JSON") + private String partyBJson; + + @Schema(description = "丙方客商明细JSON") + private String partyCJson; + + @Schema(description = "丙方") + private String partyC; + @Schema(description = "开始日期") private LocalDate startDate; 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 ec4d89f9..fb35ad1f 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 @@ -67,6 +67,12 @@ public class ContractManage extends TenantEntity { @Schema(description = "合同负责部门") private String organizationName; + @Schema(description = "平台公司ID") + private Long platformCompanyId; + + @Schema(description = "平台公司") + private String platformCompanyName; + @Schema(description = "合同类别") private String contractCategory; @@ -79,6 +85,18 @@ public class ContractManage extends TenantEntity { @Schema(description = "乙方") private String partyB; + @Schema(description = "甲方客商明细JSON") + private String partyAJson; + + @Schema(description = "乙方客商明细JSON") + private String partyBJson; + + @Schema(description = "丙方客商明细JSON") + private String partyCJson; + + @Schema(description = "丙方") + private String partyC; + @Schema(description = "开始日期") private LocalDate startDate; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlement.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlement.java index 66fbf13d..2a2b36ca 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlement.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlement.java @@ -164,6 +164,14 @@ public class FormalSettlement extends TenantEntity { * 金蝶推送失败原因(仅 failed 态展示;重推成功转 synced 后残留原因不再回显) */ private String kingdeeFailReason; + /** + * 金蝶冲销状态:unwrittenoff-未冲销 writtenoff-已冲销(null=未推送金蝶或未校验) + */ + private String kingdeeWriteOffStatus; + /** + * 金蝶冲销状态确认时间(定时回写/预检回写时间) + */ + private LocalDateTime kingdeeWriteOffTime; /** * 附件JSON */ diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/LoadingManage.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/LoadingManage.java index e037a882..90129764 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/LoadingManage.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/LoadingManage.java @@ -68,6 +68,9 @@ public class LoadingManage extends TenantEntity { @Schema(description = "联系电话") private String driverPhone; + @Schema(description = "司机ID") + private Long driverId; + @Schema(description = "押运人") private String escortName; @@ -83,6 +86,12 @@ public class LoadingManage extends TenantEntity { @Schema(description = "承运商合同ID") private Long carrierContractId; + @Schema(description = "承运客商明细行ID") + private String carrierPartyId; + + @Schema(description = "承运客商角色:B/C") + private String carrierPartyRole; + @Schema(description = "发货地址") private String departureAddress; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/MaintenanceRecord.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/MaintenanceRecord.java index 34d88f10..2d0a6219 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/MaintenanceRecord.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/MaintenanceRecord.java @@ -61,6 +61,12 @@ public class MaintenanceRecord extends TenantEntity { */ @Schema(description = "车牌号/船号") private String vehicleNo; + /** + * 关联异常处置ID(小程序录入时写入) + */ + @Schema(description = "关联异常处置ID(小程序录入时写入)") + @TableField(updateStrategy = FieldStrategy.NEVER) + private Long exceptionId; /** * 维修人 */ diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/TransportCertRecord.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/TransportCertRecord.java index 4f1346f4..bfed3f33 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/TransportCertRecord.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/TransportCertRecord.java @@ -28,6 +28,7 @@ package org.springblade.transport.pojo.entity; import com.baomidou.mybatisplus.annotation.FieldStrategy; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import io.swagger.v3.oas.annotations.media.Schema; @@ -125,6 +126,19 @@ public class TransportCertRecord extends TenantEntity { @TableField(updateStrategy = FieldStrategy.NEVER) @Schema(description = "该次提交的全量认证内容JSON") private String snapshotJson; + /** + * 审核通过时确定的修订版内容(JSON) + *

+ * 审核员可以在通过时直接改内容:司机提交的是 {@code snapshotJson}(永不可改), + * 最终写回档案、司机端读到的「当前生效内容」以本字段优先,为 null 才回退到 + * {@code snapshotJson}。为 null 即「审核员没有修订」。 + *

+ * {@code @JsonIgnore}:司机端的 {@code TransportCertRecordVO} 继承本实体, + * 修订版是审核过程的留痕,不该下发给司机。 + */ + @JsonIgnore + @Schema(description = "审核通过时确定的修订版内容JSON;null 表示未修订") + private String auditedSnapshotJson; /** * 驳回原因,仅当审核状态为已驳回时有值 */ diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Waybill.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Waybill.java index a3157095..bd85768e 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Waybill.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Waybill.java @@ -136,6 +136,12 @@ public class Waybill extends TenantEntity { @Schema(description = "承运商合同ID") private Long carrierContractId; + @Schema(description = "承运客商明细行ID") + private String carrierPartyId; + + @Schema(description = "承运客商角色:B/C") + private String carrierPartyRole; + @Schema(description = "司机ID") private Long driverId; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeBadgesVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeBadgesVO.java index bb6081fb..8b8bf43a 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeBadgesVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeBadgesVO.java @@ -38,7 +38,7 @@ public class AdminHomeBadgesVO implements Serializable { @Serial private static final long serialVersionUID = 1L; - @Schema(description = "异常处置待办数(disposalStatus≠completed)") + @Schema(description = "异常处置待办数(disposalStatus=pending)") private long exception; @Schema(description = "风险记录待办数(disposalStatus=pending)") diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ArchiveManageStatsVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ArchiveManageStatsVO.java new file mode 100644 index 00000000..085673f4 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ArchiveManageStatsVO.java @@ -0,0 +1,47 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 调度端档案统计(司机 + 车辆证件有效期) + */ +@Data +@Schema(description = "调度端档案统计") +public class ArchiveManageStatsVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "司机证件有效期统计") + private DriverExpiryStatVO driver = new DriverExpiryStatVO(); + + @Schema(description = "车辆证件有效期统计") + private TransportVehicleExpiryStatVO vehicle = new TransportVehicleExpiryStatVO(); + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/BusinessLicenseOcrVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/BusinessLicenseOcrVO.java new file mode 100644 index 00000000..2b7452e8 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/BusinessLicenseOcrVO.java @@ -0,0 +1,54 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 营业执照 OCR 识别结果。 + * + * @author Chill + */ +@Data +@Schema(description = "营业执照OCR识别结果") +public class BusinessLicenseOcrVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 按第三方返回 key 聚合的字段,例如 uscc、company_name、legal_name。 + */ + @Schema(description = "识别字段,键为第三方 OCR 的 key") + private Map fields = new LinkedHashMap<>(); + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CertAuditDetailVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CertAuditDetailVO.java index 212eb072..672a7826 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CertAuditDetailVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CertAuditDetailVO.java @@ -81,6 +81,13 @@ public class CertAuditDetailVO implements Serializable { @Schema(description = "待审快照:本次提交的全量内容") private String pendingSnapshotJson; + /** + * 审核员在本次审核中填写的修订版内容;未修订时为 null + *

+ * 只在「通过」时才会落库,驳回不产生生效内容。审核页据此回显审核员改了什么。 + */ + @Schema(description = "本次审核提交的修订版内容;未修订时为 null") + private String auditedSnapshotJson; @Schema(description = "生效快照:该对象当前生效的全量内容;从未认证通过时为 null") private String effectiveSnapshotJson; @Schema(description = "生效快照记录ID;null 表示该对象从未认证通过") diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ContractManageVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ContractManageVO.java index 783fd123..7b1e215b 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ContractManageVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ContractManageVO.java @@ -64,9 +64,13 @@ public class ContractManageVO extends ContractManage { private Boolean hcsSynced; @TableField(exist = false) - @Schema(description = "生效类型筛选") + @Schema(description = "合同状态筛选") private String effectiveType; + @TableField(exist = false) + @Schema(description = "客商(匹配甲/乙/丙方)") + private String partyName; + @TableField(exist = false) @Schema(description = "创建人姓名") private String createUserName; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverVO.java index 5b22c7ac..1072b4be 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverVO.java @@ -48,6 +48,12 @@ public class DriverVO extends Driver { @Serial private static final long serialVersionUID = 1L; + /** + * 搜索关键字:同时模糊匹配姓名与手机号(小程序列表用,管理端不传) + */ + @TableField(exist = false) + @Schema(description = "搜索关键字(姓名 / 手机号)") + private String keyword; /** * 证件有效期状态:within30-30天内到期,expired-已到期 */ diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ExceptionDisposalVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ExceptionDisposalVO.java index d120aa54..b53e854b 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ExceptionDisposalVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ExceptionDisposalVO.java @@ -106,6 +106,10 @@ public class ExceptionDisposalVO extends ExceptionDisposal { @Schema(description = "可调整的路线站点,少于两个站时为空") private List routeNodes; + @TableField(exist = false) + @Schema(description = "关联的维修记录(车辆故障维修异常录入,未录入时为空对象,判定须看 id)") + private MaintenanceRecordVO maintenanceRecord; + /** * 一个可调整的站点:站序声明、地址、该站的装卸标记与项目名 */ diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/LoadingCarrierContractVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/LoadingCarrierContractVO.java index 22a40b87..76417029 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/LoadingCarrierContractVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/LoadingCarrierContractVO.java @@ -55,4 +55,17 @@ public class LoadingCarrierContractVO implements Serializable { @Schema(description = "承运商名称") private String carrierName; + @Schema(description = "承运客商明细行ID") + private String carrierPartyId; + + @Schema(description = "承运客商角色:B/C") + private String carrierPartyRole; + + @Schema(description = "统一信用代码") + private String creditCode; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "客商档案ID") + private Long customerId; + } diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenanceRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenanceRecordVO.java index bfacf9f3..27d8923c 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenanceRecordVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenanceRecordVO.java @@ -76,4 +76,25 @@ public class MaintenanceRecordVO extends MaintenanceRecord { @Schema(description = "更新人姓名") private String updateUserName; + /** + * 关联运单号(由 exception_id 关联异常处置带出) + */ + @TableField(exist = false) + @Schema(description = "关联运单号") + private String waybillNo; + + /** + * 关联异常类型(一级) + */ + @TableField(exist = false) + @Schema(description = "关联异常类型") + private String exceptionType; + + /** + * 关联异常原因(二级) + */ + @TableField(exist = false) + @Schema(description = "关联异常原因") + private String exceptionReason; + } diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MasterOrderCarrierVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MasterOrderCarrierVO.java index 1ce202dd..f040a9ae 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MasterOrderCarrierVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MasterOrderCarrierVO.java @@ -34,4 +34,17 @@ public class MasterOrderCarrierVO implements Serializable { @Schema(description = "承运商名称") private String carrierName; + @Schema(description = "承运客商明细行ID") + private String carrierPartyId; + + @Schema(description = "承运客商角色:B/C") + private String carrierPartyRole; + + @Schema(description = "统一信用代码") + private String creditCode; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "客商档案ID") + private Long customerId; + } diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportCertRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportCertRecordVO.java index c4bf8a40..0983df03 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportCertRecordVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportCertRecordVO.java @@ -96,5 +96,13 @@ public class TransportCertRecordVO extends TransportCertRecord { @TableField(exist = false) @Schema(description = "司机手机号") private String driverMobile; + /** + * 该条记录是否经过审核员修订 + *

+ * 由服务层按 {@code auditedSnapshotJson} 是否有内容算出,前端据此打「已修订」标记。 + */ + @TableField(exist = false) + @Schema(description = "是否经过审核员修订") + private Boolean revised; } diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportVehicleVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportVehicleVO.java index bd4ab362..97106813 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportVehicleVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportVehicleVO.java @@ -48,6 +48,12 @@ public class TransportVehicleVO extends TransportVehicle { @Serial private static final long serialVersionUID = 1L; + /** + * 搜索关键字:同时模糊匹配车牌、VIN 与所有人(小程序列表用,管理端不传) + */ + @TableField(exist = false) + @Schema(description = "搜索关键字(车牌 / VIN / 所有人)") + private String keyword; /** * 证件有效期状态:within30-30天内到期,expired-已到期 */ diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripQueryVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripQueryVO.java index 750dd6d0..912b2b17 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripQueryVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripQueryVO.java @@ -34,8 +34,10 @@ import java.util.Set; /** * 运次查询条件 *

- * 司机端用 {@link #forDriver(String)} 按司机手机号收口;管理端用 {@link #forAdmin()} 不按司机收口。 - * 两者都不给时收口为空集——收口一旦缺省就等于把别人的运次发出去,故缺省不放行。 + * 司机端用 {@link #forDriver(Long)} 按司机档案 ID 收口;管理端用 {@link #forAdmin()} 不按司机收口。 + * 两者都不给时收口为空集。 + *

+ * 归属判据只有司机档案 ID;driver_phone 是派单时的号码快照,不参与判定。 */ @Data @Schema(description = "运次查询条件") @@ -47,8 +49,8 @@ public class TripQueryVO implements Serializable { @Schema(description = "运次主键(详情按它取单个运次)") private String tripId; - @Schema(description = "司机手机号") - private String driverPhone; + @Schema(description = "司机档案 ID(司机端收口判据,空或非正数一律不放行)") + private Long driverId; @Schema(description = "是否不按司机收口(管理端)") private boolean allDrivers; @@ -92,9 +94,9 @@ public class TripQueryVO implements Serializable { @Schema(description = "每页条数(按运次,不是运单)") private Integer size; - public static TripQueryVO forDriver(String driverPhone) { + public static TripQueryVO forDriver(Long driverId) { TripQueryVO query = new TripQueryVO(); - query.setDriverPhone(driverPhone); + query.setDriverId(driverId); return query; } diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/cache/UserCache.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/cache/UserCache.java index b181e1f5..aeb72901 100644 --- a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/cache/UserCache.java +++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/cache/UserCache.java @@ -35,6 +35,9 @@ import org.springblade.core.tool.utils.StringUtil; import org.springblade.system.pojo.entity.User; import org.springblade.system.feign.IUserClient; +import java.util.Collections; +import java.util.List; + import static org.springblade.core.cache.constant.CacheConstant.USER_CACHE; import static org.springblade.core.launch.constant.FlowConstant.TASK_USR_PREFIX; @@ -47,6 +50,7 @@ import static org.springblade.core.launch.constant.FlowConstant.TASK_USR_PREFIX; public class UserCache { private static final String USER_CACHE_ID = "user:id:"; private static final String USER_CACHE_ACCOUNT = "user:account:"; + private static final String USER_CACHE_MANAGE_DEPT = "user:manage-dept:"; private static IUserClient userClient; @@ -89,6 +93,26 @@ public class UserCache { } } + /** + * 获取用户「数据管理权限」组织(含下级展开) + * + * @param userId 用户主键 + * @return 可管理组织 ID 集合 + */ + @SuppressWarnings("unchecked") + public static List getManageDeptIds(Long userId) { + if (userId == null || userId <= 0) { + return Collections.emptyList(); + } + try { + List deptIdList = CacheUtil.get(USER_CACHE, USER_CACHE_MANAGE_DEPT, userId, () -> loadManageDeptIds(userId)); + return deptIdList == null ? Collections.emptyList() : deptIdList; + } catch (RuntimeException exception) { + log.warn("用户数据管理组织缓存读取失败,改为直连查询, userId={}", userId, exception); + return loadManageDeptIds(userId); + } + } + /** * 获取用户姓名 * @@ -153,6 +177,14 @@ public class UserCache { return result.getData(); } + private static List loadManageDeptIds(Long userId) { + R> result = getUserClient().manageDeptIds(userId); + if (result == null || !result.isSuccess() || result.getData() == null) { + return Collections.emptyList(); + } + return result.getData(); + } + private static void clearBrokenUserCache() { CacheUtil.clear(USER_CACHE); CacheUtil.clear(USER_CACHE, Boolean.FALSE); diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/feign/IUserClient.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/feign/IUserClient.java index 50be69e0..534d7437 100644 --- a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/feign/IUserClient.java +++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/feign/IUserClient.java @@ -37,6 +37,8 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; +import java.util.List; + /** * User Feign接口类 * @@ -62,6 +64,7 @@ public interface IUserClient { String REGISTER_USER = API_PREFIX + "/register-user"; String REMOVE_USER = API_PREFIX + "/remove-user"; String BIND_WX_MINI_OPENID = API_PREFIX + "/bind-wx-mini-openid"; + String MANAGE_DEPT_IDS = API_PREFIX + "/manage-dept-ids"; /** * 获取用户信息 @@ -72,6 +75,15 @@ public interface IUserClient { @GetMapping(USER_BY_ID) R userInfoById(@RequestParam("userId") Long userId); + /** + * 解析用户「数据管理权限」组织(含下级) + * + * @param userId 用户主键 + * @return 可管理组织 ID 集合 + */ + @GetMapping(MANAGE_DEPT_IDS) + R> manageDeptIds(@RequestParam("userId") Long userId); + /** * 根据账号获取用户信息 diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/BusinessDeptConfigController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/BusinessDeptConfigController.java new file mode 100644 index 00000000..d351b7e6 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/BusinessDeptConfigController.java @@ -0,0 +1,133 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springblade.core.boot.ctrl.BladeController; +import org.springblade.core.mp.support.Condition; +import org.springblade.core.mp.support.Query; +import org.springblade.core.secure.annotation.PreAuth; +import org.springblade.core.tenant.annotation.NonDS; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; +import org.springblade.system.pojo.entity.BusinessDeptConfig; +import org.springblade.system.pojo.vo.BusinessDeptConfigVO; +import org.springblade.system.service.IBusinessDeptConfigService; +import org.springblade.system.wrapper.BusinessDeptConfigWrapper; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 业务部门配置控制器 + * + * @author Chill + */ +@NonDS +@RestController +@AllArgsConstructor +@PreAuth(menu = "business_dept_config") +@RequestMapping("/business-dept-config") +@Tag(name = "业务部门配置", description = "业务部门配置") +public class BusinessDeptConfigController extends BladeController { + + private final IBusinessDeptConfigService businessDeptConfigService; + + /** + * 详情 + */ + @GetMapping("/detail") + @ApiOperationSupport(order = 1) + @Operation(summary = "详情", description = "传入businessDeptConfig") + public R detail(BusinessDeptConfig businessDeptConfig) { + if (Func.isEmpty(businessDeptConfig.getId())) { + return R.fail("主键不能为空"); + } + BusinessDeptConfig detail = businessDeptConfigService.getById(businessDeptConfig.getId()); + if (Func.isEmpty(detail)) { + return R.fail("业务部门配置不存在"); + } + return R.data(BusinessDeptConfigWrapper.build().entityVO(detail)); + } + + /** + * 分页 + */ + @GetMapping("/list") + @ApiOperationSupport(order = 2) + @Operation(summary = "分页", description = "传入businessDeptConfig") + public R> list(BusinessDeptConfigVO businessDeptConfig, Query query) { + IPage pages = businessDeptConfigService.selectBusinessDeptConfigPage( + Condition.getPage(query), businessDeptConfig); + return R.data(pages); + } + + /** + * 新增或修改 + */ + @PostMapping("/submit") + @ApiOperationSupport(order = 3) + @Operation(summary = "新增或修改", description = "传入businessDeptConfig") + public R submit(@Valid @RequestBody BusinessDeptConfig businessDeptConfig) { + return R.status(businessDeptConfigService.submit(businessDeptConfig)); + } + + /** + * 删除 + */ + @PostMapping("/remove") + @ApiOperationSupport(order = 4) + @Operation(summary = "逻辑删除", description = "传入ids") + public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) { + return R.status(businessDeptConfigService.deleteLogic(Func.toLongList(ids))); + } + + /** + * 按平台公司 + 业务部门查询 + */ + @GetMapping("/by-platform-dept") + @ApiOperationSupport(order = 5) + @Operation(summary = "按平台公司与业务部门查询", description = "传入platformCompanyId、businessDeptId") + public R byPlatformDept( + @Parameter(description = "平台公司ID", required = true) @RequestParam Long platformCompanyId, + @Parameter(description = "业务部门ID", required = true) @RequestParam Long businessDeptId) { + BusinessDeptConfig detail = businessDeptConfigService.getByPlatformAndDept(platformCompanyId, businessDeptId); + if (detail == null) { + return R.data(null); + } + return R.data(BusinessDeptConfigWrapper.build().entityVO(detail)); + } + +} 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 eafb7b30..0c4a3256 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 @@ -95,7 +95,9 @@ public class DeptController extends BladeController { @Parameters({ @Parameter(name = "deptName", description = "组织名称", in = ParameterIn.QUERY, schema = @Schema(type = "string")), @Parameter(name = "shortName", description = "组织简称", in = ParameterIn.QUERY, schema = @Schema(type = "string")), - @Parameter(name = "fullName", description = "部门全称", in = ParameterIn.QUERY, schema = @Schema(type = "string")) + @Parameter(name = "fullName", description = "部门全称", in = ParameterIn.QUERY, schema = @Schema(type = "string")), + @Parameter(name = "isPlatformCompany", description = "是否平台公司", in = ParameterIn.QUERY, schema = @Schema(type = "integer")), + @Parameter(name = "isBusinessDept", description = "是否业务部门", in = ParameterIn.QUERY, schema = @Schema(type = "integer")) }) @ApiOperationSupport(order = 2) @Operation(summary = "列表", description = "传入dept") @@ -111,7 +113,9 @@ public class DeptController extends BladeController { @Parameters({ @Parameter(name = "deptName", description = "组织名称", in = ParameterIn.QUERY, schema = @Schema(type = "string")), @Parameter(name = "shortName", description = "组织简称", in = ParameterIn.QUERY, schema = @Schema(type = "string")), - @Parameter(name = "fullName", description = "部门全称", in = ParameterIn.QUERY, schema = @Schema(type = "string")) + @Parameter(name = "fullName", description = "部门全称", in = ParameterIn.QUERY, schema = @Schema(type = "string")), + @Parameter(name = "isPlatformCompany", description = "是否平台公司", in = ParameterIn.QUERY, schema = @Schema(type = "integer")), + @Parameter(name = "isBusinessDept", description = "是否业务部门", in = ParameterIn.QUERY, schema = @Schema(type = "integer")) }) @ApiOperationSupport(order = 3) @Operation(summary = "懒加载列表", description = "传入dept") @@ -122,8 +126,11 @@ public class DeptController extends BladeController { /** * 获取部门树形结构 + *

+ * 登录即可调用;数据范围由角色「数据权限」中 {@code dept_tree}(树形结构)单独配置, + * 未配置时返回空树(默认不展示)。 */ - @PreAuth(menu = "dept") + @PreAuth(AuthConstant.PERMIT_ALL) @GetMapping("/tree") @ApiOperationSupport(order = 4) @Operation(summary = "树形结构", description = "树形结构") @@ -135,8 +142,9 @@ public class DeptController extends BladeController { * 懒加载获取部门树形结构 *

* tenantId 由 Service 层完整决策:超管入参为空表示查全部,非超管强制覆盖为会话租户。 + * 数据范围与 {@link #tree} 相同,由 {@code dept_tree} 单独配置,未配置时返回空树。 */ - @PreAuth(menu = "dept") + @PreAuth(AuthConstant.PERMIT_ALL) @GetMapping("/lazy-tree") @ApiOperationSupport(order = 5) @Operation(summary = "懒加载树形结构", description = "树形结构") diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java b/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java index 2dabbf04..e1167b0e 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java @@ -70,14 +70,14 @@ public interface UserConvert { @Mapping(source = "lastname", target = "realName") @Mapping(source = "mobile", target = "phone") @Mapping(source = "email", target = "email") - @Mapping(source = "loginid", target = "oaLoginId") + @Mapping(target = "oaLoginId", ignore = true) @Mapping(source = "modified", target = "oaModified") User baseConvert(OAPersonResponse person); @Mapping(source = "subcompanyname", target = "companyName") @Mapping(source = "departmentname", target = "deptName") @Mapping(source = "id", target = "oaId") - @Mapping(source = "loginid", target = "oaLoginId") + @Mapping(target = "oaLoginId", ignore = true) UserDept userDeptConvert(OAPersonResponse person); /** @@ -88,6 +88,7 @@ public interface UserConvert { */ default User person2user(OAPersonResponse person, String defaultPassword) { User user = baseConvert(person); + user.setOaLoginId(resolveOaLoginId(person)); user.setAccount(resolveAccount(person)); // OA 可能返回多号(如 133.../137...),只保留首个号码再入库加密 user.setPhone(normalizeMobile(user.getPhone())); @@ -106,6 +107,22 @@ public interface UserConvert { return user; } + /** + * 解析 OA 登录名:优先工号 workcode,其次 loginid + */ + default String resolveOaLoginId(OAPersonResponse person) { + if (person == null) { + return null; + } + if (StringUtils.isNotBlank(person.getWorkcode())) { + return person.getWorkcode().trim(); + } + if (StringUtils.isNotBlank(person.getLoginid())) { + return person.getLoginid().trim(); + } + return null; + } + /** * 规范化手机号:去空白,多号时取第一个 */ @@ -124,7 +141,7 @@ public interface UserConvert { } /** - * 解析本系统登录账号:优先 OA loginid,其次工号,最后手机号 + * 解析本系统登录账号:优先工号 workcode,其次 OA loginid,最后手机号 * * @param person OA人员 * @return 账号,无法识别时返回 null @@ -133,12 +150,12 @@ public interface UserConvert { if (person == null) { return null; } - if (StringUtils.isNotBlank(person.getLoginid())) { - return person.getLoginid().trim(); - } if (StringUtils.isNotBlank(person.getWorkcode())) { return person.getWorkcode().trim(); } + if (StringUtils.isNotBlank(person.getLoginid())) { + return person.getLoginid().trim(); + } if (StringUtils.isNotBlank(person.getMobile())) { return person.getMobile().trim(); } @@ -148,7 +165,7 @@ public interface UserConvert { /** * oa人员转本系统用户部门 * @param person - * @param userMap OA登录名与用户id + * @param userMap 同步键(W:工号)与用户id * @return */ default UserDept person2userDept(OAPersonResponse person, Map userMap) { @@ -162,9 +179,11 @@ public interface UserConvert { userDept.setStatus(status); // 排序 userDept.setSort(OAUtils.parseInt(person.getDsporder())); - // 用户id,按 OA 登录名关联本批已新增或更新的用户 - String oaLoginId = person == null ? null : StringUtils.trimToNull(person.getLoginid()); - userDept.setUserId(oaLoginId == null ? null : userMap.get(oaLoginId)); + // 用户id,按工号同步键关联本批已新增或更新的用户 + String oaLoginId = resolveOaLoginId(person); + userDept.setOaLoginId(oaLoginId); + String workcode = person == null ? null : StringUtils.trimToNull(person.getWorkcode()); + userDept.setUserId(workcode == null ? null : userMap.get("W:" + workcode)); userDept.setSyncTime(new Date()); return userDept; } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/feign/SysClient.java b/blade-service/blade-system/src/main/java/org/springblade/system/feign/SysClient.java index a4cf9718..40900821 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/feign/SysClient.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/feign/SysClient.java @@ -69,6 +69,7 @@ public class SysClient implements ISysClient { private final ICargoTypeService cargoTypeService; private final IMeasurementUnitService measurementUnitService; private final IInvoiceItemService invoiceItemService; + private final IBusinessDeptConfigService businessDeptConfigService; @Override @GetMapping(MENU) @@ -257,4 +258,10 @@ public class SysClient implements ISysClient { .orderByAsc(InvoiceItem::getCategoryName, InvoiceItem::getShortName))); } + @Override + @GetMapping(BUSINESS_DEPT_CONFIG) + public R getBusinessDeptConfig(Long platformCompanyId, Long businessDeptId) { + return R.data(businessDeptConfigService.getByPlatformAndDept(platformCompanyId, businessDeptId)); + } + } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/feign/UserClient.java b/blade-service/blade-system/src/main/java/org/springblade/system/feign/UserClient.java index f25da255..1a0cfc9b 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/feign/UserClient.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/feign/UserClient.java @@ -38,9 +38,12 @@ import org.springblade.system.service.IUserService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Hidden; +import java.util.List; + /** * 用户服务Feign实现类 * @@ -60,6 +63,12 @@ public class UserClient implements IUserClient { return R.data(service.getById(userId)); } + @Override + @GetMapping(MANAGE_DEPT_IDS) + public R> manageDeptIds(@RequestParam("userId") Long userId) { + return R.data(service.resolveManageDeptIds(userId)); + } + @Override @GetMapping(USER_BY_ACCOUNT) public R userByAccount(String tenantId, String account) { diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/BusinessDeptConfigMapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/BusinessDeptConfigMapper.java new file mode 100644 index 00000000..5c66af76 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/BusinessDeptConfigMapper.java @@ -0,0 +1,53 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import org.apache.ibatis.annotations.Param; +import org.springblade.system.pojo.entity.BusinessDeptConfig; +import org.springblade.system.pojo.vo.BusinessDeptConfigVO; + +import java.util.List; + +/** + * 业务部门配置 Mapper 接口 + * + * @author Chill + */ +public interface BusinessDeptConfigMapper extends BaseMapper { + + /** + * 自定义分页 + * + * @param page 分页 + * @param businessDeptConfig 查询条件 + * @return 分页数据 + */ + List selectBusinessDeptConfigPage(IPage page, + @Param("businessDeptConfig") BusinessDeptConfigVO businessDeptConfig); + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/BusinessDeptConfigMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/BusinessDeptConfigMapper.xml new file mode 100644 index 00000000..a954b418 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/BusinessDeptConfigMapper.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 1f38b0fd..c1242385 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 @@ -90,6 +90,12 @@ and dept.full_name like concat(concat('%', #{param3.fullName}),'%') + + and dept.is_platform_company = #{param3.isPlatformCompany} + + + and dept.is_business_dept = #{param3.isBusinessDept} + ORDER BY dept.sort diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IBusinessDeptConfigService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IBusinessDeptConfigService.java new file mode 100644 index 00000000..2d305adf --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IBusinessDeptConfigService.java @@ -0,0 +1,66 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import org.springblade.core.mp.base.BaseService; +import org.springblade.system.pojo.entity.BusinessDeptConfig; +import org.springblade.system.pojo.vo.BusinessDeptConfigVO; + +/** + * 业务部门配置服务类 + * + * @author Chill + */ +public interface IBusinessDeptConfigService extends BaseService { + + /** + * 自定义分页 + * + * @param page 分页 + * @param businessDeptConfig 查询条件 + * @return 分页数据 + */ + IPage selectBusinessDeptConfigPage(IPage page, BusinessDeptConfigVO businessDeptConfig); + + /** + * 新增或修改 + * + * @param businessDeptConfig 配置 + * @return 是否成功 + */ + boolean submit(BusinessDeptConfig businessDeptConfig); + + /** + * 按平台公司 + 业务部门查询配置 + * + * @param platformCompanyId 平台公司ID + * @param businessDeptId 业务部门ID + * @return 配置,不存在返回 null + */ + BusinessDeptConfig getByPlatformAndDept(Long platformCompanyId, Long businessDeptId); + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IRoleMenuDataScopeService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IRoleMenuDataScopeService.java index 93cb33d2..17349a22 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IRoleMenuDataScopeService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IRoleMenuDataScopeService.java @@ -66,6 +66,16 @@ public interface IRoleMenuDataScopeService extends BaseService roleIds, String menuCode, List userDeptIds); + /** + * 按菜单编码解析角色数据权限;菜单不存在或未配置时返回空权限(不回落为全部组织) + * + * @param roleIds 角色ID集合 + * @param menuCode 菜单编码 + * @param userDeptIds 用户所属组织 + * @return 解析结果 + */ + RoleDataScopeVO resolveByMenuCodeOrEmpty(List roleIds, String menuCode, List userDeptIds); + /** * 合并用户全部角色菜单配置为最宽数据权限 * diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java index fedecc77..e4160480 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java @@ -257,6 +257,14 @@ public interface IUserService extends BaseService { */ boolean removeUser(String userIds); + /** + * 解析用户「数据管理权限」组织(含下级展开) + * + * @param userId 用户主键 + * @return 可管理组织 ID 集合;无配置时返回空列表 + */ + List resolveManageDeptIds(Long userId); + /** * 导入用户数据(仅超管 + Excel 显式指定时允许跨租户,否则归属当前会话租户) * diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/BusinessDeptConfigServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/BusinessDeptConfigServiceImpl.java new file mode 100644 index 00000000..6c520b2e --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/BusinessDeptConfigServiceImpl.java @@ -0,0 +1,208 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.service.impl; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import org.springblade.core.cache.utils.CacheUtil; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.mp.base.BaseServiceImpl; +import org.springblade.core.tool.utils.Func; +import org.springblade.system.mapper.BusinessDeptConfigMapper; +import org.springblade.system.pojo.entity.BusinessDeptConfig; +import org.springblade.system.pojo.entity.Dept; +import org.springblade.system.pojo.vo.BusinessDeptConfigVO; +import org.springblade.system.service.IBusinessDeptConfigService; +import org.springblade.system.service.IDeptService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; + +import java.util.Objects; + +import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE; + +/** + * 业务部门配置服务实现类 + * + * @author Chill + */ +@Service +@RequiredArgsConstructor +public class BusinessDeptConfigServiceImpl extends BaseServiceImpl + implements IBusinessDeptConfigService { + + private static final int PREFIX_MAX_LENGTH = 64; + private static final int CODE_MAX_LENGTH = 64; + private static final int NAME_MAX_LENGTH = 100; + private static final int REMARK_MAX_LENGTH = 200; + private static final int SERIAL_MAX = 999; + + private final IDeptService deptService; + + @Override + public IPage selectBusinessDeptConfigPage(IPage page, + BusinessDeptConfigVO businessDeptConfig) { + return page.setRecords(baseMapper.selectBusinessDeptConfigPage(page, businessDeptConfig)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean submit(BusinessDeptConfig businessDeptConfig) { + prepare(businessDeptConfig); + validate(businessDeptConfig); + boolean success = saveOrUpdate(businessDeptConfig); + if (success) { + CacheUtil.clear(SYS_CACHE); + CacheUtil.clear(SYS_CACHE, Boolean.FALSE); + } + return success; + } + + @Override + public BusinessDeptConfig getByPlatformAndDept(Long platformCompanyId, Long businessDeptId) { + if (Func.isEmpty(platformCompanyId) || Func.isEmpty(businessDeptId)) { + return null; + } + return getOne(Wrappers.lambdaQuery() + .eq(BusinessDeptConfig::getPlatformCompanyId, platformCompanyId) + .eq(BusinessDeptConfig::getBusinessDeptId, businessDeptId) + .eq(BusinessDeptConfig::getIsDeleted, 0) + .last("LIMIT 1"), false); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean deleteLogic(java.util.List ids) { + boolean success = super.deleteLogic(ids); + if (success) { + CacheUtil.clear(SYS_CACHE); + CacheUtil.clear(SYS_CACHE, Boolean.FALSE); + } + return success; + } + + private void prepare(BusinessDeptConfig businessDeptConfig) { + Dept platformCompany = requirePlatformCompany(businessDeptConfig.getPlatformCompanyId()); + Dept businessDept = requireBusinessDept(businessDeptConfig.getBusinessDeptId()); + businessDeptConfig.setPlatformCompanyName(platformCompany.getDeptName()); + businessDeptConfig.setBusinessDeptName(businessDept.getDeptName()); + businessDeptConfig.setContractCodePrefix(trimToEmpty(businessDeptConfig.getContractCodePrefix())); + businessDeptConfig.setKingdeeDeptCode(trimToNull(businessDeptConfig.getKingdeeDeptCode())); + businessDeptConfig.setRemark(trimToNull(businessDeptConfig.getRemark())); + // 金蝶公司代码始终关联平台公司组织中配置的金蝶代码 + businessDeptConfig.setKingdeeCompanyCode(trimToNull(platformCompany.getKingdeeDeptCode())); + if (Func.isEmpty(businessDeptConfig.getStatus())) { + businessDeptConfig.setStatus(1); + } + } + + private void validate(BusinessDeptConfig businessDeptConfig) { + if (Func.isEmpty(businessDeptConfig.getPlatformCompanyId())) { + throw new ServiceException("请选择平台公司"); + } + if (Func.isEmpty(businessDeptConfig.getBusinessDeptId())) { + throw new ServiceException("请选择业务部门"); + } + if (Func.isEmpty(businessDeptConfig.getContractCodePrefix())) { + throw new ServiceException("请输入合同编码前缀"); + } + if (businessDeptConfig.getContractCodePrefix().length() > PREFIX_MAX_LENGTH) { + throw new ServiceException("合同编码前缀不能超过64字"); + } + if (Func.isNotEmpty(businessDeptConfig.getPlatformCompanyName()) + && businessDeptConfig.getPlatformCompanyName().length() > NAME_MAX_LENGTH) { + throw new ServiceException("平台公司名称不能超过100字"); + } + if (Func.isNotEmpty(businessDeptConfig.getBusinessDeptName()) + && businessDeptConfig.getBusinessDeptName().length() > NAME_MAX_LENGTH) { + throw new ServiceException("业务部门名称不能超过100字"); + } + if (Func.isNotEmpty(businessDeptConfig.getKingdeeCompanyCode()) + && businessDeptConfig.getKingdeeCompanyCode().length() > CODE_MAX_LENGTH) { + throw new ServiceException("金蝶公司代码不能超过64字"); + } + if (Func.isNotEmpty(businessDeptConfig.getKingdeeDeptCode()) + && businessDeptConfig.getKingdeeDeptCode().length() > CODE_MAX_LENGTH) { + throw new ServiceException("金蝶部门代码不能超过64字"); + } + if (Func.isNotEmpty(businessDeptConfig.getRemark()) + && businessDeptConfig.getRemark().length() > REMARK_MAX_LENGTH) { + throw new ServiceException("备注不能超过200字"); + } + Integer initialSerial = businessDeptConfig.getInitialSerial(); + if (initialSerial != null && (initialSerial < 1 || initialSerial > SERIAL_MAX)) { + throw new ServiceException("初始流水号必须在1到999之间"); + } + boolean duplicated = count(Wrappers.lambdaQuery() + .eq(BusinessDeptConfig::getPlatformCompanyId, businessDeptConfig.getPlatformCompanyId()) + .eq(BusinessDeptConfig::getBusinessDeptId, businessDeptConfig.getBusinessDeptId()) + .eq(BusinessDeptConfig::getIsDeleted, 0) + .ne(Func.isNotEmpty(businessDeptConfig.getId()), BusinessDeptConfig::getId, businessDeptConfig.getId())) > 0; + if (duplicated) { + throw new ServiceException("该平台公司与业务部门的配置已存在,不能重复添加"); + } + } + + private Dept requirePlatformCompany(Long platformCompanyId) { + if (Func.isEmpty(platformCompanyId)) { + throw new ServiceException("请选择平台公司"); + } + Dept dept = deptService.getById(platformCompanyId); + if (dept == null || Objects.equals(dept.getIsDeleted(), 1)) { + throw new ServiceException("平台公司不存在"); + } + if (!Objects.equals(dept.getIsPlatformCompany(), 1)) { + throw new ServiceException("所选组织不是平台公司"); + } + return dept; + } + + private Dept requireBusinessDept(Long businessDeptId) { + if (Func.isEmpty(businessDeptId)) { + throw new ServiceException("请选择业务部门"); + } + Dept dept = deptService.getById(businessDeptId); + if (dept == null || Objects.equals(dept.getIsDeleted(), 1)) { + throw new ServiceException("业务部门不存在"); + } + if (!Objects.equals(dept.getIsBusinessDept(), 1)) { + throw new ServiceException("所选组织不是业务部门"); + } + return dept; + } + + private String trimToEmpty(String value) { + return value == null ? "" : value.trim(); + } + + private String trimToNull(String value) { + String text = trimToEmpty(value); + return text.isEmpty() ? null : text; + } + +} 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 a211a169..2b2f485e 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 @@ -224,13 +224,18 @@ public class DeptServiceImpl extends ServiceImpl implements ID .toList(); } + /** + * 组织树专用数据权限菜单编码(角色「数据权限」中单独配置;未配置时组织树默认不展示)。 + */ + public static final String MENU_DEPT_TREE = "dept_tree"; + /** * 解析当前登录用户的组织树数据权限。 *

- * 超管或角色菜单数据权限最宽合并为「全部组织」时不过滤; - * 其余按角色配置的组织并集过滤。仅「本人创建」时按用户所属组织及下级展示组织树。 + * 仅读取 {@link #MENU_DEPT_TREE}「树形结构」菜单的数据权限配置,不再合并其他业务菜单; + * 超管不过滤;未配置该菜单数据权限时返回空树(默认不显示)。 * - * @return 过滤上下文;无法识别用户时按空权限处理 + * @return 过滤上下文;无法识别用户或未配置时按空权限处理 */ private DeptDataScopeFilter resolveDeptDataScopeFilter() { if (AuthUtil.isAdministrator()) { @@ -246,8 +251,11 @@ public class DeptServiceImpl extends ServiceImpl implements ID if (Func.isNotEmpty(user) && Func.isNotEmpty(user.getDeptId())) { userDeptIds = Func.toLongList(user.getDeptId()); } - RoleDataScopeVO scope = roleMenuDataScopeService.resolveWidest(roleIds, userDeptIds); - if (scope == null || scope.isAllDept()) { + RoleDataScopeVO scope = roleMenuDataScopeService.resolveByMenuCodeOrEmpty(roleIds, MENU_DEPT_TREE, userDeptIds); + if (scope == null) { + return DeptDataScopeFilter.empty(); + } + if (scope.isAllDept()) { return DeptDataScopeFilter.all(); } LinkedHashSet allowedDeptIds = new LinkedHashSet<>(); @@ -665,12 +673,9 @@ public class DeptServiceImpl extends ServiceImpl implements ID dept.setAncestors(parent.getAncestors() + StringPool.COMMA + dept.getParentId()); } dept.setIsDeleted(BladeConstant.DB_NOT_DELETED); - if (dept.getIsPlatformCompany() == null) { - dept.setIsPlatformCompany(0); - } - if (dept.getIsBusinessDept() == null) { - dept.setIsBusinessDept(0); - } + // 统一规范为 0/1,避免前端传空串、布尔或其它类型导致更新未生效 + dept.setIsPlatformCompany(normalizeYesNoFlag(dept.getIsPlatformCompany())); + dept.setIsBusinessDept(normalizeYesNoFlag(dept.getIsBusinessDept())); // 排序未填时默认 1 if (dept.getSort() == null) { dept.setSort(1); @@ -679,9 +684,24 @@ public class DeptServiceImpl extends ServiceImpl implements ID throw new ServiceException("租户ID不能为空"); } validateDeptCategory(dept); - validateDeptCode(dept, parent); + validateDeptCode(dept); validateContractInitialSerial(dept); - return saveOrUpdate(dept); + boolean saved = saveOrUpdate(dept); + if (saved && Func.isNotEmpty(dept.getId())) { + // 显式回写两个业务标志,避免部分更新路径漏改 + this.update(Wrappers.lambdaUpdate() + .set(Dept::getIsPlatformCompany, dept.getIsPlatformCompany()) + .set(Dept::getIsBusinessDept, dept.getIsBusinessDept()) + .eq(Dept::getId, dept.getId())); + } + return saved; + } + + /** + * 将是否类标志规范为 0/1;空值按 0 处理。 + */ + private Integer normalizeYesNoFlag(Integer flag) { + return Integer.valueOf(1).equals(flag) ? 1 : 0; } @Override @@ -881,9 +901,9 @@ public class DeptServiceImpl extends ServiceImpl implements ID } } - private void validateDeptCode(Dept dept, Dept parent) { + private void validateDeptCode(Dept dept) { String deptCode = dept.getDeptCode(); - // 组织编码非必填,为空时存 null,避免唯一索引冲突 + // 组织编码非必填,为空时存 null,避免唯一索引冲突;不再校验 GWWL/上级编码+三位数字格式 if (StringUtil.isBlank(deptCode)) { dept.setDeptCode(null); return; @@ -892,26 +912,6 @@ public class DeptServiceImpl extends ServiceImpl implements ID if (deptCode.length() > 30) { throw new ServiceException("组织编码不能超过30个字符"); } - // 顶级组织不依赖上级编码 - if (parent == null) { - this.ensureDeptCodeUnique(dept, deptCode); - dept.setDeptCode(deptCode); - return; - } - if (StringUtil.isBlank(parent.getDeptCode())) { - throw new ServiceException("请选择已配置组织编码的上级组织"); - } - // 公司:GWWL+三位数字;部门:所属公司编码+三位数字;其他:上级编码+三位数字 - String expectedParentCode = parent.getDeptCode(); - if (DeptCategory.COMPANY.getCode().equals(dept.getDeptCategory())) { - expectedParentCode = OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE; - } else if (DeptCategory.DEPT.getCode().equals(dept.getDeptCategory()) - && StringUtil.isNotBlank(dept.getBelongCompanyCode())) { - expectedParentCode = dept.getBelongCompanyCode(); - } - if (!org.springblade.system.util.DeptCodeUtil.matchesChildCode(expectedParentCode, deptCode)) { - throw new ServiceException("组织编码格式应为:" + expectedParentCode + "三位数字(不足补零,中间不加-)"); - } this.ensureDeptCodeUnique(dept, deptCode); dept.setDeptCode(deptCode); } 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 21d62c4a..3d40bc8e 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 @@ -278,9 +278,7 @@ public class OASyncServiceImpl implements IOASyncService { OaUserListSyncHelper.OaOrgIndex orgIndex = oaUserListSyncHelper.syncOrgsFromPersons(oaPersons); OaUserListSyncHelper.PersonSyncCount personSyncCount = oaUserListSyncHelper.handlePerson(oaPersons, orgIndex); if (startTime == null) { - oaUserListSyncHelper.removeOaUsersAbsentFrom( - oaUserListSyncHelper.extractOaLoginIds(oaPersons), - oaUserListSyncHelper.extractWorkcodes(oaPersons)); + oaUserListSyncHelper.removeOaUsersAbsentFrom(oaUserListSyncHelper.extractWorkcodes(oaPersons)); } CacheUtil.clear(USER_CACHE); CacheUtil.clear(SYS_CACHE); @@ -300,7 +298,7 @@ public class OASyncServiceImpl implements IOASyncService { private OaPersonSyncPageVO syncPersonFromOaPage(int current, int size, boolean incremental, String modifiedSince) { int pageNo = current < 1 ? 1 : current; int pageSize = size < 1 ? 20 : Math.min(size, 200); - // 全量同步首页开启原始数据收集,并开始累积登录名,最后一页再删除本地多余人员 + // 全量同步首页开启原始数据收集,并开始累积工号,最后一页再删除本地多余人员 if (!incremental && pageNo == 1) { oaSyncRawDumpHelper.beginFullDump(); oaUserListSyncHelper.beginFullSyncReconcile(); diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java index d4c89dc2..a99147a6 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java @@ -87,10 +87,8 @@ public class OaUserListSyncHelper { private static final String OA_PERSON_STATUS_RESIGNED = "5"; /** - * 全量同步跨页累积的 OA 登录名。分页接口每页独立事务,删除必须等最后一页再执行。 + * 全量同步跨页累积的 OA 工号。分页接口每页独立事务,删除必须等最后一页再执行。 */ - private final Set fullSyncLoginIds = ConcurrentHashMap.newKeySet(); - private final Set fullSyncWorkcodes = ConcurrentHashMap.newKeySet(); private volatile boolean fullSyncCollecting; @@ -409,16 +407,15 @@ public class OaUserListSyncHelper { } /** - * 全量同步从第一页开始收集 OA 登录名,最后一页再删除本地多余的 is_oa=2 人员。 + * 全量同步从第一页开始收集 OA 工号,最后一页再删除本地多余的 is_oa=2 人员。 */ public synchronized void beginFullSyncReconcile() { - this.fullSyncLoginIds.clear(); this.fullSyncWorkcodes.clear(); this.fullSyncCollecting = true; } /** - * 累积本页 OA 人员登录名,供全量同步结束后比对删除。 + * 累积本页 OA 人员工号,供全量同步结束后比对删除。 * * @param oaPersons 本页 OA 人员 */ @@ -426,49 +423,26 @@ public class OaUserListSyncHelper { if (!this.fullSyncCollecting || CollectionUtil.isEmpty(oaPersons)) { return; } - this.fullSyncLoginIds.addAll(this.extractOaLoginIds(oaPersons)); this.fullSyncWorkcodes.addAll(this.extractWorkcodes(oaPersons)); } /** - * 全量同步结束后,删除登录名不在本次 OA 数据中的 is_oa=2 人员。 + * 全量同步结束后,删除工号不在本次 OA 数据中的 is_oa=2 人员。 * - * @return 删除人数;未开始收集或登录名集合为空时返回 0,避免误删全部 OA 人员 + * @return 删除人数;未开始收集或工号集合为空时返回 0,避免误删全部 OA 人员 */ public synchronized int finishFullSyncReconcile() { if (!this.fullSyncCollecting) { return 0; } this.fullSyncCollecting = false; - Set oaLoginIds = new LinkedHashSet<>(this.fullSyncLoginIds); Set workcodes = new LinkedHashSet<>(this.fullSyncWorkcodes); - this.fullSyncLoginIds.clear(); this.fullSyncWorkcodes.clear(); - return this.removeOaUsersAbsentFrom(oaLoginIds, workcodes); + return this.removeOaUsersAbsentFrom(workcodes); } /** - * 提取仍在职的 OA 人员登录名。离职人员不计入,全量同步结束时会删掉对应本地账号。 - * - * @param oaPersons OA人员 - * @return 去重后的登录名 - */ - public Set extractOaLoginIds(List oaPersons) { - Set oaLoginIds = new LinkedHashSet<>(); - if (CollectionUtil.isEmpty(oaPersons)) { - return oaLoginIds; - } - for (OAPersonResponse oaPerson : oaPersons) { - String oaLoginId = this.resolveOaLoginId(oaPerson); - if (oaLoginId != null && !this.isOaResigned(oaPerson)) { - oaLoginIds.add(oaLoginId); - } - } - return oaLoginIds; - } - - /** - * 提取仍在职人员的工号。没有登录名时靠工号保留本地账号。 + * 提取仍在职人员的工号。离职人员不计入,全量同步结束时会删掉对应本地账号。 * * @param oaPersons OA人员 * @return 去重后的工号 @@ -488,25 +462,21 @@ public class OaUserListSyncHelper { } /** - * 删除当前租户中 is_oa=2、且既不在本次登录名里、也不是无登录名工号人员的账号。 - * 登录名和工号都没收集到时不删除。当前登录账号即使不在同步数据中也保留。 + * 删除当前租户中 is_oa=2、且工号不在本次同步工号集合中的账号。 + * 未收集到工号时不删除。当前登录账号即使不在同步数据中也保留。 * - * @param oaLoginIds 本次全量同步出现过的 OA 登录名 * @param workcodes 本次全量同步出现过的工号 * @return 删除人数 */ - public int removeOaUsersAbsentFrom(Set oaLoginIds, Set workcodes) { - boolean noLoginIds = CollectionUtil.isEmpty(oaLoginIds); - boolean noWorkcodes = CollectionUtil.isEmpty(workcodes); - if (noLoginIds && noWorkcodes) { - ComposeLogUtil.getLastLog().warn("全量同步未收集到OA登录名和工号,跳过删除本地 is_oa=2 人员"); + public int removeOaUsersAbsentFrom(Set workcodes) { + if (CollectionUtil.isEmpty(workcodes)) { + ComposeLogUtil.getLastLog().warn("全量同步未收集到OA工号,跳过删除本地 is_oa=2 人员"); return 0; } - Set loginIdSet = noLoginIds ? Set.of() : oaLoginIds; - Set workcodeSet = noWorkcodes ? Set.of() : workcodes; + Set workcodeSet = workcodes; String tenantId = this.resolveTenantId(); List oaUsers = this.userService.list(Wrappers.lambdaQuery() - .select(User::getId, User::getOaLoginId, User::getCode) + .select(User::getId, User::getCode) .eq(User::getTenantId, tenantId) .eq(User::getIsOa, CommonConstant.YES)); Long currentUserId = AuthUtil.getUserId(); @@ -519,11 +489,8 @@ public class OaUserListSyncHelper { ComposeLogUtil.getLastLog().warn("全量同步跳过删除当前登录账号 userId={}", oaUser.getId()); continue; } - String oaLoginId = StringUtils.trimToNull(oaUser.getOaLoginId()); String workcode = StringUtils.trimToNull(oaUser.getCode()); - boolean keptByLoginId = oaLoginId != null && loginIdSet.contains(oaLoginId); - boolean keptByWorkcode = oaLoginId == null && workcode != null && workcodeSet.contains(workcode); - if (!keptByLoginId && !keptByWorkcode) { + if (workcode == null || !workcodeSet.contains(workcode)) { removeIds.add(oaUser.getId()); } } @@ -535,54 +502,13 @@ public class OaUserListSyncHelper { } /** - * 按 OA 登录名删除已有 is_oa=2 人员。用于离职人员,全量和增量同步都会执行。 - * - * @param tenantId 租户 - * @param oaLoginIds 离职人员登录名 - * @return 删除人数 - */ - private int removeOaUsersByLoginIds(String tenantId, Collection oaLoginIds) { - if (CollectionUtil.isEmpty(oaLoginIds)) { - return 0; - } - List oaLoginIdList = oaLoginIds.stream() - .filter(StringUtils::isNotBlank) - .distinct() - .toList(); - if (oaLoginIdList.isEmpty()) { - return 0; - } - Long currentUserId = AuthUtil.getUserId(); - List removeIds = new ArrayList<>(); - for (int fromIndex = 0; fromIndex < oaLoginIdList.size(); fromIndex += OA_LOGIN_ID_BATCH_SIZE) { - List oaLoginIdBatch = oaLoginIdList.subList(fromIndex, Math.min(fromIndex + OA_LOGIN_ID_BATCH_SIZE, oaLoginIdList.size())); - this.userService.list(Wrappers.lambdaQuery() - .select(User::getId, User::getOaLoginId) - .eq(User::getTenantId, tenantId) - .eq(User::getIsOa, CommonConstant.YES) - .in(User::getOaLoginId, oaLoginIdBatch)) - .forEach(oaUser -> { - if (oaUser.getId() == null) { - return; - } - if (currentUserId != null && currentUserId > 0 && currentUserId.equals(oaUser.getId())) { - ComposeLogUtil.getLastLog().warn("同步跳过删除当前登录账号 userId={}", oaUser.getId()); - return; - } - removeIds.add(oaUser.getId()); - }); - } - return this.deleteOaUsers(removeIds, "同步删除OA离职人员"); - } - - /** - * 删除没有登录名、且工号属于离职人员的 is_oa=2 账号。 + * 按工号删除已有 is_oa=2 人员。用于离职人员,全量和增量同步都会执行。 * * @param tenantId 租户 * @param workcodes 离职人员工号 * @return 删除人数 */ - private int removeOaUsersByBlankLoginWorkcodes(String tenantId, Collection workcodes) { + private int removeOaUsersByWorkcodes(String tenantId, Collection workcodes) { if (CollectionUtil.isEmpty(workcodes)) { return 0; } @@ -598,11 +524,10 @@ public class OaUserListSyncHelper { for (int fromIndex = 0; fromIndex < workcodeList.size(); fromIndex += OA_LOGIN_ID_BATCH_SIZE) { List workcodeBatch = workcodeList.subList(fromIndex, Math.min(fromIndex + OA_LOGIN_ID_BATCH_SIZE, workcodeList.size())); this.userService.list(Wrappers.lambdaQuery() - .select(User::getId, User::getOaLoginId, User::getCode) + .select(User::getId, User::getCode) .eq(User::getTenantId, tenantId) .eq(User::getIsOa, CommonConstant.YES) - .in(User::getCode, workcodeBatch) - .and(wrapper -> wrapper.isNull(User::getOaLoginId).or().eq(User::getOaLoginId, ""))) + .in(User::getCode, workcodeBatch)) .forEach(oaUser -> { if (oaUser.getId() == null) { return; @@ -614,7 +539,7 @@ public class OaUserListSyncHelper { removeIds.add(oaUser.getId()); }); } - return this.deleteOaUsers(removeIds, "同步删除无登录名的OA离职人员"); + return this.deleteOaUsers(removeIds, "同步删除OA离职人员"); } /** @@ -645,7 +570,8 @@ public class OaUserListSyncHelper { } /** - * 同步人员并绑定到三级部门。有登录名按登录名更新,没有登录名但有工号则按工号更新且不回填登录名。离职人员直接删除。 + * 同步人员并绑定到三级部门。按工号 workcode(对应本地 code)匹配已有用户; + * OA登录名仍优先填 workcode,其次 loginid。无工号则跳过。离职人员按工号删除。 * * @param oaPersons OA人员 * @param orgIndex 组织索引 @@ -656,7 +582,6 @@ public class OaUserListSyncHelper { return new PersonSyncCount(0, 0, 0); } Map uniquePersonMap = new LinkedHashMap<>(); - Set resignedLoginIds = new LinkedHashSet<>(); Set resignedWorkcodes = new LinkedHashSet<>(); int skippedCount = 0; for (OAPersonResponse oaPerson : oaPersons) { @@ -665,49 +590,36 @@ public class OaUserListSyncHelper { skippedCount++; continue; } - String oaLoginId = this.resolveOaLoginId(oaPerson); String workcode = this.resolveWorkcode(oaPerson); if (this.isOaResigned(oaPerson)) { uniquePersonMap.remove(syncKey); - if (oaLoginId != null) { - resignedLoginIds.add(oaLoginId); - } else if (workcode != null) { + if (workcode != null) { resignedWorkcodes.add(workcode); } continue; } - if (oaLoginId != null) { - resignedLoginIds.remove(oaLoginId); - } else if (workcode != null) { + if (workcode != null) { resignedWorkcodes.remove(workcode); } uniquePersonMap.put(syncKey, oaPerson); } String tenantId = resolveTenantId(); - int removedCount = this.removeOaUsersByLoginIds(tenantId, resignedLoginIds) - + this.removeOaUsersByBlankLoginWorkcodes(tenantId, resignedWorkcodes); + int removedCount = this.removeOaUsersByWorkcodes(tenantId, resignedWorkcodes); if (uniquePersonMap.isEmpty()) { - if (resignedLoginIds.isEmpty() && resignedWorkcodes.isEmpty()) { - ComposeLogUtil.getLastLog().warn("OA人员均缺少登录名和工号,跳过人员同步"); + if (resignedWorkcodes.isEmpty()) { + ComposeLogUtil.getLastLog().warn("OA人员均缺少工号,跳过人员同步"); } return new PersonSyncCount(0, skippedCount, removedCount); } String defaultPassword = DigestUtil.encrypt(ParamCache.getValue(DEFAULT_PARAM_PASSWORD)); - Set oaLoginIds = new LinkedHashSet<>(); Set workcodes = new LinkedHashSet<>(); for (OAPersonResponse oaPerson : uniquePersonMap.values()) { - String oaLoginId = this.resolveOaLoginId(oaPerson); - if (oaLoginId != null) { - oaLoginIds.add(oaLoginId); - continue; - } String workcode = this.resolveWorkcode(oaPerson); if (workcode != null) { workcodes.add(workcode); } } - Map existingByOaLoginId = this.loadUsersByOaLoginId(tenantId, oaLoginIds); - Map existingByWorkcode = this.loadUsersByBlankLoginWorkcode(tenantId, workcodes); + Map existingByWorkcode = this.loadUsersByWorkcode(tenantId, workcodes); List users = new ArrayList<>(); Map userMap = new HashMap<>(); Set existsUserIds = new HashSet<>(); @@ -717,7 +629,7 @@ public class OaUserListSyncHelper { String workcode = this.resolveWorkcode(oaPerson); User user = this.userConvert.person2user(oaPerson, defaultPassword); user.setTenantId(tenantId); - User existingUser = oaLoginId != null ? existingByOaLoginId.get(oaLoginId) : existingByWorkcode.get(workcode); + User existingUser = workcode == null ? null : existingByWorkcode.get(workcode); if (oaLoginId != null) { user.setOaLoginId(oaLoginId); user.setAccount(oaLoginId); @@ -757,7 +669,7 @@ public class OaUserListSyncHelper { ComposeLogUtil.getLastLog().info("批量修改用户:{}", updateUsers.size()); userService.updateBatchById(updateUsers); } - ComposeLogUtil.getLastLog().info("缺少登录名和工号已跳过的人员:{}", skippedCount); + ComposeLogUtil.getLastLog().info("缺少工号已跳过的人员:{}", skippedCount); int syncedCount = addUsers.size() + updateUsers.size(); if (userMap.isEmpty()) { return new PersonSyncCount(0, skippedCount, removedCount); @@ -882,50 +794,9 @@ public class OaUserListSyncHelper { } /** - * 按 OA 登录名加载已有 is_oa=2 用户。同一登录名有多条时保留较早的一条,后续同步都更新这一条。 + * 按工号(User.code)加载已有 is_oa=2 用户。同一工号有多条时保留较早的一条,后续同步都更新这一条。 */ - private Map loadUsersByOaLoginId(String tenantId, Collection oaLoginIds) { - Map existingByOaLoginId = new HashMap<>(); - if (CollectionUtil.isEmpty(oaLoginIds)) { - return existingByOaLoginId; - } - List oaLoginIdList = oaLoginIds.stream() - .filter(StringUtils::isNotBlank) - .distinct() - .toList(); - for (int fromIndex = 0; fromIndex < oaLoginIdList.size(); fromIndex += OA_LOGIN_ID_BATCH_SIZE) { - List oaLoginIdBatch = oaLoginIdList.subList(fromIndex, Math.min(fromIndex + OA_LOGIN_ID_BATCH_SIZE, oaLoginIdList.size())); - userService.list(Wrappers.lambdaQuery() - .eq(User::getTenantId, tenantId) - .eq(User::getIsOa, CommonConstant.YES) - .in(User::getOaLoginId, oaLoginIdBatch)) - .forEach(user -> { - String oaLoginId = StringUtils.trimToNull(user.getOaLoginId()); - if (oaLoginId == null) { - return; - } - User previous = existingByOaLoginId.get(oaLoginId); - if (previous == null) { - existingByOaLoginId.put(oaLoginId, user); - return; - } - if (user.getId() != null && previous.getId() != null && user.getId() < previous.getId()) { - ComposeLogUtil.getLastLog().warn("OA登录名重复,保留较早用户 oaLoginId={} keepUserId={} dropUserId={}", - oaLoginId, user.getId(), previous.getId()); - existingByOaLoginId.put(oaLoginId, user); - return; - } - ComposeLogUtil.getLastLog().warn("OA登录名重复,保留较早用户 oaLoginId={} keepUserId={} dropUserId={}", - oaLoginId, previous.getId(), user.getId()); - }); - } - return existingByOaLoginId; - } - - /** - * 按工号加载没有登录名的 is_oa=2 用户。同一工号有多条时保留较早的一条。 - */ - private Map loadUsersByBlankLoginWorkcode(String tenantId, Collection workcodes) { + private Map loadUsersByWorkcode(String tenantId, Collection workcodes) { Map existingByWorkcode = new HashMap<>(); if (CollectionUtil.isEmpty(workcodes)) { return existingByWorkcode; @@ -939,26 +810,41 @@ public class OaUserListSyncHelper { this.userService.list(Wrappers.lambdaQuery() .eq(User::getTenantId, tenantId) .eq(User::getIsOa, CommonConstant.YES) - .in(User::getCode, workcodeBatch) - .and(wrapper -> wrapper.isNull(User::getOaLoginId).or().eq(User::getOaLoginId, ""))) + .in(User::getCode, workcodeBatch)) .forEach(user -> { String workcode = StringUtils.trimToNull(user.getCode()); if (workcode == null) { return; } User previous = existingByWorkcode.get(workcode); - if (previous == null || (user.getId() != null && previous.getId() != null && user.getId() < previous.getId())) { + if (previous == null) { existingByWorkcode.put(workcode, user); + return; } + if (user.getId() != null && previous.getId() != null && user.getId() < previous.getId()) { + ComposeLogUtil.getLastLog().warn("OA工号重复,保留较早用户 workcode={} keepUserId={} dropUserId={}", + workcode, user.getId(), previous.getId()); + existingByWorkcode.put(workcode, user); + return; + } + ComposeLogUtil.getLastLog().warn("OA工号重复,保留较早用户 workcode={} keepUserId={} dropUserId={}", + workcode, previous.getId(), user.getId()); }); } return existingByWorkcode; } + /** + * OA登录名:优先 workcode,其次 loginid。仅用于填充 oaLoginId/account,不参与匹配。 + */ private String resolveOaLoginId(OAPersonResponse oaPerson) { if (oaPerson == null) { return null; } + String workcode = StringUtils.trimToNull(oaPerson.getWorkcode()); + if (workcode != null) { + return workcode; + } return StringUtils.trimToNull(oaPerson.getLoginid()); } @@ -970,13 +856,9 @@ public class OaUserListSyncHelper { } /** - * 有登录名用登录名,没有登录名但有工号用工号。两者都没有则不同步。 + * 同步去重与匹配键:仅使用工号。无工号则不同步。 */ private String resolveSyncKey(OAPersonResponse oaPerson) { - String oaLoginId = this.resolveOaLoginId(oaPerson); - if (oaLoginId != null) { - return "L:" + oaLoginId; - } String workcode = this.resolveWorkcode(oaPerson); if (workcode != null) { return "W:" + workcode; diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RoleMenuDataScopeServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RoleMenuDataScopeServiceImpl.java index 0314afc9..04d62d5b 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RoleMenuDataScopeServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RoleMenuDataScopeServiceImpl.java @@ -184,20 +184,44 @@ public class RoleMenuDataScopeServiceImpl extends BaseServiceImpllambdaQuery() - .eq(Menu::getCode, menuCode) - .eq(Menu::getIsDeleted, 0) - .last("limit 1")); + Menu menu = findMenuByCode(menuCode); if (Func.isEmpty(menu)) { return RoleDataScopeVO.all(); } - List scopeList = list(Wrappers.lambdaQuery() - .in(RoleMenuDataScope::getRoleId, roleIds) - .eq(RoleMenuDataScope::getMenuId, menu.getId()) - .eq(RoleMenuDataScope::getIsDeleted, 0)); + List scopeList = listScopes(roleIds, menu.getId()); return mergeScopes(scopeList, userDeptIds); } + @Override + public RoleDataScopeVO resolveByMenuCodeOrEmpty(List roleIds, String menuCode, List userDeptIds) { + if (Func.isEmpty(roleIds) || Func.isBlank(menuCode)) { + return RoleDataScopeVO.none(); + } + Menu menu = findMenuByCode(menuCode); + if (Func.isEmpty(menu)) { + return RoleDataScopeVO.none(); + } + List scopeList = listScopes(roleIds, menu.getId()); + if (Func.isEmpty(scopeList)) { + return RoleDataScopeVO.none(); + } + return mergeScopes(scopeList, userDeptIds); + } + + private Menu findMenuByCode(String menuCode) { + return menuMapper.selectOne(Wrappers.

lambdaQuery() + .eq(Menu::getCode, menuCode) + .eq(Menu::getIsDeleted, 0) + .last("limit 1")); + } + + private List listScopes(List roleIds, Long menuId) { + return list(Wrappers.lambdaQuery() + .in(RoleMenuDataScope::getRoleId, roleIds) + .eq(RoleMenuDataScope::getMenuId, menuId) + .eq(RoleMenuDataScope::getIsDeleted, 0)); + } + @Override public RoleDataScopeVO resolveWidest(List roleIds, List userDeptIds) { if (Func.isEmpty(roleIds)) { diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java index 67ef69fb..67122919 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java @@ -86,6 +86,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -663,6 +664,7 @@ public class UserServiceImpl extends BaseServiceImpl implement boolean tempUser = this.deleteLogic(Func.toLongList(userIds)); boolean tempUserDept = userDeptService.remove(Wrappers.lambdaQuery().in(UserDept::getUserId, Func.toLongList(userIds))); if (tempUser && tempUserDept) { + userDataScopeMapper.delete(Wrappers.lambdaQuery().in(UserDataScope::getUserId, idList)); UserOauth userOauth = new UserOauth(); userOauth.delete(Wrappers.lambdaQuery().in(UserOauth::getUserId, Func.toLongList(userIds))); UserWeb userWeb = new UserWeb(); @@ -672,6 +674,7 @@ public class UserServiceImpl extends BaseServiceImpl implement UserOther userOther = new UserOther(); userOther.delete(Wrappers.lambdaQuery().in(UserOther::getUserId, Func.toLongList(userIds))); CacheUtil.clear(USER_CACHE); + CacheUtil.clear(USER_CACHE, Boolean.FALSE); return true; } else { throw new ServiceException("删除用户失败!"); @@ -1119,8 +1122,10 @@ public class UserServiceImpl extends BaseServiceImpl implement if (user.getPersonCategory() == null) { user.setPersonCategory(1); } - // 组织/客商数据权限已迁移至角色菜单配置,用户侧字段仅保留兼容默认值,不再参与过滤。 - if (user.getDataScopeRange() == null) { + // 有「数据管理权限」组织时标记为自定义;否则仅补兼容默认值(业务过滤以角色权限 ∪ 用户管理组织为准) + if (Func.isNotEmpty(user.getDataScopeDeptIds())) { + user.setDataScopeRange(3); + } else if (user.getDataScopeRange() == null) { user.setDataScopeRange(2); } if (user.getDataLevelRange() == null) { @@ -1141,8 +1146,33 @@ public class UserServiceImpl extends BaseServiceImpl implement return null; } + /** + * 保存用户「数据管理权限」组织;dataScopeDeptIds 为 null 时保留原配置(兼容 IAM/同步等未传该字段的调用)。 + */ private boolean submitUserScopes(User user) { - // 用户级组织/客商数据权限已停用,改由角色菜单数据权限配置。 + if (user == null || user.getId() == null) { + return true; + } + if (user.getDataScopeDeptIds() == null) { + return true; + } + userDataScopeMapper.delete(Wrappers.lambdaQuery().eq(UserDataScope::getUserId, user.getId())); + List deptIdList = user.getDataScopeDeptIds().stream() + .filter(Objects::nonNull) + .filter(deptId -> deptId > 0) + .distinct() + .toList(); + for (Long deptId : deptIdList) { + UserDataScope scope = new UserDataScope(); + scope.setUserId(user.getId()); + scope.setDeptId(deptId); + userDataScopeMapper.insert(scope); + } + this.update(Wrappers.lambdaUpdate() + .set(User::getDataScopeRange, deptIdList.isEmpty() ? 2 : 3) + .eq(User::getId, user.getId())); + CacheUtil.clear(USER_CACHE); + CacheUtil.clear(USER_CACHE, Boolean.FALSE); return true; } @@ -1150,11 +1180,46 @@ public class UserServiceImpl extends BaseServiceImpl implement if (user == null || user.getId() == null) { return user; } - user.setDataScopeDeptIds(Collections.emptyList()); + List deptIdList = userDataScopeMapper.selectList(Wrappers.lambdaQuery() + .eq(UserDataScope::getUserId, user.getId())) + .stream() + .map(UserDataScope::getDeptId) + .filter(Objects::nonNull) + .distinct() + .toList(); + user.setDataScopeDeptIds(deptIdList); user.setCustomerIds(Collections.emptyList()); return user; } + @Override + public List resolveManageDeptIds(Long userId) { + if (Func.isEmpty(userId) || userId <= 0) { + return Collections.emptyList(); + } + List baseDeptIds = userDataScopeMapper.selectList(Wrappers.lambdaQuery() + .eq(UserDataScope::getUserId, userId)) + .stream() + .map(UserDataScope::getDeptId) + .filter(Objects::nonNull) + .filter(deptId -> deptId > 0) + .distinct() + .toList(); + if (Func.isEmpty(baseDeptIds)) { + return Collections.emptyList(); + } + LinkedHashSet allowedDeptIds = new LinkedHashSet<>(); + for (Long deptId : baseDeptIds) { + List childIds = SysCache.getDeptChildIds(deptId); + if (Func.isNotEmpty(childIds)) { + allowedDeptIds.addAll(childIds); + } else { + allowedDeptIds.add(deptId); + } + } + return List.copyOf(allowedDeptIds); + } + /** * 统一的租户范围过滤:超管放行,其他用户强制限定为当前会话租户。 */ diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/BusinessDeptConfigWrapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/BusinessDeptConfigWrapper.java new file mode 100644 index 00000000..e2f8e077 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/BusinessDeptConfigWrapper.java @@ -0,0 +1,56 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.wrapper; + +import org.springblade.core.mp.support.BaseEntityWrapper; +import org.springblade.core.tool.utils.BeanUtil; +import org.springblade.system.cache.UserCache; +import org.springblade.system.pojo.entity.BusinessDeptConfig; +import org.springblade.system.pojo.vo.BusinessDeptConfigVO; + +import java.util.Objects; + +/** + * 业务部门配置包装类 + * + * @author Chill + */ +public class BusinessDeptConfigWrapper extends BaseEntityWrapper { + + public static BusinessDeptConfigWrapper build() { + return new BusinessDeptConfigWrapper(); + } + + @Override + public BusinessDeptConfigVO entityVO(BusinessDeptConfig businessDeptConfig) { + BusinessDeptConfigVO businessDeptConfigVO = Objects.requireNonNull( + BeanUtil.copyProperties(businessDeptConfig, BusinessDeptConfigVO.class)); + businessDeptConfigVO.setCreateUserName(UserCache.getUserRealName(businessDeptConfig.getCreateUser())); + businessDeptConfigVO.setUpdateUserName(UserCache.getUserRealName(businessDeptConfig.getUpdateUser())); + return businessDeptConfigVO; + } + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java index e61d785d..19174df4 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java @@ -110,6 +110,11 @@ public class BfiProperties { */ private ArResult arResult = new ArResult(); + /** + * 金蝶冲销状态回写配置(A102-4/A302-4) + */ + private WriteOffResult writeOffResult = new WriteOffResult(); + /** * 金蝶单据锁定补偿配置 */ @@ -254,7 +259,7 @@ public class BfiProperties { private long lockLeaseSeconds = 1800; /** - * A103-4 付款处理查询业务日期窗口天数,默认 60 + * A103-4 付款处理查询业务日期窗口天数(接口必填参数,按 awrp_sourcebillno 精确过滤单据),默认 60 */ private int queryWindowDays = 60; } @@ -364,6 +369,24 @@ public class BfiProperties { private long lockLeaseSeconds = 1800; } + /** + * 金蝶冲销状态回写配置项(A102-4/A302-4):定时回写开关、防重键批量查询与结算单批量更新的分批大小, + * 防止单批过大超出金蝶查询接口与批量落库承载能力 + */ + @Data + public static class WriteOffResult { + + /** + * 冲销状态回写任务开关,默认关闭(安全兜底),由 Nacos 按环境显式开启 + */ + private boolean syncEnabled = false; + + /** + * 冲销状态批量查询/批量更新分批大小,默认 1000 + */ + private int queryBatchSize = 1000; + } + /** * 金蝶单据锁定补偿配置项 */ diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/KingdeeBfiConstant.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/KingdeeBfiConstant.java index 055be3f3..353583f7 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/KingdeeBfiConstant.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/KingdeeBfiConstant.java @@ -171,6 +171,12 @@ public final class KingdeeBfiConstant { /** 金蝶同步状态:生成失败(推送异常/金蝶返回失败,可重推)。 */ public static final String KINGDEE_STATUS_FAILED = "failed"; + /** 金蝶冲销状态:未冲销(旧应付/应收单在金蝶尚未冲销)。 */ + public static final String KINGDEE_WRITEOFF_UNWRITTEN = "unwrittenoff"; + + /** 金蝶冲销状态:已冲销(旧应付/应收单已在金蝶冲销,部分冲销视同已冲销)。 */ + public static final String KINGDEE_WRITEOFF_WRITTEN = "writtenoff"; + /** 收票单推送状态:未推送(新建单据默认,票池绑定层的 kingdee_status 与此无关)。 */ public static final String RECEIVED_PUSH_UNSYNCED = "unsynced"; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BaiduOcrController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BaiduOcrController.java index c94cc068..56c682b0 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BaiduOcrController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BaiduOcrController.java @@ -36,7 +36,9 @@ import org.springblade.core.log.exception.ServiceException; import org.springblade.core.tool.api.R; import org.springblade.transport.ocr.constant.BaiduOcrType; import org.springblade.transport.ocr.service.IBaiduOcrService; +import org.springblade.transport.ocr.service.IBusinessLicenseOcrService; import org.springblade.transport.pojo.vo.BaiduOcrResultVO; +import org.springblade.transport.pojo.vo.BusinessLicenseOcrVO; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -60,6 +62,7 @@ import java.io.IOException; public class BaiduOcrController extends BladeController { private final IBaiduOcrService baiduOcrService; + private final IBusinessLicenseOcrService businessLicenseOcrService; /** * 识别上传的图片。 @@ -109,6 +112,20 @@ public class BaiduOcrController extends BladeController { return R.data(baiduOcrService.recognizeUrl(resolveType(type), side, imageUrl)); } + /** + * 营业执照图片地址识别(第三方 /businessLicense)。 + * + * @param imageUrl 图片地址 + * @return 识别结果 + */ + @PostMapping("/business-license") + @ApiOperationSupport(order = 3) + @Operation(summary = "营业执照OCR识别", description = "按图片地址调用第三方营业执照识别接口") + public R recognizeBusinessLicense( + @Parameter(description = "图片地址", required = true) @RequestParam String imageUrl) { + return R.data(businessLicenseOcrService.recognizeUrl(imageUrl)); + } + private BaiduOcrType resolveType(String type) { try { return BaiduOcrType.from(type); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CertificationAuditController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CertificationAuditController.java index 6e6a024f..77c50af3 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CertificationAuditController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CertificationAuditController.java @@ -36,6 +36,7 @@ import org.springblade.core.mp.support.Condition; import org.springblade.core.mp.support.Query; import org.springblade.core.secure.annotation.PreAuth; import org.springblade.core.tool.api.R; +import org.springblade.transport.pojo.dto.CertAuditRequest; import org.springblade.transport.pojo.vo.CertAuditCapabilityVO; import org.springblade.transport.pojo.vo.CertAuditDetailVO; import org.springblade.transport.pojo.vo.TransportCertRecordVO; @@ -43,10 +44,13 @@ import org.springblade.transport.service.ICertAuditService; import org.springblade.transport.support.CertAuditPermissions; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.function.Function; + /** * 认证审核(管理端) *

@@ -92,13 +96,26 @@ public class CertificationAuditController extends BladeController { @PostMapping("/audit") @ApiOperationSupport(order = 3) - @Operation(summary = "审核认证记录", description = "通过使该记录成为生效快照;驳回只终结该记录,生效快照不变") + @Operation(summary = "审核认证记录", description = "通过使该记录成为生效快照;驳回只终结该记录,生效快照不变。通过时可携带修订后的全量认证内容,它将成为新的生效内容并原样留痕") @PreAuth(CertAuditPermissions.AUDIT_ACTION) public R audit( - @Parameter(description = "认证记录主键", required = true) @RequestParam Long id, - @Parameter(description = "是否通过", required = true) @RequestParam Boolean pass, + @Parameter(description = "审核请求体(推荐):id、pass、rejectReason、auditedSnapshotJson") + @RequestBody(required = false) CertAuditRequest body, + @Parameter(description = "认证记录主键(兼容老调用)") @RequestParam(required = false) Long id, + @Parameter(description = "是否通过(兼容老调用)") @RequestParam(required = false) Boolean pass, @Parameter(description = "驳回原因,驳回时必填") @RequestParam(required = false) String rejectReason) { - return R.status(certAuditService.audit(id, Boolean.TRUE.equals(pass), rejectReason)); + // 同一字段 body 与 query 都可传,两处都给时以 body 为准。 + Long recordId = preferBody(body, CertAuditRequest::getId, id); + Boolean approved = preferBody(body, CertAuditRequest::getPass, pass); + String reason = preferBody(body, CertAuditRequest::getRejectReason, rejectReason); + String revision = preferBody(body, CertAuditRequest::getAuditedSnapshotJson, null); + if (recordId == null) { + return R.fail("缺少认证记录ID"); + } + if (approved == null) { + return R.fail("缺少审核结果"); + } + return R.status(certAuditService.audit(recordId, Boolean.TRUE.equals(approved), reason, revision)); } // 本接口不挂 @PreAuth;无权限时返回 false。 @@ -109,4 +126,21 @@ public class CertificationAuditController extends BladeController { return R.data(certAuditService.capability()); } + /** + * 取一个审核参数:body 里给了就用 body 的,否则用 query 的 + * + * @param body 审核请求体,可为 null + * @param getter 从请求体取该字段 + * @param queryValue 该字段的 query 参数值 + * @param 字段类型 + * @return body 字段值;body 为 null 或该字段为 null 时返回 query 参数值 + */ + private static T preferBody(CertAuditRequest body, Function getter, T queryValue) { + if (body == null) { + return queryValue; + } + T bodyValue = getter.apply(body); + return bodyValue == null ? queryValue : bodyValue; + } + } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ContractManageController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ContractManageController.java index 364dbcc9..eb0584db 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ContractManageController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ContractManageController.java @@ -188,10 +188,11 @@ public class ContractManageController extends BladeController { @GetMapping("/next-contract-no") @ApiOperationSupport(order = 16) - @Operation(summary = "预览下一合同编号", description = "按合同负责部门编码前缀+年份+三位流水预览") + @Operation(summary = "预览下一合同编号", description = "按平台公司+业务部门配置的前缀+年份+三位流水预览") public R nextContractNo( - @Parameter(description = "合同负责部门ID", required = true) @RequestParam Long organizationId) { - return R.data(contractManageService.nextContractNo(organizationId)); + @Parameter(description = "合同负责部门ID", required = true) @RequestParam Long organizationId, + @Parameter(description = "平台公司ID", required = true) @RequestParam Long platformCompanyId) { + return R.data(contractManageService.nextContractNo(organizationId, platformCompanyId)); } @GetMapping("/export-contract-manage") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverAppController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverAppController.java index 6040145d..e43b197f 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverAppController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverAppController.java @@ -54,9 +54,10 @@ public class DriverAppController extends BladeController { @GetMapping("/mine") @ApiOperationSupport(order = 1) - @Operation(summary = "当前登录司机档案", description = "按手机号匹配 blade_transport_driver.mobile;可传 mobile,未传则从登录态解析") + @Operation(summary = "当前登录司机档案", description = "按登录用户的 user_id 匹配 blade_transport_driver.user_id") + // mobile:旧版小程序仍会带这个参数,已不参与解析,保留只为不让老客户端报错 public R mine(@RequestParam(required = false) String mobile) { - return R.data(driverAppService.currentByPhone(mobile)); + return R.data(driverAppService.current()); } @GetMapping("/vehicles") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverWaybillController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverWaybillController.java index 06eaf200..96a6e25d 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverWaybillController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverWaybillController.java @@ -65,14 +65,14 @@ public class DriverWaybillController extends BladeController { @GetMapping("/current-task") @ApiOperationSupport(order = 1) - @Operation(summary = "首页:当前运输中任务", description = "当前用户手机号匹配运单 driverPhone 且 businessStatus=running 的最新一条运单") + @Operation(summary = "首页:当前运输中任务", description = "当前司机档案(按登录用户解析)名下、businessStatus=running 的最新一条运单;归属判据是运单 driver_id") public R currentTask() { return R.data(driverWaybillService.currentTask()); } @GetMapping("/pending-preview") @ApiOperationSupport(order = 2) - @Operation(summary = "首页:待接运单预览", description = "当前用户手机号匹配运单 driverPhone 且 businessStatus=pending 的预览列表与总数") + @Operation(summary = "首页:待接运单预览", description = "当前司机档案名下、businessStatus=pending 的预览列表与总数;归属判据是运单 driver_id") public R pendingPreview( @Parameter(description = "预览条数,默认 2") @RequestParam(required = false) Integer size) { return R.data(driverWaybillService.pendingPreview(size)); @@ -80,14 +80,14 @@ public class DriverWaybillController extends BladeController { @GetMapping("/counts") @ApiOperationSupport(order = 3) - @Operation(summary = "运单 Tab 统计", description = "仅统计当前用户手机号匹配运单 driverPhone 的运单:全部 / 待接单 / 进行中 / 已完成") + @Operation(summary = "运单 Tab 统计", description = "仅统计当前司机档案名下(运单 driver_id)的运单:全部 / 待接单 / 进行中 / 已完成") public R counts() { return R.data(driverWaybillService.tabCounts()); } @GetMapping("/page") @ApiOperationSupport(order = 4) - @Operation(summary = "运单分页列表", description = "仅返回运单 driverPhone 等于当前用户手机号的数据;status:空=全部,0待接单,1运输中,2已完成") + @Operation(summary = "运单分页列表", description = "仅返回运单 driver_id 等于当前司机档案的数据;status:空=全部,0待接单,1运输中,2已完成") public R> page( @Parameter(description = "当前页") @RequestParam(required = false) Integer current, @Parameter(description = "每页条数") @RequestParam(required = false) Integer size, @@ -107,14 +107,14 @@ public class DriverWaybillController extends BladeController { @GetMapping("/task/current") @ApiOperationSupport(order = 21) - @Operation(summary = "首页:当前运输中运次", description = "当前用户手机号可见的运输中或待确认(待回单确认)运次(有配载单的一趟车算一个)最新一条") + @Operation(summary = "首页:当前运输中运次", description = "当前司机档案可见的运输中或待确认(待回单确认)运次(有配载单的一趟车算一个)最新一条;配载段按配载单 driver_id,未配载段按运单 driver_id") public R tripCurrentTask() { return R.data(driverWaybillService.tripCurrentTask()); } @GetMapping("/task/pending-preview") @ApiOperationSupport(order = 22) - @Operation(summary = "首页:待接运次预览", description = "当前用户手机号可见的待接运次预览与总数(按运次计)") + @Operation(summary = "首页:待接运次预览", description = "当前司机档案可见的待接运次预览与总数(按运次计)") public R tripPendingPreview( @Parameter(description = "预览条数,默认 2") @RequestParam(required = false) Integer size) { return R.data(driverWaybillService.tripPendingPreview(size)); @@ -166,7 +166,7 @@ public class DriverWaybillController extends BladeController { @PostMapping("/track") @ApiOperationSupport(order = 11) - @Operation(summary = "司机端行驶轨迹", description = "按当前用户手机号校验归属后,根据打卡记录/计划时间自动推导起止日期并查询 LBS 轨迹(WGS-84)") + @Operation(summary = "司机端行驶轨迹", description = "按当前司机档案校验归属后,根据打卡记录/计划时间自动推导起止日期并查询 LBS 轨迹(WGS-84)") public R track(@Parameter(description = "运单ID", required = true) @RequestParam Long id) { return R.data(driverWaybillService.track(id)); } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ExceptionDisposalController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ExceptionDisposalController.java index 2b42643a..d9151be7 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ExceptionDisposalController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ExceptionDisposalController.java @@ -36,6 +36,7 @@ import org.springblade.transport.pojo.dto.ExceptionDisposalFollowRequest; import org.springblade.transport.pojo.dto.ExceptionDisposalRejectRequest; import org.springblade.transport.pojo.dto.ExceptionDisposalRouteAdjustRequest; import org.springblade.transport.pojo.dto.ExceptionDisposalUpdateRequest; +import org.springblade.transport.pojo.dto.ExceptionMaintenanceRecordRequest; import org.springblade.transport.pojo.entity.ExceptionDisposal; import org.springblade.transport.pojo.vo.ExceptionDisposalVO; import org.springblade.transport.service.IExceptionDisposalService; @@ -140,4 +141,12 @@ public class ExceptionDisposalController extends BladeController { return R.success("已调整路线"); } + @PostMapping("/maintenance-record") + @ApiOperationSupport(order = 11) + @Operation(summary = "录入维修记录", description = "车辆故障维修类异常的子流程:写一条关联维修记录并追加跟进记录,不结案;一条异常至多一条;仅需登录态") + public R submitMaintenanceRecord(@RequestBody ExceptionMaintenanceRecordRequest request) { + exceptionDisposalService.submitMaintenanceRecord(request); + return R.success("已保存维修记录"); + } + } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/HcsSyncController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/HcsSyncController.java index 0a721310..0a6722c1 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/HcsSyncController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/HcsSyncController.java @@ -38,6 +38,7 @@ import org.springblade.core.tenant.annotation.TenantIgnore; import org.springblade.core.tool.api.R; import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.StringUtil; +import org.springblade.hcs.config.HcsProperties; import org.springblade.hcs.dto.HcsContractLegalResultDTO; import org.springblade.hcs.dto.HcsCurrencyVO; import org.springblade.hcs.dto.HcsLegalResultDTO; @@ -70,7 +71,9 @@ import java.io.OutputStream; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * HCS 合规推送端点(设计文档 §5.0 手动入口) @@ -99,6 +102,20 @@ public class HcsSyncController { private final HcsInvoiceRegSyncSupport hcsInvoiceRegSyncSupport; private final HcsReceiptBillSyncSupport hcsReceiptBillSyncSupport; private final HcsMasterDataSupport hcsMasterDataSupport; + private final HcsProperties hcsProperties; + + /** + * 推送开关配置(供项目管理/合同管理列表控制「推送合规」「批量推送合规」显隐) + */ + @GetMapping("/push-config") + @ApiOperationSupport(order = 0) + @Operation(summary = "查询HCS推送开关", description = "返回 thirdParty.hcs.push.immediate-enabled,前端据此隐藏即时推送入口") + public R> pushConfig() { + boolean immediateEnabled = hcsProperties.getPush() != null && hcsProperties.getPush().isImmediateEnabled(); + Map config = new HashMap<>(2); + config.put("immediateEnabled", immediateEnabled); + return R.data(config); + } /** * 单条重推(bizType:project=项目立项 / contract=合同 / payment=付款申请 / invoice-application=开票申请 / invoice-receipt=收票单) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/LoadingManageController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/LoadingManageController.java index 320c8b22..c3b1e790 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/LoadingManageController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/LoadingManageController.java @@ -56,7 +56,7 @@ public class LoadingManageController extends BladeController { @GetMapping("/carrier-contracts") @ApiOperationSupport(order = 13) - @Operation(summary = "可选承运商合同", description = "查询已审核生效的承运商合同;传入 waybillIds 时按子运单客户合同甲方匹配后对乙方取交集") + @Operation(summary = "可选承运商合同", description = "查询已审核生效的承运商合同;传入 waybillIds 时按子运单客户合同乙方=承运商合同甲方匹配后对乙方取交集") public R> carrierContracts( @RequestParam List projectIds, @RequestParam(required = false) List waybillIds) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageArchiveController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageArchiveController.java new file mode 100644 index 00000000..ac6a36bc --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageArchiveController.java @@ -0,0 +1,94 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.controller; + +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import org.springblade.core.boot.ctrl.BladeController; +import org.springblade.core.tool.api.R; +import org.springblade.transport.pojo.vo.ArchiveManageStatsVO; +import org.springblade.transport.pojo.vo.DeptTreeNodeVO; +import org.springblade.transport.service.IManageArchiveService; +import org.springblade.transport.support.MpUserDataScopeSupport; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 调度端档案(小程序管理端) + *

+ * 对外完整路径:{@code /api/blade-transport/archive/manage/**} + * (网关 StripPrefix 去掉 {@code blade-transport} 后落入 {@code /archive/manage/**})。 + * 同时兼容未去前缀直连({@code /blade-transport/archive/manage/**})。 + * 仅需登录态,不挂管理端菜单鉴权。 + *

+ * 为什么单独一个接口:管理端 {@code /driver/expiry-stat}、{@code /transport-vehicle/expiry-stat} + * 挂在 {@code @PreAuth(menu = "driver")} / {@code @PreAuth(menu = "transport_vehicle")} 下, + * 小程序登录的令牌 roleId 是占位值 "0"、查不到 blade_role_menu,必然被拒(401 请求未授权)。 + */ +@RestController +@AllArgsConstructor +@RequestMapping({"/archive/manage", "/blade-transport/archive/manage"}) +@Tag(name = "调度端档案", description = "小程序调度端资料页档案统计") +public class ManageArchiveController extends BladeController { + + /** + * 权限不足的业务码。小程序请求层把 401 当会话失效,接口一律不得回 401。 + */ + private static final int FORBIDDEN_CODE = 403; + + private static final String FORBIDDEN_MSG = "仅调度端管理员可选择组织"; + + private final IManageArchiveService manageArchiveService; + private final MpUserDataScopeSupport mpUserDataScopeSupport; + + @GetMapping("/stats") + @ApiOperationSupport(order = 1) + @Operation(summary = "档案统计", description = "司机档案与车辆档案的总数、30 天内到期数、已到期数;不按组织收口") + public R stats() { + return R.data(manageArchiveService.stats()); + } + + // 本接口不挂 @PreAuth;无权限返回 false,不抛 403,供小程序决定空态。 + @GetMapping("/capability") + @ApiOperationSupport(order = 2) + @Operation(summary = "档案管理能力判定", description = "当前登录是否为调度端管理员;false 时小程序显示无权限空态,不踢登录") + public R capability() { + return R.data(mpUserDataScopeSupport.isMpDispatcher()); + } + + @GetMapping("/dept/tree") + @ApiOperationSupport(order = 3) + @Operation(summary = "组织树", description = "小程序组织选择器用:启用中的部门树,已剔除「外部组织」整支;非管理员回业务 403") + public R> deptTree() { + if (!mpUserDataScopeSupport.isMpDispatcher()) { + return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG); + } + return R.data(manageArchiveService.deptTree()); + } + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageArchiveDriverController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageArchiveDriverController.java new file mode 100644 index 00000000..e5e25fcb --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageArchiveDriverController.java @@ -0,0 +1,180 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springblade.core.boot.ctrl.BladeController; +import org.springblade.core.mp.support.Condition; +import org.springblade.core.mp.support.Query; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; +import org.springblade.transport.pojo.entity.Driver; +import org.springblade.transport.pojo.vo.DriverVO; +import org.springblade.transport.service.IDriverService; +import org.springblade.transport.support.MpUserDataScopeSupport; +import org.springblade.transport.wrapper.DriverWrapper; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDate; + +/** + * 调度端司机档案管理(小程序) + *

+ * 对外完整路径:{@code /api/blade-transport/archive/manage/driver/**} + * (网关 StripPrefix 去掉 {@code blade-transport} 后落入 {@code /archive/manage/driver/**}), + * 同时兼容未去前缀直连。 + *

+ * 为什么不复用管理端的 {@code /driver/**}:那些接口挂在 {@code @PreAuth(menu = "driver")} 下, + * 小程序令牌的 roleId 是占位值 "0"、查不到 blade_role_menu,对任何小程序账号都必然被拒。 + * 这里不挂 {@code @PreAuth},改成方法内显式判定管理员身份,且**权限不足必须回业务 403**: + * 小程序请求层把 401 一律当会话失效(清令牌 + 回登录页),回 401 会把越权访问变成被踢下线。 + * + * @author Chill + */ +@RestController +@AllArgsConstructor +@RequestMapping({"/archive/manage/driver", "/blade-transport/archive/manage/driver"}) +@Tag(name = "调度端司机档案", description = "小程序调度端司机管理") +public class ManageArchiveDriverController extends BladeController { + + private static final int DEFAULT_CURRENT = 1; + private static final int DEFAULT_SIZE = 10; + private static final int MAX_SIZE = 100; + + /** + * 权限不足的业务码。小程序请求层把 401 当会话失效,接口一律不得回 401。 + */ + private static final int FORBIDDEN_CODE = 403; + + private static final String FORBIDDEN_MSG = "仅调度端管理员可管理司机档案"; + + private final IDriverService driverService; + private final MpUserDataScopeSupport mpUserDataScopeSupport; + + /** + * 分页 + */ + @GetMapping("/page") + @ApiOperationSupport(order = 1) + @Operation(summary = "司机档案分页", description = "keyword 同时模糊匹配姓名与手机号;status 不传为启用停用全量;expireStatus 取 within30 / expired") + public R> page(DriverVO driver, Query query) { + if (!canManage()) { + return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG); + } + fillExpiryDate(driver); + return R.data(driverService.selectDriverPage(Condition.getPage(normalizeQuery(query)), driver)); + } + + /** + * 详情 + */ + @GetMapping("/detail") + @ApiOperationSupport(order = 2) + @Operation(summary = "司机档案详情") + public R detail(@Parameter(description = "司机主键", required = true) @RequestParam Long id) { + if (!canManage()) { + return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG); + } + Driver query = new Driver(); + query.setId(id); + return R.data(DriverWrapper.build().entityVO(driverService.getOne(Condition.getQueryWrapper(query)))); + } + + /** + * 新增或修改 + */ + @PostMapping("/submit") + @ApiOperationSupport(order = 3) + @Operation(summary = "新增或修改司机档案", description = "直接写档案、立即生效,不生成待审快照") + public R submit(@Valid @RequestBody Driver driver) { + if (!canManage()) { + return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG); + } + // 生效指针只由认证审核流水写(通过才建立生效快照),不接受管理端写入 + driver.setEffectiveCertRecordId(null); + boolean saved = driverService.submit(driver); + // submit 内部把空串 trimToNull,update-strategy=not_null 会跳过该字段,导致删不掉大头照;这里显式清空 + if (saved && driver.getId() != null && Func.isBlank(driver.getHeadPhoto())) { + driverService.update(Wrappers.lambdaUpdate() + .eq(Driver::getId, driver.getId()) + .set(Driver::getHeadPhoto, null)); + } + return R.status(saved); + } + + /** + * 启停用 + */ + @PostMapping("/status") + @ApiOperationSupport(order = 4) + @Operation(summary = "启用 / 停用司机", description = "status:1 启用,2 停用。停用司机不进调度检索,但仍可登录") + public R status(@Parameter(description = "主键", required = true) @RequestParam Long id, + @Parameter(description = "状态:1 启用,2 停用", required = true) @RequestParam Integer status) { + if (!canManage()) { + return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG); + } + return R.status(driverService.changeStatus(id, status)); + } + + private boolean canManage() { + return mpUserDataScopeSupport.isMpDispatcher(); + } + + private Query normalizeQuery(Query query) { + if (query == null) { + query = new Query(); + } + if (query.getCurrent() == null || query.getCurrent() < DEFAULT_CURRENT) { + query.setCurrent(DEFAULT_CURRENT); + } + if (query.getSize() == null || query.getSize() <= 0) { + query.setSize(DEFAULT_SIZE); + } + if (query.getSize() > MAX_SIZE) { + query.setSize(MAX_SIZE); + } + return query; + } + + private void fillExpiryDate(DriverVO driver) { + if (driver.getToday() == null) { + driver.setToday(LocalDate.now()); + } + if (driver.getWarningDate() == null) { + driver.setWarningDate(driver.getToday().plusDays(30)); + } + } + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageArchiveVehicleController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageArchiveVehicleController.java new file mode 100644 index 00000000..b6f930a1 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageArchiveVehicleController.java @@ -0,0 +1,169 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springblade.core.boot.ctrl.BladeController; +import org.springblade.core.mp.support.Condition; +import org.springblade.core.mp.support.Query; +import org.springblade.core.tool.api.R; +import org.springblade.transport.pojo.entity.TransportVehicle; +import org.springblade.transport.pojo.vo.TransportVehicleVO; +import org.springblade.transport.service.ITransportVehicleService; +import org.springblade.transport.support.MpUserDataScopeSupport; +import org.springblade.transport.wrapper.TransportVehicleWrapper; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDate; + +/** + * 调度端车辆档案管理(小程序) + *

+ * 对外完整路径:{@code /api/blade-transport/archive/manage/vehicle/**} + * (网关 StripPrefix 去掉 {@code blade-transport} 后落入 {@code /archive/manage/vehicle/**}), + * 同时兼容未去前缀直连。 + *

+ * 与管理端 {@code /transport-vehicle/**} 的关系、以及为什么必须回业务 403 而不是 401, + * 见 {@link ManageArchiveDriverController} 的类注释。 + * + * @author Chill + */ +@RestController +@AllArgsConstructor +@RequestMapping({"/archive/manage/vehicle", "/blade-transport/archive/manage/vehicle"}) +@Tag(name = "调度端车辆档案", description = "小程序调度端车辆管理") +public class ManageArchiveVehicleController extends BladeController { + + private static final int DEFAULT_CURRENT = 1; + private static final int DEFAULT_SIZE = 10; + private static final int MAX_SIZE = 100; + + /** + * 权限不足的业务码。小程序请求层把 401 当会话失效,接口一律不得回 401。 + */ + private static final int FORBIDDEN_CODE = 403; + + private static final String FORBIDDEN_MSG = "仅调度端管理员可管理车辆档案"; + + private final ITransportVehicleService transportVehicleService; + private final MpUserDataScopeSupport mpUserDataScopeSupport; + + /** + * 分页 + */ + @GetMapping("/page") + @ApiOperationSupport(order = 1) + @Operation(summary = "车辆档案分页", description = "keyword 同时模糊匹配车牌、VIN 与所有人;status 不传为启用停用全量;expireStatus 取 within30 / expired") + public R> page(TransportVehicleVO vehicle, Query query) { + if (!canManage()) { + return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG); + } + fillExpiryDate(vehicle); + return R.data(transportVehicleService.selectTransportVehiclePage(Condition.getPage(normalizeQuery(query)), vehicle)); + } + + /** + * 详情 + */ + @GetMapping("/detail") + @ApiOperationSupport(order = 2) + @Operation(summary = "车辆档案详情") + public R detail(@Parameter(description = "车辆主键", required = true) @RequestParam Long id) { + if (!canManage()) { + return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG); + } + TransportVehicle query = new TransportVehicle(); + query.setId(id); + return R.data(TransportVehicleWrapper.build().entityVO(transportVehicleService.getOne(Condition.getQueryWrapper(query)))); + } + + /** + * 新增或修改 + */ + @PostMapping("/submit") + @ApiOperationSupport(order = 3) + @Operation(summary = "新增或修改车辆档案", description = "直接写档案、立即生效,不生成待审快照,也不改动认证状态") + public R submit(@Valid @RequestBody TransportVehicle vehicle) { + if (!canManage()) { + return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG); + } + // 生效指针只由认证审核流水写(通过才建立生效快照),不接受管理端写入 + vehicle.setEffectiveCertRecordId(null); + return R.status(transportVehicleService.submit(vehicle)); + } + + /** + * 启停用 + */ + @PostMapping("/status") + @ApiOperationSupport(order = 4) + @Operation(summary = "启用 / 停用车辆", description = "status:1 启用,2 停用。停用车辆不进车辆检索、司机端车辆列表不再出现") + public R status(@Parameter(description = "主键", required = true) @RequestParam Long id, + @Parameter(description = "状态:1 启用,2 停用", required = true) @RequestParam Integer status) { + if (!canManage()) { + return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG); + } + return R.status(transportVehicleService.changeStatus(id, status)); + } + + private boolean canManage() { + return mpUserDataScopeSupport.isMpDispatcher(); + } + + private Query normalizeQuery(Query query) { + if (query == null) { + query = new Query(); + } + if (query.getCurrent() == null || query.getCurrent() < DEFAULT_CURRENT) { + query.setCurrent(DEFAULT_CURRENT); + } + if (query.getSize() == null || query.getSize() <= 0) { + query.setSize(DEFAULT_SIZE); + } + if (query.getSize() > MAX_SIZE) { + query.setSize(MAX_SIZE); + } + return query; + } + + private void fillExpiryDate(TransportVehicleVO vehicle) { + if (vehicle.getToday() == null) { + vehicle.setToday(LocalDate.now()); + } + if (vehicle.getWarningDate() == null) { + vehicle.setWarningDate(vehicle.getToday().plusDays(30)); + } + } + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/PaymentApplicationController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/PaymentApplicationController.java index 56bc27de..f2003c5f 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/PaymentApplicationController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/PaymentApplicationController.java @@ -114,7 +114,7 @@ public class PaymentApplicationController extends BladeController { } PaymentApplication payment = paymentApplicationService.getById(id); String specStatus = payment == null ? null : payment.getHcsSpecStatus(); - if (HcsSyncConstant.SPEC_STATUS_NEED.equals(specStatus)) { + if (hcsImmediatePushSupport.isSpecPayEnabled() && HcsSyncConstant.SPEC_STATUS_NEED.equals(specStatus)) { return R.success("审批通过,付款特批待发起,可在详情页重推合规"); } if (Arrays.asList(HcsSyncConstant.SPEC_STATUS_PUSHING, HcsSyncConstant.SPEC_STATUS_PROCESSING).contains(specStatus)) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ContractManageExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ContractManageExcel.java index 2e9fdbc0..11eccfd6 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ContractManageExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ContractManageExcel.java @@ -66,6 +66,8 @@ public class ContractManageExcel implements Serializable { private String partyA; @ExcelProperty("乙方") private String partyB; + @ExcelProperty("丙方") + private String partyC; @ExcelProperty("开始日期") private LocalDate startDate; @ExcelProperty("结束日期") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/listener/KingdeeApprovalSyncListener.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/listener/KingdeeApprovalSyncListener.java index 4657465c..491f5a36 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/listener/KingdeeApprovalSyncListener.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/listener/KingdeeApprovalSyncListener.java @@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j; import org.springblade.core.tool.api.R; import org.springblade.transport.config.BfiProperties; import org.springblade.transport.constant.HcsSyncConstant; +import org.springblade.hcs.config.HcsProperties; import org.springblade.transport.event.KingdeeBillApprovedEvent; import org.springblade.transport.pojo.entity.PaymentApplication; import org.springblade.transport.service.IFormalSettlementService; @@ -65,6 +66,7 @@ public class KingdeeApprovalSyncListener { private final IInvoiceApplicationService invoiceApplicationService; private final IInvoiceReceiptService invoiceReceiptService; private final BfiProperties bfiProperties; + private final HcsProperties hcsProperties; /** 当前审批请求线程的自动推送失败原因,供 Controller 取走提示前端(见类注释线程说明) */ private static final ThreadLocal SYNC_FAILURE = new ThreadLocal<>(); @@ -183,9 +185,10 @@ public class KingdeeApprovalSyncListener { return; } // 付款特批未完成(高风险/黑名单/额度超额)时金蝶必然拒推:跳过而非置失败, - // 特批通过后由 HcsPaymentSyncSupport 重新发布本事件自动补推 + // 特批通过后由 HcsPaymentSyncSupport 重新发布本事件自动补推; + // 特批不走合同系统(spec-pay.enabled=false)时不跳过 PaymentApplication payment = paymentApplicationService.getById(id); - if (payment != null && payment.getHcsSpecStatus() != null + if (hcsProperties.getSpecPay().isEnabled() && payment != null && payment.getHcsSpecStatus() != null && List.of(HcsSyncConstant.SPEC_STATUS_NEED, HcsSyncConstant.SPEC_STATUS_PUSHING, HcsSyncConstant.SPEC_STATUS_PROCESSING).contains(payment.getHcsSpecStatus())) { log.info("付款申请[{}]审批通过自动同步金蝶跳过(付款特批未完成):hcsSpecStatus={}", id, payment.getHcsSpecStatus()); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/DriverMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/DriverMapper.xml index 2f1b5e2a..1cdc59b7 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/DriverMapper.xml +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/DriverMapper.xml @@ -147,6 +147,11 @@ is_deleted = 0 + + + + AND (driver_name LIKE #{driverKeywordLike} OR mobile LIKE #{driverKeywordLike}) + AND driver_name LIKE #{driverNameLike} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MaintenanceRecordMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MaintenanceRecordMapper.xml index 527af228..01a85e04 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MaintenanceRecordMapper.xml +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MaintenanceRecordMapper.xml @@ -14,6 +14,7 @@ + @@ -27,55 +28,63 @@ + + + diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ManageArchiveDeptMapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ManageArchiveDeptMapper.java new file mode 100644 index 00000000..73ae3d92 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ManageArchiveDeptMapper.java @@ -0,0 +1,26 @@ +package org.springblade.transport.mapper; + +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.springblade.system.pojo.entity.Dept; + +import java.util.List; + +/** + * 组织树只读查询(小程序组织选择器) + *

+ * 不复用 blade-system 的部门接口:那些接口全挂在 {@code @PreAuth(menu = "dept")} 下, + * 小程序令牌的 roleId 是占位值 "0"、查不到 blade_role_menu,任何小程序账号都会被拒。 + * 这里直接按租户读表,只取组织选择需要的四列。 + */ +public interface ManageArchiveDeptMapper { + + /** + * 租户内启用中的部门,按排序号升序(不含已删除) + */ + @Select("SELECT id, parent_id AS parentId, dept_name AS deptName, sort FROM blade_dept " + + "WHERE is_deleted = 0 AND status = 1 AND tenant_id = #{tenantId} " + + "ORDER BY sort, id") + List selectEnabledByTenant(@Param("tenantId") String tenantId); + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/TransportVehicleMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/TransportVehicleMapper.xml index 0d5857fd..f261e329 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/TransportVehicleMapper.xml +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/TransportVehicleMapper.xml @@ -116,6 +116,11 @@ is_deleted = 0 + + + + AND (plate_no LIKE #{vehicleKeywordLike} OR vin LIKE #{vehicleKeywordLike} OR owner_name LIKE #{vehicleKeywordLike}) +