package cn.iocoder.yudao.module.pms.service.iotoperationmeeting;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.pms.controller.admin.iotoperationmeeting.vo.IotOperationMeetingPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotoperationmeeting.vo.IotOperationMeetingRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotoperationmeeting.vo.IotOperationMeetingSaveBatchVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotoperationmeeting.vo.IotOperationMeetingSaveReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotoperationmeetingattrs.vo.IotOperationMeetingAttrsPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotoperationmeetingdetail.vo.IotOperationMeetingDetailPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotoperationmeetingdetail.vo.IotOperationMeetingDetailRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotoperationmeetingdetail.vo.IotOperationMeetingDetailSaveReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.iotoperationmeeting.IotOperationMeetingDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.iotoperationmeetingattrs.IotOperationMeetingAttrsDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.iotoperationmeetingdetail.IotOperationMeetingDetailDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.iotprojecttaskattrs.IotTaskAttrModelProperty;
import cn.iocoder.yudao.module.pms.dal.mysql.iotoperationmeeting.IotOperationMeetingMapper;
import cn.iocoder.yudao.module.pms.dal.mysql.iotoperationmeetingattrs.IotOperationMeetingAttrsMapper;
import cn.iocoder.yudao.module.pms.dal.mysql.iotoperationmeetingdetail.IotOperationMeetingDetailMapper;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.service.dept.DeptService;
import com.google.common.collect.ImmutableMap;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstant.*;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.DEPT_NOT_FOUND;

/**
 * 生产运营会 Service 实现类
 *
 * @author ruiqi
 */
@Service
@Validated
public class IotOperationMeetingServiceImpl implements IotOperationMeetingService {

    @Resource
    private IotOperationMeetingMapper iotOperationMeetingMapper;
    @Resource
    private IotOperationMeetingDetailMapper iotOperationMeetingDetailMapper;
    @Resource
    private DeptService deptservice;
    @Resource
    private IotOperationMeetingAttrsMapper iotOperationMeetingAttrsMapper;


    @Override
    public Long createIotOperationMeeting(IotOperationMeetingSaveReqVO createReqVO) {
        // 插入
        IotOperationMeetingDO iotOperationMeeting = BeanUtils.toBean(createReqVO, IotOperationMeetingDO.class);
        // 如果没有传递 公司 参数 则获得当前登录人所属公司
        if (ObjUtil.isEmpty(iotOperationMeeting.getDeptId())) {
            // 查询当前登录人所属公司
            Long loginUserDeptId = SecurityFrameworkUtils.getLoginUserDeptId();
            DeptDO dept = deptservice.getDept(loginUserDeptId);
            if (ObjUtil.isEmpty(dept)) {
                throw exception(DEPT_NOT_FOUND);
            }
            if (!"1".equals(dept.getType())) {
                throw new AccessDeniedException("没有权限");
            }
            iotOperationMeeting.setDeptId(loginUserDeptId);
        }
        iotOperationMeetingMapper.insert(iotOperationMeeting);
        // 返回
        return iotOperationMeeting.getId();
    }

    @Override
    public void updateIotOperationMeeting(IotOperationMeetingSaveReqVO updateReqVO) {
        // 校验存在
        validateIotOperationMeetingExists(updateReqVO.getId());
        // 更新
        IotOperationMeetingDO updateObj = BeanUtils.toBean(updateReqVO, IotOperationMeetingDO.class);
        iotOperationMeetingMapper.updateById(updateObj);
    }

    @Override
    public void deleteIotOperationMeeting(Long id) {
        // 校验存在
        validateIotOperationMeetingExists(id);
        // 删除
        iotOperationMeetingMapper.deleteById(id);
    }

    private void validateIotOperationMeetingExists(Long id) {
        if (iotOperationMeetingMapper.selectById(id) == null) {
            throw exception(IOT_OPERATION_MEETING_NOT_EXISTS);
        }
    }

    @Override
    public IotOperationMeetingDO getIotOperationMeeting(Long id) {
        return iotOperationMeetingMapper.selectById(id);
    }

    @Override
    public PageResult<IotOperationMeetingDO> getIotOperationMeetingPage(IotOperationMeetingPageReqVO pageReqVO) {
        return iotOperationMeetingMapper.selectPage(pageReqVO);
    }

