package com.jeeplus.finance.invoice.controller;

import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.jeeplus.aop.demo.annotation.DemoMode;
import com.jeeplus.common.TokenProvider;
import com.jeeplus.common.excel.ExcelOptions;
import com.jeeplus.common.excel.annotation.ExportMode;
import com.jeeplus.common.redis.RedisUtils;
import com.jeeplus.common.utils.ResponseUtil;
import com.jeeplus.finance.invoice.domain.CwFinanceInvoice;
import com.jeeplus.finance.invoice.service.CwFinanceInvoiceService;
import com.jeeplus.finance.invoice.service.OMS.*;
import com.jeeplus.finance.invoice.service.dto.*;
import com.jeeplus.finance.invoice.util.EasyPoiUtil;
import com.jeeplus.finance.utils.FileUtil;
import com.jeeplus.flowable.feign.IFlowableApi;
import com.jeeplus.logging.annotation.ApiLog;
import com.jeeplus.logging.constant.enums.LogTypeEnum;
import com.jeeplus.sys.feign.IDictApi;
import com.jeeplus.sys.feign.IUserApi;
import com.jeeplus.sys.feign.IWorkAttachmentApi;
import com.jeeplus.sys.service.dto.UserDTO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Param;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.stream.Collectors;

@Api("财务管理-发票")
@RestController
@RequestMapping(value = "/cw_finance/invoice")
public class CwFinanceInvoiceController {

    private static Logger log = LoggerFactory.getLogger(CwFinanceInvoiceController.class);

    @Resource
    private CwFinanceInvoiceService cwFinanceInvoiceService;

    @Resource
    private IFlowableApi flowTaskService;

    @Resource
    private InvoiceDownloadService invoiceDownloadService;
    @Resource
    private RedInvoiceDownloadService redInvoiceDownloadService;

    @Resource
    private RedInvoiceScheduledService redInvoiceScheduledService;

    @Resource
    private RedInvoiceRetryScheduledService redInvoiceRetryScheduledService;

    /**
     * 查询发票列表
     * @param cwFinanceInvoiceDTO
     * @param page
     * @return
     * @throws Exception
     */
    @ApiLog("查询发票列表")
    @PreAuthorize("hasAuthority('cw_finance:invoice:list')")
    @GetMapping("list")
    public ResponseEntity<IPage<CwFinanceInvoiceDTO>> data(CwFinanceInvoiceDTO cwFinanceInvoiceDTO, Page<CwFinanceInvoiceDTO> page) throws Exception {
        IPage<CwFinanceInvoiceDTO> result = new Page<CwFinanceInvoiceDTO>();
        result = cwFinanceInvoiceService.findList (page, cwFinanceInvoiceDTO,"");
        System.out.println("当前时间1："+System.currentTimeMillis());
        result.getRecords().stream().forEach(i -> {
            // 发票申请
            if (StringUtils.isNotBlank(i.getTaskId()) && StringUtils.isNotBlank(i.getStatus())) {
                if ("2".equals(i.getStatus())) { // “审核中”的数据要获取数据审核人
                    i.setAuditUserIds(flowTaskService.getTaskAuditUsers(i.getTaskId()));  // 获取数据审核人
                }
            }
            // 发票作废
            if (StringUtils.isNotBlank(i.getInvalidTaskId()) && StringUtils.isNotBlank(i.getStatus())) {
                if ("6".equals(i.getStatus())) { // “审核中”的数据要获取数据审核人
                    i.setAuditUserIdsInvalid(flowTaskService.getTaskAuditUsers(i.getInvalidTaskId()));  // 获取数据审核人
                }
            }
            if(StringUtils.isNotBlank(i.getBillingType()) && "2".equals(i.getBillingType()) && StringUtils.isNotBlank(i.getName())){
                i.setBillingWorkplaceReal(i.getName());
            }
        });
        System.out.println("当前时间2："+System.currentTimeMillis());
        return ResponseEntity.ok (result);
    }

    /**
     * 查询发票列表
     * @param cwFinanceInvoiceDTO
     * @param page
     * @return
     * @throws Exception
     */
    @ApiLog("查询发票列表")
    @PreAuthorize("hasAuthority('cw_finance:invoice:list')")
    @GetMapping("redList")
    public ResponseEntity<IPage<CwFinanceInvoiceDTO>> redList(CwFinanceInvoiceDTO cwFinanceInvoiceDTO, Page<CwFinanceInvoiceDTO> page) throws Exception {
        IPage<CwFinanceInvoiceDTO> result = new Page<CwFinanceInvoiceDTO>();
        result = cwFinanceInvoiceService.findRedList (page, cwFinanceInvoiceDTO,"");
        return ResponseEntity.ok (result);
    }

    /**
     * 查询发票数据
     * @param id
     * @return
     */
    @ApiLog("查询发票数据")
    //@PreAuthorize ("hasAnyAuthority('cw_finance:invoice:view','cw_finance:invoice:add','cw_finance:invoice:edit')")
    @GetMapping("queryById")
    public ResponseEntity queryById(@RequestParam("id") String id) {
        CwFinanceInvoiceDTO cwFinanceInvoiceDTO = cwFinanceInvoiceService.queryById ( id );
        return ResponseEntity.ok (cwFinanceInvoiceDTO);
    }

    /**
     * 查询发票号是否存在
     * @param number
     * @return
     */
    @ApiLog("查询发票号是否存在")
    @GetMapping("queryByNumber")
    public ResponseEntity queryByNumber(@RequestParam("number") String number, @RequestParam(value="id", required = false) String id) {
        Boolean bool = cwFinanceInvoiceService.queryByNumber ( number, id );
        return ResponseEntity.ok (bool);
    }

    /**
     * 保存发票数据
     * @param cwFinanceInvoiceDTO
     * @return
     */
    @ApiLog(value = "修改/新增发票数据", type = LogTypeEnum.SAVE)
    @PreAuthorize("hasAnyAuthority('cw_finance:invoice:add','cw_finance:invoice:edit')")
    @PostMapping("save")
    public ResponseEntity save(@Valid @RequestBody CwFinanceInvoiceDTO cwFinanceInvoiceDTO) throws Exception {
        cwFinanceInvoiceService.saveInvoice(cwFinanceInvoiceDTO);
        return ResponseEntity.ok ("操作成功");
    }

    /**
     * 保存发票数据-流程
     * @param cwFinanceInvoiceDTO
     * @return
     */
    @ApiLog(value = "保存发票数据-流程", type = LogTypeEnum.SAVE)
    @PreAuthorize("hasAnyAuthority('cw_finance:invoice:add','cw_finance:invoice:edit')")
    @PostMapping("saveForm")
    public ResponseEntity saveForm(@Valid @RequestBody CwFinanceInvoiceDTO cwFinanceInvoiceDTO) throws Exception {
        CwFinanceInvoice cwFinanceInvoice = cwFinanceInvoiceService.saveForm(cwFinanceInvoiceDTO);
        return ResponseUtil.newInstance().add("businessTable", "cw_finance_invoice")
                .add("businessId", cwFinanceInvoice.getId())
                .add("recordType",cwFinanceInvoice.getRecordType())
                .add("no", cwFinanceInvoice.getNo())
                .ok ("操作成功");
    }

