diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/controller/AgencyController.java b/server/src/main/java/com/aisino/iles/lawenforcement/controller/AgencyController.java new file mode 100644 index 0000000..240b63e --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/controller/AgencyController.java @@ -0,0 +1,149 @@ +package com.aisino.iles.lawenforcement.controller; + +import com.aisino.iles.common.model.Fail; +import com.aisino.iles.common.model.Ok; +import com.aisino.iles.common.model.PageResult; +import com.aisino.iles.common.model.Result; +import com.aisino.iles.lawenforcement.model.Agency; +import com.aisino.iles.lawenforcement.model.query.AgencyQuery; +import com.aisino.iles.lawenforcement.service.AgencyService; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 综合执法/执法机构管理 + */ +@RestController +@RequestMapping("/api/lawenforcement/agencies") +public class AgencyController { + + private final AgencyService agencyService; + + public AgencyController(AgencyService agencyService) { + this.agencyService = agencyService; + } + + /** + * 创建执法机构 + * + * @param agency 执法机构信息 + * @return 创建结果 + */ + @PostMapping + public Result createAgency(@RequestBody Agency agency) { + return Ok.of(agencyService.saveAgency(agency)); + } + + + + /** + * 根据ID查询执法机构 + * + * @param agencyId 执法机构ID + * @return 执法机构信息 + */ + @GetMapping("/{agencyId}") + public Result getAgencyById(@PathVariable String agencyId) { + return agencyService.findAgencyById(agencyId) + .map(a->((Result) Ok.of(a))) + .orElse(Fail.of("执法机构不存在")); + } + + /** + * 根据执法机构代码查询执法机构 + * + * @param agencyCode 执法机构代码 + * @return 执法机构信息 + */ + @GetMapping("/code/{agencyCode}") + public Result getAgencyByCode(@PathVariable String agencyCode) { + return agencyService.findAgencyByAgencyCode(agencyCode) + .map(a->((Result) Ok.of(a))) + .orElse(Fail.of("执法机构不存在")); + } + + + + /** + * 分页查询执法机构 + * + * @return 分页执法机构信息 + */ + @GetMapping + public PageResult getAgenciesPage(AgencyQuery agencyQuery) { + return PageResult.of(agencyService.findAgenciesPage(agencyQuery)); + } + + /** + * 更新执法机构 + * + * @param agencyId 执法机构ID + * @param agency 执法机构信息 + * @return 更新结果 + */ + @PutMapping("/{agencyId}") + public Result updateAgency(@PathVariable String agencyId, @RequestBody Agency agency) { + if (!agencyService.existsAgencyById(agencyId)) { + return Fail.of("执法机构不存在"); + } + agency.setAgencyId(agencyId); + return Ok.of(agencyService.saveAgency(agency)); + } + + /** + * 根据ID删除执法机构 + * + * @param agencyId 执法机构ID + * @return 删除结果 + */ + @DeleteMapping("/{agencyId}") + public Result deleteAgencyById(@PathVariable String agencyId) { + if (!agencyService.existsAgencyById(agencyId)) { + return Fail.of("执法机构不存在"); + } + agencyService.deleteAgencyById(agencyId); + return Ok.of(); + } + + /** + * 批量删除执法机构 + * + * @param agencyIds 执法机构ID列表 + * @return 删除结果 + */ + @DeleteMapping + public Result deleteAgenciesByIds(@RequestBody List agencyIds) { + agencyService.deleteAgenciesByIds(agencyIds); + return Ok.of(); + } + + /** + * 查询执法机构列表 + * + * @return 执法机构列表 + */ + @GetMapping("/list") + public Result> getAgencies(AgencyQuery query) { + return Ok.of(agencyService.findAgencies(query)); + } + + /** + * 查询执法机构列表(树形) + * @param query 查询条件 + * @return 执法机构列表(树形) + */ + @GetMapping("/tree") + public Result> getAgenciesTree(AgencyQuery query) { + return Ok.of(agencyService.findAgenciesTree(query)); + } + + /** + * 获取指定父机构下的下一个排序号 + * @param parentId 上级机构ID,根节点传 0 或不传 + */ + @GetMapping("/nextOrderNum") + public Result getNextOrderNum(@RequestParam(required = false) String parentId) { + return Ok.of(agencyService.getNextOrderNum(parentId)); + } +} diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/repository/AgencyRepository.java b/server/src/main/java/com/aisino/iles/lawenforcement/repository/AgencyRepository.java new file mode 100644 index 0000000..66c2568 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/repository/AgencyRepository.java @@ -0,0 +1,55 @@ +package com.aisino.iles.lawenforcement.repository; + +import com.aisino.iles.core.repository.BaseRepo; +import com.aisino.iles.lawenforcement.model.Agency; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +/** + * 执法机构仓库接口 + */ +@Repository +public interface AgencyRepository extends BaseRepo { + + /** + * 根据执法机构代码查询执法机构 + * + * @param agencyCode 执法机构代码 + * @return 执法机构信息 + */ + Optional findByAgencyCode(String agencyCode); + + /** + * 根据执法机构名称查询执法机构 + * + * @param agencyName 执法机构名称 + * @return 执法机构信息 + */ + Optional findByAgencyName(String agencyName); + + List findByAgencyId(String parentId); + + List findByParentIsNull(); + + List findByParentAgencyId(String agencyId); + + @Query("select a from Agency a where a.agencyCode like :agencyCode and a.agencyLevel = :agencyLevel") + List findLikeAgencyCode(String agencyCode, Integer agencyLevel); + + /** + * 查询指定父机构下的最大排序号 + */ + @Query("select coalesce(max(a.orderNum),0) from Agency a where a.parent.agencyId = :parentId") + Integer findMaxOrderNumByParentId(@Param("parentId") String parentId); + + /** + * 查询根节点(父为空)下的最大排序号 + */ + @Query("select coalesce(max(a.orderNum),0) from Agency a where a.parent is null") + Integer findMaxOrderNumOfRoot(); + +} diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/service/AgencyService.java b/server/src/main/java/com/aisino/iles/lawenforcement/service/AgencyService.java new file mode 100644 index 0000000..9cc5110 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/service/AgencyService.java @@ -0,0 +1,425 @@ +package com.aisino.iles.lawenforcement.service; + +import cn.hutool.core.util.StrUtil; +import com.aisino.iles.common.util.BeanUtils; +import com.aisino.iles.common.util.PageableHelper; +import com.aisino.iles.common.util.StringUtils; +import com.aisino.iles.lawenforcement.model.Agency; +import com.aisino.iles.lawenforcement.model.Agency_; +import com.aisino.iles.lawenforcement.model.query.AgencyQuery; +import com.aisino.iles.lawenforcement.repository.AgencyRepository; +import com.aisino.iles.lawenforcement.repository.CaseRepository; +import com.aisino.iles.lawenforcement.repository.OfficerRepository; +import jakarta.persistence.criteria.Predicate; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * 执法机构服务类 + */ +@Service +@Slf4j +@Transactional(readOnly = true) +public class AgencyService { + + private final AgencyRepository agencyRepository; + private final OfficerRepository officerRepository; + private final CaseRepository caseRepository; + + public AgencyService(AgencyRepository agencyRepository, OfficerRepository officerRepository, CaseRepository caseRepository) { + this.agencyRepository = agencyRepository; + this.officerRepository = officerRepository; + this.caseRepository = caseRepository; + } + + /** + * 保存执法机构信息 + * + * @param agency 执法机构信息 + * @return 保存后的执法机构信息 + */ + @Transactional + public Agency saveAgency(Agency agency) { + // 基础校验与去空格 + String name = StrUtil.trimToNull(agency.getAgencyName()); + String code = StrUtil.trimToNull(agency.getAgencyCode()); + + if (StrUtil.isBlank(name)) { + throw new IllegalArgumentException("机构名称不能为空"); + } + if (StrUtil.isBlank(code)) { + throw new IllegalArgumentException("机构代码不能为空"); + } + + agency.setAgencyName(name); + agency.setAgencyCode(code); + + // 生成机构简码(去偶数0),若前端未传 + if (StrUtil.isBlank(agency.getAgencySimpleCode())) { + agency.setAgencySimpleCode(StringUtils.trimEven0(code)); + } + + boolean isCreate = StrUtil.isBlank(agency.getAgencyId()); + + // 唯一性校验:名称 & 代码 + agencyRepository.findByAgencyName(agency.getAgencyName()) + .filter(existing -> isCreate || !existing.getAgencyId().equals(agency.getAgencyId())) + .ifPresent(e -> { + throw new IllegalStateException("机构名称已存在"); + }); + agencyRepository.findByAgencyCode(agency.getAgencyCode()) + .filter(existing -> isCreate || !existing.getAgencyId().equals(agency.getAgencyId())) + .ifPresent(e -> { + throw new IllegalStateException("机构代码已存在"); + }); + + // 解析上级机构(支持仅携带 agencyId 的 parent 对象,或使用 transient 的 parentAgencyId) + Agency parent = null; + String parentAgencyId = agency.getParent() != null ? agency.getParent().getAgencyId() : null; + + + if (StrUtil.isNotBlank(parentAgencyId)) { + parent = agencyRepository.findById(parentAgencyId) + .orElseThrow(() -> new IllegalArgumentException("上级机构不存在")); + agency.setParent(parent); + } else { + agency.setParent(null); + } + + if (isCreate) { + // 新增流程 + agency.setLeaf(true); + agency.setAgencyLevel(parent == null ? 1 : (Optional.ofNullable(parent.getAgencyLevel()).orElse(0) + 1)); + + Agency saved = agencyRepository.save(agency); + + // 计算并设置机构路径(沿父链使用机构简码拼接) + saved.setAgencyPath(computeAgencyPath(saved)); + + // 更新父节点 leaf 状态 + if (parent != null && Boolean.TRUE.equals(parent.getLeaf())) { + parent.setLeaf(false); + agencyRepository.save(parent); + } + + return agencyRepository.save(saved); + } else { + // 修改流程 + Agency existing = agencyRepository.findById(agency.getAgencyId()) + .orElseThrow(() -> new IllegalArgumentException("执法机构不存在")); + + Agency oldParent = existing.getParent(); + Agency newParent = parent; // 可能为 null + + boolean parentChanged = (oldParent == null && newParent != null) + || (oldParent != null && (newParent == null || !oldParent.getAgencyId().equals(newParent.getAgencyId()))); + + // 防止循环:新父节点不能在当前节点的子树中 + if (parentChanged && newParent != null && isDescendant(newParent, existing.getAgencyId())) { + throw new IllegalStateException("不允许将机构设置为自己的子机构"); + } + + // 应用基础字段更新 + existing.setAgencyName(agency.getAgencyName()); + existing.setAgencyCode(agency.getAgencyCode()); + existing.setAgencySimpleCode(agency.getAgencySimpleCode()); + existing.setOrderNum(agency.getOrderNum()); + existing.setParent(newParent); + + // 重新计算当前节点 level 和 path + existing.setAgencyLevel(newParent == null ? 1 : (Optional.ofNullable(newParent.getAgencyLevel()).orElse(0) + 1)); + existing.setAgencyPath(computeAgencyPath(existing)); + + Agency saved = agencyRepository.save(existing); + + // 递归更新所有子节点 level 和 path + updateChildrenLevelsAndPaths(saved); + + // 维护父节点 leaf 状态 + if (oldParent != null) { + boolean hasOtherChildren = !agencyRepository.findByParentAgencyId(oldParent.getAgencyId()).isEmpty(); + if (!hasOtherChildren) { + oldParent.setLeaf(true); + agencyRepository.save(oldParent); + } + } + if (newParent != null) { + newParent.setLeaf(false); + agencyRepository.save(newParent); + } + + // 维护当前节点 leaf 状态 + boolean hasChildren = !agencyRepository.findByParentAgencyId(saved.getAgencyId()).isEmpty(); + saved.setLeaf(!hasChildren); + return agencyRepository.save(saved); + } + } + + /** + * 批量保存执法机构信息 + * + * @param agencies 执法机构信息列表 + * @return 保存后的执法机构信息列表 + */ + @Transactional + public List saveAgencies(List agencies) { + return agencyRepository.saveAll(agencies); + } + + /** + * 根据ID查询执法机构信息 + * + * @param agencyId 执法机构ID + * @return 执法机构信息 + */ + public Optional findAgencyById(String agencyId) { + return agencyRepository.findById(agencyId).map(a -> { + Agency agency = new Agency(); + BeanUtils.copyProperties(a, agency, Agency_.PARENT, Agency_.CHILDREN); + Agency parent = new Agency(); + BeanUtils.copyProperties(a.getParent(), parent, Agency_.PARENT, Agency_.CHILDREN); + agency.setParent(parent); + return agency; + }); + } + + /** + * 根据执法机构代码查询执法机构信息 + * + * @param agencyCode 执法机构代码 + * @return 执法机构信息 + */ + public Optional findAgencyByAgencyCode(String agencyCode) { + return agencyRepository.findByAgencyCode(agencyCode); + } + + + /** + * 分页查询执法机构信息 + * + * @param query 分页请求 + * @return 分页执法机构信息 + */ + public Page findAgenciesPage(AgencyQuery query) { + return agencyRepository.findAll(buildSpec(query), PageableHelper.buildPageRequest(query.page(), query.pageSize(), query.sort(), query.dir())); + } + + private Specification buildSpec(AgencyQuery agencyQuery) { + return (root, query, builder) -> { + List predicates = Stream.of( + Optional.ofNullable(agencyQuery.getAgencyId()) + .map(StrUtil::trimToNull) + .map(a -> builder.equal(root.get(Agency_.agencyId), a)), + Optional.ofNullable(agencyQuery.getAgencyCode()) + .map(StrUtil::trimToNull) + .map(a -> builder.equal(root.get(Agency_.agencyCode), a)), + Optional.ofNullable(agencyQuery.getAgencyName()) + .map(StrUtil::trimToNull) + .map(a -> builder.like(root.get(Agency_.agencyName), "%" + a + "%")), + Optional.ofNullable(agencyQuery.getParentId()) + .map(StrUtil::trimToNull) + .map(p -> builder.equal(root.get(Agency_.parent).get(Agency_.agencyId), p)) + ).filter(Optional::isPresent) + .map(Optional::get) + .toList(); + return builder.and(predicates.toArray(new Predicate[0])); + }; + } + + /** + * 条件查询执法机构信息 + * + * @param agencyQuery@return 执法机构信息列表 + */ + public List findAgencies(AgencyQuery agencyQuery) { + return agencyRepository.findAll(buildSpec(agencyQuery)); + } + + + /** + * 根据ID删除执法机构信息 + * + * @param agencyId 执法机构ID + */ + @Transactional + public void deleteAgencyById(String agencyId) { + Agency agency = agencyRepository.findById(agencyId) + .orElseThrow(() -> new IllegalArgumentException("执法机构不存在")); + + // 1. 有无子机构 + if (!agencyRepository.findByParentAgencyId(agencyId).isEmpty()) { + throw new IllegalStateException("存在子机构,不允许删除"); + } + // 2. 是否有关联执法人员 + if (!officerRepository.findByAgencyId(agencyId).isEmpty()) { + throw new IllegalStateException("存在关联执法人员,不允许删除"); + } + // 3. 是否有关联案件 + if (caseRepository.existsByAgencyId(agencyId)) { + throw new IllegalStateException("存在关联案件,不允许删除"); + } + + // 删除 + agencyRepository.deleteById(agencyId); + + // 更新上级 leaf 状态 + Agency parent = agency.getParent(); + if (parent != null) { + boolean hasOtherChildren = !agencyRepository.findByParentAgencyId(parent.getAgencyId()).isEmpty(); + if (!hasOtherChildren) { + parent.setLeaf(true); + agencyRepository.save(parent); + } + } + } + + /** + * 批量删除执法机构信息 + * + * @param agencyIds 执法机构ID列表 + */ + @Transactional + public void deleteAgenciesByIds(List agencyIds) { + for (String id : agencyIds) { + deleteAgencyById(id); + } + } + + /** + * 检查执法机构是否存在 + * + * @param agencyId 执法机构ID + * @return 是否存在 + */ + public boolean existsAgencyById(String agencyId) { + return agencyRepository.existsById(agencyId); + } + + /** + * 获取执法机构总数 + * + * @return 执法机构总数 + */ + public long countAgencies() { + return agencyRepository.count(); + } + + /** + * 获取指定父机构下的下一个排序号 + * parentId 为空或等于 "0" 时,表示根节点 + */ + public Integer getNextOrderNum(String parentId) { + String pid = StrUtil.trimToNull(parentId); + if (pid == null || "0".equals(pid)) { + Integer max = Optional.ofNullable(agencyRepository.findMaxOrderNumOfRoot()).orElse(0); + return max + 1; + } + // 校验父节点 + if (!agencyRepository.existsById(pid)) { + throw new IllegalArgumentException("上级机构不存在"); + } + Integer max = Optional.ofNullable(agencyRepository.findMaxOrderNumByParentId(pid)).orElse(0); + return max + 1; + } + + public List findAgenciesTree(AgencyQuery query) { + List result = new ArrayList<>(); + + List rootAgencies; + if (StrUtil.isNotBlank(query.getParentId())) { + rootAgencies = agencyRepository.findByParentAgencyId(query.getParentId()); + } else if (StrUtil.isNotBlank(query.getAgencyId())) { + rootAgencies = new ArrayList<>(); + agencyRepository.findById(query.getAgencyId()).ifPresent(rootAgencies::add); + } else { + // 顶级机构:parent 为空 + rootAgencies = agencyRepository.findByParentIsNull(); + } + + for (Agency root : rootAgencies) { + populateChildrenRecursively(root, 0, 50); + result.add(root); + } + + return result; + } + + /** + * 递归填充子机构 + */ + private void populateChildrenRecursively(Agency parent, int depth, int maxDepth) { + if (parent == null || depth > maxDepth) return; + List children = agencyRepository.findByParentAgencyId(parent.getAgencyId()); + parent.setChildren(children); + if (children == null || children.isEmpty()) { + parent.setLeaf(true); + return; + } + parent.setLeaf(false); + for (Agency child : children) { + populateChildrenRecursively(child, depth + 1, maxDepth); + } + } + + /** + * 递归更新所有子节点的 level 和 path + */ + private void updateChildrenLevelsAndPaths(Agency parent) { + List children = agencyRepository.findByParentAgencyId(parent.getAgencyId()); + for (Agency child : children) { + child.setAgencyLevel(Optional.ofNullable(parent.getAgencyLevel()).orElse(0) + 1); + child.setAgencyPath(computeAgencyPath(child)); + agencyRepository.save(child); + updateChildrenLevelsAndPaths(child); + } + } + + /** + * 计算机构路径:沿父链使用机构简码拼接,避免依赖数据库中已保存的路径。 + */ + private String computeAgencyPath(Agency current) { + if (current == null) return null; + List parts = new ArrayList<>(); + Agency cursor = current; + int depth = 0; + final int maxDepth = 100; + while (cursor != null && depth < maxDepth) { + String part = StrUtil.trimToNull(cursor.getAgencySimpleCode()); + if (part == null) { + part = StringUtils.trimEven0(StrUtil.emptyIfNull(cursor.getAgencyCode())); + cursor.setAgencySimpleCode(part); + } + parts.add(part); + cursor = cursor.getParent(); + depth++; + } + Collections.reverse(parts); + return String.join(".", parts); + } + + /** + * 判断 candidate 是否在 ancestorId 对应机构的子树中(用于防止循环引用)。 + */ + private boolean isDescendant(Agency candidate, String ancestorId) { + Agency cursor = candidate; + int depth = 0; + final int maxDepth = 100; + while (cursor != null && depth < maxDepth) { + if (ancestorId.equals(cursor.getAgencyId())) { + return true; + } + cursor = cursor.getParent(); + depth++; + } + return false; + } +}