# ParseJournalByCashierAndTransactionCodeServiceByOther.java

    package cloud.wconnect.frontoffice.parsereport.adjustment.impl;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.SystemPlatformEnum;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.InternalServerErrorException;
    import cloud.wconnect.commonlib.exception.ParseReportException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationNumberDTO;
    import cloud.wconnect.commonlib.pojo.dto.scheduler.JobParameterDTO;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.frontoffice.cons.S3BucketFunctionCons;
    import cloud.wconnect.frontoffice.entity.adjustment.AdJournalLog;
    import cloud.wconnect.frontoffice.entity.adjustment.AdjTrxCodeConfig;
    import cloud.wconnect.frontoffice.parsereport.adjustment.IParseJournalByCashierAndTransactionCodeService;
    import cloud.wconnect.frontoffice.pojo.dto.adjustment.JournalDTO;
    import cloud.wconnect.frontoffice.service.AmazonS3Service;
    import cloud.wconnect.frontoffice.service.adjustment.IAdJournalLogService;
    import cloud.wconnect.frontoffice.service.adjustment.IAdRequestService;
    import cloud.wconnect.frontoffice.service.adjustment.IAdjTrxCodeConfigService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Maps;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.poi.ss.usermodel.Row;
    import org.apache.poi.ss.usermodel.Sheet;
    import org.apache.poi.ss.usermodel.Workbook;
    import org.apache.poi.ss.usermodel.WorkbookFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    import org.springframework.web.multipart.MultipartFile;

    import java.io.IOException;
    import java.io.InputStream;
    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    import java.time.format.DateTimeParseException;
    import java.util.*;

    /**
     * 解析Other类型 Journal by Cashier and Transaction Code/service 报告
     *
     * @author lichen
     * @date 2024/7/26
     */
    @Slf4j
    @Component
    @DS("logbook")
    public class ParseJournalByCashierAndTransactionCodeServiceByOther implements IParseJournalByCashierAndTransactionCodeService {

        @Autowired
        private IAdjTrxCodeConfigService adjTrxCodeConfigService;
        @Value("${aws-s3.third-party.key.adjustment.journal}")
        private String s3FilePath;
        @Autowired
        private AmazonS3Service amazonS3Service;
        @Autowired
        private IAdJournalLogService adJournalLogService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Autowired
        private IAdRequestService adRequestService;

        /**
         * 判断是否支持
         *
         * @param source 来源
         * @return boolean
         * @author lichen
         * @date 2024/7/26
         */
        @Override
        public boolean supports(String source) {
            return SystemPlatformEnum.OTHER.getDesc().equals(source);
        }

        /**
         * 解析报告
         *
         * @param in          文件流
         * @param propertyDTO 资产
         * @param dateRange   数据范围
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.dto.adjustment.JournalDTO>
         * @author lichen
         * @date 2025/12/4
         */
        @Override
        public List<JournalDTO> parse(InputStream in, PropertyDTO propertyDTO, Map<String, Long> dateRange) throws ParseReportException {
            Long startDate = dateRange.get(JobParameterDTO.START_DATE);
            Long endDate = dateRange.get(JobParameterDTO.END_DATE);

            //获取资产时区
            String timezone = propertyDTO.getTimezone();

            //获取国际化配置
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            //查询忽略的TrxCode
            List<AdjTrxCodeConfig> adjTrxCodeConfigs = adjTrxCodeConfigService.listByPropertyId(propertyDTO.getId());

            ArrayList<JournalDTO> journalDTOS = Lists.newArrayList();
            try (Workbook workbook = WorkbookFactory.create(in)) {
                Sheet sheet = workbook.getSheetAt(0);
                //获取最大行数
                int lastRowNum = sheet.getLastRowNum();
                //只包含标题
                if (lastRowNum < 1) return journalDTOS;

                long nowTimestamp = DateTimeUtils.nowTimestamp();

                for (int i = 1; i <= lastRowNum; i++) {
                    Row row = sheet.getRow(i);

                    //日期
                    String dateStr = CellUtils.convertCellStr(row.getCell(0));

                    //业务日期
                    long businessDate = DateTimeUtils.dateStrToEpochSecond(dateStr, BaseConstant.DATE_FORMAT_YYYY_MM_DD, timezone);
                    //判断是否符合日期范围
                    if (businessDate < startDate || businessDate > endDate) {
                        continue;
                    }

                    //获取交易代码
                    String trxCode = CellUtils.convertCellStr(row.getCell(5));
                    //根据校验代码，判断数据是否需要过滤
                    if (adjTrxCodeConfigService.isIgnore(adjTrxCodeConfigs, trxCode)) {
                        continue;
                    }

                    //PMS系统账户
                    String pmsAccount = CellUtils.convertCellStr(row.getCell(1));
                    //房间号
                    String roomNumber = CellUtils.convertCellStr(row.getCell(2));
                    //客人姓名
                    String guestName = CellUtils.convertCellStr(row.getCell(3));
                    guestName = GuestNameUtil.removeSpaces(guestName);
                    //调账金额
                    String amountStr = CellUtils.convertCellStr(row.getCell(4));
                    BigDecimal amount = new BigDecimal(amountStr).setScale(BaseConstant.DATA_PRECISION, roundingMode);
                    //交易描述
                    String trxDesc = CellUtils.convertCellStr(row.getCell(6));

                    //资产的PMS Type等于PEP或Shiji时才存储ConfNumber
                    String confNumber = "";
                    if (Lists.newArrayList(SystemPlatformEnum.SHI_JI.getDesc(), SystemPlatformEnum.PEP.getDesc())
                            .contains(propertyDTO.getPmsType())) {
                        confNumber = CellUtils.convertCellStr(row.getCell(7));
                    }

                    JournalDTO journalDTO = new JournalDTO();
                    journalDTO.setCreateTime(nowTimestamp);
                    journalDTO.setGuestName(guestName);
                    journalDTO.setRoomNumber(roomNumber);
                    journalDTO.setAdjustedDollar(amount.abs());
                    journalDTO.setTrxDesc(trxDesc);
                    journalDTO.setBusinessDate(Integer.valueOf(dateStr.replaceAll("-", "")));
                    journalDTO.setPmsAccount(pmsAccount);
                    journalDTO.setTrxCode(trxCode);
                    journalDTO.setConfNumber(confNumber);
                    journalDTOS.add(journalDTO);
                }

                return journalDTOS;
            } catch (IOException ioException) {
                throw new InternalServerErrorException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "parse journal info excel error");
            }
        }

        /**
         * 校验文件内容是否合法
         *
         * @param file        文件
         * @param propertyDTO 资产DTO
         * @author lichen
         * @date 2024/7/26
         */
        @Override
        public void checkFile(MultipartFile file, PropertyDTO propertyDTO) {
            //校验文件格式
            UploadUtils.checkFileFormat(file, Lists.newArrayList("xlsx", "xls"));
            try (InputStream in = file.getInputStream();
                 Workbook workbook = WorkbookFactory.create(in)) {
                Sheet sheet = workbook.getSheetAt(0);
                //获取最大行数
                int lastRowNum = sheet.getLastRowNum();
                //只包含标题
                if (lastRowNum < 1) return;

                //标题行
                Row titleRow = sheet.getRow(0);
                int lastCellNum;
                if (Lists.newArrayList(SystemPlatformEnum.SHI_JI.getDesc(), SystemPlatformEnum.PEP.getDesc())
                        .contains(propertyDTO.getPmsType())) {
                    lastCellNum = 9;
                } else {
                    lastCellNum = 8;
                }
                //标题集合
                ArrayList<String> titles = Lists.newArrayList();
                for (int i = 0; i < lastCellNum; i++) {
                    titles.add(CellUtils.convertCellStr(titleRow.getCell(i)));
                }

                for (int i = 1; i <= lastRowNum; i++) {
                    Row row = sheet.getRow(i);
                    //日期
                    String dateStr = CellUtils.convertCellStr(row.getCell(0));
                    if (StringUtils.isEmpty(dateStr)) {
                        //获取国际化的参数
                        Map<String, List<Object>> language2ParamList = i18nMessageUtils.getErrorParamMapNotNeedTranslate(titles.get(0));
                        throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY, language2ParamList,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "The date cannot be null");
                    } else {
                        try {
                            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYY_MM_DD, Locale.ENGLISH);
                            LocalDate.parse(dateStr, formatter);
                        } catch (DateTimeParseException e) {
                            //获取国际化的参数
                            Map<String, List<Object>> language2ParamList = i18nMessageUtils.getErrorParamMapNotNeedTranslate(titles.get(0),
                                    BaseConstant.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MM_SS);
                            throw new BadRequestException(CommonErrorI18nConstant.FORMAT_INCORRECT, language2ParamList,
                                    BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "The date format is incorrect, date = " + dateStr);
                        }
                    }

                    //PMS系统账户
                    String pmsAccount = CellUtils.convertCellStr(row.getCell(1));
                    if (StringUtils.isEmpty(pmsAccount)) {
                        //获取国际化的参数
                        Map<String, List<Object>> language2ParamList = i18nMessageUtils.getErrorParamMapNotNeedTranslate(titles.get(1));
                        throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY, language2ParamList,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "The PMS account cannot be null");
                    } else {
                        if (pmsAccount.length() > 60) {
                            //获取国际化的参数
                            Map<String, List<Object>> language2ParamList = i18nMessageUtils.getErrorParamMapNotNeedTranslate(titles.get(1),
                                    "60");
                            //获取所有语言的错误描述
                            throw new BadRequestException(CommonErrorI18nConstant.OVERLENGTH, language2ParamList,
                                    BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                    "The PMS account length cannot be greater than 60, pmsAccount = " + pmsAccount);
                        }
                    }

                    //房间号
                    String roomNumber = CellUtils.convertCellStr(row.getCell(2));
                    if (StringUtils.isEmpty(roomNumber)) {
                        //获取国际化的参数
                        Map<String, List<Object>> language2ParamList = i18nMessageUtils.getErrorParamMapNotNeedTranslate(titles.get(2));
                        throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY, language2ParamList,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "The room number cannot be null");
                    } else {
                        if (roomNumber.length() > 10) {
                            //获取国际化的参数
                            Map<String, List<Object>> language2ParamList = i18nMessageUtils.getErrorParamMapNotNeedTranslate(titles.get(2),
                                    "10");
                            throw new BadRequestException(CommonErrorI18nConstant.OVERLENGTH, language2ParamList,
                                    BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                    "The room number length cannot be greater than 10, roomNumber = " + roomNumber);
                        }
                    }

                    //客人姓名
                    String guestName = CellUtils.convertCellStr(row.getCell(3));
                    if (StringUtils.isEmpty(guestName)) {
                        //获取国际化的参数
                        Map<String, List<Object>> language2ParamList = i18nMessageUtils.getErrorParamMapNotNeedTranslate(titles.get(3));
                        throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY, language2ParamList,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "The guest name cannot be null");
                    } else {
                        if (guestName.length() > 60) {
                            //获取国际化的参数
                            Map<String, List<Object>> language2ParamList = i18nMessageUtils.getErrorParamMapNotNeedTranslate(titles.get(3),
                                    "60");
                            throw new BadRequestException(CommonErrorI18nConstant.OVERLENGTH, language2ParamList,
                                    BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                    "The guest name length cannot be greater than 60, guestName = " + guestName);
                        }
                    }

                    //调账金额
                    String amountStr = CellUtils.convertCellStr(row.getCell(4));
                    if (StringUtils.isEmpty(amountStr)) {
                        //获取国际化的参数
                        Map<String, List<Object>> language2ParamList = i18nMessageUtils.getErrorParamMapNotNeedTranslate(titles.get(4));
                        throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY, language2ParamList,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "The Adjusted amount (before-tax) cannot be null");
                    } else {
                        try {
                            BigDecimal amount = new BigDecimal(amountStr);
                            if (amount.compareTo(BigDecimal.ZERO) >= 0) {
                                //获取国际化的参数
                                Map<String, List<Object>> language2ParamList = i18nMessageUtils.getErrorParamMapNotNeedTranslate(titles.get(4));
                                throw new BadRequestException(CommonErrorI18nConstant.MUST_LESS_THAN_ZERO, language2ParamList,
                                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                        "The Adjusted amount (before-tax) must be less than 0, amount  = " + amountStr);
                            }

                            //校验整数位是否超过限制
                            int intLength = amount.abs().toBigInteger().toString().length();
                            if (intLength > 8) {
                                //获取国际化的参数
                                Map<String, List<Object>> language2ParamList = i18nMessageUtils.getErrorParamMapNotNeedTranslate(titles.get(4),
                                        "8");
                                throw new BadRequestException(CommonErrorI18nConstant.NUMBER_INTEGER_OVERLENGTH, language2ParamList,
                                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                        "The integer of the Adjusted amount (before-tax) exceeds the limit, amount  = " + amountStr);
                            }
                        } catch (NumberFormatException exception) {
                            //获取国际化的参数
                            Map<String, List<Object>> language2ParamList = i18nMessageUtils.getErrorParamMapNotNeedTranslate(titles.get(4));
                            throw new BadRequestException(CommonErrorI18nConstant.MUST_NUMBER, language2ParamList,
                                    BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                    "The Adjusted amount (before-tax) must be a number, amount  = " + amountStr);
                        }
                    }

                    //获取交易代码
                    String trxCode = CellUtils.convertCellStr(row.getCell(5));
                    if (StringUtils.isEmpty(trxCode)) {
                        //获取国际化的参数
                        Map<String, List<Object>> language2ParamList = i18nMessageUtils.getErrorParamMapNotNeedTranslate(titles.get(5));
                        throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY, language2ParamList,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "The transaction code cannot be null");
                    }

                    //资产的PMS Type等于PEP或Shiji时才校验ConfNumber
                    if (Lists.newArrayList(SystemPlatformEnum.SHI_JI.getDesc(), SystemPlatformEnum.PEP.getDesc()).contains(propertyDTO.getPmsType())) {
                        //获取交易代码
                        String confNumber = CellUtils.convertCellStr(row.getCell(7));
                        if (StringUtils.isNotEmpty(confNumber)) {
                            if (confNumber.length() > 30) {
                                //获取国际化的参数
                                Map<String, List<Object>> language2ParamList = i18nMessageUtils.getErrorParamMapNotNeedTranslate(titles.get(7), 30);
                                //获取所有语言的错误描述
                                throw new BadRequestException(CommonErrorI18nConstant.OVERLENGTH, language2ParamList,
                                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                        "The Conf.# length cannot be greater than 30, confNumber = " + confNumber);
                            }
                        }
                    }
                }
            } catch (IOException ioException) {
                throw new InternalServerErrorException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "parse journal info excel error");
            }
        }

        /**
         * 处理数据
         *
         * @param propertyDTO  资产DTO
         * @param file         文件
         * @param adJournalLog 导入日志
         * @param userId       操作人ID
         * @author lichen
         * @date 2024/7/29
         */
        @Override
        public void handleData(PropertyDTO propertyDTO, MultipartFile file,
                               AdJournalLog adJournalLog, int userId) {
            String tenantSymbol = propertyDTO.getTenantSymbol();
            String abbr = propertyDTO.getAbbr();

            //数据日期
            Map<String, Long> dateRange = Maps.newHashMapWithExpectedSize(2);
            dateRange.put(JobParameterDTO.START_DATE, adJournalLog.getStartDate());
            dateRange.put(JobParameterDTO.END_DATE, adJournalLog.getEndDate());

            String key = tenantSymbol + "/" + abbr + "/" + s3FilePath +
                    UploadUtils.getRandomFileName(file.getOriginalFilename());

            boolean result = true;
            try (InputStream inputStream = file.getInputStream()) {
                amazonS3Service.uploadFile(S3BucketFunctionCons.ADJUSTMENT_IMPORT_RECORD, file, key);
                //解析报告
                List<JournalDTO> journalDTOS = parse(inputStream, propertyDTO, dateRange);
                //处理报告数据
                adRequestService.handleReportData(propertyDTO, journalDTOS, userId, dateRange);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                result = false;
            } finally {
                adJournalLogService.updateLog(adJournalLog.getId(), userId, key, result);
            }
        }
    }