# CbCaseController.java

    package cloud.wconnect.finance.controller.chargeback;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.CommonFieldI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.pojo.InternalPropertyTrxCodeQuery;
    import cloud.wconnect.commonlib.pojo.bo.SymbolBO;
    import cloud.wconnect.commonlib.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.group.Addition;
    import cloud.wconnect.commonlib.pojo.dto.group.Edition;
    import cloud.wconnect.commonlib.pojo.dto.group.QueryActionA;
    import cloud.wconnect.commonlib.pojo.dto.group.QueryActionB;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.PropertyInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.finance.cons.chargeback.CaseErrorCons;
    import cloud.wconnect.finance.cons.chargeback.StageCons;
    import cloud.wconnect.finance.entity.chargeback.CbCase;
    import cloud.wconnect.finance.entity.chargeback.CbCaseImportLog;
    import cloud.wconnect.finance.entity.chargeback.CbCaseNotChangeDay;
    import cloud.wconnect.finance.entity.chargeback.CbCaseOverTimeDay;
    import cloud.wconnect.finance.pojo.bo.chargeback.DisputeAmountItemBO;
    import cloud.wconnect.finance.pojo.dto.chargeback.*;
    import cloud.wconnect.finance.pojo.query.chargeback.CbCaseQuery;
    import cloud.wconnect.finance.pojo.query.chargeback.ImportCaseLogQuery;
    import cloud.wconnect.finance.pojo.query.chargeback.OtherCaseQuery;
    import cloud.wconnect.finance.pojo.vo.chargeback.ExportCaseVO;
    import cloud.wconnect.finance.service.chargeback.*;
    import cloud.wconnect.finance.service.chargeback.importcase.IImportCase;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.ImmutableMap;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    import jakarta.servlet.http.HttpServletResponse;
    import jakarta.validation.Valid;
    import org.apache.commons.collections.CollectionUtils;
    import org.jxls.transform.poi.JxlsPoiTemplateFillerBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.i18n.LocaleContextHolder;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.multipart.MultipartFile;

    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.charset.StandardCharsets;
    import java.util.*;

    /**
     * <p>
     * 前端控制器
     * </p>
     *
     * @author yyt
     * @since 2023-08-22
     */
    @RestController
    @RequestMapping("/charge-back")
    public class CbCaseController {

        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private ICbCaseService cbCaseService;
        @Autowired
        private ICbCaseOverTimeDayService cbCaseOverTimeDayService;
        @Autowired
        private ICbCaseImportLogService cbCaseImportLogService;
        @Autowired
        private ICbCaseNotifyUserLogService cbCaseNotifyUserLogService;
        @Autowired
        private ICbCaseNotChangeDayService cbCaseNotChangeDayService;
        @Autowired
        private ICbCaseBankService cbCaseBankService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private ApplicationContext applicationContext;
        @Autowired
        private PropertyInternalService propertyInternalService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Autowired
        private ObjectMapper objectMapper;

        /**
         * ZIP方式导入
         */
        private static final int IMPORT_ZIP = 1;

        /**
         * Excel方式导入
         */
        private static final int IMPORT_EXCEL = 2;

        /**
         * 检索列表
         *
         * @param property 资产标识
         * @param query    搜索条件
         * @return case 列表
         * @author yyt
         * @date 2023/8/23
         */
        @ApiId(555)
        @GetMapping
        public ListPagedDTO<CbCaseDTO> listPage(String property, @Validated(Addition.class) CbCaseQuery query) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(sessionContext.getPropertyId(property));
            return cbCaseService.listPage(propertyDTO, query);
        }

        /**
         * 获取统计数
         *
         * @param property 资产标识
         * @return 统计数
         * @author yyt
         * @date 2023/8/23
         */
        @ApiId(556)
        @GetMapping("/statistics")
        public CbCaseStatisticsDTO statistics(String property) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            CbCaseStatisticsDTO cbCaseStatisticsDTO = new CbCaseStatisticsDTO();
            cbCaseStatisticsDTO.setOverTimeCount(cbCaseService.statisticsOverTimeCase(propertyDTO));
            cbCaseStatisticsDTO.setStatusNotChangeCount(cbCaseService.statisticsStatusNotChangeCase(propertyDTO));
            cbCaseStatisticsDTO.setWaitProcessingCount(cbCaseService.statisticsWaitProcessingCase(propertyId, sessionContext.getLoginUserId()));
            cbCaseStatisticsDTO.setIncompleteCount(cbCaseService.statisticsIncompleteCase(propertyId));
            return cbCaseStatisticsDTO;
        }

        /**
         * 检索统计数据
         *
         * @param property 资产标识
         * @param query    搜索条件
         * @return case 列表
         * @author yyt
         * @date 2023/8/24
         */
        @ApiId(557)
        @GetMapping("/statistics/list")
        public ListPagedDTO<CbCaseDTO> listStatisticsPage(String property, @Validated(Edition.class) CbCaseQuery query) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(sessionContext.getPropertyId(property));
            return cbCaseService.listStatisticsPage(propertyDTO, query, sessionContext.getLoginUserId());
        }

        /**
         * 获取超期天数
         *
         * @param property 资产标识
         * @return 超期天数
         * @author yyt
         * @date 2023/8/24
         */
        @ApiId(592)
        @GetMapping("/over-time")
        public OverTimeDTO getOverTime(String property) {
            CbCaseOverTimeDay overTimeDay = cbCaseOverTimeDayService.getOverTimeDay(sessionContext.getPropertyId(property));
            return new OverTimeDTO(Objects.isNull(overTimeDay) ? null : overTimeDay.getDay());
        }

        /**
         * 设置超期天数
         *
         * @param property    资产标识
         * @param overTimeDTO 超期天数
         * @return {}
         * @author yyt
         * @date 2023/8/24
         */
        @ApiId(593)
        @PostMapping("/over-time")
        public Map saveOverTime(String property, @Valid @RequestBody OverTimeDTO overTimeDTO) {
            cbCaseOverTimeDayService.saveOrUpdateRewrite(new CbCaseOverTimeDay().setPropertyId(sessionContext.getPropertyId(property))
                    .setDay(overTimeDTO.getDay()).setUpdatedBy(sessionContext.getLoginUserId()).setUpdatedTime(DateTimeUtils.nowTimestamp()));
            return Collections.emptyMap();
        }

        /**
         * Other 列表
         *
         * @param id       case id
         * @param property 资产标识
         * @param query    查询条件
         * @return case
         * @author yyt
         * @date 2023/8/26
         */
        @ApiId(558)
        @GetMapping("/{id:\\d+}/other")
        public List<CbCaseDTO> others(@PathVariable long id, String property, @Valid OtherCaseQuery query) {
            CbCase cs = cbCaseService.checkExist(id);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(sessionContext.getPropertyId(property));
            checkBelong(cs, propertyDTO.getId());

            switch (query.getType()) {
                case OtherCaseQuery.SEARCH_OTHER_LIST:
                    return cbCaseService.listOthers(propertyDTO, query, cs);
                case OtherCaseQuery.SEARCH_SAME_CASE_NUMBER_LIST:
                    return cbCaseService.listSameCaseNumber(propertyDTO, query, cs);
                default:
                    throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("type", Lists.newArrayList(OtherCaseQuery.SEARCH_OTHER_LIST, OtherCaseQuery.SEARCH_SAME_CASE_NUMBER_LIST)),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "the type must in [1：case others search，2：click case order number search]");
            }
        }

        /**
         * 获取导入 case 记录
         *
         * @param property 资产标识
         * @param query    查询条件
         * @return 导入 case 记录
         * @author yyt
         * @date 2023/8/28
         */
        @ApiId(559)
        @GetMapping("/import-log")
        public ListPagedDTO<ImportCaseLogDTO> listImportCaseLog(String property, @Valid ImportCaseLogQuery query) {
            return cbCaseImportLogService.listImportCaseLogDTO(sessionContext.getTenantSymbolBO().getId(), sessionContext.getPropertyId(property), query);
        }

        /**
         * 导入 case
         *
         * @param property 资产标识
         * @param type     类型
         * @param file     文件
         * @return {}
         * @author yyt
         * @date 2023/8/28
         */
        @ApiId(562)
        @PostMapping("/import")
        public Map importCase(String property, Integer type, MultipartFile file) {
            if (file == null) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.FILE),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "the file is not found");
            }
            if (type != IMPORT_ZIP && type != IMPORT_EXCEL) {
                throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("type", Lists.newArrayList(IMPORT_ZIP, IMPORT_EXCEL)),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "the type must in [1.zip，2.excel]");
            }

            SymbolBO tenant = sessionContext.getTenantSymbolBO();
            PropertyDTO propertyDTO = propertyI18nRedisService.get(sessionContext.getPropertyId(property));
            int userId = sessionContext.getLoginUserId();
            if (type == IMPORT_ZIP) {
                // 检查是否存在当前登录人进行中的记录
                Integer propertyId = propertyDTO.getId();
                boolean isExist = cbCaseImportLogService.checkIsExistInProgressLog(userId, propertyId);
                if (isExist) {
                    throw new BadRequestException(CaseErrorCons.CB_CASES_STILL_IMPORTING,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "is exist in progress log,so can not import");
                }
            }

            Map<String, IImportCase> beans = applicationContext.getBeansOfType(IImportCase.class);
            for (IImportCase bean : beans.values()) {
                if (bean.supports(type)) {
                    ImportCaseDTO importDTO = bean.checkFile(propertyDTO, file);

                    CbCaseImportLog importLog = bean.handleImportLog(tenant, propertyDTO, file.getOriginalFilename(), importDTO.getKey(), userId);

                    bean.handleFile(new ImportCaseDTO(tenant, propertyDTO, importDTO.getKey(), importDTO.getZipFilePath(), importLog, userId));
                    return Collections.emptyMap();
                }
            }

            return Collections.emptyMap();
        }

        /**
         * 导出
         *
         * @param property 资产标识
         * @param query    查询条件
         * @param response 响应信息
         * @author yyt
         * @date 2023/8/29
         */
        @ApiId(560)
        @GetMapping("/export")
        public void exportCase(String property, @Validated(QueryActionA.class) CbCaseQuery query, HttpServletResponse response) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(sessionContext.getPropertyId(property));

            List<ExportCaseVO> caseVOs = cbCaseService.packageExportCase(propertyDTO, query, sessionContext.getLoginUserId());

            Locale locale = LocaleContextHolder.getLocale();
            try (InputStream inputStream = new ClassPathResource("/tmpl-xls/case_tmpl_" + locale + ".xlsx").getInputStream()) {
                HashMap<String, Object> data = new HashMap<>();
                data.put("cases", caseVOs);
                String fileName = propertyDTO.getAbbr() + "_" + "case.xlsx";
                fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
                response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
                response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
                response.setHeader("Content-Disposition", String.join("", "attachment; filename=", fileName));
                JxlsPoiTemplateFillerBuilder.newInstance()
                        .withTemplate(inputStream)
                        .buildAndFill(data, response::getOutputStream);
            } catch (IOException e) {
                throw new BadRequestException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "Export file failed; exception:" + e);
            }
        }

        /**
         * 上一个、下一个 case
         *
         * @param property 资产标识
         * @param query    查询条件
         * @return case
         * @author yyt
         * @date 2023/8/29
         */
        @ApiId(563)
        @GetMapping("/last/next")
        public Map<String, Long> lastNextCase(String property, @Validated({Addition.class, QueryActionB.class}) CbCaseQuery query) {
            return cbCaseService.handleLastNext(propertyI18nRedisService.get(sessionContext.getPropertyId(property)), query, sessionContext.getLoginUserId());
        }

        /**
         * 新增、编辑 case
         *
         * @param id       case id
         * @param property 资产标识
         * @param caseDTO  case 信息
         * @return {}
         * @author yyt
         * @date 2023/8/30
         */
        @ApiId(564)
        @PostMapping("/{id:\\d+}")
        public Map save(@PathVariable long id, String property, @Valid @RequestBody SaveCaseDTO caseDTO) {
            int propertyId = sessionContext.getPropertyId(property);

            //校验trxCode是否存在
            List<DisputeAmountItemBO> disputeAmountItemBOS = JsonUtils.convertStringToList(objectMapper, caseDTO.getDisputeAmountItems(), DisputeAmountItemBO.class);
            checkTrxCode(propertyId, disputeAmountItemBOS);

            if (id == BaseConstant.ZERO) {
                cbCaseBankService.checkExist(caseDTO.getVendor());

                return ImmutableMap.of("id", cbCaseService.save(propertyId, caseDTO));
            } else {
                cbCaseBankService.checkExist(caseDTO.getVendor());

                CbCase cs = cbCaseService.checkExist(id);
                checkBelong(cs, propertyId);

                cbCaseService.update(cs, caseDTO);
                return ImmutableMap.of("id", id);
            }
        }

        /**
         * 编辑采集 case
         *
         * @param id       case id
         * @param property 资产标识
         * @param caseDTO  case 信息
         * @return {}
         * @author yyt
         * @date 2023/8/30
         */
        @ApiId(565)
        @PutMapping("/crawl/{id:\\d+}")
        public Map updateCrawlCase(@PathVariable long id, String property, @Valid @RequestBody SaveCrawlCaseDTO caseDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            CbCase cs = cbCaseService.checkExist(id);
            checkBelong(cs, propertyId);

            //校验trxCode是否存在
            List<DisputeAmountItemBO> disputeAmountItemBOS = JsonUtils.convertStringToList(objectMapper, caseDTO.getDisputeAmountItems(), DisputeAmountItemBO.class);
            checkTrxCode(propertyId, disputeAmountItemBOS);

            cbCaseService.updateCrawlCase(cs, caseDTO);

            return ImmutableMap.of("id", id);
        }

        /**
         * 忽略
         *
         * @param id            case id
         * @param property      资产标识
         * @param ignoreCaseDTO 忽略
         * @return {}
         * @author yyt
         * @date 2023/8/30
         */
        @ApiId(566)
        @PutMapping("/{id:\\d+}/ignore")
        public Map ignore(@PathVariable long id, String property, @Valid @RequestBody IgnoreCaseDTO ignoreCaseDTO) {
            Integer propertyId = sessionContext.getPropertyId(property);
            CbCase cs = cbCaseService.checkExist(id);
            checkBelong(cs, propertyId);

            int userId = sessionContext.getLoginUserId();
            Boolean ignore = ignoreCaseDTO.getIgnore();
            if (ignore) {
                cbCaseService.ignore(cs, userId);
            } else {
                cbCaseService.revocationIgnore(cs, userId);
            }

            return Collections.emptyMap();
        }

        /**
         * 详情
         *
         * @param id       case id
         * @param property 资产标识
         * @return 详情
         * @author yyt
         * @date 2023/8/30
         */
        @ApiId(567)
        @GetMapping("/{id:\\d+}")
        public CaseDetailDTO detail(@PathVariable long id, String property) {
            int propertyId = sessionContext.getPropertyId(property);
            CbCase cs = cbCaseService.checkExist(id);
            checkBelong(cs, propertyId);

            return cbCaseService.getCaseDetail(cs);
        }

        /**
         * 提醒处理 case
         *
         * @param id           case id
         * @param targetUserId 处理人，为 0 时表示需要通知阶段处理人
         * @param property     资产标识
         * @return {}
         * @author yyt
         * @date 2023/9/1
         */
        @ApiId(561)
        @PutMapping("/{id:\\d+}/notify-handle/{targetUserId:\\d+}")
        public Map notifyHandle(@PathVariable long id, @PathVariable int targetUserId, String property) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(sessionContext.getPropertyId(property));
            CbCase cs = cbCaseService.checkExist(id);
            checkBelong(cs, propertyDTO.getId());

            cbCaseNotifyUserLogService.notifyHandle(cs, propertyDTO, targetUserId);

            return Collections.emptyMap();
        }

        /**
         * 变更 stage
         *
         * @param id       case id
         * @param stage    stage（1:init，2:hotel operation，3:overdue，4:portal，5:patch，6:final）
         * @param property 资产标识
         * @return {}
         * @author yyt
         * @date 2023/9/7
         */
        @ApiId(579)
        @PutMapping("/{id:\\d+}/stage/{stage:\\d+}")
        public Map changeStage(@PathVariable long id, @PathVariable int stage, String property) {
            List<Integer> stages = Lists.newArrayList(StageCons.INITIAL_CASE, StageCons.HOTEL_OPERATION, StageCons.OVERDUE,
                    StageCons.PORTAL, StageCons.PATCH, StageCons.FINAL);
            if (!stages.contains(stage)) {
                throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("stage", stages),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "the stage must in [1:init，2:hotel operation，3:overdue，4:portal，5:patch，6:final]");
            }

            CbCase cs = cbCaseService.checkExist(id);
            checkBelong(cs, sessionContext.getPropertyId(property));

            cbCaseService.changeStage(cs, stage);

            return Collections.emptyMap();
        }

        /**
         * 获取 case 当前阶段审批日志
         *
         * @param id case id
         * @return 审批日志
         * @author yyt
         * @date 2023/9/9
         */
        @ApiId(634)
        @GetMapping("/{id:\\d+}/approve-log")
        public List<CaseStageApproveLevelDTO> listApproveLog(@PathVariable long id, @RequestParam String property) {
            CbCase cs = cbCaseService.checkExist(id);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(sessionContext.getPropertyId(property));
            checkBelong(cs, propertyDTO.getId());

            return cbCaseService.listApproveRecord(cs, propertyDTO);
        }

        /**
         * 获取状态未变更天数
         *
         * @param property 资产标识
         * @return 状态未变更天数
         * @author yyt
         * @date 2023/11/22
         */
        @ApiId(902)
        @GetMapping("/not-change")
        public StatusNotChangeDTO getNotChangeDay(String property) {
            CbCaseNotChangeDay notChangeDay = cbCaseNotChangeDayService.getNotChangeDay(sessionContext.getPropertyId(property));
            return new StatusNotChangeDTO(Objects.isNull(notChangeDay) ? null : notChangeDay.getDay());
        }

        /**
         * 设置状态未变更天数
         *
         * @param property     资产标识
         * @param notChangeDTO 状态未变更天数
         * @return {}
         * @author yyt
         * @date 2023/11/22
         */
        @ApiId(903)
        @PostMapping("/not-change")
        public Map saveOverTime(String property, @Valid @RequestBody StatusNotChangeDTO notChangeDTO) {
            cbCaseNotChangeDayService.saveOrUpdateRewrite(new CbCaseNotChangeDay().setPropertyId(sessionContext.getPropertyId(property))
                    .setDay(notChangeDTO.getDay()).setUpdatedBy(sessionContext.getLoginUserId()).setUpdatedTime(DateTimeUtils.nowTimestamp()));
            return Collections.emptyMap();
        }

        /**
         * 校验数据归属
         *
         * @param cs         case
         * @param propertyId 资产 id
         * @author yyt
         * @date 2023/8/26
         */
        private void checkBelong(CbCase cs, Integer propertyId) {
            if (!cs.getPropertyId().equals(propertyId)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(), "the property id parameter is different from " +
                        "the property id of the cbCase;propertyId:" + propertyId + ",cbCasePropertyId:" + cs.getPropertyId() +
                        ",cbCaseId:" + cs.getId());
            }
        }

        /**
         * 校验交易代码是否合法
         *
         * @param propertyId           资产id
         * @param disputeAmountItemBOS 纠纷金额明细
         * @return void
         * @author lichen
         * @date 2023/9/27
         */
        private void checkTrxCode(int propertyId, List<DisputeAmountItemBO> disputeAmountItemBOS) {
            if (CollectionUtils.isEmpty(disputeAmountItemBOS)) return;

            //收集trxCode
            HashSet<String> trxCodes = Sets.newHashSetWithExpectedSize(disputeAmountItemBOS.size() * 2);
            for (DisputeAmountItemBO disputeAmountItemBO : disputeAmountItemBOS) {
                trxCodes.add(disputeAmountItemBO.getTrxCode());
                trxCodes.add(disputeAmountItemBO.getTrxCodePms());
            }

            Boolean exist = propertyInternalService.checkTrxCodeExist(new InternalPropertyTrxCodeQuery(propertyId, Lists.newArrayList(trxCodes)));
            if (!exist) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_INVALID,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.TRX_CODE),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Trx code is invalid.");
            }
        }

    }