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;
|
|
/**
|
* <p>
|
* 总账科目表 服务实现类
|
* </p>
|
*
|
* @author 芯导软件(江苏)有限公司
|
* @since 2026-05-07 04:45:30
|
*/
|
@Service
|
@RequiredArgsConstructor
|
public class AccountSubjectServiceImpl extends ServiceImpl<AccountSubjectMapper, AccountSubject> implements AccountSubjectService {
|
|
private final AccountSubjectMapper accountSubjectMapper;
|
|
@Override
|
public IPage<AccountSubjectVo> baseList(Page<AccountSubjectDto> page, AccountSubjectDto accountSubjectDto) {
|
Page<AccountSubjectDto> requestPage = page == null ? new Page<>(1, 10) : page;
|
List<AccountSubject> allSubjects = list(loadBaseQueryWrapper(accountSubjectDto));
|
List<AccountSubject> filteredSubjects = applyTreeFilter(allSubjects, accountSubjectDto);
|
List<AccountSubjectVo> 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<AccountSubjectVo> pagedRoots = fromIndex >= toIndex
|
? Collections.emptyList()
|
: fullTree.subList(fromIndex, toIndex);
|
|
Page<AccountSubjectVo> 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<Long> ids) {
|
if (ids == null || ids.isEmpty()) {
|
return true;
|
}
|
List<AccountSubject> allSubjects = list();
|
if (allSubjects == null || allSubjects.isEmpty()) {
|
return true;
|
}
|
Map<Long, List<Long>> childrenIdMap = buildChildrenIdMap(allSubjects);
|
Set<Long> removeIds = new LinkedHashSet<>();
|
for (Long id : ids) {
|
collectDescendantIds(id, childrenIdMap, removeIds);
|
}
|
if (removeIds.isEmpty()) {
|
return true;
|
}
|
List<String> 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<AccountSubject> list = accountSubjectMapper.selectList(null);
|
List<AccountSubjectImportDto> importDtos = list.stream().map(accountSubject -> {
|
AccountSubjectImportDto accountSubjectImportDto = new AccountSubjectImportDto();
|
BeanUtils.copyProperties(accountSubject, accountSubjectImportDto);
|
return accountSubjectImportDto;
|
}).collect(Collectors.toList());
|
ExcelUtil<AccountSubjectImportDto> 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<AccountSubject> 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<AccountSubject> loadBaseQueryWrapper(AccountSubjectDto accountSubjectDto) {
|
LambdaQueryWrapper<AccountSubject> 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<AccountSubject> applyTreeFilter(List<AccountSubject> 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<Long, AccountSubject> subjectMap = allSubjects.stream()
|
.filter(item -> item.getId() != null)
|
.collect(Collectors.toMap(AccountSubject::getId, item -> item, (a, b) -> a, LinkedHashMap::new));
|
Map<Long, List<AccountSubject>> childrenMap = buildChildrenMap(allSubjects);
|
|
Set<Long> 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<Long> 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<Long, AccountSubject> subjectMap, Set<Long> 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<Long, List<AccountSubject>> childrenMap, Set<Long> resultIds) {
|
List<AccountSubject> 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<Long, List<AccountSubject>> buildChildrenMap(List<AccountSubject> subjects) {
|
Map<Long, List<AccountSubject>> 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<AccountSubjectVo> buildTree(List<AccountSubject> subjects) {
|
if (subjects == null || subjects.isEmpty()) {
|
return Collections.emptyList();
|
}
|
List<AccountSubject> sortedSubjects = new ArrayList<>(subjects);
|
sortedSubjects.sort(Comparator
|
.comparing(AccountSubject::getSubjectCode, Comparator.nullsLast(String::compareTo))
|
.thenComparing(AccountSubject::getId, Comparator.nullsLast(Long::compareTo)));
|
|
Map<Long, AccountSubjectVo> 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<AccountSubjectVo> 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<AccountSubjectVo> nodes) {
|
for (AccountSubjectVo node : nodes) {
|
List<AccountSubjectVo> 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<Long> 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<Long, List<Long>> buildChildrenIdMap(List<AccountSubject> subjects) {
|
Map<Long, List<Long>> 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<Long, List<Long>> childrenIdMap, Set<Long> result) {
|
if (id == null || !result.add(id)) {
|
return;
|
}
|
List<Long> children = childrenIdMap.getOrDefault(id, Collections.emptyList());
|
for (Long childId : children) {
|
collectDescendantIds(childId, childrenIdMap, result);
|
}
|
}
|
}
|