    @Override
    public Long saveBatch(IotOperationMeetingSaveBatchVO saveBatchVO) {
        if (ObjUtil.isEmpty(saveBatchVO)) {
            throw exception(IOT_OPERATION_MEETING_NOT_EXISTS);
        }
        // 兼容 新增 编辑
        if (ObjUtil.isEmpty(saveBatchVO.getOperationMeeting())) {
            throw exception(IOT_OPERATION_MEETING_NOT_EXISTS);
        }
        IotOperationMeetingSaveReqVO meeting = saveBatchVO.getOperationMeeting();
        // 如果没有传递 公司 参数 则获得当前登录人所属公司
        if (ObjUtil.isEmpty(meeting.getDeptId())) {
            // 查询当前登录人所属公司
            Long loginUserDeptId = SecurityFrameworkUtils.getLoginUserDeptId();
            DeptDO dept = deptservice.getDept(loginUserDeptId);
            if (ObjUtil.isEmpty(dept)) {
                throw exception(DEPT_NOT_FOUND);
            }
            if (!"1".equals(dept.getType())) {
                throw new AccessDeniedException("没有权限");
            }
            meeting.setDeptId(loginUserDeptId);
        }
        List<Long> meetingIds = new ArrayList<>();
        long resultId = 0l;
        // 根据 期次 '第8期' 赋值 sort = 8
        Optional.ofNullable(meeting.getMeetingSeries())
                .map(s -> s.replaceAll("\\D", "")) // 提取所有数字
                .filter(StrUtil::isNumeric)    // 确保是数字
                .map(Integer::valueOf)
                .ifPresent(meeting::setSort);
        // 校验当前公司 当前日期所属年 当前期次是否有重复的记录
        IotOperationMeetingPageReqVO reqVO = new IotOperationMeetingPageReqVO();
        reqVO.setSort(meeting.getSort());
        LocalDateTime [] times = {LocalDateTime.now(), LocalDateTime.now()};
        reqVO.setCreateTime(times);
        List<IotOperationMeetingDO> meetings = iotOperationMeetingMapper.selectExistMeetingByYearAndSort(reqVO);

        if (ObjUtil.isNotEmpty(meeting.getId())) {
            // 更新
            // 校验存在
            validateIotOperationMeetingExists(meeting.getId());
            // 校验当期运营会议是否有重复
            if (CollUtil.isNotEmpty(meetings)) {
                meetings.forEach(mtg -> {
                    if (!mtg.getId().equals(meeting.getId())) {
                        throw exception(IOT_OPERATION_MEETING_EXISTS);
                    }
                });
            }
            IotOperationMeetingDO updateObj = BeanUtils.toBean(meeting, IotOperationMeetingDO.class);
            int id = iotOperationMeetingMapper.updateById(updateObj);
            resultId = id;
            // 删除主表对应的明细
            int count = iotOperationMeetingDetailMapper.deleteByMap(ImmutableMap.of(
                    "meeting_id", meeting.getId()
            ));
            meetingIds.add(updateObj.getId());
        } else {
            // 新增
            if (CollUtil.isNotEmpty(meetings)) {
                throw exception(IOT_OPERATION_MEETING_EXISTS);
            }
            IotOperationMeetingDO iotOperationMeeting = BeanUtils.toBean(meeting, IotOperationMeetingDO.class);
            int id = iotOperationMeetingMapper.insert(iotOperationMeeting);
            resultId = id;
            meetingIds.add(iotOperationMeeting.getId());
        }

        // 查询当年上一期次的会议内容 公司所属项目工作量细化数据 计算环比使用 设备利用率...
        IotOperationMeetingDetailPageReqVO beforeReqVO = new IotOperationMeetingDetailPageReqVO();
        // 当前日期所属年
        LocalDateTime currentDate = LocalDateTime.now();
        beforeReqVO.setYear(currentDate.getYear());
        beforeReqVO.setSort(meeting.getSort() - 1);
        List<IotOperationMeetingDetailDO> beforeSummarizedDetails = iotOperationMeetingDetailMapper.summarizedProjectDetails(beforeReqVO);
        // key项目名称  value项目设备利用率
        Map<String, BigDecimal> projectUtilizationPair = new HashMap<>();
        if (CollUtil.isNotEmpty(beforeSummarizedDetails)) {
            // 查询每个项目的设备利用率
            beforeSummarizedDetails.forEach(detail -> {
                // 每个项目的工作量扩展属性列表
                List<IotTaskAttrModelProperty> properties = detail.getExtProperty();
                if (CollUtil.isNotEmpty(properties)) {
                    // 瑞恒设备利用率
                    BigDecimal utilizationRate = getActualValue(properties, "utilizationRate");
                    projectUtilizationPair.put(detail.getProjectName(), utilizationRate);
                }
            });
        }

        // 编辑时先删除明细 再新增明细
        if (ObjUtil.isNotEmpty(saveBatchVO.getDetails())) {
            List<IotOperationMeetingDetailSaveReqVO> details = saveBatchVO.getDetails();
            List<IotOperationMeetingDetailDO> meetingDetails = new ArrayList<>();
            details.forEach(detail -> {
                // 保存项目明细时 计算工作量 汇总数据 完成比 设备利用率 ...
                List<IotTaskAttrModelProperty> properties = detail.getExtProperty();
                if (CollUtil.isNotEmpty(properties)) {
                    // 找到每个项目的 拆分后的工作量字段值
                    // 157 瑞恒 注气完成比 gasCompletionRatio = 实际注气量/计划注气量
                    if (Long.valueOf(157).equals(meeting.getDeptId())) {
                        BigDecimal planGasInjection = getActualValue(properties, "planGasInjection");
                        BigDecimal actualGasInjection = getActualValue(properties, "actualGasInjection");
                        if (planGasInjection.compareTo(BigDecimal.ZERO) > 0) {
                            // 注气完成比 gasCompletionRatio = 实际注气量/计划注气量 23.85%
                            BigDecimal gasCompletionRatio =  actualGasInjection.divide(planGasInjection, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
                            // 将 完成比 作为单独的1个元素添加到 extProperty
                            IotTaskAttrModelProperty completionRatioProperty = new IotTaskAttrModelProperty();
                            completionRatioProperty.setName("注气完成比");
                            completionRatioProperty.setAccessMode("r");
                            completionRatioProperty.setRequired(0);
                            completionRatioProperty.setSort(3);
                            completionRatioProperty.setUnit("%");
                            completionRatioProperty.setDataType("double");
                            completionRatioProperty.setIdentifier("gasCompletionRatio");
                            completionRatioProperty.setActualValue(gasCompletionRatio.toString());
                            completionRatioProperty.setMaxValue(StrUtil.EMPTY);
                            completionRatioProperty.setMinValue(StrUtil.EMPTY);
                            completionRatioProperty.setDefaultValue(StrUtil.EMPTY);
                            properties.add(completionRatioProperty);
                        }
                    }

                    // 瑞鹰 进尺完成比 井次完成比
                    if (Long.valueOf(158).equals(meeting.getDeptId())) {
                        // 计划进尺
                        BigDecimal planFootage = getActualValue(properties, "planFootage");
                        // 实际进尺
                        BigDecimal actualFootage = getActualValue(properties, "actualFootage");
                        if (planFootage.compareTo(BigDecimal.ZERO) > 0) {
                            // 进尺完成比 footageCompletionRatio = 实际进尺/计划进尺 23.85%
                            BigDecimal footageCompletionRatio =  actualFootage.divide(planFootage, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
                            // 将 完成比 作为单独的1个元素添加到 extProperty
                            IotTaskAttrModelProperty footageCompletionRatioProperty = new IotTaskAttrModelProperty();
                            footageCompletionRatioProperty.setName("进尺完成比");
                            footageCompletionRatioProperty.setAccessMode("r");
                            footageCompletionRatioProperty.setRequired(0);
                            footageCompletionRatioProperty.setSort(3);
                            footageCompletionRatioProperty.setUnit("%");
                            footageCompletionRatioProperty.setDataType("double");
                            footageCompletionRatioProperty.setIdentifier("footageCompletionRatio");
                            footageCompletionRatioProperty.setActualValue(footageCompletionRatio.toString());
                            footageCompletionRatioProperty.setMaxValue(StrUtil.EMPTY);
                            footageCompletionRatioProperty.setMinValue(StrUtil.EMPTY);
                            footageCompletionRatioProperty.setDefaultValue(StrUtil.EMPTY);
                            properties.add(footageCompletionRatioProperty);
                        }
                    }

                    // 瑞都(层数完成比 井次完成比)   5#公司 层数完成比
                    if (Long.valueOf(163).equals(meeting.getDeptId()) || Long.valueOf(388).equals(meeting.getDeptId())) {
                        // 计划层数
                        BigDecimal planLayers = getActualValue(properties, "planLayers");
                        // 实际层数
                        BigDecimal actualLayers = getActualValue(properties, "actualLayers");
                        if (planLayers.compareTo(BigDecimal.ZERO) > 0) {
                            // 层数完成比 layersCompletionRatio = 实际层数/计划层数 23.85%
                            BigDecimal layersCompletionRatio =  actualLayers.divide(planLayers, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
                            // 将 完成比 作为单独的1个元素添加到 extProperty
                            IotTaskAttrModelProperty layersCompletionRatioProperty = new IotTaskAttrModelProperty();
                            layersCompletionRatioProperty.setName("层数完成比");
                            layersCompletionRatioProperty.setAccessMode("r");
                            layersCompletionRatioProperty.setRequired(0);
                            layersCompletionRatioProperty.setSort(3);
                            layersCompletionRatioProperty.setUnit("%");
                            layersCompletionRatioProperty.setDataType("double");
                            layersCompletionRatioProperty.setIdentifier("layersCompletionRatio");
                            layersCompletionRatioProperty.setActualValue(layersCompletionRatio.toString());
                            layersCompletionRatioProperty.setMaxValue(StrUtil.EMPTY);
                            layersCompletionRatioProperty.setMinValue(StrUtil.EMPTY);
                            layersCompletionRatioProperty.setDefaultValue(StrUtil.EMPTY);
                            properties.add(layersCompletionRatioProperty);
                        }
                    }

                    // 瑞鹰 瑞都 同时 具备 井次 工作量
                    if (Long.valueOf(163).equals(meeting.getDeptId()) || Long.valueOf(158).equals(meeting.getDeptId())) {
                        // 计划井次
                        BigDecimal planWellTrips = getActualValue(properties, "planWellTrips");
                        // 实际井次
                        BigDecimal actualWellTrips = getActualValue(properties, "actualWellTrips");
                        if (planWellTrips.compareTo(BigDecimal.ZERO) > 0) {
                            // 井次完成比 wellTripsCompletionRatio = 实际井次/计划井次 23.85%
                            BigDecimal wellTripsCompletionRatio =  actualWellTrips.divide(planWellTrips, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
                            // 将 完成比 作为单独的1个元素添加到 extProperty
                            IotTaskAttrModelProperty wellTripsCompletionRatioProperty = new IotTaskAttrModelProperty();
                            wellTripsCompletionRatioProperty.setName("井次完成比");
                            wellTripsCompletionRatioProperty.setAccessMode("r");
                            wellTripsCompletionRatioProperty.setRequired(0);
                            wellTripsCompletionRatioProperty.setSort(7);
                            wellTripsCompletionRatioProperty.setUnit("%");
                            wellTripsCompletionRatioProperty.setDataType("double");
                            wellTripsCompletionRatioProperty.setIdentifier("wellTripsCompletionRatio");
                            wellTripsCompletionRatioProperty.setActualValue(wellTripsCompletionRatio.toString());
                            wellTripsCompletionRatioProperty.setMaxValue(StrUtil.EMPTY);
                            wellTripsCompletionRatioProperty.setMinValue(StrUtil.EMPTY);
                            wellTripsCompletionRatioProperty.setDefaultValue(StrUtil.EMPTY);
                            properties.add(wellTripsCompletionRatioProperty);
                        }
                    }

                    // 每个专业公司设备利用率计算逻辑相同 utilizationRate
                    // 设备利用率 环比 (当前期利用率 - 上一期利用率) / 上一期利用率
                    // 计算当前期次会议 当前项目的设备利用率 施工设备数量/投运设备数量 已经通过前端 计算赋值
                    BigDecimal utilizationRate = getActualValue(properties, "utilizationRate");

                    String projectName = detail.getProjectName();
                    BigDecimal beforeUtilizationRate = BigDecimal.ZERO;
                    if (StrUtil.isNotBlank(projectName) && projectUtilizationPair.containsKey(projectName)) {
                        // 上一期次的设备利用率
                        beforeUtilizationRate = projectUtilizationPair.get(projectName);
                    }
                    if (beforeUtilizationRate.compareTo(BigDecimal.ZERO) > 0) {
                        BigDecimal periodOnPeriod = utilizationRate.subtract(beforeUtilizationRate)
                                .divide(beforeUtilizationRate, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
                        // 将 设备利用率 环比 作为单独的1个元素添加到 extProperty
                        IotTaskAttrModelProperty periodOnPeriodProperty = new IotTaskAttrModelProperty();
                        periodOnPeriodProperty.setName("环比");
                        periodOnPeriodProperty.setAccessMode("r");
                        periodOnPeriodProperty.setRequired(0);
                        periodOnPeriodProperty.setSort(8);
                        periodOnPeriodProperty.setUnit("%");
                        periodOnPeriodProperty.setDataType("double");
                        periodOnPeriodProperty.setIdentifier("periodOnPeriod");
                        periodOnPeriodProperty.setActualValue(periodOnPeriod.toString());
                        periodOnPeriodProperty.setMaxValue(StrUtil.EMPTY);
                        periodOnPeriodProperty.setMinValue(StrUtil.EMPTY);
                        periodOnPeriodProperty.setDefaultValue(StrUtil.EMPTY);
                        properties.add(periodOnPeriodProperty);
                    }

                    detail.setExtProperty(properties);
                }
                IotOperationMeetingDetailDO detailDO = BeanUtils.toBean(detail, IotOperationMeetingDetailDO.class);
                detailDO.setId(null);
                detailDO.setMeetingId(meetingIds.get(0));
                detailDO.setDeptId(meeting.getDeptId());    // 所属公司
                detailDO.setSort(meeting.getSort());        // 期次索引
                meetingDetails.add(detailDO);
            });
            iotOperationMeetingDetailMapper.insertBatch(meetingDetails);
        }
        return resultId;
    }

    /**
     * 从工作量扩展属性中获取 指定名称的值
     */
    private BigDecimal getActualValue(List<IotTaskAttrModelProperty> properties, String identifier) {
        // 空值直接返回 0
        if (CollUtil.isEmpty(properties)) {
            return BigDecimal.ZERO;
        }

        // Java 8 流式优雅查找目标属性
        return properties.stream()
                // 过滤标识符 = planGasInjection
                .filter(property -> identifier.equals(property.getIdentifier()))
                // 提取值
                .map(IotTaskAttrModelProperty::getActualValue)
                .filter(StrUtil::isNotBlank)
                // 只取第一个匹配的
                .findFirst()
                // 转成 BigDecimal，无值则返回 0
                .map(BigDecimal::new)
                .orElse(BigDecimal.ZERO);
    }

    @Override
    public Set<String> cachedProjects() {
        Set<String> projectNames = new HashSet<>();
        // 查询当前登录人所属公司下 填写过的 项目名称
        IotOperationMeetingDetailPageReqVO reqVO = new IotOperationMeetingDetailPageReqVO();
        reqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        PageResult<IotOperationMeetingDetailDO> meetingPage = iotOperationMeetingDetailMapper.selectPage(reqVO);
        if (ObjUtil.isNotEmpty(meetingPage)) {
            List<IotOperationMeetingDetailDO> meetingDetails = meetingPage.getList();
            if (CollUtil.isNotEmpty(meetingDetails)) {
                meetingDetails.forEach(detail -> {
                    projectNames.add(detail.getProjectName());
                });
            }
        }
        return projectNames;
    }

    /**
     * 从指定部门出发，向上查找类型为"1"（公司）的部门ID。
     * 如果当前部门就是公司则直接返回；否则循环查找父级，直到找到公司或达到上限。
     * 若始终找不到公司，则返回原部门的ID。
     */
    private Long findCompanyDeptId(DeptDO dept) {
        if ("1".equals(dept.getType())) {
            return dept.getId();
        }

        DeptDO current = dept;
        int maxLevel = 10; // 防止意外的循环引用
        while (current != null && !"1".equals(current.getType())
                && current.getParentId() != null && maxLevel-- > 0) {
            current = deptservice.getDept(current.getParentId());
        }

        if (current != null && "1".equals(current.getType())) {
            return current.getId();
        }
        // 找不到公司时，保持与原逻辑一致，返回当前部门的ID
        return dept.getId();
    }

    @Override
    public IotOperationMeetingDetailRespVO previousWorkPlan(IotOperationMeetingDetailPageReqVO pageReqVO) {
        // 填报运营会议明细时查询上期填写的工作计划
        String projectName = pageReqVO.getProjectName();
        Long meetingId = pageReqVO.getMeetingId();   // 会议id
        Long meetingDetailId = pageReqVO.getId();    // 会议明细id
        // 如果传了 明细Id 说明是编辑状态 此时 不需要查询上一期次的工作计划
        // 如果没传 明细Id 说明是新增项目状态 此时需要查询当前公司上一期次相同项目名称的会议明细 下期工作计划
        // 会议期次 根据 项目名称 期次 查询 当前公司 上一期次(sort-1) 相同项目名称的 ‘下期工作计划-计划工作量’ ‘下期工作计划-重点工作事项’
        // 没传 明细Id 有2种情况：新建会议 or 编辑会议新增项目
        // 新建会议(会议id为null) sort 最大的记录即为上一期次的会议
        // 编辑会议新增项目(会议id不为null) sort 倒序排列 取第2位次的会议即为上一期次的会议
        if (ObjUtil.isNotEmpty(meetingDetailId) || StrUtil.isBlank(projectName)) {
            return null;
        }

        IotOperationMeetingDetailPageReqVO reqVO = new IotOperationMeetingDetailPageReqVO();
        reqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        reqVO.setProjectName(projectName);
        LocalDateTime [] times = {LocalDateTime.now(), LocalDateTime.now()};
        reqVO.setCreateTime(times);
        List<IotOperationMeetingDetailDO> meetingDetails = iotOperationMeetingDetailMapper.selectMaxSortByYearAndProject(reqVO);
        if (CollUtil.isEmpty(meetingDetails)) {
            return null;
        }
        if (ObjUtil.isEmpty(meetingId)) {
            // 新建会议
            // 查询当前用户所属公司最新的会议明细 当前日期所属年
            // 只取第1条记录
            return BeanUtils.toBean(meetingDetails.get(0), IotOperationMeetingDetailRespVO.class);
        }
        // 编辑会议新增项目
        // 查询当前的会议的 sort-1 即为上一期会议
        IotOperationMeetingDO meeting = iotOperationMeetingMapper.selectById(meetingId);
        if (ObjUtil.isEmpty(meeting)) {
            return null;
        }

        Integer lastMeetingSort = meeting.getSort() - 1;
        return meetingDetails.stream()
                .filter(detail -> Objects.equals(detail.getSort(), lastMeetingSort))
                .findFirst()
                .map(detail -> BeanUtils.toBean(detail, IotOperationMeetingDetailRespVO.class))
                .orElse(null);
    }

    @Override
    public IotOperationMeetingRespVO mandatoryOrNot() {
        IotOperationMeetingRespVO resp = new IotOperationMeetingRespVO();
        // 当前登录人所属公司的 email 字段 如果等于 NODEVICE 则不必填
        Long loginUserDeptId = SecurityFrameworkUtils.getLoginUserDeptId();
        DeptDO dept = deptservice.getDept(loginUserDeptId);
        if (ObjUtil.isEmpty(dept)) {
            throw exception(DEPT_NOT_FOUND);
        }
        if ("NODEVICE@keruioil.com".equals(dept.getEmail())) {
            resp.setMandatory(false);
        } else {
            resp.setMandatory(true);
        }
        return resp;
    }

    @Override
    public Map<String, List<IotOperationMeetingDO>> integratedMeeting(IotOperationMeetingPageReqVO pageReqVO) {
        // 石油技术查询整合后 各公司的 项目明细
        if (StrUtil.isNotBlank(pageReqVO.getMeetingSeries())) {

            // 提取 meetingSeries 中的数字
            Optional.ofNullable(pageReqVO.getMeetingSeries())
                    .map(s -> s.replaceAll("\\D", "")) // 提取所有数字
                    .filter(StrUtil::isNumeric)    // 确保是数字
                    .map(Integer::valueOf)
                    .ifPresent(pageReqVO::setSort);
        }
        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        PageResult<IotOperationMeetingDO> pageMeetings = iotOperationMeetingMapper.selectPage(pageReqVO);
        // key格式2026-第8期   value当期公司会议集合
        Map<String, List<IotOperationMeetingDO>> companyMeetingPair = new HashMap<>();
        if (ObjUtil.isNotEmpty(pageMeetings)) {
            List<IotOperationMeetingDO> meetings = pageMeetings.getList();
            if (CollUtil.isNotEmpty(meetings)) {
                meetings.forEach(meeting -> {
                    LocalDateTime meetingDate = meeting.getMeetingDate();
                    // 获取日期中的年 yyyy
                    Integer year = meetingDate.getYear();
                    String uniqueKey = StrUtil.join("-", year, meeting.getMeetingSeries());
                    if (companyMeetingPair.containsKey(uniqueKey)) {
                        List<IotOperationMeetingDO> tempMeetings = companyMeetingPair.get(uniqueKey);
                        tempMeetings.add(meeting);
                        companyMeetingPair.put(uniqueKey, tempMeetings);
                    } else {
                        List<IotOperationMeetingDO> tempMeetings = new ArrayList<>();
                        tempMeetings.add(meeting);
                        companyMeetingPair.put(uniqueKey, tempMeetings);
                    }
                });
            }
        }
        return companyMeetingPair;
    }

    @Override
    public List<IotOperationMeetingRespVO> summarizedProjectDetails(IotOperationMeetingPageReqVO pageReqVO) {
        List<IotOperationMeetingRespVO> resultMeetings = new ArrayList<>();
        // 石油技术 查询 下属各专业公司 指定期次的 项目明细
        String meetingSeries = pageReqVO.getMeetingSeries();        // 期次
        Integer year = pageReqVO.getYear();     // 会议日期
        if (StrUtil.isBlank(meetingSeries) || ObjUtil.isEmpty(year)) {
            return resultMeetings;
        }
        // 根据 期次 日期yyyy 查询石油技术下所有专业公司的项目明细
        IotOperationMeetingDetailPageReqVO reqVO = new IotOperationMeetingDetailPageReqVO();
        reqVO.setYear(year);
        // 提取 meetingSeries 中的数字
        Optional.ofNullable(meetingSeries)
                .map(s -> s.replaceAll("\\D", "")) // 提取所有数字
                .filter(StrUtil::isNumeric)    // 确保是数字
                .map(Integer::valueOf)
                .ifPresent(reqVO::setSort);
        List<IotOperationMeetingDetailDO> summarizedDetails = iotOperationMeetingDetailMapper.summarizedProjectDetails(reqVO);
        if (CollUtil.isEmpty(summarizedDetails)) {
            return resultMeetings;
        }

        // 计算本期各公司财务数据集合
        // key公司id   value本期次公司本期收入
        Map<Long, BigDecimal> currentRevenuePair = summarizedDetails.stream()
                .collect(Collectors.groupingBy(IotOperationMeetingDetailDO::getDeptId,
                        Collectors.reducing(BigDecimal.ZERO, IotOperationMeetingDetailDO::getCurrentRevenue, BigDecimal::add)));
        // key公司id   value本期次公司累计收入
        Map<Long, BigDecimal> currentTotalRevenuePair = summarizedDetails.stream()
                .collect(Collectors.groupingBy(IotOperationMeetingDetailDO::getDeptId,
                        Collectors.reducing(BigDecimal.ZERO, IotOperationMeetingDetailDO::getCumulativeRevenue, BigDecimal::add)));
        // key公司id   value本期次公司本期挂账
        Map<Long, BigDecimal> currentOnAccountPair = summarizedDetails.stream()
                .collect(Collectors.groupingBy(IotOperationMeetingDetailDO::getDeptId,
                        Collectors.reducing(BigDecimal.ZERO, IotOperationMeetingDetailDO::getCurrentOnAccount, BigDecimal::add)));
        // key公司id   value本期次公司累计挂账
        Map<Long, BigDecimal> currentTotalOnAccountPair = summarizedDetails.stream()
                .collect(Collectors.groupingBy(IotOperationMeetingDetailDO::getDeptId,
                        Collectors.reducing(BigDecimal.ZERO, IotOperationMeetingDetailDO::getCumulativeOnAccount, BigDecimal::add)));
        // key公司id   value本期次公司本期回款
        Map<Long, BigDecimal> currentPaymentPair = summarizedDetails.stream()
                .collect(Collectors.groupingBy(IotOperationMeetingDetailDO::getDeptId,
                        Collectors.reducing(BigDecimal.ZERO, IotOperationMeetingDetailDO::getCurrentPayment, BigDecimal::add)));
        // key公司id   value本期次公司累计回款
        Map<Long, BigDecimal> currentTotalPaymentPair = summarizedDetails.stream()
                .collect(Collectors.groupingBy(IotOperationMeetingDetailDO::getDeptId,
                        Collectors.reducing(BigDecimal.ZERO, IotOperationMeetingDetailDO::getCumulativePayment, BigDecimal::add)));

        Set<Long> companyIds = new HashSet<>();
        // key会议id   value当前会议/公司下的项目明细
        Map<Long, List<IotOperationMeetingDetailDO>> companyMeetingIdDetailPair = new HashMap<>();
        Set<Long> meetingIds = new HashSet<>();
        summarizedDetails.forEach(detail -> {
            companyIds.add(detail.getDeptId());
        });
        // 查询 companyIds 各公司id对应的公司名称
        List<DeptDO> companyNames = deptservice.getDeptList(companyIds);
        Map<Long, String> companyPair = new HashMap<>();
        if (CollUtil.isEmpty(companyNames)) {
            return resultMeetings;
        }
        Set<Long> nonCumulativeDeptIds = new HashSet<>();
        companyNames.forEach(name -> {
            companyPair.put(name.getId(), name.getName());
            // 查询部门对应的字段 phone 设置是否需要累加财务数据
            if ("18000000000".equals(name.getPhone())) {
                nonCumulativeDeptIds.add(name.getId());
            }
        });
        // key专业公司id   value专业公司计划注气量累加
        Map<Long, BigDecimal> companyPlanGasInjectionPair = new HashMap<>();
        // key专业公司id   value专业公司实际注气量累加
        Map<Long, BigDecimal> companyActualGasInjectionPair = new HashMap<>();
        // key专业公司id   value专业公司累计注气量累加
        Map<Long, BigDecimal> companyCumulativeGasInjectionPair = new HashMap<>();

        // 上期次会议 key专业公司id   value专业公司投运设备数量累加
        Map<Long, BigDecimal> beforeCompanyInServiceDeviceNumPair = new HashMap<>();
        // 上期次会议 key专业公司id   value专业公司施工设备数量累加
        Map<Long, BigDecimal> beforeCompanyConstructionDeviceNumPair = new HashMap<>();

        // key专业公司id   value专业公司投运设备数量累加
        Map<Long, BigDecimal> companyInServiceDeviceNumPair = new HashMap<>();
        // key专业公司id   value专业公司施工设备数量累加
        Map<Long, BigDecimal> companyConstructionDeviceNumPair = new HashMap<>();

        // key专业公司id   value专业公司各项目计划进尺累加
        Map<Long, BigDecimal> companyPlanFootagePair = new HashMap<>();
        // key专业公司id   value专业公司各项目实际进尺累加
        Map<Long, BigDecimal> companyActualFootagePair = new HashMap<>();
        // key专业公司id   value专业公司各项目累计进尺累加
        Map<Long, BigDecimal> companyCumulativeFootagePair = new HashMap<>();

        // key专业公司id   value专业公司各项目计划井次累加
        Map<Long, BigDecimal> companyPlanWellTripsPair = new HashMap<>();
        // key专业公司id   value专业公司各项目实际井次累加
        Map<Long, BigDecimal> companyActualWellTripsPair = new HashMap<>();
        // key专业公司id   value专业公司各项目累计井次累加
        Map<Long, BigDecimal> companyCumulativeWellTripsPair = new HashMap<>();

        // key专业公司id   value专业公司各项目计划层数累加
        Map<Long, BigDecimal> companyPlanLayersPair = new HashMap<>();
        // key专业公司id   value专业公司各项目实际层数累加
        Map<Long, BigDecimal> companyActualLayersPair = new HashMap<>();
        // key专业公司id   value专业公司各项目累计层数累加
        Map<Long, BigDecimal> companyCumulativeLayersPair = new HashMap<>();

        // key部门id   value当前会议/公司下的项目明细
        Map<Long, List<IotOperationMeetingDetailDO>> companyDeptIdDetailPair = summarizedDetails.stream()
                .collect(Collectors.groupingBy(IotOperationMeetingDetailDO::getDeptId));
        if (CollUtil.isEmpty(companyDeptIdDetailPair)) {
            return resultMeetings;
        }
        companyDeptIdDetailPair.forEach((deptId, details) -> {
            // key专业公司id    details公司各项目明细
            if (CollUtil.isNotEmpty(details)) {
                details.forEach(detail -> {
                    // 组装细分的工作量
                    List<IotTaskAttrModelProperty> properties = detail.getExtProperty();
                    if (CollUtil.isNotEmpty(properties)) {
                        // 公共属性 投运设备 施工设备
                        BigDecimal inServiceDeviceNum = getActualValue(properties, "inServiceDeviceNum");
                        BigDecimal constructionDeviceNum = getActualValue(properties, "constructionDeviceNum");
                        companyInServiceDeviceNumPair.merge(deptId, inServiceDeviceNum, BigDecimal::add);
                        companyConstructionDeviceNumPair.merge(deptId, constructionDeviceNum, BigDecimal::add);
                        // 163 瑞都
                        // 388 5#公司
                        if (Long.valueOf(157).equals(deptId)) {
                            // 157 瑞恒 计划注气 实际注气 累计注气 总设备利用率及环比 总注气量完成比
                            BigDecimal planGasInjection = getActualValue(properties, "planGasInjection");
                            BigDecimal actualGasInjection = getActualValue(properties, "actualGasInjection");
                            BigDecimal cumulativeGasInjection = getActualValue(properties, "cumulativeGasInjection");
                            companyPlanGasInjectionPair.merge(deptId, planGasInjection, BigDecimal::add);
                            companyActualGasInjectionPair.merge(deptId, actualGasInjection, BigDecimal::add);
                            companyCumulativeGasInjectionPair.merge(deptId, cumulativeGasInjection, BigDecimal::add);
                        }
                        if (Long.valueOf(158).equals(deptId)) {
                            // 158 瑞鹰 计划进尺 实际进尺 完成比 累计进尺 总设备利用率及环比 计划井次 实际井次 完成比 累计井次
                            BigDecimal planFootage = getActualValue(properties, "planFootage");
                            BigDecimal actualFootage = getActualValue(properties, "actualFootage");
                            BigDecimal cumulativeFootage = getActualValue(properties, "cumulativeFootage");
                            companyPlanFootagePair.merge(deptId, planFootage, BigDecimal::add);
                            companyActualFootagePair.merge(deptId, actualFootage, BigDecimal::add);
                            companyCumulativeFootagePair.merge(deptId, cumulativeFootage, BigDecimal::add);
                        }
                        if (Long.valueOf(163).equals(deptId) || Long.valueOf(388).equals(deptId)) {
                            // 163 瑞都 计划层数 实际层数 完成比 累计层数 总设备利用率及环比 计划井次 实际井次 完成比 累计井次
                            BigDecimal planLayers = getActualValue(properties, "planLayers");
                            BigDecimal actualLayers = getActualValue(properties, "actualLayers");
                            BigDecimal cumulativeLayers = getActualValue(properties, "cumulativeLayers");
                            companyPlanLayersPair.merge(deptId, planLayers, BigDecimal::add);
                            companyActualLayersPair.merge(deptId, actualLayers, BigDecimal::add);
                            companyCumulativeLayersPair.merge(deptId, cumulativeLayers, BigDecimal::add);
                        }
                        if (Long.valueOf(163).equals(deptId) || Long.valueOf(158).equals(deptId)) {
                            // 瑞都 瑞鹰 同时具备 井次属性
                            BigDecimal planWellTrips = getActualValue(properties, "planWellTrips");
                            BigDecimal actualWellTrips = getActualValue(properties, "actualWellTrips");
                            BigDecimal cumulativeWellTrips = getActualValue(properties, "cumulativeWellTrips");
                            companyPlanWellTripsPair.merge(deptId, planWellTrips, BigDecimal::add);
                            companyActualWellTripsPair.merge(deptId, actualWellTrips, BigDecimal::add);
                            companyCumulativeWellTripsPair.merge(deptId, cumulativeWellTrips, BigDecimal::add);
                        }
                    }
                });
            }
        });

        summarizedDetails.forEach(detail -> {
            meetingIds.add(detail.getMeetingId());
            /* if (companyMeetingIdDetailPair.containsKey(detail.getMeetingId())) {
                List<IotOperationMeetingDetailDO> tempDetails = companyMeetingIdDetailPair.get(detail.getMeetingId());
                tempDetails.add(detail);
                companyMeetingIdDetailPair.put(detail.getMeetingId(), tempDetails);
            } else {
                List<IotOperationMeetingDetailDO> tempDetails = new ArrayList<>();
                tempDetails.add(detail);
                companyMeetingIdDetailPair.put(detail.getMeetingId(), tempDetails);
            } */
            companyMeetingIdDetailPair.computeIfAbsent(detail.getMeetingId(), k -> new ArrayList<>()).add(detail);

        });
        // 根据 meetingIds 集合查询各会议的汇总信息(需要集团支持事项)
        IotOperationMeetingPageReqVO meetingReqVO = new IotOperationMeetingPageReqVO();
        meetingReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        meetingReqVO.setMeetingIds(meetingIds);
        PageResult<IotOperationMeetingDO> pageMeetings = iotOperationMeetingMapper.selectPage(meetingReqVO);
        if (ObjUtil.isEmpty(pageMeetings)) {
            return resultMeetings;
        }
        List<IotOperationMeetingDO> meetings = pageMeetings.getList();
        if (CollUtil.isEmpty(meetings)) {
            return resultMeetings;
        }

        // 查询 当年 上一期次的会议财务数据 汇总后计算 收入 挂账 回款 环比
        Set<Long> beforeCompanyIds = new HashSet<>();
        IotOperationMeetingDetailPageReqVO beforeReqVO = new IotOperationMeetingDetailPageReqVO();
        beforeReqVO.setYear(year);
        beforeReqVO.setSort(reqVO.getSort() - 1);
        List<IotOperationMeetingDetailDO> beforeSummarizedDetails = iotOperationMeetingDetailMapper.summarizedProjectDetails(beforeReqVO);
        // 组装每个专业公司各项目的累加财务数据 本期 收入 挂账 回款 并计算 环比
        if (CollUtil.isNotEmpty(beforeSummarizedDetails)) {
            // 先以公司为单位 组装每个专业公司 的项目明细 然后再组装各专业公司的工作量细化数据
            Map<Long, List<IotOperationMeetingDetailDO>> beforeCompanyDeptIdDetailPair = beforeSummarizedDetails.stream()
                    .collect(Collectors.groupingBy(IotOperationMeetingDetailDO::getDeptId));
            if (CollUtil.isNotEmpty(beforeCompanyDeptIdDetailPair)) {
                beforeCompanyDeptIdDetailPair.forEach((deptId, details) -> {
                    // key专业公司id    details公司各项目明细
                    if (CollUtil.isNotEmpty(details)) {
                        details.forEach(detail -> {
                            // 组装细分的工作量
                            List<IotTaskAttrModelProperty> properties = detail.getExtProperty();
                            if (CollUtil.isNotEmpty(properties)) {
                                // 公共属性 投运设备 施工设备
                                BigDecimal inServiceDeviceNum = getActualValue(properties, "inServiceDeviceNum");
                                BigDecimal constructionDeviceNum = getActualValue(properties, "constructionDeviceNum");
                                beforeCompanyInServiceDeviceNumPair.merge(deptId, inServiceDeviceNum, BigDecimal::add);
                                beforeCompanyConstructionDeviceNumPair.merge(deptId, constructionDeviceNum, BigDecimal::add);
                            }
                        });
                    }
                });
            }
            // key公司id   value上一期次公司本期收入
            Map<Long, BigDecimal> beforeRevenuePair = beforeSummarizedDetails.stream()
                    .collect(Collectors.groupingBy(IotOperationMeetingDetailDO::getDeptId,
                            Collectors.reducing(BigDecimal.ZERO, IotOperationMeetingDetailDO::getCurrentRevenue, BigDecimal::add)));
            // key公司id   value上一期次公司本期挂账
            Map<Long, BigDecimal> beforeOnAccountPair = beforeSummarizedDetails.stream()
                    .collect(Collectors.groupingBy(IotOperationMeetingDetailDO::getDeptId,
                            Collectors.reducing(BigDecimal.ZERO, IotOperationMeetingDetailDO::getCurrentOnAccount, BigDecimal::add)));
            // key公司id   value上一期次公司本期回款
            Map<Long, BigDecimal> beforePaymentPair = beforeSummarizedDetails.stream()
                    .collect(Collectors.groupingBy(IotOperationMeetingDetailDO::getDeptId,
                            Collectors.reducing(BigDecimal.ZERO, IotOperationMeetingDetailDO::getCurrentPayment, BigDecimal::add)));

        }

        // 组装每个会议的需要集团支持的事项
        Map<Long, IotOperationMeetingDO> supportPair = new HashMap<>();
        meetings.forEach(meeting -> {
            supportPair.put(meeting.getId(), meeting);
        });
        // 排序 按照 部门 sort 值正序排列
        // 1. 构建部门ID -> sort 的映射（类似你已有的 nameSortMap 思路）
        Map<Long, Integer> deptSortMap = companyNames.stream()
                .collect(Collectors.toMap(DeptDO::getId, DeptDO::getSort));

        // 2. 对 companyMeetingIdDetailPair 中每个明细列表按部门 sort 升序排列
        companyMeetingIdDetailPair.values().forEach(list ->
                list.sort(Comparator.comparingInt(detail ->
                        deptSortMap.getOrDefault(detail.getDeptId(), Integer.MAX_VALUE))));

        companyMeetingIdDetailPair.forEach((meetingId, details) -> {
            // 各专业公司会议 工作量扩展属性集合
            List<IotTaskAttrModelProperty> companyExtProperties = new ArrayList<>();
            // meetingId会议id   details各专业公司的会议明细
            IotOperationMeetingRespVO tempMeeting = new IotOperationMeetingRespVO();
            if (supportPair.containsKey(meetingId)) {
                IotOperationMeetingDO existMeeting = supportPair.get(meetingId);
                Long deptId = existMeeting.getDeptId();
                tempMeeting.setDeptId(deptId);
                tempMeeting.setSupport(existMeeting.getSupport());
                tempMeeting.setMeetingSeries(existMeeting.getMeetingSeries());
                tempMeeting.setMeetingDate(existMeeting.getMeetingDate());
                if (companyPair.containsKey(deptId)) {
                    tempMeeting.setCompanyName(companyPair.get(deptId));
                }
                // 判断公司的财务数据是否需要累计 新业务开发 非洲地区 中东地区 3个公司不累计
                if (nonCumulativeDeptIds.contains(deptId)) {
                    tempMeeting.setCumulative(false);
                } else {
                    tempMeeting.setCumulative(true);
                }
                // 上期收入
                BigDecimal beforeRevenue = CollUtil.isEmpty(beforeSummarizedDetails) ? BigDecimal.ZERO :
                        beforeSummarizedDetails.stream()
                                .filter(d -> deptId.equals(d.getDeptId()))
                                .map(IotOperationMeetingDetailDO::getCurrentRevenue)
                                .reduce(BigDecimal.ZERO, BigDecimal::add);
                // 设置 各专业公司 财务数据汇总
                if (currentRevenuePair.containsKey(deptId)) {
                    BigDecimal currentRevenue = currentRevenuePair.get(deptId);
                    // 本期收入
                    tempMeeting.setCurrentRevenue(currentRevenue);
                    // 计算本期收入 环比 （本期收入-上期收入）/上期收入
                    if (beforeRevenue.compareTo(BigDecimal.ZERO) > 0) {
                        BigDecimal revenuePeriodOnPeriod = currentRevenue.subtract(beforeRevenue).divide(beforeRevenue, 4, RoundingMode.HALF_UP);
                        tempMeeting.setRevenuePeriodOnPeriod(revenuePeriodOnPeriod);
                    }
                }
                tempMeeting.setBeforeRevenue(beforeRevenue);
                if (currentTotalRevenuePair.containsKey(deptId)) {
                    // 本期收入 累计
                    tempMeeting.setCumulativeRevenue(currentTotalRevenuePair.get(deptId));
                }
                // 上期挂账
                BigDecimal beforeOnAccount = CollUtil.isEmpty(beforeSummarizedDetails) ? BigDecimal.ZERO :
                        beforeSummarizedDetails.stream()
                                .filter(d -> deptId.equals(d.getDeptId()))
                                .map(IotOperationMeetingDetailDO::getCurrentOnAccount)
                                .reduce(BigDecimal.ZERO, BigDecimal::add);
                if (currentOnAccountPair.containsKey(deptId)) {
                    BigDecimal currentOnAccount = currentOnAccountPair.get(deptId);
                    // 本期挂账
                    tempMeeting.setCurrentOnAccount(currentOnAccount);
                    // 计算本期挂账 环比 （本期挂账-上期挂账）/上期挂账
                    if (beforeOnAccount.compareTo(BigDecimal.ZERO) > 0) {
                        BigDecimal onAccountPeriodOnPeriod = currentOnAccount.subtract(beforeOnAccount).divide(beforeOnAccount, 4, RoundingMode.HALF_UP);
                        tempMeeting.setOnAccountPeriodOnPeriod(onAccountPeriodOnPeriod);
                    }
                }
                tempMeeting.setBeforeOnAccount(beforeOnAccount);
                if (currentTotalOnAccountPair.containsKey(deptId)) {
                    // 本期挂账 累计
                    tempMeeting.setCumulativeOnAccount(currentTotalOnAccountPair.get(deptId));
                }
                // 上期回款
                BigDecimal beforePayment = CollUtil.isEmpty(beforeSummarizedDetails) ? BigDecimal.ZERO :
                        beforeSummarizedDetails.stream()
                                .filter(d -> deptId.equals(d.getDeptId()))
                                .map(IotOperationMeetingDetailDO::getCurrentPayment)
                                .reduce(BigDecimal.ZERO, BigDecimal::add);
                if (currentPaymentPair.containsKey(deptId)) {
                    // 本期回款
                    BigDecimal currentPayment = currentPaymentPair.get(deptId);
                    tempMeeting.setCurrentPayment(currentPayment);
                    // 计算回款 环比 （本期回款-上期回款）/上期回款
                    if (beforePayment.compareTo(BigDecimal.ZERO) > 0) {
                        BigDecimal paymentPeriodOnPeriod = currentPayment.subtract(beforePayment).divide(beforePayment, 4, RoundingMode.HALF_UP);
                        tempMeeting.setPaymentPeriodOnPeriod(paymentPeriodOnPeriod);
                    }
                }
                tempMeeting.setBeforePayment(beforePayment);
                if (currentTotalPaymentPair.containsKey(deptId)) {
                    // 本期回款 累计
                    tempMeeting.setCumulativePayment(currentTotalPaymentPair.get(deptId));
                }
                // 设置各专业公司的工作量细化数据
                // 瑞恒 注气量 细化数据
                if (companyPlanGasInjectionPair.containsKey(deptId)) {
                    IotTaskAttrModelProperty planGasProperty = new IotTaskAttrModelProperty();
                    planGasProperty.setName("计划注气量");
                    planGasProperty.setAccessMode("r");
                    planGasProperty.setRequired(1);
                    planGasProperty.setSort(1);
                    planGasProperty.setUnit("万方");
                    planGasProperty.setDataType("double");
                    planGasProperty.setIdentifier("planGasInjection");
                    planGasProperty.setActualValue(companyPlanGasInjectionPair.get(deptId).toString());
                    planGasProperty.setMaxValue(StrUtil.EMPTY);
                    planGasProperty.setMinValue(StrUtil.EMPTY);
                    planGasProperty.setDefaultValue(StrUtil.EMPTY);
                    companyExtProperties.add(planGasProperty);
                }
                if (companyActualGasInjectionPair.containsKey(deptId)) {
                    IotTaskAttrModelProperty actualGasProperty = new IotTaskAttrModelProperty();
                    actualGasProperty.setName("实际注气量");
                    actualGasProperty.setAccessMode("r");
                    actualGasProperty.setRequired(1);
                    actualGasProperty.setSort(2);
                    actualGasProperty.setUnit("万方");
                    actualGasProperty.setDataType("double");
                    actualGasProperty.setIdentifier("actualGasInjection");
                    actualGasProperty.setActualValue(companyActualGasInjectionPair.get(deptId).toString());
                    actualGasProperty.setMaxValue(StrUtil.EMPTY);
                    actualGasProperty.setMinValue(StrUtil.EMPTY);
                    actualGasProperty.setDefaultValue(StrUtil.EMPTY);
                    companyExtProperties.add(actualGasProperty);
                }
                if (companyCumulativeGasInjectionPair.containsKey(deptId)) {
                    IotTaskAttrModelProperty cumulativeGasProperty = new IotTaskAttrModelProperty();
                    cumulativeGasProperty.setName("累计注气量");
                    cumulativeGasProperty.setAccessMode("r");
                    cumulativeGasProperty.setRequired(1);
                    cumulativeGasProperty.setSort(4);
                    cumulativeGasProperty.setUnit("万方");
                    cumulativeGasProperty.setDataType("double");
                    cumulativeGasProperty.setIdentifier("cumulativeGasInjection");
                    cumulativeGasProperty.setActualValue(companyCumulativeGasInjectionPair.get(deptId).toString());
                    cumulativeGasProperty.setMaxValue(StrUtil.EMPTY);
                    cumulativeGasProperty.setMinValue(StrUtil.EMPTY);
                    cumulativeGasProperty.setDefaultValue(StrUtil.EMPTY);
                    companyExtProperties.add(cumulativeGasProperty);
                }
                // 注气量完成比
                if (companyActualGasInjectionPair.containsKey(deptId) && companyPlanGasInjectionPair.containsKey(deptId)) {
                    BigDecimal actualGasInjection = companyActualGasInjectionPair.get(deptId);
                    BigDecimal planGasInjection = companyPlanGasInjectionPair.get(deptId);
                    if (planGasInjection.compareTo(BigDecimal.ZERO) > 0) {
                        BigDecimal gasCompletionRatio = actualGasInjection.divide(planGasInjection, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
                        IotTaskAttrModelProperty gasCompletionRatioProperty = new IotTaskAttrModelProperty();
                        gasCompletionRatioProperty.setName("注气完成比");
                        gasCompletionRatioProperty.setAccessMode("r");
                        gasCompletionRatioProperty.setRequired(1);
                        gasCompletionRatioProperty.setSort(3);
                        gasCompletionRatioProperty.setUnit("%");
                        gasCompletionRatioProperty.setDataType("double");
                        gasCompletionRatioProperty.setIdentifier("gasCompletionRatio");
                        gasCompletionRatioProperty.setActualValue(gasCompletionRatio.toString());
                        gasCompletionRatioProperty.setMaxValue(StrUtil.EMPTY);
                        gasCompletionRatioProperty.setMinValue(StrUtil.EMPTY);
                        gasCompletionRatioProperty.setDefaultValue(StrUtil.EMPTY);
                        companyExtProperties.add(gasCompletionRatioProperty);
                    }
                }

                // 瑞鹰 进尺 井次 细化数据
                if (companyPlanFootagePair.containsKey(deptId)) {
                    IotTaskAttrModelProperty planFootageProperty = new IotTaskAttrModelProperty();
                    planFootageProperty.setName("计划进尺");
                    planFootageProperty.setAccessMode("r");
                    planFootageProperty.setRequired(1);
                    planFootageProperty.setSort(1);
                    planFootageProperty.setUnit("m");
                    planFootageProperty.setDataType("double");
                    planFootageProperty.setIdentifier("planFootage");
                    planFootageProperty.setActualValue(companyPlanFootagePair.get(deptId).toString());
                    planFootageProperty.setMaxValue(StrUtil.EMPTY);
                    planFootageProperty.setMinValue(StrUtil.EMPTY);
                    planFootageProperty.setDefaultValue(StrUtil.EMPTY);
                    companyExtProperties.add(planFootageProperty);
                }
                if (companyActualFootagePair.containsKey(deptId)) {
                    IotTaskAttrModelProperty actualFootageProperty = new IotTaskAttrModelProperty();
                    actualFootageProperty.setName("实际进尺");
                    actualFootageProperty.setAccessMode("r");
                    actualFootageProperty.setRequired(1);
                    actualFootageProperty.setSort(2);
                    actualFootageProperty.setUnit("m");
                    actualFootageProperty.setDataType("double");
                    actualFootageProperty.setIdentifier("actualFootage");
                    actualFootageProperty.setActualValue(companyActualFootagePair.get(deptId).toString());
                    actualFootageProperty.setMaxValue(StrUtil.EMPTY);
                    actualFootageProperty.setMinValue(StrUtil.EMPTY);
                    actualFootageProperty.setDefaultValue(StrUtil.EMPTY);
                    companyExtProperties.add(actualFootageProperty);
                }
                if (companyCumulativeFootagePair.containsKey(deptId)) {
                    IotTaskAttrModelProperty cumulativeFootageProperty = new IotTaskAttrModelProperty();
                    cumulativeFootageProperty.setName("累计进尺");
                    cumulativeFootageProperty.setAccessMode("r");
                    cumulativeFootageProperty.setRequired(1);
                    cumulativeFootageProperty.setSort(4);
                    cumulativeFootageProperty.setUnit("m");
                    cumulativeFootageProperty.setDataType("double");
                    cumulativeFootageProperty.setIdentifier("cumulativeFootage");
                    cumulativeFootageProperty.setActualValue(companyCumulativeFootagePair.get(deptId).toString());
                    cumulativeFootageProperty.setMaxValue(StrUtil.EMPTY);
                    cumulativeFootageProperty.setMinValue(StrUtil.EMPTY);
                    cumulativeFootageProperty.setDefaultValue(StrUtil.EMPTY);
                    companyExtProperties.add(cumulativeFootageProperty);
                }
                // 进尺完成比
                if (companyActualFootagePair.containsKey(deptId) && companyPlanFootagePair.containsKey(deptId)) {
                    BigDecimal actualFootage = companyActualFootagePair.get(deptId);
                    BigDecimal planFootage = companyPlanFootagePair.get(deptId);
                    if (planFootage.compareTo(BigDecimal.ZERO) > 0) {
                        BigDecimal footageCompletionRatio = actualFootage.divide(planFootage, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
                        IotTaskAttrModelProperty footageCompletionRatioProperty = new IotTaskAttrModelProperty();
                        footageCompletionRatioProperty.setName("进尺完成比");
                        footageCompletionRatioProperty.setAccessMode("r");
                        footageCompletionRatioProperty.setRequired(1);
                        footageCompletionRatioProperty.setSort(3);
                        footageCompletionRatioProperty.setUnit("%");
                        footageCompletionRatioProperty.setDataType("double");
                        footageCompletionRatioProperty.setIdentifier("footageCompletionRatio");
                        footageCompletionRatioProperty.setActualValue(footageCompletionRatio.toString());
                        footageCompletionRatioProperty.setMaxValue(StrUtil.EMPTY);
                        footageCompletionRatioProperty.setMinValue(StrUtil.EMPTY);
                        footageCompletionRatioProperty.setDefaultValue(StrUtil.EMPTY);
                        companyExtProperties.add(footageCompletionRatioProperty);
                    }
                }
                // 瑞鹰 瑞都 井次
                if (companyPlanWellTripsPair.containsKey(deptId)) {
                    IotTaskAttrModelProperty planWellTripsProperty = new IotTaskAttrModelProperty();
                    planWellTripsProperty.setName("计划井次");
                    planWellTripsProperty.setAccessMode("r");
                    planWellTripsProperty.setRequired(1);
                    planWellTripsProperty.setSort(5);
                    planWellTripsProperty.setUnit("井");
                    planWellTripsProperty.setDataType("double");
                    planWellTripsProperty.setIdentifier("planWellTrips");
                    planWellTripsProperty.setActualValue(companyPlanWellTripsPair.get(deptId).toString());
                    planWellTripsProperty.setMaxValue(StrUtil.EMPTY);
                    planWellTripsProperty.setMinValue(StrUtil.EMPTY);
                    planWellTripsProperty.setDefaultValue(StrUtil.EMPTY);
                    companyExtProperties.add(planWellTripsProperty);
                }
                if (companyActualWellTripsPair.containsKey(deptId)) {
                    IotTaskAttrModelProperty actualWellTripsProperty = new IotTaskAttrModelProperty();
                    actualWellTripsProperty.setName("实际井次");
                    actualWellTripsProperty.setAccessMode("r");
                    actualWellTripsProperty.setRequired(1);
                    actualWellTripsProperty.setSort(6);
                    actualWellTripsProperty.setUnit("井");
                    actualWellTripsProperty.setDataType("double");
                    actualWellTripsProperty.setIdentifier("actualWellTrips");
                    actualWellTripsProperty.setActualValue(companyActualWellTripsPair.get(deptId).toString());
                    actualWellTripsProperty.setMaxValue(StrUtil.EMPTY);
                    actualWellTripsProperty.setMinValue(StrUtil.EMPTY);
                    actualWellTripsProperty.setDefaultValue(StrUtil.EMPTY);
                    companyExtProperties.add(actualWellTripsProperty);
                }
                if (companyCumulativeWellTripsPair.containsKey(deptId)) {
                    IotTaskAttrModelProperty cumulativeWellTripsProperty = new IotTaskAttrModelProperty();
                    cumulativeWellTripsProperty.setName("累计井次");
                    cumulativeWellTripsProperty.setAccessMode("r");
                    cumulativeWellTripsProperty.setRequired(1);
                    cumulativeWellTripsProperty.setSort(8);
                    cumulativeWellTripsProperty.setUnit("井");
                    cumulativeWellTripsProperty.setDataType("double");
                    cumulativeWellTripsProperty.setIdentifier("cumulativeWellTrips");
                    cumulativeWellTripsProperty.setActualValue(companyCumulativeWellTripsPair.get(deptId).toString());
                    cumulativeWellTripsProperty.setMaxValue(StrUtil.EMPTY);
                    cumulativeWellTripsProperty.setMinValue(StrUtil.EMPTY);
                    cumulativeWellTripsProperty.setDefaultValue(StrUtil.EMPTY);
                    companyExtProperties.add(cumulativeWellTripsProperty);
                }
                // 井次完成比
                if (companyActualWellTripsPair.containsKey(deptId) && companyPlanWellTripsPair.containsKey(deptId)) {
                    BigDecimal actualWellTrips = companyActualWellTripsPair.get(deptId);
                    BigDecimal planWellTrips = companyPlanWellTripsPair.get(deptId);
                    if (planWellTrips.compareTo(BigDecimal.ZERO) > 0) {
                        BigDecimal wellTripsCompletionRatio = actualWellTrips.divide(planWellTrips, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
                        IotTaskAttrModelProperty wellTripsCompletionRatioProperty = new IotTaskAttrModelProperty();
                        wellTripsCompletionRatioProperty.setName("井次完成比");
                        wellTripsCompletionRatioProperty.setAccessMode("r");
                        wellTripsCompletionRatioProperty.setRequired(1);
                        wellTripsCompletionRatioProperty.setSort(7);
                        wellTripsCompletionRatioProperty.setUnit("%");
                        wellTripsCompletionRatioProperty.setDataType("double");
                        wellTripsCompletionRatioProperty.setIdentifier("wellTripsCompletionRatio");
                        wellTripsCompletionRatioProperty.setActualValue(wellTripsCompletionRatio.toString());
                        wellTripsCompletionRatioProperty.setMaxValue(StrUtil.EMPTY);
                        wellTripsCompletionRatioProperty.setMinValue(StrUtil.EMPTY);
                        wellTripsCompletionRatioProperty.setDefaultValue(StrUtil.EMPTY);
                        companyExtProperties.add(wellTripsCompletionRatioProperty);
                    }
                }

                // 5#公司 瑞都 层数
                if (companyPlanLayersPair.containsKey(deptId)) {
                    IotTaskAttrModelProperty planLayersProperty = new IotTaskAttrModelProperty();
                    planLayersProperty.setName("计划层数");
                    planLayersProperty.setAccessMode("r");
                    planLayersProperty.setRequired(1);
                    planLayersProperty.setSort(1);
                    planLayersProperty.setUnit("层");
                    planLayersProperty.setDataType("double");
                    planLayersProperty.setIdentifier("planLayers");
                    planLayersProperty.setActualValue(companyPlanLayersPair.get(deptId).toString());
                    planLayersProperty.setMaxValue(StrUtil.EMPTY);
                    planLayersProperty.setMinValue(StrUtil.EMPTY);
                    planLayersProperty.setDefaultValue(StrUtil.EMPTY);
                    companyExtProperties.add(planLayersProperty);
                }
                if (companyActualLayersPair.containsKey(deptId)) {
                    IotTaskAttrModelProperty actualLayersProperty = new IotTaskAttrModelProperty();
                    actualLayersProperty.setName("实际层数");
                    actualLayersProperty.setAccessMode("r");
                    actualLayersProperty.setRequired(1);
                    actualLayersProperty.setSort(2);
                    actualLayersProperty.setUnit("层");
                    actualLayersProperty.setDataType("double");
                    actualLayersProperty.setIdentifier("actualLayers");
                    actualLayersProperty.setActualValue(companyActualLayersPair.get(deptId).toString());
                    actualLayersProperty.setMaxValue(StrUtil.EMPTY);
                    actualLayersProperty.setMinValue(StrUtil.EMPTY);
                    actualLayersProperty.setDefaultValue(StrUtil.EMPTY);
                    companyExtProperties.add(actualLayersProperty);
                }
                if (companyCumulativeLayersPair.containsKey(deptId)) {
                    IotTaskAttrModelProperty cumulativeLayersProperty = new IotTaskAttrModelProperty();
                    cumulativeLayersProperty.setName("累计层数");
                    cumulativeLayersProperty.setAccessMode("r");
                    cumulativeLayersProperty.setRequired(1);
                    cumulativeLayersProperty.setSort(4);
                    cumulativeLayersProperty.setUnit("层");
                    cumulativeLayersProperty.setDataType("double");
                    cumulativeLayersProperty.setIdentifier("cumulativeLayers");
                    cumulativeLayersProperty.setActualValue(companyCumulativeLayersPair.get(deptId).toString());
                    cumulativeLayersProperty.setMaxValue(StrUtil.EMPTY);
                    cumulativeLayersProperty.setMinValue(StrUtil.EMPTY);
                    cumulativeLayersProperty.setDefaultValue(StrUtil.EMPTY);
                    companyExtProperties.add(cumulativeLayersProperty);
                }
                // 层数完成比
                if (companyActualLayersPair.containsKey(deptId) && companyPlanLayersPair.containsKey(deptId)) {
                    BigDecimal actualLayers = companyActualLayersPair.get(deptId);
                    BigDecimal planLayers = companyPlanLayersPair.get(deptId);
                    if (planLayers.compareTo(BigDecimal.ZERO) > 0) {
                        BigDecimal layersCompletionRatio = actualLayers.divide(planLayers, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
                        IotTaskAttrModelProperty layersCompletionRatioProperty = new IotTaskAttrModelProperty();
                        layersCompletionRatioProperty.setName("层数完成比");
                        layersCompletionRatioProperty.setAccessMode("r");
                        layersCompletionRatioProperty.setRequired(1);
                        layersCompletionRatioProperty.setSort(3);
                        layersCompletionRatioProperty.setUnit("%");
                        layersCompletionRatioProperty.setDataType("double");
                        layersCompletionRatioProperty.setIdentifier("layersCompletionRatio");
                        layersCompletionRatioProperty.setActualValue(layersCompletionRatio.toString());
                        layersCompletionRatioProperty.setMaxValue(StrUtil.EMPTY);
                        layersCompletionRatioProperty.setMinValue(StrUtil.EMPTY);
                        layersCompletionRatioProperty.setDefaultValue(StrUtil.EMPTY);
                        companyExtProperties.add(layersCompletionRatioProperty);
                    }
                }
                // 各专业公司公共属性 施工设备 投运设备
                if (companyInServiceDeviceNumPair.containsKey(deptId)) {
                    IotTaskAttrModelProperty inServiceDeviceProperty = new IotTaskAttrModelProperty();
                    inServiceDeviceProperty.setName("投运设备数量");
                    inServiceDeviceProperty.setAccessMode("r");
                    inServiceDeviceProperty.setRequired(0);
                    inServiceDeviceProperty.setSort(9);
                    inServiceDeviceProperty.setUnit("台");
                    inServiceDeviceProperty.setDataType("double");
                    inServiceDeviceProperty.setIdentifier("inServiceDeviceNum");
                    inServiceDeviceProperty.setActualValue(companyInServiceDeviceNumPair.get(deptId).toString());
                    inServiceDeviceProperty.setMaxValue(StrUtil.EMPTY);
                    inServiceDeviceProperty.setMinValue(StrUtil.EMPTY);
                    inServiceDeviceProperty.setDefaultValue(StrUtil.EMPTY);
                    companyExtProperties.add(inServiceDeviceProperty);
                }
                if (companyConstructionDeviceNumPair.containsKey(deptId)) {
                    IotTaskAttrModelProperty constructionDeviceProperty = new IotTaskAttrModelProperty();
                    constructionDeviceProperty.setName("施工设备数量");
                    constructionDeviceProperty.setAccessMode("r");
                    constructionDeviceProperty.setRequired(0);
                    constructionDeviceProperty.setSort(10);
                    constructionDeviceProperty.setUnit("台");
                    constructionDeviceProperty.setDataType("double");
                    constructionDeviceProperty.setIdentifier("constructionDeviceNum");
                    constructionDeviceProperty.setActualValue(companyConstructionDeviceNumPair.get(deptId).toString());
                    constructionDeviceProperty.setMaxValue(StrUtil.EMPTY);
                    constructionDeviceProperty.setMinValue(StrUtil.EMPTY);
                    constructionDeviceProperty.setDefaultValue(StrUtil.EMPTY);
                    companyExtProperties.add(constructionDeviceProperty);
                }

                if (companyInServiceDeviceNumPair.containsKey(deptId) && companyConstructionDeviceNumPair.containsKey(deptId)) {
                    // 各专业公司共用属性 设备利用率 施工设备/投运设备
                    BigDecimal inServiceDeviceNum = companyInServiceDeviceNumPair.get(deptId);
                    BigDecimal constructionDeviceNum = companyConstructionDeviceNumPair.get(deptId);
                    if (inServiceDeviceNum.compareTo(BigDecimal.ZERO) > 0) {
                        BigDecimal utilizationRate = constructionDeviceNum.divide(inServiceDeviceNum, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
                        IotTaskAttrModelProperty utilizationRateProperty = new IotTaskAttrModelProperty();
                        utilizationRateProperty.setName("设备利用率");
                        utilizationRateProperty.setAccessMode("r");
                        utilizationRateProperty.setRequired(1);
                        utilizationRateProperty.setSort(11);
                        utilizationRateProperty.setUnit("%");
                        utilizationRateProperty.setDataType("double");
                        utilizationRateProperty.setIdentifier("utilizationRate");
                        utilizationRateProperty.setActualValue(utilizationRate.toString());
                        utilizationRateProperty.setMaxValue(StrUtil.EMPTY);
                        utilizationRateProperty.setMinValue(StrUtil.EMPTY);
                        utilizationRateProperty.setDefaultValue(StrUtil.EMPTY);
                        companyExtProperties.add(utilizationRateProperty);
                        // 设备利用率 环比 (本期-上期)/上期
                        if (beforeCompanyInServiceDeviceNumPair.containsKey(deptId) && beforeCompanyConstructionDeviceNumPair.containsKey(deptId)) {
                            // 计算上期会议 对应专业公司的设备利用率
                            BigDecimal beforeInServiceDeviceNum = beforeCompanyInServiceDeviceNumPair.get(deptId);
                            BigDecimal beforeConstructionDeviceNum = beforeCompanyConstructionDeviceNumPair.get(deptId);
                            if (beforeInServiceDeviceNum.compareTo(BigDecimal.ZERO) > 0) {
                                BigDecimal beforeUtilizationRate = beforeConstructionDeviceNum.divide(beforeInServiceDeviceNum, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
                                if (beforeUtilizationRate.compareTo(BigDecimal.ZERO) > 0) {
                                    BigDecimal periodOnPeriod = utilizationRate.subtract(beforeUtilizationRate).
                                            divide(beforeUtilizationRate, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
                                    IotTaskAttrModelProperty periodOnPeriodProperty = new IotTaskAttrModelProperty();
                                    periodOnPeriodProperty.setName("环比");
                                    periodOnPeriodProperty.setAccessMode("r");
                                    periodOnPeriodProperty.setRequired(1);
                                    periodOnPeriodProperty.setSort(12);
                                    periodOnPeriodProperty.setUnit("%");
                                    periodOnPeriodProperty.setDataType("double");
                                    periodOnPeriodProperty.setIdentifier("periodOnPeriod");
                                    periodOnPeriodProperty.setActualValue(periodOnPeriod.toString());
                                    periodOnPeriodProperty.setMaxValue(StrUtil.EMPTY);
                                    periodOnPeriodProperty.setMinValue(StrUtil.EMPTY);
                                    periodOnPeriodProperty.setDefaultValue(StrUtil.EMPTY);
                                    companyExtProperties.add(periodOnPeriodProperty);
                                }
                            }
                        }
                    }

                }
                // 设置各专业公司的工作量汇总数据
                // 将拆分工作量明细 按照 sort 升序排列
                companyExtProperties.sort(Comparator.comparing(
                        IotTaskAttrModelProperty::getSort,
                        Comparator.nullsLast(Integer::compareTo)
                ));
                tempMeeting.setExtProperty(companyExtProperties);
                List<IotOperationMeetingDetailRespVO> meetingDetails = BeanUtils.toBean(details, IotOperationMeetingDetailRespVO.class);
                tempMeeting.setDetails(meetingDetails);
                resultMeetings.add(tempMeeting);
            }
        });
        // 按照会议所属部门的 sort 升序排列
        resultMeetings.sort(Comparator.comparingInt(vo ->
                Optional.ofNullable(vo.getDeptId())
                        .map(deptSortMap::get)
                        .orElse(Integer.MAX_VALUE)
        ));
        return resultMeetings;
    }

    @Override
    public Set<String> availableQueryPeriods() {
        // 查询所有公司的运营会议 可供查询的期次 组装后的数据格式 2026-第8期

        return Collections.emptySet();
    }

    @Override
    public List<IotTaskAttrModelProperty> currentCompanyExtProperties() {
        List<IotTaskAttrModelProperty> workloadProperties = new ArrayList<>();
        // 当前登录人所属公司的deptId 关联表 rq_iot_operation_meeting_attrs
        Long loginUserDeptId = SecurityFrameworkUtils.getLoginUserDeptId();
        DeptDO dept = deptservice.getDept(loginUserDeptId);
        if (ObjUtil.isEmpty(dept)) {
            throw exception(DEPT_NOT_FOUND);
        }
        // 向上查找所属公司ID（类型为"1"的部门），找不到则保留当前部门ID
        Long companyDeptId = findCompanyDeptId(dept);

        IotOperationMeetingAttrsPageReqVO reqVO = new IotOperationMeetingAttrsPageReqVO();
        reqVO.setDeptId(companyDeptId);
        reqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        PageResult<IotOperationMeetingAttrsDO> pageAttrs = iotOperationMeetingAttrsMapper.selectPage(reqVO);
        if (ObjUtil.isEmpty(pageAttrs)) {
            throw exception(IOT_OPERATION_MEETING_ATTRS_NOT_EXISTS);
        }
        List<IotOperationMeetingAttrsDO> attrs = pageAttrs.getList();
        if (CollUtil.isEmpty(attrs)) {
            throw exception(IOT_OPERATION_MEETING_ATTRS_NOT_EXISTS);
        }
        attrs.forEach(attr -> {
            // 下期工作计划 标识 defaultValue = next
            // 可见性 标识 required = 1
            // 只读 标识 accessMode = r
            // 可读可写 标识 accessMode = rw
            IotTaskAttrModelProperty property = attr.getExtProperty();
            if (ObjUtil.isNotEmpty(property) && ObjUtil.isNotEmpty(property.getRequired()) && 1 == property.getRequired()) {
                workloadProperties.add(attr.getExtProperty());
            }
        });
        // 按照 sort 值 升序 排序
        if (CollUtil.isNotEmpty(workloadProperties)) {
            workloadProperties.sort(Comparator.comparing(
                    IotTaskAttrModelProperty::getSort,
                    Comparator.nullsLast(Comparator.naturalOrder())
            ));
        }
        return workloadProperties;
    }

    @Override
    public List<IotOperationMeetingRespVO> companyWorkloadDetails(IotOperationMeetingPageReqVO pageReqVO) {
        // 列表中只有1个专业公司 deptId 对象
        List<IotOperationMeetingRespVO> resultMeetings = new ArrayList<>();
        pageReqVO.getMeetingSeries();   // 会议期次
        pageReqVO.getYear();            // 年份
        pageReqVO.getDeptId();          // 专业公司id
        String meetingSeries = pageReqVO.getMeetingSeries();        // 期次
        Integer year = pageReqVO.getYear();                         // 年份
        Long deptId = pageReqVO.getDeptId();                        // 专业公司id
        if (StrUtil.isBlank(meetingSeries) || ObjUtil.isEmpty(year) || ObjUtil.isEmpty(deptId)) {
            return resultMeetings;
        }
        // 根据 期次 日期yyyy 查询专业公司deptId下所有的项目明细
        IotOperationMeetingDetailPageReqVO reqVO = new IotOperationMeetingDetailPageReqVO();
        reqVO.setYear(year);
        // 提取 meetingSeries 中的数字
        Optional.ofNullable(meetingSeries)
                .map(s -> s.replaceAll("\\D", "")) // 提取所有数字
                .filter(StrUtil::isNumeric)    // 确保是数字
                .map(Integer::valueOf)
                .ifPresent(reqVO::setSort);
        reqVO.setDeptId(deptId);
        List<IotOperationMeetingDetailDO> summarizedDetails = iotOperationMeetingDetailMapper.summarizedProjectDetails(reqVO);
        if (CollUtil.isEmpty(summarizedDetails)) {
            return resultMeetings;
        }


        return Collections.emptyList();
    }

}