    /**
     * 仅作为单独保存发票数据接口使用
     * @param cwFinanceInvoiceDTO
     * @return
     */
    @ApiLog(value = "仅作为单独保存发票数据接口使用", type = LogTypeEnum.SAVE)
    @PreAuthorize("hasAnyAuthority('cw_finance:invoice:add','cw_finance:invoice:edit')")
    @PostMapping("saveFormOnly")
    public ResponseEntity saveFormOnly(@Valid @RequestBody CwFinanceInvoiceDTO cwFinanceInvoiceDTO) throws Exception {
        CwFinanceInvoice cwFinanceInvoice = cwFinanceInvoiceService.saveFormOnly(cwFinanceInvoiceDTO);
        return ResponseUtil.newInstance().add("businessTable", "cw_finance_invoice")
                .add("businessId", cwFinanceInvoice.getId())
                .add("recordType",cwFinanceInvoice.getRecordType())
                .add("no", cwFinanceInvoice.getNo())
                .ok ("操作成功");
    }


    /**
     * 作废发票数据-流程
     * @param cwFinanceInvoiceDTO
     * @return
     */
    @ApiLog(value = "作废发票数据-流程", type = LogTypeEnum.SAVE)
    @PreAuthorize("hasAnyAuthority('cw_finance:invoice:add','cw_finance:invoice:edit')")
    @PostMapping("saveFormInvalid")
    public ResponseEntity saveFormInvalid(@Valid @RequestBody CwFinanceInvoiceDTO cwFinanceInvoiceDTO) throws Exception {
        String id = cwFinanceInvoiceService.saveFormInvalid(cwFinanceInvoiceDTO);
        return ResponseUtil.newInstance().add("businessTable", "cw_finance_invoice_invalid").add("businessId", id).ok ("操作成功");
    }

    /**
     * 根据发票作废数据的id查询发票id
     * @param id
     * @return
     */
    @GetMapping("queryIdByInvalidId")
    @ApiOperation(value = "查询发票号是否存在")
    public ResponseEntity<CwFinanceDTO> queryIdByInvalidId(@RequestParam String id) {
        String businessId = cwFinanceInvoiceService.queryIdByInvalidId ( id );
        CwFinanceDTO cwFinanceDTO = new CwFinanceDTO();
        cwFinanceDTO.setBusinessId(businessId);
        return ResponseEntity.ok(cwFinanceDTO);
    }

    /**
     * 删除附件类型
     * @param ids
     * @return
     */
    @ApiLog(value = "删除附件类型", type = LogTypeEnum.SAVE)
    @PreAuthorize ("hasAuthority('cw_finance:invoice:del')")
    @DeleteMapping("delete")
    public ResponseEntity delete(String ids) {
        return cwFinanceInvoiceService.deleteByIds(ids);
    }

    /**
     * 根据id修改状态值status
     * @param dto
     * @return
     */
    @ApiOperation(value = "根据id修改状态值status")
    @PostMapping(value = "/updateStatusById")
    public ResponseEntity<String> updateStatusById(@RequestBody CwFinanceInvoiceDTO dto) {
        String s = cwFinanceInvoiceService.updateStatusById(dto);
        return ResponseEntity.ok(s);
    }

    /**
     * 确认收款
     * @param dto
     * @return
     */
    @ApiOperation(value = "确认收款")
    @PostMapping(value = "/isReceivables")
    public ResponseEntity<String> isReceivables(@RequestBody CwFinanceInvoiceDTO dto) {
        String s = cwFinanceInvoiceService.isReceivables(dto);
        return ResponseEntity.ok(s);
    }

