# SalesKpiWeeklyServiceImpl.java

    package cloud.wconnect.workperformance.service.saleskpi.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.workperformance.entity.saleskpi.SalesKpiConfig;
    import cloud.wconnect.workperformance.entity.saleskpi.SalesKpiWeekly;
    import cloud.wconnect.workperformance.mapper.saleskpi.SalesKpiWeeklyMapper;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.weekly.SalesKpiSalesmanItemDataDTO;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.weekly.SalesKpiWeeklyDTO;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.weekly.SalesKpiWeeklyNotesDTO;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.weekly.SalesKpiWeeklyValueDTO;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.weekly.basic.SalesKpiWeeklyBasicDTO;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.weekly.basic.SalesKpiWeeklyNotesBasicDTO;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.weekly.basic.SalesKpiWeeklyValueBasicDTO;
    import cloud.wconnect.workperformance.pojo.query.saleskpi.SalesKpiWeeklyQuery;
    import cloud.wconnect.workperformance.service.saleskpi.ISalesKpiConfigService;
    import cloud.wconnect.workperformance.service.saleskpi.ISalesKpiFirstDayLogService;
    import cloud.wconnect.workperformance.service.saleskpi.ISalesKpiWeeklyService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Sets;
    import org.apache.commons.collections4.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.lang.Nullable;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import jakarta.annotation.Resource;
    import java.math.BigDecimal;
    import java.text.MessageFormat;
    import java.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * sales kpi weekly 服务实现类
     * </p>
     *
     * @author zc
     * @since 2023-09-05
     */
    @Service
    @DS("work_performance")
    public class SalesKpiWeeklyServiceImpl extends ServiceImpl<SalesKpiWeeklyMapper, SalesKpiWeekly> implements ISalesKpiWeeklyService {
        @Lazy
        @Resource
        private ISalesKpiConfigService salesKpiConfigService;
        @Resource
        private ISalesKpiFirstDayLogService salesKpiFirstDayLogService;
        @Resource
        private UserInternalService userInternalService;
        @Resource
        private ObjectMapper objectMapper;
        @Resource
        private BeanMapper beanMapper;

        /**
         * 根据资产、周期获取 Weekly 数据
         *
         * @param propertyId 资产 id
         * @param salesIds   Sales config id
         * @param weekly     周期
         * @return SalesKpiWeekly 集合
         * @author zc
         * @date 2023/9/6
         */
        @Override
        public List<SalesKpiWeekly> listByWeekly(@Nullable Integer propertyId, @Nullable List<Integer> salesIds, long weekly) {
            return list(new QueryWrapper<SalesKpiWeekly>().lambda()
                    .eq(Objects.nonNull(propertyId), SalesKpiWeekly::getPropertyId, propertyId)
                    .eq(CollectionUtils.isNotEmpty(salesIds), SalesKpiWeekly::getSalesId, salesIds)
                    .eq(SalesKpiWeekly::getWeekly, weekly));
        }

        /**
         * 生成初始化数据
         *
         * @param propertyId 资产 id
         * @param weekly     周（yyyyMMdd）
         * @return SalesKpiWeekly 集合
         * @author zc
         * @date 2023/9/9
         */
        @Override
        public List<SalesKpiWeekly> generateInitData(int propertyId, long weekly) {
            // 获取开启的业务员配置数据
            List<SalesKpiConfig> salesKpiConfigs = salesKpiConfigService.listConfigs(propertyId, Boolean.TRUE);
            if (CollectionUtils.isEmpty(salesKpiConfigs)) {
                // 没有业务员配置数据则直接返回空集合
                return Collections.emptyList();
            }

            return salesKpiConfigs.stream().map(item ->
                    SalesKpiWeekly.builder()
                            .propertyId(propertyId)
                            .weekly(weekly)
                            .salesId(item.getId())
                            .salesmanId(item.getSalesmanId())
                            .salesAccount(item.getSalesAccount())
                            .jsonDetail(JsonUtils.convertObjToString(objectMapper,
                                    SalesKpiWeeklyBasicDTO.builder()
                                            .total(new SalesKpiWeeklyValueBasicDTO())
                                            .target(SalesKpiWeeklyValueBasicDTO.builder()
                                                    .value(item.getWeeklyTarget())
                                                    .crawlValue(item.getWeeklyTarget())
                                                    .build())
                                            .notes(new SalesKpiWeeklyNotesBasicDTO())
                                            .build())
                            ).build()
            ).toList();
        }

        /**
         * 获取 Weekly 数据
         *
         * @param tenantId            租户 id
         * @param propertyId          资产 id
         * @param timezone            时区
         * @param weekly              周（yyyyMMdd）
         * @param salesKpiWeeklyQuery 查询条件
         * @return SalesKpiWeeklyDTO
         * @author zc
         * @date 2023/9/9
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public ListPagedDTO<SalesKpiWeeklyDTO> list(int tenantId, int propertyId, String timezone, long weekly,
                                                    SalesKpiWeeklyQuery salesKpiWeeklyQuery) {
            // 根据周起始日的变更记录计算并转换为 Weekly 所在周的周起始日
            long firstDayOfWeek = salesKpiFirstDayLogService.conversionToFirstDayOfWeek(propertyId, timezone, weekly);

            // 校验指定周期下是否有数据，没有则生成初始数据
            checkWeeklyList(propertyId, firstDayOfWeek);

            // 获取开启的销售人 id
            List<Integer> statusOnSalesManIds = salesKpiConfigService.listConfigs(propertyId, Boolean.TRUE).stream()
                    .map(SalesKpiConfig::getSalesmanId).toList();
            if (CollectionUtils.isEmpty(statusOnSalesManIds)) {
                return new ListPagedDTO<>();
            }
            Page<SalesKpiWeekly> page = page(new Page<>(salesKpiWeeklyQuery.getPage(), salesKpiWeeklyQuery.getSize()),
                    new QueryWrapper<SalesKpiWeekly>().lambda()
                            .eq(SalesKpiWeekly::getPropertyId, propertyId)
                            .eq(SalesKpiWeekly::getWeekly, firstDayOfWeek)
                            .in(SalesKpiWeekly::getSalesmanId, statusOnSalesManIds));
            List<SalesKpiWeekly> salesKpiWeeklyList = page.getRecords();
            if (CollectionUtils.isEmpty(salesKpiWeeklyList)) {
                return new ListPagedDTO<>();
            }

            // 提取操作人名称
            Map<Integer, String> userId2Name = extractOperatorName(tenantId, salesKpiWeeklyList);

            // 组装返回参数
            List<SalesKpiWeeklyDTO> resultList = beanMapper.mapList(salesKpiWeeklyList, SalesKpiWeeklyDTO.class, (entity, dto) -> {
                dto.setSalesmanName(userId2Name.get(entity.getSalesmanId()));

                SalesKpiWeeklyBasicDTO salesKpiWeeklyBasic = JsonUtils.convertStringToObject(objectMapper,
                        entity.getJsonDetail(), SalesKpiWeeklyBasicDTO.class);

                SalesKpiWeeklyValueDTO total = beanMapper.map(salesKpiWeeklyBasic.getTotal(), SalesKpiWeeklyValueDTO.class);
                total.setUpdateName(userId2Name.get(total.getUpdateUid()));
                dto.setTotal(total);

                SalesKpiWeeklyValueDTO target = beanMapper.map(salesKpiWeeklyBasic.getTarget(), SalesKpiWeeklyValueDTO.class);
                target.setUpdateName(userId2Name.get(target.getUpdateUid()));
                dto.setTarget(target);

                SalesKpiWeeklyNotesDTO notes = beanMapper.map(salesKpiWeeklyBasic.getNotes(), SalesKpiWeeklyNotesDTO.class);
                notes.setUpdateName(userId2Name.get(notes.getUpdateUid()));
                dto.setNotes(notes);
            });

            return new ListPagedDTO<>(page.getTotal(), resultList);
        }

        /**
         * 校验指定周期下是否有数据，没有则生成初始数据
         *
         * @param propertyId 资产 id
         * @param weekly     周（yyyyMMdd）
         * @return 周数据
         * @author zc
         * @date 2023/9/9
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public List<SalesKpiWeekly> checkWeeklyList(int propertyId, long weekly) {
            List<SalesKpiWeekly> salesKpiWeeklyList = list(new QueryWrapper<SalesKpiWeekly>().lambda()
                    .eq(SalesKpiWeekly::getPropertyId, propertyId)
                    .eq(SalesKpiWeekly::getWeekly, weekly));
            if (CollectionUtils.isEmpty(salesKpiWeeklyList)) {
                // 生成并保存指定周的初始数据
                salesKpiWeeklyList = generateInitData(propertyId, weekly);
                if (CollectionUtils.isNotEmpty(salesKpiWeeklyList)) {
                    saveBatch(salesKpiWeeklyList);
                }
            }
            return salesKpiWeeklyList;
        }

        /**
         * 提取操作人名称
         *
         * @param tenantId           租户 id
         * @param salesKpiWeeklyList weekly 数据
         * @return 操作人名称
         * @author zc
         * @date 2023/9/11
         */
        private Map<Integer, String> extractOperatorName(int tenantId, List<SalesKpiWeekly> salesKpiWeeklyList) {
            Set<Integer> userIds = Sets.newHashSet();

            salesKpiWeeklyList.forEach(item -> {
                SalesKpiWeeklyBasicDTO salesKpiWeeklyBasic = JsonUtils.convertStringToObject(objectMapper,
                        item.getJsonDetail(), SalesKpiWeeklyBasicDTO.class);
                userIds.addAll(salesKpiWeeklyBasic.summaryUserIds());
                userIds.add(item.getSalesmanId());
            });
            return userInternalService.getNameMapByIds(tenantId, userIds);
        }

        /**
         * 校验数据是否存在
         *
         * @param id 数据 id
         * @return SalesKpiWeekly
         * @author zc
         * @date 2023/9/11
         */
        @Override
        public SalesKpiWeekly checkExist(int id) {
            SalesKpiWeekly salesKpiWeekly = getById(id);
            if (Objects.isNull(salesKpiWeekly)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The salesKpiWeekly is not found by id: " + id);
            }
            return salesKpiWeekly;
        }

        /**
         * 校验数据归属
         *
         * @param propertyId           资产 id
         * @param originSalesKpiWeekly 源数据
         * @author zc
         * @date 2023/9/11
         */
        @Override
        public void checkBelong(int propertyId, SalesKpiWeekly originSalesKpiWeekly) {
            if (!originSalesKpiWeekly.getPropertyId().equals(propertyId)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        MessageFormat.format("The salesKpiWeekly does not belong to this property; propertyId:{0}; salesKpiWeeklyId:{1}",
                                propertyId, originSalesKpiWeekly.getId()));
            }
        }

        /**
         * 保存数据
         *
         * @param id          数据 id
         * @param origin      原 Weekly 数据
         * @param weeklyBasic Weekly 数据
         * @param operatorId  操作人 id
         * @author zc
         * @date 2023/9/11
         */
        @Override
        public void save(int id, SalesKpiWeekly origin, SalesKpiWeeklyBasicDTO weeklyBasic, int operatorId) {
            // 组装参数
            long nowTimestamp = DateTimeUtils.nowTimestamp();

            SalesKpiWeeklyBasicDTO originBasic = JsonUtils.convertStringToObject(objectMapper,
                    origin.getJsonDetail(), SalesKpiWeeklyBasicDTO.class);

            setWeeklyValue(originBasic.getTotal(), weeklyBasic.getTotal(), operatorId, nowTimestamp);
            setWeeklyValue(originBasic.getTarget(), weeklyBasic.getTarget(), operatorId, nowTimestamp);
            setWeeklyNote(originBasic.getNotes(), weeklyBasic.getNotes(), operatorId, nowTimestamp);

            // 更新数据
            updateById(SalesKpiWeekly.builder()
                    .id(id)
                    .jsonDetail(JsonUtils.convertObjToString(objectMapper, originBasic))
                    .build());
        }

        /**
         * 保存业务员指标数据
         *
         * @param propertyId      资产 id
         * @param weekly          周（yyyyMMdd）
         * @param itemDataDTOList 指标数据
         * @author zc
         * @date 2023/9/11
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveSalesmanData(int propertyId, long weekly, List<SalesKpiSalesmanItemDataDTO> itemDataDTOList) {
            // 校验指定周期下是否有数据，没有则生成初始数据
            List<SalesKpiWeekly> salesKpiWeeklyList = checkWeeklyList(propertyId, weekly);

            Map<Integer, SalesKpiSalesmanItemDataDTO> salesId2ItemData = itemDataDTOList.stream()
                    .collect(Collectors.toMap(SalesKpiSalesmanItemDataDTO::getSalesId, Function.identity()));

            // 组装 Weekly 数据
            salesKpiWeeklyList.forEach(item -> {
                SalesKpiSalesmanItemDataDTO itemDataDTO = salesId2ItemData.get(item.getSalesId());
                if (Objects.nonNull(itemDataDTO)) {
                    SalesKpiWeeklyBasicDTO salesKpiWeeklyBasic = JsonUtils.convertStringToObject(objectMapper,
                            item.getJsonDetail(), SalesKpiWeeklyBasicDTO.class);
                    setWeeklyCrawlValue(salesKpiWeeklyBasic.getTotal(), itemDataDTO.getValue());
                    item.setJsonDetail(JsonUtils.convertObjToString(objectMapper, salesKpiWeeklyBasic));
                }
            });
            // 保存 Weekly 数据
            updateBatchById(salesKpiWeeklyList);
        }

        /**
         * 设置 value 值
         *
         * @param weeklyValueBasicDTO weeklyValueBasicDTO
         * @param value               actual
         * @author zc
         * @date 2023/9/11
         */
        private void setWeeklyCrawlValue(SalesKpiWeeklyValueBasicDTO weeklyValueBasicDTO, BigDecimal value) {
            if (Objects.isNull(weeklyValueBasicDTO.getValue())
                    || Objects.isNull(weeklyValueBasicDTO.getUpdateUid())) {
                weeklyValueBasicDTO.setValue(value);
                weeklyValueBasicDTO.setRemark(null);
                weeklyValueBasicDTO.setUpdateUid(null);
                weeklyValueBasicDTO.setUpdateTime(null);
            }
            weeklyValueBasicDTO.setCrawlValue(value);
        }

        /**
         * 设置编辑内容
         *
         * @param origin      原数据
         * @param current     当前数据
         * @param operatorId  操作人 id
         * @param operateTime 操作时间
         * @author zc
         * @date 2023/9/12
         */
        private void setWeeklyValue(SalesKpiWeeklyValueBasicDTO origin, SalesKpiWeeklyValueBasicDTO current,
                                    int operatorId, long operateTime) {
            boolean changed = compareNotEqual(origin.getValue(), current.getValue())
                    || !StringUtils.equals(origin.getRemark(), current.getRemark());
            if (changed) {
                origin.setValue(current.getValue());
                origin.setRemark(current.getRemark());
                origin.setUpdateUid(operatorId);
                origin.setUpdateTime(operateTime);
            }
        }

        /**
         * 比较数值是否不相等
         *
         * @param value1 数值 1
         * @param value2 数值 2
         * @return true: 不相等 false：相等
         * @author zc
         * @date 2024/2/8
         */
        private boolean compareNotEqual(BigDecimal value1, BigDecimal value2) {
            boolean notEqual = false;
            if (value1 != null && value2 != null) {
                if (value1.compareTo(value2) != 0) {
                    notEqual = true;
                }
            } else if (!Objects.equals(value1, value2)){
                notEqual = true;
            }
            return notEqual;
        }

        /**
         * 设置编辑备注
         *
         * @param origin      原数据
         * @param current     当前数据
         * @param operatorId  操作人 id
         * @param operateTime 操作时间
         * @author zc
         * @date 2023/9/12
         */
        private void setWeeklyNote(SalesKpiWeeklyNotesBasicDTO origin, SalesKpiWeeklyNotesBasicDTO current,
                                   int operatorId, long operateTime) {
            if (!StringUtils.equals(Objects.isNull(origin.getContent()) ? StringUtils.EMPTY : origin.getContent(),
                    Objects.isNull(current.getContent()) ? StringUtils.EMPTY : current.getContent())) {
                origin.setContent(current.getContent());
                origin.setUpdateUid(operatorId);
                origin.setUpdateTime(operateTime);
            }
        }
    }