package com.ruoyi.account.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.account.bean.dto.AccountSubjectDto; import com.ruoyi.account.bean.dto.AccountSubjectImportDto; import com.ruoyi.account.bean.vo.AccountSubjectVo; import com.ruoyi.account.mapper.AccountSubjectMapper; import com.ruoyi.account.pojo.AccountSubject; import com.ruoyi.account.service.AccountSubjectService; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; /** *

* 总账科目表 服务实现类 *

* * @author 芯导软件(江苏)有限公司 * @since 2026-05-07 04:45:30 */ @Service @RequiredArgsConstructor public class AccountSubjectServiceImpl extends ServiceImpl implements AccountSubjectService { private final AccountSubjectMapper accountSubjectMapper; @Override public IPage baseList(Page page, AccountSubjectDto accountSubjectDto) { Page requestPage = page == null ? new Page<>(1, 10) : page; List allSubjects = list(loadBaseQueryWrapper(accountSubjectDto)); List filteredSubjects = applyTreeFilter(allSubjects, accountSubjectDto); List fullTree = buildTree(filteredSubjects); long current = requestPage.getCurrent() <= 0 ? 1 : requestPage.getCurrent(); long size = requestPage.getSize() <= 0 ? 10 : requestPage.getSize(); int fromIndex = (int) Math.min((current - 1) * size, fullTree.size()); int toIndex = (int) Math.min(fromIndex + size, fullTree.size()); List pagedRoots = fromIndex >= toIndex ? Collections.emptyList() : fullTree.subList(fromIndex, toIndex); Page resultPage = new Page<>(current, size, fullTree.size()); resultPage.setRecords(pagedRoots); return resultPage; } @Override public Boolean saveAccountSubject(AccountSubjectDto accountSubjectDto) { validateSubjectRequiredFields(accountSubjectDto); validateSubjectCodeUnique(accountSubjectDto, false); validateParent(accountSubjectDto.getParentId(), null); if (accountSubjectDto.getStatus() == null) { accountSubjectDto.setStatus(0); } return save(accountSubjectDto); } @Override public Boolean updateAccountSubject(AccountSubjectDto accountSubjectDto) { if (accountSubjectDto == null || accountSubjectDto.getId() == null) { throw new ServiceException("修改失败,科目ID不能为空"); } if (getById(accountSubjectDto.getId()) == null) { throw new ServiceException("修改失败,未找到对应科目"); } validateParent(accountSubjectDto.getParentId(), accountSubjectDto.getId()); validateSubjectRequiredFields(accountSubjectDto); validateSubjectCodeUnique(accountSubjectDto, true); return updateById(accountSubjectDto); } @Override public Boolean removeAccountSubjectByIds(List ids) { if (ids == null || ids.isEmpty()) { return true; } List allSubjects = list(); if (allSubjects == null || allSubjects.isEmpty()) { return true; } Map> childrenIdMap = buildChildrenIdMap(allSubjects); Set removeIds = new LinkedHashSet<>(); for (Long id : ids) { collectDescendantIds(id, childrenIdMap, removeIds); } if (removeIds.isEmpty()) { return true; } List subjectCodes = allSubjects.stream() .filter(subject -> removeIds.contains(subject.getId())) .map(AccountSubject::getSubjectCode) .filter(StringUtils::isNotEmpty) .collect(Collectors.toList()); if (!subjectCodes.isEmpty()) { Long referencedCount = accountSubjectMapper.countReferencedBySubjectCodes(subjectCodes); if (referencedCount != null && referencedCount > 0) { throw new ServiceException("删除失败,科目已被凭证分录引用"); } } return removeByIds(removeIds); } @Override public void exportAccountSubject(HttpServletResponse response) { List list = accountSubjectMapper.selectList(null); List importDtos = list.stream().map(accountSubject -> { AccountSubjectImportDto accountSubjectImportDto = new AccountSubjectImportDto(); BeanUtils.copyProperties(accountSubject, accountSubjectImportDto); return accountSubjectImportDto; }).collect(Collectors.toList()); ExcelUtil util = new ExcelUtil<>(AccountSubjectImportDto.class); util.exportExcel(response, importDtos , "总账科目"); } /** * 校验科目必填字段,避免脏数据写入。 */ private void validateSubjectRequiredFields(AccountSubjectDto accountSubjectDto) { if (accountSubjectDto == null) { throw new ServiceException("总账科目数据不能为空"); } if (StringUtils.isEmpty(accountSubjectDto.getSubjectCode())) { throw new ServiceException("科目编码不能为空"); } if (StringUtils.isEmpty(accountSubjectDto.getSubjectName())) { throw new ServiceException("科目名称不能为空"); } if (StringUtils.isEmpty(accountSubjectDto.getSubjectType())) { throw new ServiceException("科目类型不能为空"); } } /** * 校验科目编码唯一,新增和修改都要执行。 */ private void validateSubjectCodeUnique(AccountSubjectDto accountSubjectDto, boolean isUpdate) { LambdaQueryWrapper codeQueryWrapper = new LambdaQueryWrapper<>(); codeQueryWrapper.eq(AccountSubject::getSubjectCode, accountSubjectDto.getSubjectCode()); if (isUpdate) { codeQueryWrapper.ne(AccountSubject::getId, accountSubjectDto.getId()); } AccountSubject exists = getOne(codeQueryWrapper, false); if (Objects.nonNull(exists)) { throw new ServiceException("科目编码已存在,请勿重复提交"); } } /** * 仅按通用过滤条件查询基础数据(树形过滤后续再做)。 */ private LambdaQueryWrapper loadBaseQueryWrapper(AccountSubjectDto accountSubjectDto) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); if (accountSubjectDto != null && accountSubjectDto.getStatus() != null) { queryWrapper.eq(AccountSubject::getStatus, accountSubjectDto.getStatus()); } queryWrapper.orderByAsc(AccountSubject::getSubjectCode).orderByAsc(AccountSubject::getId); return queryWrapper; } /** * 树形过滤:命中节点后保留其父链与子树,保证递归结构完整。 */ private List applyTreeFilter(List allSubjects, AccountSubjectDto queryDto) { if (allSubjects == null || allSubjects.isEmpty()) { return Collections.emptyList(); } boolean hasFilter = queryDto != null && ( StringUtils.isNotEmpty(queryDto.getSubjectCode()) || StringUtils.isNotEmpty(queryDto.getSubjectName()) || StringUtils.isNotEmpty(queryDto.getSubjectType()) ); if (!hasFilter) { return allSubjects; } Map subjectMap = allSubjects.stream() .filter(item -> item.getId() != null) .collect(Collectors.toMap(AccountSubject::getId, item -> item, (a, b) -> a, LinkedHashMap::new)); Map> childrenMap = buildChildrenMap(allSubjects); Set matchedIds = new LinkedHashSet<>(); for (AccountSubject subject : allSubjects) { if (subject.getId() == null) { continue; } if (matchesFilter(subject, queryDto)) { matchedIds.add(subject.getId()); } } if (matchedIds.isEmpty()) { return Collections.emptyList(); } Set resultIds = new LinkedHashSet<>(matchedIds); for (Long matchedId : matchedIds) { addAncestors(matchedId, subjectMap, resultIds); addDescendants(matchedId, childrenMap, resultIds); } return allSubjects.stream() .filter(item -> item.getId() != null && resultIds.contains(item.getId())) .collect(Collectors.toList()); } private boolean matchesFilter(AccountSubject subject, AccountSubjectDto queryDto) { if (queryDto == null) { return true; } if (StringUtils.isNotEmpty(queryDto.getSubjectCode()) && (subject.getSubjectCode() == null || !subject.getSubjectCode().contains(queryDto.getSubjectCode()))) { return false; } if (StringUtils.isNotEmpty(queryDto.getSubjectName()) && (subject.getSubjectName() == null || !subject.getSubjectName().contains(queryDto.getSubjectName()))) { return false; } if (StringUtils.isNotEmpty(queryDto.getSubjectType()) && !queryDto.getSubjectType().equals(subject.getSubjectType())) { return false; } return true; } private void addAncestors(Long subjectId, Map subjectMap, Set resultIds) { AccountSubject current = subjectMap.get(subjectId); if (current == null) { return; } Long parentId = current.getParentId(); while (parentId != null && parentId > 0) { AccountSubject parent = subjectMap.get(parentId); if (parent == null) { break; } if (!resultIds.add(parent.getId())) { break; } parentId = parent.getParentId(); } } private void addDescendants(Long subjectId, Map> childrenMap, Set resultIds) { List children = childrenMap.getOrDefault(subjectId, Collections.emptyList()); for (AccountSubject child : children) { if (child.getId() == null) { continue; } if (resultIds.add(child.getId())) { addDescendants(child.getId(), childrenMap, resultIds); } } } private Map> buildChildrenMap(List subjects) { Map> childrenMap = new HashMap<>(); for (AccountSubject subject : subjects) { if (subject.getId() == null) { continue; } Long parentId = subject.getParentId(); if (parentId == null || parentId <= 0) { continue; } childrenMap.computeIfAbsent(parentId, key -> new ArrayList<>()).add(subject); } return childrenMap; } /** * 基于 parentId 递归构建科目树。 */ private List buildTree(List subjects) { if (subjects == null || subjects.isEmpty()) { return Collections.emptyList(); } List sortedSubjects = new ArrayList<>(subjects); sortedSubjects.sort(Comparator .comparing(AccountSubject::getSubjectCode, Comparator.nullsLast(String::compareTo)) .thenComparing(AccountSubject::getId, Comparator.nullsLast(Long::compareTo))); Map subjectVoMap = new LinkedHashMap<>(); for (AccountSubject subject : sortedSubjects) { if (subject.getId() == null) { continue; } AccountSubjectVo vo = new AccountSubjectVo(); BeanUtils.copyProperties(subject, vo); subjectVoMap.put(subject.getId(), vo); } List roots = new ArrayList<>(); for (AccountSubject subject : sortedSubjects) { if (subject.getId() == null) { continue; } AccountSubjectVo current = subjectVoMap.get(subject.getId()); Long parentId = subject.getParentId(); if (parentId != null && parentId > 0 && subjectVoMap.containsKey(parentId)) { subjectVoMap.get(parentId).getChildren().add(current); } else { roots.add(current); } } markLeafRecursively(roots); return roots; } private void markLeafRecursively(List nodes) { for (AccountSubjectVo node : nodes) { List children = node.getChildren(); node.setLeaf(children == null || children.isEmpty()); if (children != null && !children.isEmpty()) { markLeafRecursively(children); } } } /** * 校验父子关系:父节点必须存在,且不能形成循环引用。 */ private void validateParent(Long parentId, Long currentId) { if (parentId == null || parentId <= 0) { return; } if (currentId != null && parentId.equals(currentId)) { throw new ServiceException("父科目不能选择自身"); } AccountSubject parent = getById(parentId); if (parent == null) { throw new ServiceException("父科目不存在,请重新选择"); } // 防止形成环:更新时,父节点不能是当前节点的任意子孙节点。 if (currentId != null) { Set visited = new HashSet<>(); Long traceParentId = parentId; while (traceParentId != null && traceParentId > 0) { if (!visited.add(traceParentId)) { throw new ServiceException("科目层级存在循环引用,请检查父科目设置"); } if (traceParentId.equals(currentId)) { throw new ServiceException("父科目不能是当前科目或其子科目"); } AccountSubject traceNode = getById(traceParentId); if (traceNode == null) { break; } traceParentId = traceNode.getParentId(); } } } private Map> buildChildrenIdMap(List subjects) { Map> map = new HashMap<>(); for (AccountSubject subject : subjects) { if (subject.getId() == null || subject.getParentId() == null || subject.getParentId() <= 0) { continue; } map.computeIfAbsent(subject.getParentId(), key -> new ArrayList<>()).add(subject.getId()); } return map; } /** * 收集待删除节点及其所有子孙节点。 */ private void collectDescendantIds(Long id, Map> childrenIdMap, Set result) { if (id == null || !result.add(id)) { return; } List children = childrenIdMap.getOrDefault(id, Collections.emptyList()); for (Long childId : children) { collectDescendantIds(childId, childrenIdMap, result); } } }