    /**
     * 下载发票明细导入模板
     *
     * @param response
     * @return
     */
    @GetMapping("/importDetail/template")
    @ApiOperation(value = "下载模板")
    public void importFileTemplate(HttpServletResponse response, HttpServletRequest request) {
        try {
            InputStream inputStream = this.getClass().getResourceAsStream("/dot/发票明细导入模板.xlsx");
            //强制下载不打开
            response.setContentType("application/force-download");
            OutputStream out = response.getOutputStream();
            //使用URLEncoder来防止文件名乱码或者读取错误
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("finance_invoice_detail_template.xlsx", "UTF-8"));
            int b = 0;
            byte[] buffer = new byte[1000000];
            while (b != -1) {
                b = inputStream.read(buffer);
                if (b != -1) out.write(buffer, 0, b);
            }
            inputStream.close();
            out.close();
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 导入发票明细数据
     *
     * @return
     */
    @DemoMode
    @PostMapping("/importDetail")
    @ApiLog(value = "导入发票明细数据excel", type = LogTypeEnum.IMPORT)
    public ResponseEntity importDetail(MultipartFile file, HttpServletRequest request) throws IOException {

        ArrayList<CwFinanceInvoiceDetailDTO> arrayList = new ArrayList<>();
        HashMap<String,String> hashMap = new HashMap<>();

        List<CwFinanceInvoiceDetailDTO> listA = new ArrayList<>();
        //获取sheet
        listA = EasyPoiUtil.importExcel(file, 0, 1, CwFinanceInvoiceDetailDTO.class);
        //去除excel中的空行
        listA = getExcelList(listA);
        //导入前检测数据
        String resultA = cwFinanceInvoiceService.importDecide(listA, arrayList, hashMap);
        if(StringUtils.isNotBlank(resultA)){
            //有返回值，说明导入的数据不正确。向用户抛提示
            return ResponseEntity.badRequest().body  (resultA);
        }
        //判断文件中是否有重复的发票号
        if(hashMap.size() != listA.size()){
            return ResponseEntity.badRequest().body  ("文件中存在重复的发票号");
        }

        return ResponseEntity.ok(arrayList);
    }

    /**
     * 去除excel中的空行
     * @param list
     * @return
     */
    public ArrayList<CwFinanceInvoiceDetailDTO> getExcelList(List<CwFinanceInvoiceDetailDTO> list){

        ArrayList<CwFinanceInvoiceDetailDTO> cwFinanceInvoiceDetailDTOS = new ArrayList<>();

        list.stream().forEach(item->{
            if(objectCheckIsNull(item)){
                cwFinanceInvoiceDetailDTOS.add(item);
            }
        });

        return cwFinanceInvoiceDetailDTOS;
    }

    /**
     * 根据项目id查询项目关联发票
     * @param projectId
     * @param cwFinanceInvoiceDTO
     * @return
     * @throws Exception
     */
    @GetMapping("getByIds")
    @ApiOperation(value = "根据项目id查询项目关联发票")
    public ResponseEntity<List<CwFinanceInvoiceDTO>> getByIds(String projectId,CwFinanceInvoiceDTO cwFinanceInvoiceDTO) throws Exception {
        List<CwFinanceInvoiceDTO> cwFinanceDTO = cwFinanceInvoiceService.getByIds(projectId, cwFinanceInvoiceDTO);
        return ResponseEntity.ok(cwFinanceDTO);
    }

    /**
     * 根据项目id查询项目关联发票(用于项目登记修改)
     * @param projectId
     * @param cwFinanceInvoiceDTO
     * @return
     * @throws Exception
     */
    @GetMapping("getByIds2")
    @ApiOperation(value = "根据项目id查询项目关联发票")
    public ResponseEntity<List<CwFinanceInvoiceDTO>> getByIds2(String projectId,CwFinanceInvoiceDTO cwFinanceInvoiceDTO) throws Exception {
        List<CwFinanceInvoiceDTO> cwFinanceDTO = cwFinanceInvoiceService.getByIds2(projectId, cwFinanceInvoiceDTO);
        return ResponseEntity.ok(cwFinanceDTO);
    }

    /**
     * 根据合同id查询项目关联发票
     * @param contractId
     * @param cwFinanceInvoiceDTO
     * @return
     * @throws Exception
     */
    @GetMapping("getByContractId")
    @ApiOperation(value = "根据合同id查询项目关联发票")
    public ResponseEntity<List<CwFinanceInvoiceDTO>> getByContractId(String contractId,CwFinanceInvoiceDTO cwFinanceInvoiceDTO) throws Exception {
        List<CwFinanceInvoiceDTO> cwFinanceDTO = cwFinanceInvoiceService.getByContractId(contractId, cwFinanceInvoiceDTO);
        return ResponseEntity.ok(cwFinanceDTO);
    }

    /**
     * 根据客户id查询项目关联发票
     * @param clientId
     * @param cwFinanceInvoiceDTO
     * @return
     * @throws Exception
     */
    @GetMapping("getIdByClientId")
    @ApiOperation(value = "根据客户id查询项目关联发票")
    public ResponseEntity<List<CwFinanceInvoiceDTO>> getIdByClientId(String clientId,CwFinanceInvoiceDTO cwFinanceInvoiceDTO) throws Exception {
        List<CwFinanceInvoiceDTO> cwFinanceDTO = cwFinanceInvoiceService.getIdByClientId(clientId, cwFinanceInvoiceDTO);
        return ResponseEntity.ok(cwFinanceDTO);
    }

    @ApiLog(value = "导出发票数据", type = LogTypeEnum.EXPORT)
    @GetMapping("export")
    //@PreAuthorize ("hasAuthority('cw_finance:invoice:export')")
    @ApiOperation(value = "导出发票数据")
    public void exportFile(CwFinanceInvoiceDTO cwFinanceInvoiceDTO, Page <CwFinanceInvoiceDTO> page, ExcelOptions options, HttpServletResponse response) throws Exception {
        String fileName = options.getFilename();
        String sheetName = options.getSheetName();
        List<CwFinanceInvoiceDTO> result = new ArrayList<>();
        if ( ExportMode.current.equals ( options.getMode() ) ) {
            result = cwFinanceInvoiceService.findList (page,cwFinanceInvoiceDTO,"export").getRecords();
        } else if (ExportMode.selected.equals ( options.getMode() )) {
            result = cwFinanceInvoiceService.findList (page,cwFinanceInvoiceDTO,"export").getRecords().stream ( ).filter ( item ->
                    options.getSelectIds ( ).contains ( item.getId ( ) )
            ).collect ( Collectors.toList ( ) );
        } else {
            page.setSize (-1);
            page.setCurrent (0);
            result = cwFinanceInvoiceService.findList (page,cwFinanceInvoiceDTO,"export").getRecords();
        }
        //对导出数据进行处理
        List<CwFinanceInvoiceDTO> resultList = cwFinanceInvoiceService.disposeExportList(result);


        //获取字典数据
        String cwWorkClientReportTypeDatas = SpringUtil.getBean ( IDictApi.class ).getDictListMapByDict ("cw_work_client_report_type");
        Map<String,Object> cwWorkClientReportTypeValueDTOs = JSON.parseObject(cwWorkClientReportTypeDatas, new TypeReference<Map<String,Object>>() {});

        String projectClassificationDatas = SpringUtil.getBean ( IDictApi.class ).getDictListMapByDict ("project_classification");
        Map<String,Object> projectClassificationValueDTOs = JSON.parseObject(projectClassificationDatas, new TypeReference<Map<String,Object>>() {});

        String invoiceBillingContentDatas = SpringUtil.getBean ( IDictApi.class ).getDictListMapByDict ("invoice_billing_content");
        Map<String,Object> invoiceBillingContentValueDTOs = JSON.parseObject(invoiceBillingContentDatas, new TypeReference<Map<String,Object>>() {});

        String invoiceTypeDatas = SpringUtil.getBean ( IDictApi.class ).getDictListMapByDict ("invoice_type");
        Map<String,Object> invoiceTypeValueDTOs = JSON.parseObject(invoiceTypeDatas, new TypeReference<Map<String,Object>>() {});

        String invoiceStatusDatas = SpringUtil.getBean ( IDictApi.class ).getDictListMapByDict ("invoice_status");
        Map<String,Object> invoiceStatusValueDTOs = JSON.parseObject(invoiceStatusDatas, new TypeReference<Map<String,Object>>() {});

        for (CwFinanceInvoiceDTO info : resultList) {
            //处理开票类型
            String billingType = info.getBillingType();
            if(StringUtils.isNotBlank(billingType)){
                switch (billingType){
                    case "1":
                        info.setBillingType("企业开票");
                        break;
                    case "2":
                        info.setBillingType("个人开票");
                        info.setBillingWorkplaceReal(info.getName());
                        break;
                    default:
                        info.setBillingType("");
                        break;
                }
            }


            if(StringUtils.isNotBlank(info.getReportType())){
                for (String key : cwWorkClientReportTypeValueDTOs.keySet()) {
                    if(info.getReportType().equals(key)){
                        info.setReportType(String.valueOf(cwWorkClientReportTypeValueDTOs.get(key)));
                        break;
                    }
                }
            }
            if(StringUtils.isNotBlank(info.getProjectClassification())){
                for (String key : projectClassificationValueDTOs.keySet()) {
                    if(info.getProjectClassification().equals(key)){
                        info.setProjectClassification(String.valueOf(projectClassificationValueDTOs.get(key)));
                        break;
                    }
                }
            }
            if(StringUtils.isNotBlank(info.getBillingContent())){
                for (String key : invoiceBillingContentValueDTOs.keySet()) {
                    if(info.getBillingContent().equals(key)){
                        info.setBillingContent(String.valueOf(invoiceBillingContentValueDTOs.get(key)));
                        break;
                    }
                }
            }
            if(StringUtils.isNotBlank(info.getType())){
                for (String key : invoiceTypeValueDTOs.keySet()) {
                    if(info.getType().equals(key)){
                        info.setType(String.valueOf(invoiceTypeValueDTOs.get(key)));
                        break;
                    }
                }
            }
            if(StringUtils.isNotBlank(info.getStatus())){
                for (String key : invoiceStatusValueDTOs.keySet()) {
                    if(info.getStatus().equals(key)){
                        info.setStatus(String.valueOf(invoiceStatusValueDTOs.get(key)));
                        break;
                    }
                }
            }

            if(StringUtils.isNotBlank(info.getReceivablesStatus())){
                switch (info.getReceivablesStatus()){
                    case "1":
                        info.setReceivablesStatus("是");
                        break;
                    case "0":
                        info.setReceivablesStatus("否");
                        break;
                    default:
                        info.setReceivablesStatus("");
                        break;
                }
            }else{
                info.setReceivablesStatus("");
            }

            if(StringUtils.isNotBlank(info.getIsPreInvoice())){
                switch (info.getIsPreInvoice()){
                    case "1":
                        info.setIsPreInvoice("是");
                        break;
                    case "0":
                        info.setIsPreInvoice("否");
                        break;
                    default:
                        info.setIsPreInvoice("");
                        break;
                }
            }else{
                info.setIsPreInvoice("");
            }

            if(StringUtils.isNotBlank(info.getIsCompleteInvoice())){
                switch (info.getIsCompleteInvoice()){
                    case "1":
                    case "2":
                        info.setIsCompleteInvoice("是");
                        break;
                    case "0":
                        info.setIsCompleteInvoice("否");
                        break;
                    default:
                        info.setIsCompleteInvoice("");
                        break;
                }
            }else{
                info.setIsCompleteInvoice("");
            }

        }
        EasyPoiUtil.exportExcel ( resultList, sheetName,  sheetName, CwFinanceInvoiceDTO.class, fileName, response );

    }

    public static boolean objectCheckIsNull(Object object) {
        boolean flag = false; //定义返回结果，默认为true

        if (Objects.isNull(object)) {
            flag = false;
        } else {
            Class clazz = (Class) object.getClass(); // 得到类对象
            Field fields[] = clazz.getDeclaredFields(); // 得到所有属性
            for (Field field : fields) {
                if("serialVersionUID".equals(field.getName()) || "BIZ_CODE".equalsIgnoreCase(field.getName())){
                    continue;
                }
                field.setAccessible(true);
                Object fieldValue = null;
                try {
                    fieldValue = field.get(object); //得到属性值
                    Type fieldType = field.getGenericType();//得到属性类型
                    String fieldName = field.getName(); // 得到属性名
                    log.info("属性类型：" + fieldType + ",属性名：" + fieldName + ",属性值：" + fieldValue);
                } catch (IllegalArgumentException e) {
                    log.error(e.getMessage(), e);
                } catch (IllegalAccessException e) {
                    log.error(e.getMessage(), e);
                }
                if (fieldValue != null && fieldValue != "") {  //只要有一个属性值不为null 就返回false 表示对象不为null
                    flag = true;
                    break;
                }
            }
        }

        return flag;
    }

    /**
     * 根据项目id查询发票信息
     * @param cwFinanceInvoiceDTO
     * @param page
     * @return
     * @throws Exception
     */
    @ApiLog("根据项目id查询发票信息")
    @GetMapping("listByProgramId")
    public ResponseEntity<IPage<CwFinanceInvoiceDTO>> listByProgramId(CwFinanceInvoiceDTO cwFinanceInvoiceDTO, Page<CwFinanceInvoiceDTO> page) throws Exception {
        IPage<CwFinanceInvoiceDTO> result = new Page<CwFinanceInvoiceDTO>();
        result = cwFinanceInvoiceService.listByProgramId (page, cwFinanceInvoiceDTO);
        return ResponseEntity.ok(result);
    }


    /**
     * 根据发票编号查询是否与预开票项目下的报告完成关联
     */
    @ApiLog("根据发票编号查询是否与预开票项目下的报告完成关联")
    @GetMapping("getByNo")
    public Boolean getByNo(String no){
        boolean data = cwFinanceInvoiceService.getByNo(no);
        return data;
    }


    /**
     * 查询开票时间距今超过1个月的开票数据
     */
    @ApiLog("查询开票时间距今超过1个月的开票数据")
    @GetMapping("getExceedCwFinance")
    public String getExceedCwFinance(){
        List<CwFinanceInvoiceDTO> invoiceList = cwFinanceInvoiceService.getExceedCwFinance();
        return JSON.toJSONString(invoiceList);
    }



    /**
     * 下载发票导入模板
     *
     * @param response
     * @return
     */
    @GetMapping("/importFinance/template")
    @ApiOperation(value = "下载模板")
    public void importFinanceTemplate(HttpServletResponse response, HttpServletRequest request) {
        try {
            InputStream inputStream = this.getClass().getResourceAsStream("/dot/发票导入模板.xlsx");
            //强制下载不打开
            response.setContentType("application/force-download");
            OutputStream out = response.getOutputStream();
            //使用URLEncoder来防止文件名乱码或者读取错误
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("finance_invoice_template.xlsx", "UTF-8"));
            int b = 0;
            byte[] buffer = new byte[1000000];
            while (b != -1) {
                b = inputStream.read(buffer);
                if (b != -1) out.write(buffer, 0, b);
            }
            inputStream.close();
            out.close();
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }




    /**
     * 导入发票数据
     *
     * @return
     */
    @DemoMode
    @PostMapping("/importFinance")
    @ApiLog(value = "导入发票数据excel", type = LogTypeEnum.IMPORT)
    public ResponseEntity importFinance(MultipartFile file, HttpServletRequest request) throws IOException {

        ArrayList<CwFinanceImportDTO> arrayList = new ArrayList<>();
        HashMap<String,String> hashMap = new HashMap<>();

        List<CwFinanceImportDTO> listA = new ArrayList<>();
        //获取sheet
        listA = EasyPoiUtil.importExcel(file, 1, 1, CwFinanceImportDTO.class);
        //去除excel中的空行
        listA = getExList(listA);
        //去除示例行
        listA.removeIf(item -> {
            String number = (item).getReportNo();
            return number != null && number.contains("例");
        });
        //导入前检测数据
        String resultA = cwFinanceInvoiceService.importFinance(listA, arrayList, hashMap);
        if(StringUtils.isNotBlank(resultA)){
            //有返回值，说明导入的数据不正确。向用户抛提示
            return ResponseEntity.badRequest().body  (resultA);
        }

        return ResponseEntity.ok("导入成功");
    }

    /**
     * 去除excel中的空行
     * @param list
     * @return
     */
    public ArrayList<CwFinanceImportDTO> getExList(List<CwFinanceImportDTO> list){

        ArrayList<CwFinanceImportDTO> cwFinanceImportDTOS = new ArrayList<>();

        list.stream().forEach(item->{
            if(objectCheckIsNull(item)){
                cwFinanceImportDTOS.add(item);
            }
        });

        return cwFinanceImportDTOS;
    }


    /**
     * 批量发起开票申请流程
     *
     * @param list
     * @return
     */
    @ApiOperation(value = "批量发起开票申请流程")
    @PostMapping(value = "/batchInvoicePush")
    public ResponseEntity<List<String>> batchInvoicePush(@RequestBody List<CwFinanceInvoiceDTO> list) {
        List<String> strings = cwFinanceInvoiceService.batchInvoicePush(list);
        return ResponseEntity.ok(strings);
    }



    /**
     * 给accessToken查询的有效时间设置为1天
     */
    @Autowired
    private OMSDisposeService omsDisposeService;
    //private static final String appId = Global.getConfig("omsAppId");
    //private static final String appKey = Global.getConfig("omsAppKey");
    //private static final String deptCode = Global.getConfig("omsDeptCode");

    /**
     * 开具蓝票
     * OMS发票测试 完整最终版【最终最终定稿，完全匹配你的所有要求】
     * 精准码值规则：
     * 0000=成功执行下载 | 9998=5次重试/30秒 | 0003=清token从头执行 | 0001/0002/其他码=直接执行兜底方法修改系统信息
     * @param workInvoiceId   对应的应该是发票管理中的 id  即 work_invoice表中需要开票的id（这个id应该被传到redis中进行记录，方便后期回调的时候进行处理）
     * @return
     */
    @GetMapping(value = "/invoiceOMSView")
    @Transactional(readOnly = false)
    public Map<String,Object> invoiceOMSView(@RequestParam("invoiceId") String workInvoiceId){
        Map<String,Object> map = new HashMap<>();
        // 调用抽离后的核心业务方法，实现流程复用（0003时可重新调用）
        omsDisposeService.doInvoiceBusiness(map, workInvoiceId, "21");
        return map;
    }


    /**
     * 开具红票(快速红冲)
     * OMS发票测试 完整最终版【最终最终定稿，完全匹配你的所有要求】
     * 精准码值规则：
     * 0000=成功执行下载 | 9998=5次重试/30秒 | 0003=清token从头执行 | 0001/0002/其他码=直接执行兜底方法修改系统信息
     */
    @GetMapping(value = "/invoiceFastRedOMSView")
    @Transactional(readOnly = false)
    public Map<String,Object> invoiceFastRedOMSView(@Param("allEinvno")String allEinvno, @Param("workInvoiceId")String workInvoiceId) {
        Map<String,Object> map = new HashMap<>();
        // 调用抽离后的核心业务方法，实现流程复用（0003时可重新调用）
        omsDisposeService.doFastRedInvoiceBusiness(map, allEinvno, workInvoiceId,"21");
        return map;
    }




    /**
     * 开具红票(全场景红冲，包含已入账红冲处理。我方发起)
     * @param workInvoiceId   申请单号
     * @param redInvoiceRelevancyId 开红票对应蓝票的invoiceid
     * @param originalInvno   原蓝票发票号码（数电票号码）
     * @return
     */
    @GetMapping(value = "/invoiceAllScenarioRedOMSView")
    @Transactional(readOnly = false)
    public Map<String,Object> invoiceAllScenarioRedOMSView(@RequestParam("workInvoiceId")String workInvoiceId,@RequestParam("redInvoiceRelevancyId") String redInvoiceRelevancyId,@RequestParam("originalInvno") String originalInvno) {
        Map<String,Object> map = new HashMap<>();
        // 调用抽离后的核心业务方法，实现流程复用（0003时可重新调用）
        omsDisposeService.doAllScenarioRedInvoiceBusiness(map, workInvoiceId, redInvoiceRelevancyId, originalInvno,"21");
        return map;
    }

    /**
     * 临时访问接口
     * @param redisKeyPrefix
     */
    /**
     * 临时访问接口：按前缀批量删除Redis key
     * @param redisKeyPrefix 传入Redis key前缀（如OMS_invoice_download）
     * @return 包含操作状态和key集合的结果
     */
    @GetMapping(value = "/deleteRedisByKey")
    public Map<String, Object> deleteRedisByKey(String redisKeyPrefix) {
        Map<String, Object> resultMap = new HashMap<>();
        // 初始化返回结果：默认操作失败
        resultMap.put("success", false);
        resultMap.put("code", 500);
        resultMap.put("msg", "删除失败，未知异常");
        RedisUtils jedis = null;

        try {
            // 1. 获取Redis连接
            jedis = RedisUtils.getInstance();
            // 测试用：查询删除前的key集合（硬编码，可保留）
            Set<String> downloadTaskKeys1 = jedis.keys(redisKeyPrefix + ":*");
            resultMap.put("downloadTaskKeys1", downloadTaskKeys1);

            // 2. 全量判空：连接非空 + 前缀非空非空串
            if (jedis == null) {
                resultMap.put("msg", "获取Redis连接失败");
                return resultMap;
            }
            if (redisKeyPrefix == null || redisKeyPrefix.trim().isEmpty()) {
                resultMap.put("msg", "Redis key前缀不能为空");
                return resultMap;
            }

            // 3. 拼接通配符，匹配前缀下所有key
            String redisPattern = redisKeyPrefix.trim() + "*";
            // 4. 查询匹配的key集合
            Set<String> matchKeys = jedis.keys(redisPattern);
            if (matchKeys == null || matchKeys.isEmpty()) {
                // 无匹配key，返回成功提示
                resultMap.put("success", true);
                resultMap.put("code", 200);
                resultMap.put("msg", "未查询到匹配的Redis key，无需删除");
                // 查询删除后（实际无删除）的key集合
                Set<String> downloadTaskKeys2 = jedis.keys(redisKeyPrefix + ":*");
                resultMap.put("downloadTaskKeys2", downloadTaskKeys2);
                return resultMap;
            }

            // 5. 批量删除：Set转数组，调用del方法
            String[] keyArray = matchKeys.toArray(new String[0]);
            Collection<String> keyCollection = Arrays.asList(keyArray);
            jedis.delete(keyCollection);

            // 6. 关键：用有效连接查询删除后的真实key集合
            Set<String> downloadTaskKeys2 = jedis.keys(redisKeyPrefix + ":*");
            resultMap.put("downloadTaskKeys2", downloadTaskKeys2);

            // 7. 更新返回结果：删除成功
            resultMap.put("success", true);
            resultMap.put("code", 200);
            resultMap.put("msg", "成功删除" + matchKeys.size() + "个Redis key");

        } catch (Exception e) {
            // 捕获所有Redis操作异常，记录日志（建议添加日志框架，如logback/log4j2）
            e.printStackTrace(); // 临时用，生产环境替换为日志记录
            resultMap.put("msg", "Redis操作异常：" + e.getMessage());
        } finally {
            // 仅释放资源：关闭Jedis连接，归还到连接池
            if (jedis != null) {
                try {
                    //jedis.close();
                } catch (Exception e) {
                    e.printStackTrace(); // 关闭连接异常也记录
                }
            }
        }
        return resultMap;
    }


    /**
     * 以下是提供给定时任务调用的
     */
    /**
     * 用于发票开票获取数电票信息处理
     * 和开票系统相关的定时任务
     */
    //@Scheduled(cron = "0 */1 * * * ?")
    @GetMapping(value = "/processInvoiceDownloadTasks")
    @Transactional(readOnly = false)
    public void processInvoiceDownloadTasks() {
        invoiceDownloadService.processInvoiceDownloadTasks();
        redInvoiceDownloadService.processRedInvoiceDownloadTasks();
    }

    /**
     * 用于发票开票红冲定时任务信息处理
     * 和开票系统相关的定时任务
     */
    //@Scheduled(cron = "0 */1 * * * ?")
    @GetMapping(value = "/processRedInvoiceScheduledTask")
    @Transactional(readOnly = false)
    public void processRedInvoiceScheduledTask() {
        redInvoiceScheduledService.processAllRedInvoiceTasks();
    }

    /**
     * 用于发票开票时报9998错误时进行重新处理的方法
     * 和开票系统相关的定时任务
     */
    //@Scheduled(cron = "0 */1 * * * ?")
    @GetMapping(value = "/redInvoiceRetryScheduledTask")
    @Transactional(readOnly = false)
    public void redInvoiceRetryScheduledTask() {
        redInvoiceRetryScheduledService.handleInvoice9998RetryTask();
    }


    /**
     * 下载附件(文件没有时间戳前缀)
     */
    @GetMapping("/downLoadOMSInvoiceAttachzip")
    public void downLoadOMSInvoiceAttachzip(@RequestParam("fileUrl")String fileUrl,@RequestParam("invoiceNo")String invoiceNo, HttpServletResponse response) {
        long timestamp = System.currentTimeMillis();
        UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
        String filePath = null;
        if (System.getProperty("os.name").toLowerCase().contains("win")) {
            filePath = "D:/attachment-file/"+invoiceNo;
        } else {
            filePath = "/attachment-file/"+invoiceNo;
        }

        File folder = new File(filePath);
        if (!folder.exists()) {
            if (folder.mkdirs()) {
                System.out.println("文件夹创建成功！");
            } else {
                System.out.println("文件夹创建失败！");
            }
        } else {
            System.out.println("文件夹已存在！");
        }
        String path = filePath + "/";

        try {
            //文件下载到指定文件夹
            String aliyunUrl = "http://cdn.gangwaninfo.com";
            String file = aliyunUrl + fileUrl;
            SpringUtil.getBean(IWorkAttachmentApi.class).downloadFolderFromOss(fileUrl, path );
            //文件打包压缩成zip
            FileUtil.zipFolder(filePath, filePath + ".zip");
            String downUrl = filePath + ".zip";

            OutputStream outputStream = null;
            InputStream inputStream = null;
            BufferedInputStream bufferedInputStream = null;
            byte[] bytes = new byte[1024];
            File downFile = new File(downUrl);
            String downFileName = downFile.getName();
            // 获取输出流
            try {
                response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(downFileName, "UTF-8"));
                // 以流的形式返回文件
                response.setContentType("application/octet-stream;charset=utf-8");
                inputStream = new FileInputStream(downFile);
                bufferedInputStream = new BufferedInputStream(inputStream);
                outputStream = response.getOutputStream();
                int i = bufferedInputStream.read(bytes);
                while (i != -1) {
                    outputStream.write(bytes, 0, i);
                    i = bufferedInputStream.read(bytes);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                    if (outputStream != null) {
                        outputStream.flush();
                    }
                    if (bufferedInputStream != null) {
                        bufferedInputStream.close();
                    }

                    //根据路径创建文件对象
                    File downloadFile = new File(filePath);
                    deleteFile(downloadFile);

                    //根据路径创建文件对象
                    File downFileZip = new File(downUrl);
                    //路径是个文件且不为空时删除文件
                    if (downFileZip.isFile() && downFileZip.exists()) {
                        downFileZip.delete();
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void deleteFile(File folder) {
        if (folder.isDirectory()) {
            File[] files = folder.listFiles();
            if (files != null) { // 如果文件夹为空，files可能为null
                for (File file : files) {
                    deleteFile(file); // 递归删除子文件夹和文件
                }
            }
        }
        folder.delete(); // 删除空文件夹或者文件
    }



    /**
     * 修改字典，提供给发票管理员使用
     */
    @PostMapping(value = "/updateDictValue")
    public ResponseEntity<String> updateDictValue(@RequestBody CwFinanceDictDTO dto) {
       try {
           SpringUtil.getBean ( IDictApi.class ).updateDictValue (dto.getLabel(), dto.getValue());
           return ResponseEntity.ok("操作成功");
       }catch (RuntimeException e){
           return ResponseEntity.ok("操作失败，请稍后重试");
       }
    }

    /**
     * 获取钉钉通知字典值
     */
    @PostMapping(value = "/getDingNoticeDictValue")
    public ResponseEntity<String> getDingNoticeDictValue(@RequestBody CwFinanceDictDTO dto) {
        try {
            String realTimeDictValue = SpringUtil.getBean(IDictApi.class).getRealTimeDictValue(dto.getLabel());
            return ResponseEntity.ok(realTimeDictValue);
        }catch (RuntimeException e){
            return ResponseEntity.ok("操作失败，请稍后重试");
        }
    }





    /**
     * 下载项目导入模板
     *
     * @param response
     * @return
     */
    @GetMapping("/importCwFinance/template")
    @ApiOperation(value = "下载模板")
    public void importCwFinanceTemplate(HttpServletResponse response, HttpServletRequest request) {
        try {
            // 去掉 this.getClass()，用当前线程的类加载器，路径去掉开头的 /
            InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("dot/收款导入模版.xlsx");
            //强制下载不打开
            response.setContentType("application/force-download");
            OutputStream out = response.getOutputStream();
            //使用URLEncoder来防止文件名乱码或者读取错误
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("project_records_template.xlsx", "UTF-8"));
            int b = 0;
            byte[] buffer = new byte[1000000];
            while (b != -1) {
                b = inputStream.read(buffer);
                if (b != -1) out.write(buffer, 0, b);
            }
            inputStream.close();
            out.close();
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 导入项目数据excel
     *
     * @return
     */
    @DemoMode
    @PostMapping("/importProjectRecords")
    @ApiLog(value = "导入项目数据excel", type = LogTypeEnum.IMPORT)
    public ResponseEntity importProjectRecords(MultipartFile file, HttpServletRequest request) throws IOException {
        // 初始化返回结果
        Map<String, Object> result = new HashMap<>();
        result.put("success", false); // 默认失败
        result.put("msg", "");
        result.put("excelFileName", null); // Excel文件名

        List<CwWorkInvoiceReceiptInfo> effectiveList = Lists.newArrayList();
        List<CwWorkInvoiceReceiptInfo> errorList = Lists.newArrayList();
        String excelFileName = null;


        try {
            StringBuilder failureMsg = new StringBuilder();
            List<CwWorkInvoiceReceiptInfo> list = new ArrayList<>();
            //获取sheet
            list = EasyPoiUtil.importExcel(file, 1, 1, CwWorkInvoiceReceiptInfo.class);
            //去除excel中的空行
            list = getImportExList(list);

            // 【原有逻辑完全不变】迭代器过滤错误数据
            Iterator<CwWorkInvoiceReceiptInfo> iterator = list.iterator();
            while (iterator.hasNext()){
                CwWorkInvoiceReceiptInfo workInvoiceReceiptInfo = iterator.next();
                if(StringUtils.isBlank(workInvoiceReceiptInfo.getInvoiceNumber()) || StringUtils.isBlank(workInvoiceReceiptInfo.getBuyerName()) || null == workInvoiceReceiptInfo.getMoney() ){
                    if(StringUtils.isNotBlank(workInvoiceReceiptInfo.getInvoiceNumber())){
                        workInvoiceReceiptInfo.setErrorMessage("数据中发票号");
                    }else if(StringUtils.isNotBlank(workInvoiceReceiptInfo.getBuyerName())){
                        workInvoiceReceiptInfo.setErrorMessage("数据中缺少购买企业名称");
                    }else if(null == workInvoiceReceiptInfo.getMoney()){
                        workInvoiceReceiptInfo.setErrorMessage("数据中缺少金额");
                    }
                    errorList.add(workInvoiceReceiptInfo);
                    iterator.remove();
                }else{
                    effectiveList.add(workInvoiceReceiptInfo);
                }
            }

            // 【原有逻辑完全不变】有效数据去重
            for (CwWorkInvoiceReceiptInfo info : effectiveList) {
                String invoiceName = (info.getInvoiceNumber() != null) ? info.getInvoiceNumber() : "";
                String buyerName = (info.getBuyerName() != null) ? info.getBuyerName() : "";
                String money = (info.getMoney() != null) ? info.getMoney().toString() : "";
                String resultStr = invoiceName + "," + buyerName + "," + money;
                info.setDistinctStr(resultStr);
            }

            Map<String,List<CwWorkInvoiceReceiptInfo>> map = cwFinanceInvoiceService.distinctProjectMaterialStorage(effectiveList);

            // 【原有逻辑完全不变】各类错误数据标记
            List<CwWorkInvoiceReceiptInfo> uniqueList = map.get("uniqueList");
            List<CwWorkInvoiceReceiptInfo> duplicateList = map.get("duplicateList");
            for (CwWorkInvoiceReceiptInfo info : duplicateList) {
                info.setErrorMessage("重复数据，添加失败");
            }
            List<CwWorkInvoiceReceiptInfo> alreadyExistList = map.get("alreadyExistList");
            for (CwWorkInvoiceReceiptInfo info : alreadyExistList) {
                info.setErrorMessage("该发票已存在收款信息，添加失败");
            }
            List<CwWorkInvoiceReceiptInfo> inexistenceList = map.get("inexistenceList");
            for (CwWorkInvoiceReceiptInfo info : inexistenceList) {
                info.setErrorMessage("系统中不存在该发票信息，添加失败");
            }
            List<CwWorkInvoiceReceiptInfo> moneyMismatchingList = map.get("moneyMismatchingList");
            for (CwWorkInvoiceReceiptInfo info : moneyMismatchingList) {
                info.setErrorMessage("发票金额大于系统开票金额，添加失败");
            }

            // 【核心逻辑不变】生成Excel文件到指定文件夹
            if(list.size() > uniqueList.size()){
                excelFileName = generateImportResultExcel(uniqueList, errorList, duplicateList, alreadyExistList,
                        inexistenceList, moneyMismatchingList, list);
                result.put("msg", "导入数据存在问题，可下载文档查看详情。");
                result.put("excelFileName", excelFileName); // 返回Excel文件名
            }else{
                result.put("msg", "已成功导入 "+list.size()+" 条发票收款记录"+failureMsg);
            }

            result.put("success", true); // 业务处理成功（即使有Excel，也是处理完成）

        } catch (Exception e) {
            result.put("msg", "导入发票收款记录失败！失败信息："+e.getMessage());
        }

        return ResponseEntity.ok(result);
    }


    /**
     * 去除excel中的空行
     * @param list
     * @return
     */
    public ArrayList<CwWorkInvoiceReceiptInfo> getImportExList(List<CwWorkInvoiceReceiptInfo> list){

        ArrayList<CwWorkInvoiceReceiptInfo> cwProjectRecordsImportDTOS = new ArrayList<>();

        list.stream().forEach(item->{
            if(objectInvoiceImportCheckIsNull(item)){
                cwProjectRecordsImportDTOS.add(item);
            }
        });

        //return cwFinanceImportDTOS;
        return cwProjectRecordsImportDTOS;
    }


    public static boolean objectInvoiceImportCheckIsNull(Object object) {
        boolean flag = false; //定义返回结果，默认为true

        if (Objects.isNull(object)) {
            flag = false;
        } else {
            Class clazz = (Class) object.getClass(); // 得到类对象
            Field fields[] = clazz.getDeclaredFields(); // 得到所有属性
            for (Field field : fields) {
                if("serialVersionUID".equals(field.getName()) || "BIZ_CODE".equalsIgnoreCase(field.getName())){
                    continue;
                }
                field.setAccessible(true);
                Object fieldValue = null;
                try {
                    fieldValue = field.get(object); //得到属性值
                    Type fieldType = field.getGenericType();//得到属性类型
                    String fieldName = field.getName(); // 得到属性名
                    log.info("属性类型：" + fieldType + ",属性名：" + fieldName + ",属性值：" + fieldValue);
                } catch (IllegalArgumentException e) {
                    log.error(e.getMessage(), e);
                } catch (IllegalAccessException e) {
                    log.error(e.getMessage(), e);
                }
                if (fieldValue != null && fieldValue != "") {  //只要有一个属性值不为null 就返回false 表示对象不为null
                    flag = true;
                    break;
                }
            }
        }

        return flag;
    }

    /**
     * 生成导入结果Excel，分2个Sheet：有效导入数据、错误数据（含原因）
     * @param uniqueList 唯一有效数据
     * @param errorList 错误数据
     * @param duplicateList 重复数据
     * @param alreadyExistList 该发票已存在收款信息
     * @param inexistenceList 系统中不存在该发票信息
     * @param moneyMismatchingList 发票金额大于系统开票金额
     * @param metadataList 导入原数据
     * @param response 响应对象，用于输出下载流
     */
    /**
     * 改造后：生成Excel到指定文件夹，返回唯一文件名（不再直接输出到response）
     */
    private String generateImportResultExcel(List<CwWorkInvoiceReceiptInfo> uniqueList,
                                             List<CwWorkInvoiceReceiptInfo> errorList,
                                             List<CwWorkInvoiceReceiptInfo> duplicateList,
                                             List<CwWorkInvoiceReceiptInfo> alreadyExistList,
                                             List<CwWorkInvoiceReceiptInfo> inexistenceList,
                                             List<CwWorkInvoiceReceiptInfo> moneyMismatchingList,
                                             List<CwWorkInvoiceReceiptInfo> metadataList) throws Exception {

        String path = System.getProperty("os.name").toLowerCase().contains("win")
                ? "D:/attachment-file/invoiceBatch/"
                : "/attachment-file/invoiceBatch/";

        File saveDir = new File(path);
        if (!saveDir.exists()) {
            saveDir.mkdirs();
        }

        UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
        String fileName = "发票收款导入数据汇总_" + userDTO.getId() + ".xlsx";
        String fullFilePath = path + fileName;

        Workbook workbook = new XSSFWorkbook();

        // ========== 统一导出所有Sheet，格式完全对齐图二 ==========
        exportSheet(workbook, "有效数据", "批量收款导入有效数据", uniqueList);
        exportSheet(workbook, "错误数据", "批量收款导入错误数据", errorList);
        exportSheet(workbook, "重复数据", "批量收款导入重复数据", duplicateList);
        exportSheet(workbook, "已存在收款数据", "批量收款导入已存在收款数据", alreadyExistList);
        exportSheet(workbook, "不存在的发票信息", "批量收款导入不存在的发票信息", inexistenceList);
        exportSheet(workbook, "发票金额大于系统开票金额", "批量收款导入发票金额大于系统开票金额数据", moneyMismatchingList);
        exportSheet(workbook, "原数据", "批量收款导入原数据", metadataList);

        try (FileOutputStream fos = new FileOutputStream(fullFilePath)) {
            workbook.write(fos);
        } finally {
            workbook.close();
        }

        return fileName;
    }

    /**
     * 统一导出Sheet方法：完美复刻图二样式（大标题+灰色表头+边框+居中）
     */
    private void exportSheet(Workbook workbook, String sheetName, String title, List<CwWorkInvoiceReceiptInfo> dataList) {
        if (dataList == null || dataList.isEmpty()) {
            return;
        }

        // 1. 创建Sheet
        Sheet sheet = workbook.createSheet(sheetName);

        // 2. 创建样式（完全对齐图二）
        // 大标题样式：居中、加粗、大号字体
        CellStyle titleStyle = workbook.createCellStyle();
        Font titleFont = workbook.createFont();
        titleFont.setFontHeightInPoints((short) 24);
        titleFont.setBold(true);
        titleStyle.setFont(titleFont);
        titleStyle.setAlignment(HorizontalAlignment.CENTER);
        titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        // 表头样式：灰色背景、居中、加粗、边框
        CellStyle headerStyle = workbook.createCellStyle();
        Font headerFont = workbook.createFont();
        headerFont.setBold(true);
        headerFont.setFontHeightInPoints((short) 12);
        headerStyle.setFont(headerFont);
        headerStyle.setAlignment(HorizontalAlignment.CENTER);
        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        // 全边框
        headerStyle.setBorderBottom(BorderStyle.THIN);
        headerStyle.setBorderTop(BorderStyle.THIN);
        headerStyle.setBorderLeft(BorderStyle.THIN);
        headerStyle.setBorderRight(BorderStyle.THIN);

        // 数据样式：居中、边框
        CellStyle dataStyle = workbook.createCellStyle();
        dataStyle.setAlignment(HorizontalAlignment.CENTER);
        dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        dataStyle.setBorderBottom(BorderStyle.THIN);
        dataStyle.setBorderTop(BorderStyle.THIN);
        dataStyle.setBorderLeft(BorderStyle.THIN);
        dataStyle.setBorderRight(BorderStyle.THIN);
        Font dataFont = workbook.createFont();
        dataFont.setFontHeightInPoints((short) 11);
        dataStyle.setFont(dataFont);

        // 3. 写入大标题（第1行，合并A-D列，完全对齐图二）
        Row titleRow = sheet.createRow(0);
        titleRow.setHeightInPoints(40); // 标题行高
        Cell titleCell = titleRow.createCell(0);
        titleCell.setCellValue(title);
        titleCell.setCellStyle(titleStyle);
        // 合并单元格（根据你的4个字段，合并A-D列，0-3）
        sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 3));

        // 4. 写入表头（第2行，灰色背景，完全对齐图二）
        Row headerRow = sheet.createRow(1);
        headerRow.setHeightInPoints(25);
        String[] headers = {"*发票号码", "*购方企业名称", "*收款金额", "*提醒"};
        for (int i = 0; i < headers.length; i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers[i]);
            cell.setCellStyle(headerStyle);
            // 设置列宽（适配内容）
            sheet.setColumnWidth(i, 25 * 256);
        }

        // 5. 写入数据（从第3行开始）
        for (int i = 0; i < dataList.size(); i++) {
            CwWorkInvoiceReceiptInfo info = dataList.get(i);
            Row dataRow = sheet.createRow(i + 2);
            dataRow.setHeightInPoints(20);

            // 按顺序写入4个字段
            Cell cell0 = dataRow.createCell(0);
            cell0.setCellValue(info.getInvoiceNumber());
            cell0.setCellStyle(dataStyle);

            Cell cell1 = dataRow.createCell(1);
            cell1.setCellValue(info.getBuyerName());
            cell1.setCellStyle(dataStyle);

            Cell cell2 = dataRow.createCell(2);
            cell2.setCellValue(info.getMoney());
            cell2.setCellStyle(dataStyle);

            Cell cell3 = dataRow.createCell(3);
            cell3.setCellValue(info.getErrorMessage() == null ? "" : info.getErrorMessage());
            cell3.setCellStyle(dataStyle);
        }
    }

    @GetMapping("/downloadImportResult")
    public void downloadImportResult(String fileName, HttpServletResponse response) throws Exception {
        String path = System.getProperty("os.name").toLowerCase().contains("win")
                ? "D:/attachment-file/invoiceBatch/"
                : "/attachment-file/invoiceBatch/";

        File file = new File(path + fileName);
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));

        Files.copy(file.toPath(), response.getOutputStream());
    }

}
