From 55d694aaae4e421b5e55cd941500e08f85cb5d4d Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 27 八月 2026 18:21:33 +0800
Subject: [PATCH] feat(sales): 添加框架合同订单功能并优化销售台账管理

---
 src/main/java/com/ruoyi/device/service/impl/DeviceAreaServiceImpl.java |  194 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 194 insertions(+), 0 deletions(-)

diff --git a/src/main/java/com/ruoyi/device/service/impl/DeviceAreaServiceImpl.java b/src/main/java/com/ruoyi/device/service/impl/DeviceAreaServiceImpl.java
new file mode 100644
index 0000000..06340f6
--- /dev/null
+++ b/src/main/java/com/ruoyi/device/service/impl/DeviceAreaServiceImpl.java
@@ -0,0 +1,194 @@
+package com.ruoyi.device.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.device.dto.DeviceAreaDto;
+import com.ruoyi.device.mapper.DeviceAreaMapper;
+import com.ruoyi.device.mapper.DeviceLedgerMapper;
+import com.ruoyi.device.pojo.DeviceArea;
+import com.ruoyi.device.pojo.DeviceLedger;
+import com.ruoyi.device.service.IDeviceAreaService;
+import com.ruoyi.device.vo.DeviceAreaTreeDto;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 璁惧鍖哄煙 Service 瀹炵幇
+ */
+@Service
+@RequiredArgsConstructor
+public class DeviceAreaServiceImpl extends ServiceImpl<DeviceAreaMapper, DeviceArea> implements IDeviceAreaService {
+
+    private final DeviceAreaMapper deviceAreaMapper;
+    private final DeviceLedgerMapper deviceLedgerMapper;
+
+    @Override
+    public List<DeviceAreaTreeDto> selectAreaTree() {
+        List<DeviceArea> all = this.list(new LambdaQueryWrapper<DeviceArea>().orderByAsc(DeviceArea::getSort));
+        if (all == null || all.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 缁熻姣忎釜鍖哄煙鐩存帴鎸傝浇鐨勮澶囨暟閲�
+        Map<Long, Long> directCountMap = deviceLedgerMapper.selectList(
+                        new LambdaQueryWrapper<DeviceLedger>()
+                                .select(DeviceLedger::getAreaId)
+                                .isNotNull(DeviceLedger::getAreaId))
+                .stream()
+                .filter(d -> d.getAreaId() != null)
+                .collect(Collectors.groupingBy(DeviceLedger::getAreaId, Collectors.counting()));
+
+        Map<Long, DeviceArea> areaMap = all.stream()
+                .collect(Collectors.toMap(DeviceArea::getId, a -> a));
+
+        // 閫掑綊姹囨�绘瘡涓妭鐐圭殑璁惧鏁伴噺锛堝惈鎵�鏈夊瓙绾э級
+        Map<Long, Long> countMap = directCountMap.entrySet().stream()
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+        for (DeviceArea area : all) {
+            accumulate(area.getId(), directCountMap.getOrDefault(area.getId(), 0L), countMap, areaMap);
+        }
+
+        List<Long> rootIds = all.stream()
+                .filter(a -> a.getParentId() == null || !areaMap.containsKey(a.getParentId()))
+                .map(DeviceArea::getId)
+                .collect(Collectors.toList());
+
+        List<DeviceAreaTreeDto> tree = new ArrayList<>();
+        for (Long rootId : rootIds) {
+            DeviceArea root = areaMap.get(rootId);
+            tree.add(buildNode(root, all, countMap));
+        }
+        return tree;
+    }
+
+    // 灏嗗綋鍓嶈妭鐐规暟閲忕疮鍔犲埌鎵�鏈夌绾ц妭鐐�
+    private void accumulate(Long areaId, Long count, Map<Long, Long> countMap, Map<Long, DeviceArea> areaMap) {
+        DeviceArea area = areaMap.get(areaId);
+        if (area == null) {
+            return;
+        }
+        countMap.merge(areaId, count, Long::sum);
+        // 娌跨鍏堥摼缁х画绱姞锛堥伩鍏嶇鍏堥摼寰幆渚濊禆锛�
+        if (area.getParentId() != null && areaMap.containsKey(area.getParentId())) {
+            accumulate(area.getParentId(), count, countMap, areaMap);
+        }
+    }
+
+    private DeviceAreaTreeDto buildNode(DeviceArea area, List<DeviceArea> all, Map<Long, Long> countMap) {
+        DeviceAreaTreeDto node = new DeviceAreaTreeDto();
+        node.setId(area.getId());
+        node.setParentId(area.getParentId());
+        node.setLabel(area.getName());
+        node.setDeviceCount(countMap.getOrDefault(area.getId(), 0L));
+        for (DeviceArea child : all) {
+            if (area.getId().equals(child.getParentId())) {
+                node.getChildren().add(buildNode(child, all, countMap));
+            }
+        }
+        return node;
+    }
+
+    @Override
+    public AjaxResult addArea(DeviceAreaDto dto) {
+        if (StringUtils.isEmpty(dto.getName())) {
+            return AjaxResult.error("鍖哄煙鍚嶇О涓嶈兘涓虹┖");
+        }
+        DeviceArea area = new DeviceArea();
+        area.setName(dto.getName());
+        area.setParentId(dto.getParentId());
+        area.setSort(dto.getSort());
+        area.setDeptId(dto.getDeptId());
+        area.setTenantId(SecurityUtils.getLoginUser().getTenantId());
+
+        if (dto.getParentId() != null) {
+            DeviceArea parent = this.getById(dto.getParentId());
+            if (parent == null) {
+                return AjaxResult.error("鐖剁骇鍖哄煙涓嶅瓨鍦�");
+            }
+            area.setAncestors(StringUtils.isEmpty(parent.getAncestors())
+                    ? String.valueOf(parent.getId())
+                    : parent.getAncestors() + "," + parent.getId());
+        }
+        this.save(area);
+        return AjaxResult.success();
+    }
+
+    @Override
+    public AjaxResult updateArea(DeviceAreaDto dto) {
+        if (dto.getId() == null) {
+            return AjaxResult.error("鍖哄煙ID涓嶈兘涓虹┖");
+        }
+        DeviceArea area = this.getById(dto.getId());
+        if (area == null) {
+            return AjaxResult.error("鍖哄煙涓嶅瓨鍦�");
+        }
+        // 涓嶅厑璁告妸鍖哄煙鎸傚埌鑷繁鐨勫瓙绾т笅锛岄伩鍏嶅惊鐜�
+        if (dto.getParentId() != null && dto.getParentId().equals(dto.getId())) {
+            return AjaxResult.error("涓嶈兘灏嗗尯鍩熸寕杞藉埌鑷韩");
+        }
+        if (dto.getParentId() != null && isDescendant(dto.getId(), dto.getParentId())) {
+            return AjaxResult.error("涓嶈兘灏嗗尯鍩熸寕杞藉埌鑷繁鐨勫瓙绾т笅");
+        }
+        area.setName(dto.getName());
+        area.setSort(dto.getSort());
+        area.setDeptId(dto.getDeptId());
+        if (!java.util.Objects.equals(dto.getParentId(), area.getParentId())) {
+            area.setParentId(dto.getParentId());
+            if (dto.getParentId() == null) {
+                area.setAncestors(null);
+            } else {
+                DeviceArea parent = this.getById(dto.getParentId());
+                if (parent == null) {
+                    return AjaxResult.error("鐖剁骇鍖哄煙涓嶅瓨鍦�");
+                }
+                area.setAncestors(StringUtils.isEmpty(parent.getAncestors())
+                        ? String.valueOf(parent.getId())
+                        : parent.getAncestors() + "," + parent.getId());
+            }
+        }
+        this.updateById(area);
+        return AjaxResult.success();
+    }
+
+    private boolean isDescendant(Long targetId, Long candidateParentId) {
+        DeviceArea cur = this.getById(candidateParentId);
+        while (cur != null && cur.getParentId() != null) {
+            if (cur.getParentId().equals(targetId)) {
+                return true;
+            }
+            cur = this.getById(cur.getParentId());
+        }
+        return false;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public AjaxResult deleteArea(List<Long> ids) {
+        for (Long id : ids) {
+            DeviceArea area = this.getById(id);
+            if (area == null) {
+                return AjaxResult.error("鍖哄煙涓嶅瓨鍦�");
+            }
+            long childCount = this.count(new LambdaQueryWrapper<DeviceArea>().eq(DeviceArea::getParentId, id));
+            if (childCount > 0) {
+                return AjaxResult.error("璇ュ尯鍩熷瓨鍦ㄥ瓙绾у尯鍩燂紝璇峰厛鍒犻櫎瀛愮骇");
+            }
+            long deviceCount = deviceLedgerMapper.selectCount(
+                    new LambdaQueryWrapper<DeviceLedger>().eq(DeviceLedger::getAreaId, id));
+            if (deviceCount > 0) {
+                return AjaxResult.error("璇ュ尯鍩熶笅瀛樺湪璁惧锛岃鍏堢Щ闄よ澶囧叧鑱�");
+            }
+        }
+        this.removeByIds(ids);
+        return AjaxResult.success();
+    }
+}
\ No newline at end of file

--
Gitblit v1.9.3