# M3JournalEntryServiceImpl.java

    package cloud.wconnect.finance.service.m3.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.finance.cons.pl.AccountTypeEnum;
    import cloud.wconnect.finance.entity.m3.M3JournalEntry;
    import cloud.wconnect.finance.mapper.m3.M3JournalEntryMapper;
    import cloud.wconnect.finance.pojo.dto.m3.CalculatedM3JournalEntryDTO;
    import cloud.wconnect.finance.pojo.dto.m3.M3JournalEntryValueDTO;
    import cloud.wconnect.finance.pojo.dto.pl.m3api.JournalEntryDTO;
    import cloud.wconnect.finance.service.m3.IM3JournalEntryService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.math.BigDecimal;
    import java.time.LocalDate;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.stream.Collectors;

    /**
     * <p>
     * M3系统的Journal Entry数据 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2025-10-14
     */
    @Slf4j
    @Service
    @DS("finance")
    public class M3JournalEntryServiceImpl extends ServiceImpl<M3JournalEntryMapper, M3JournalEntry> implements IM3JournalEntryService {

        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private ObjectMapper objectMapper;

        /**
         * 保存基础数据
         *
         * @param propertyDTO      资产DTO
         * @param journalEntryDTOS M3 Journal Entry数据
         * @param startLocalDate   开始日期
         * @param endLocalDate     结束日期
         * @author lichen
         * @date 2025/10/14
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void save(PropertyDTO propertyDTO, List<JournalEntryDTO> journalEntryDTOS, LocalDate startLocalDate, LocalDate endLocalDate) {
            if (CollectionUtils.isEmpty(journalEntryDTOS)) return;

            //按照发布日期分组
            Map<String, List<JournalEntryDTO>> postDate2JournalEntryDTOList = journalEntryDTOS.stream().collect(Collectors.groupingBy(JournalEntryDTO::getPostDate));

            ArrayList<M3JournalEntry> m3JournalEntryList = Lists.newArrayListWithCapacity(postDate2JournalEntryDTOList.size());

            postDate2JournalEntryDTOList.keySet().stream().sorted().forEach(postDate -> {
                List<JournalEntryDTO> journalEntryDTOSByPostDate = postDate2JournalEntryDTOList.get(postDate);
                //指定日期的JournalEntry数据转化为json字符串
                List<M3JournalEntryValueDTO> m3JournalEntryValueDTOS = beanMapper.mapList(journalEntryDTOSByPostDate, M3JournalEntryValueDTO.class,
                        (journalEntryDTO, m3JournalEntryValueDTO) -> {
                            m3JournalEntryValueDTO.setAccountNumber(journalEntryDTO.getGlAccount().replace(".000", ""));
                        });
                String value = JsonUtils.convertObjListToString(objectMapper, m3JournalEntryValueDTOS);

                M3JournalEntry m3JournalEntry = new M3JournalEntry()
                        .setTenantId(propertyDTO.getTenantId())
                        .setPropertyId(propertyDTO.getId())
                        .setPostDate(Integer.valueOf(postDate.replace("-", "")))
                        .setValue(value)
                        .setCreateTime(DateTimeUtils.nowTimestamp());
                m3JournalEntryList.add(m3JournalEntry);
            });

            //删除指定资产指定日期的数据
            remove(new LambdaUpdateWrapper<M3JournalEntry>()
                    .eq(M3JournalEntry::getPropertyId, propertyDTO.getId())
                    .between(M3JournalEntry::getPostDate,
                            Integer.valueOf(DateTimeUtils.localDateToStr(startLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD)),
                            Integer.valueOf(DateTimeUtils.localDateToStr(endLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD))));

            // 分批处理，每200条记录一批
            int batchSize = 200;
            for (int i = 0; i < m3JournalEntryList.size(); i += batchSize) {
                int endIndex = Math.min(i + batchSize, m3JournalEntryList.size());
                List<M3JournalEntry> batch = m3JournalEntryList.subList(i, endIndex);
                //批量新增
                baseMapper.insertBatch(batch);
            }
        }

        /**
         * 按日期查询数据
         *
         * @param propertyId 资产ID
         * @param startDate  开始日期
         * @param endDate    结束日期
         * @return java.util.List<cloud.wconnect.finance.entity.m3.M3JournalEntry>
         * @author lichen
         * @date 2025/10/17
         */
        @Override
        public List<M3JournalEntry> listByPostDate(Integer propertyId, Integer startDate, Integer endDate) {
            return list(new LambdaUpdateWrapper<M3JournalEntry>().eq(M3JournalEntry::getPropertyId, propertyId)
                    .between(M3JournalEntry::getPostDate, startDate, endDate));
        }

        /**
         * 计算指定GLAccount和指定日期范围的数据
         *
         * @param propertyId                资产ID
         * @param accountNumber2AccountType GLAccount集合
         * @param startLocalDate            开始日期
         * @param endLocalDate              结束日期
         * @return java.util.List<cloud.wconnect.finance.pojo.dto.m3.CalculatedM3JournalEntryDTO>
         * @author lichen
         * @date 2025/10/17
         */
        @Override
        public List<CalculatedM3JournalEntryDTO> calculateByAccountNumberAndDateRange(Integer propertyId, Map<String, Integer> accountNumber2AccountType,
                                                                                      LocalDate startLocalDate, LocalDate endLocalDate) {
            Integer startDate = Integer.valueOf(DateTimeUtils.localDateToStr(startLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
            Integer endDate = Integer.valueOf(DateTimeUtils.localDateToStr(endLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));

            //基础数据
            List<M3JournalEntry> m3JournalEntries = listByPostDate(propertyId, startDate, endDate);

            //待计算的accountNumber集合
            Set<String> accountNumbers = accountNumber2AccountType.keySet();

            ArrayList<CalculatedM3JournalEntryDTO> calculatedList = Lists.newArrayListWithCapacity(accountNumbers.size() * 15);

            for (M3JournalEntry m3JournalEntry : m3JournalEntries) {
                List<M3JournalEntryValueDTO> m3JournalEntryValueDTOS = JsonUtils.convertStringToList(objectMapper, m3JournalEntry.getValue(), M3JournalEntryValueDTO.class);

                Map<String, List<M3JournalEntryValueDTO>> accountNumber2List = m3JournalEntryValueDTOS.stream()
                        .filter(m3JournalEntryValueDTO -> accountNumbers.contains(m3JournalEntryValueDTO.getAccountNumber()))
                        .collect(Collectors.groupingBy(M3JournalEntryValueDTO::getAccountNumber));

                for (Map.Entry<String, List<M3JournalEntryValueDTO>> entry : accountNumber2List.entrySet()) {
                    String accountNumber = entry.getKey();
                    //计算值
                    BigDecimal amount = calculationGlAccountAmount(accountNumber, accountNumber2AccountType, entry.getValue());

                    CalculatedM3JournalEntryDTO calculatedM3JournalEntryDTO = new CalculatedM3JournalEntryDTO();
                    calculatedM3JournalEntryDTO.setAccountNumber(accountNumber);
                    calculatedM3JournalEntryDTO.setPostDate(m3JournalEntry.getPostDate());
                    calculatedM3JournalEntryDTO.setAmount(amount);
                    calculatedList.add(calculatedM3JournalEntryDTO);
                }
            }
            return calculatedList;
        }

        /**
         * 计算GlAccount的值
         *
         * @param glAccount                 GLAccount
         * @param accountNumber2AccountType GLAccount的类型
         * @param journalEntrys             journal entry数据
         * @return java.math.BigDecimal
         * @author lichen
         * @date 2025/10/17
         */
        private BigDecimal calculationGlAccountAmount(String glAccount, Map<String, Integer> accountNumber2AccountType, List<M3JournalEntryValueDTO> journalEntrys) {
            Map<String, BigDecimal> transType2Amount = journalEntrys.stream()
                    .filter(item -> item.getAmount() != null)  // 过滤掉 null 值
                    .collect(Collectors.groupingBy(M3JournalEntryValueDTO::getTransType,
                            Collectors.reducing(BigDecimal.ZERO, M3JournalEntryValueDTO::getAmount, BigDecimal::add)));
            BigDecimal d = transType2Amount.getOrDefault(JournalEntryDTO.TRANS_TYPE_D, BigDecimal.ZERO);
            BigDecimal c = transType2Amount.getOrDefault(JournalEntryDTO.TRANS_TYPE_C, BigDecimal.ZERO);
            Integer accountType = accountNumber2AccountType.get(glAccount);
            if (accountType == AccountTypeEnum.INCOME.getType()) {
                return c.subtract(d);
            } else {
                return d.subtract(c);
            }
        }

    }