package com.zg.project.wisdom.service.impl;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

import com.zg.common.exception.ServiceException;
import com.zg.common.utils.DateUtils;
import com.zg.common.utils.SecurityUtils;
import com.zg.common.utils.StringUtils;
import com.zg.project.Inventory.AutoInventory.mapper.InventoryMatchScanMapper;
import com.zg.project.Inventory.Task.mapper.InventoryTaskMapper;
import com.zg.project.Inventory.domain.dto.QueryDTO;
import com.zg.project.Inventory.domain.entity.InventoryMatchScan;
import com.zg.project.Inventory.domain.vo.ChartDataVO;
import com.zg.project.Inventory.domain.vo.PcdeCntVO;
import com.zg.project.information.mapper.PcdeDetailMapper;
import com.zg.project.system.service.ISysConfigService;
import com.zg.project.wisdom.domain.AuditSignature;
import com.zg.project.wisdom.domain.GysJh;
import com.zg.project.wisdom.domain.dto.*;
import com.zg.project.wisdom.domain.vo.PcodeQtyVO;
import com.zg.project.wisdom.mapper.AuditSignatureMapper;
import com.zg.project.wisdom.mapper.GysJhMapper;
import com.zg.project.wisdom.utils.BillNoUtil;
import com.zg.project.wisdom.utils.CodeConvertUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.zg.project.wisdom.mapper.RkInfoMapper;
import com.zg.project.wisdom.domain.RkInfo;
import com.zg.project.wisdom.service.IRkInfoService;
import org.springframework.transaction.annotation.Transactional;

/**
 * 库存单据主Service业务层处理
 * 
 * @author zg
 * @date 2025-05-28
 */
@Service
public class RkInfoServiceImpl implements IRkInfoService
{

    @Autowired
    private RkInfoMapper rkInfoMapper;

    @Autowired
    private GysJhMapper gysJhMapper;

    @Autowired
    private AuditSignatureMapper auditSignatureMapper;

    @Autowired
    private InventoryMatchScanMapper matchScanMapper;

    @Autowired
    private InventoryTaskMapper taskMapper;

    @Autowired
    private ISysConfigService configService;

    @Autowired
    private PcdeDetailMapper pcdeDetailMapper;


    /**
     * 查询库存单据主
     *
     * @param id 库存单据主主键
     * @return 库存单据主
     */
    @Override
    public RkInfo selectRkInfoById(Long id)
    {
        return rkInfoMapper.selectRkInfoById(id);
    }

    /**
     * 查询库存单据主列表
     *
     * @param rkInfo 库存单据主
     * @return 库存单据主
     */
    @Override
    public List<RkInfo> selectRkInfoList(RkInfo rkInfo) {
        boolean needAudit = "1".equals(configService.selectConfigByKey("rk.audit.enabled"));

        List<RkInfo> list = rkInfoMapper.selectRkInfoList(rkInfo);
        LocalDate today = LocalDate.now();

        for (RkInfo info : list) {
            // 计算库龄
            if (info.getRkTime() != null) {
                LocalDate rkDate = info.getRkTime().toInstant()
                        .atZone(ZoneId.systemDefault())
                        .toLocalDate();
                long days = ChronoUnit.DAYS.between(rkDate, today);
                info.setStockAge(days);
            }

            // 查询现场图片 + 审核结果
            AuditSignature signature = auditSignatureMapper.selectPhotoUrlByRkId(info.getId());
            if (signature != null) {
                info.setScenePhotoUrl(signature.getSignUrl());
                info.setAuditResult(signature.getAuditResult());
                info.setApproverId(signature.getApproverId());
            } else {
                info.setScenePhotoUrl(null);
                info.setAuditResult(null);
                info.setApproverId(null);
            }
        }

        // 审核开启，过滤掉审核结果不是“通过” 且 审核人不为空（说明是审核失败）
        if (needAudit) {
            list = list.stream()
                    .filter(info ->
                            // ① 未审核过（approverId 为空）或
                            info.getApproverId() == null ||
                                    // ② 审核通过
                                    "1".equals(info.getAuditResult())
                    )
                    .collect(Collectors.toList());
        }

        return list;
    }

    @Override
    public List<RkInfo> selectGroupedByBill(RkInfoQueryDTO query) {
        // 读取审核开关（1=开启；其它=关闭）
        boolean needAudit = "1".equals(configService.selectConfigByKey("rk.audit.enabled"));
        // 直接传给 Mapper；不改 DTO 结构，走多参数方式
        return rkInfoMapper.selectGroupedByBill(query, needAudit ? 1 : 0);
    }

    /**
     * 修改库存单据主
     *
     * @param rkInfo 库存单据主
     * @return 结果
     */
    @Override
    public int updateRkInfo(RkInfo rkInfo)
    {
        rkInfo.setUpdateTime(DateUtils.getNowDate());
        rkInfo.setUpdateBy(SecurityUtils.getUserId().toString());
        return rkInfoMapper.updateRkInfo(rkInfo);
    }

    /**
     * 批量处理撤销入库 / 撤销出库的逻辑（不是通用删除接口）
     *
     * 规则说明：
     * - isChuku = 4：表示撤销入库
     *      - 若存在 gysJhId 和 realQty，则处理供应计划回补
     *      - 最终物理删除该库存单据
     * - isChuku = 5：表示撤销出库
     *      - 不删除，只将 isChuku 改为 0（未出库状态）
     * - 其他 isChuku 状态：不做任何处理
     *
     * @param ids 需要处理的库存单据ID数组（必须是入库/出库撤销）
     * @return 实际删除的条数（仅统计撤销入库场景）
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteRkInfoByIds(Long[] ids) {
        int deleteCount = 0;
        Date now = new Date();
        String userId = SecurityUtils.getUserId().toString();

        for (Long id : ids) {
            RkInfo info = rkInfoMapper.selectRkInfoById(id);
            if (info == null) continue;

            String isChuku = info.getIsChuku();

            // 撤销出库：只更新状态为“未出库”，不删除
            if ("5".equals(isChuku)) {
                info.setIsChuku("0");
                info.setUpdateBy(userId);
                info.setUpdateTime(now);
                rkInfoMapper.updateRkInfo(info);
                continue;
            }

            // 撤销入库：更新供应计划，删除单据
            if ("4".equals(isChuku)) {
                Long gysJhId = info.getGysJhId();
                BigDecimal realQty = info.getRealQty();

                if (gysJhId != null && realQty != null) {
                    GysJh jh = gysJhMapper.selectGysJhById(gysJhId);
                    if (jh != null) {
                        String status = jh.getStatus();
                        if ("2".equals(status)) {
                            // 回补 jh_qty，改为未到货
                            BigDecimal updatedQty = (jh.getJhQty() == null ? BigDecimal.ZERO : new BigDecimal(jh.getJhQty()))
                                    .add(realQty);
                            jh.setJhQty(updatedQty.longValue());
                            jh.setStatus("0");
                            jh.setUpdateBy(userId);
                            jh.setUpdateTime(now);
                            gysJhMapper.updateGysJh(jh);
                        } else if ("1".equals(status)) {
                            // 只更新状态
                            jh.setStatus("0");
                            jh.setUpdateBy(userId);
                            jh.setUpdateTime(now);
                            gysJhMapper.updateGysJh(jh);
                        }
                    }
                }

                rkInfoMapper.deleteRkInfoById(id);
                deleteCount++;
            }
        }

        return deleteCount;
    }

    /**
     * 删除库存单据主信息
     *
     * @param
     * @return 结果
     */
    /**
     * 撤销入库单据，并根据供应计划状态回退数量或修改状态
     * 逻辑说明：
     *  - 如果供应计划ID存在，查询对应供应计划记录
     *    - 若状态为“2”（部分出库）且实际入库数量 realQty 不为空：
     *        - 将供应计划中的 jhQty 加上 realQty 回退数量
     *        - 并将状态修改为“0”（未入库）
     *    - 若状态不是“2”，也将状态修改为“0”
     *  - 最后根据入库单据ID删除 rk_info 表中的对应入库记录
     *
     * @param dto 包含主键id、实际入库数量realQty、供应计划IDgysJhId
     * @return 删除成功的记录数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteRkInfoById(RkCancelDTO dto) {
        Long id = dto.getId(); // 获取要撤销的入库记录ID

        // 校验是否存在
        RkInfo rkInfo = rkInfoMapper.selectRkInfoById(id);
        if (rkInfo == null) {
            throw new ServiceException("入库记录不存在");
        }

        // 仅更新 is_chuku 字段为 "4" 表示已撤销
        RkInfo update = new RkInfo();
        update.setId(id);
        update.setIsChuku("4"); // 4 = 已撤销
        update.setUpdateBy(SecurityUtils.getUsername());
        update.setUpdateTime(DateUtils.getNowDate());

        return rkInfoMapper.updateRkInfo(update);
    }


    /**
     * 新增入库单据
     * @param dto
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchInsert(PcRkInfoBatchDTO dto) {
        String billNo = BillNoUtil.generateTodayBillNo("RK");
        List<PcRkInfoItemDTO> list = dto.getRkList();
        List<RkInfo> rkInfos = new ArrayList<>();
        List<AuditSignature> records = new ArrayList<>();

        boolean needAudit = "1".equals(configService.selectConfigByKey("rk.audit.enabled"));
        String userId = SecurityUtils.getUserId().toString();
        Date now = DateUtils.getNowDate();

        // ✅ 第1步：构建 gysJhId -> realQty 映射
        Map<Long, BigDecimal> realQtyMap = list.stream()
                .filter(item -> item.getGysJhId() != null && item.getRealQty() != null)
                .collect(Collectors.toMap(
                        PcRkInfoItemDTO::getGysJhId,
                        PcRkInfoItemDTO::getRealQty,
                        (a, b) -> b
                ));

        // ✅ 第2-4步：若包含供应计划ID，则处理计划扣减与状态更新
        if (!realQtyMap.isEmpty()) {
            // ✅ 查询对应供应计划
            List<GysJh> jhList = gysJhMapper.selectByIds(new ArrayList<>(realQtyMap.keySet()));
            Set<Long> idsToUpdateStatus = new HashSet<>();

            // ✅ 更新 jh_qty 和 status（根据是否开启审核判断逻辑）
            for (GysJh jh : jhList) {
                Long jhId = jh.getId();
                BigDecimal planQty = BigDecimal.valueOf(jh.getJhQty());
                BigDecimal realQty = realQtyMap.get(jhId);
                if (realQty == null) continue;

                boolean isEqual = realQty.compareTo(planQty) == 0;

                if (!isEqual) {
                    // 实到数量小于计划数量，执行扣减 + 设为部分入库（2）
                    gysJhMapper.decreaseJhQtyById(jhId, realQty);
                    if (!needAudit) {
                        gysJhMapper.updateStatusById(jhId, "2"); // 2 = 部分入库
                    }
                } else {
                    // 实到数量等于计划数量，状态设为全部入库（1），不做扣减
                    if (!needAudit) {
                        gysJhMapper.updateStatusById(jhId, "1"); // 1 = 全部入库
                    }
                }
            }

            // ✅ 批量更新 status = 2
            if (!idsToUpdateStatus.isEmpty()) {
                gysJhMapper.batchUpdateStatusByIds(idsToUpdateStatus);
            }
        }

        // ✅ 第5步：构建 RkInfo 入库记录
        for (PcRkInfoItemDTO item : list) {
            RkInfo rk = new RkInfo();
            BeanUtils.copyProperties(item, rk);
            String encodedId = pcdeDetailMapper.selectEncodedIdByPcode(item.getPcode());
            rk.setPcodeId(encodedId);
            rk.setBillNo(billNo);
            rk.setRkType(dto.getRkType());
            rk.setWlType(dto.getWlType());
            rk.setLihuoY(dto.getLihuoY());
            rk.setRkTime(now);
            rk.setCangku(dto.getCangku());
            rk.setCreateBy(userId);
            rk.setCreateTime(now);
            rk.setIsDelete("0");
            rk.setGysJhId(item.getGysJhId());

            if (needAudit) {
                rk.setStatus("0");     // 待审核
                rk.setIsChuku("2");    // 待入库
            } else {
                rk.setStatus("1");     // 审核通过
                rk.setIsChuku("0");    // 已入库
            }

            rkInfos.add(rk);
        }

        // ✅ 第6步：保存入库记录
        rkInfoMapper.batchInsertRkInfo(rkInfos);

        // ✅ 第7步：照片记录（审核开启时）
        for (int i = 0; i < rkInfos.size(); i++) {
            RkInfo rk = rkInfos.get(i);
            PcRkInfoItemDTO item = list.get(i);

            if (needAudit && StringUtils.isNotBlank(item.getPhotoUrl())) {
                AuditSignature photo = new AuditSignature();
                photo.setRkId(rk.getId());
                photo.setBillNo(billNo);
                photo.setBillType("0");
                photo.setAuditResult("2");
                photo.setSignerId(userId);
                photo.setSignerRole("2");
                photo.setImageType("1");
                photo.setSignUrl(item.getPhotoUrl());
                photo.setSignTime(now);
                photo.setPcode(item.getPcode());
                photo.setTrayCode(item.getTrayCode());
                photo.setApproverId(dto.getApproverId());
                photo.setIsCurrent("1");
                photo.setIsDelete("0");
                photo.setCreateBy(userId);
                photo.setCreateTime(now);
                records.add(photo);
            }
        }

        // ✅ 第8步：发起人签字记录（审核开启时）
        if (needAudit) {
            boolean hasOldSign = auditSignatureMapper.existsCurrentSigner(billNo, "0");
            if (hasOldSign) {
                auditSignatureMapper.updateIsCurrentToZero(billNo, "0");
            }

            AuditSignature mainSign = new AuditSignature();
            mainSign.setBillNo(billNo);
            mainSign.setBillType("0");
            mainSign.setSignerId(userId);
            mainSign.setSignerRole("0");
            mainSign.setApproverId(dto.getApproverId());
            mainSign.setSignUrl(dto.getSignatureUrl());
            mainSign.setImageType("0");
            mainSign.setSignTime(now);
            mainSign.setIsCurrent("1");
            mainSign.setAuditResult("2");
            mainSign.setIsDelete("0");
            mainSign.setCreateBy(userId);
            mainSign.setCreateTime(now);
            records.add(mainSign);

            auditSignatureMapper.batchInsert(records);
        }
    }


    /**
     * 新增入库单据（APP）
     * @param dto
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int batchInsertApp(RkInfoBatchDTO dto) {
        List<RkInfo> saveList = new ArrayList<>();
        String userId = SecurityUtils.getUserId().toString();
        Date now = DateUtils.getNowDate();

        if (dto.getRkList() == null || dto.getRkList().isEmpty()) {
            throw new ServiceException("rkList 入库明细列表不能为空");
        }

        boolean needAudit = "1".equals(configService.selectConfigByKey("rk.audit.enabled"));
        String billNo = BillNoUtil.generateTodayBillNo("RK");

        // ✅ 第1步：构造 gysJhId → realQty 的映射（从 scanList 中提取）
        Map<Long, BigDecimal> realQtyMap = new HashMap<>();
        for (RkInfoItemDTO item : dto.getRkList()) {
            if (item.getScanList() == null) continue;
            for (RkInfoScanDTO scan : item.getScanList()) {
                Long jhId = scan.getGysJhId();
                BigDecimal realQty = scan.getRealQty();
                if (jhId != null && realQty != null) {
                    realQtyMap.put(jhId, realQty); // ✅ 一物一图场景，每个 jhId 只有一条 scan 数据
                }
            }
        }

        // ✅ 第2步：更新 gys_jh 的 jh_qty 与 status
        Set<Long> idsToUpdateStatus = new HashSet<>();
        List<Long> allJhIds = new ArrayList<>(realQtyMap.keySet());

        if (!allJhIds.isEmpty()) {
            List<GysJh> jhList = gysJhMapper.selectByIds(allJhIds);
            for (GysJh jh : jhList) {
                Long jhId = jh.getId();
                BigDecimal planQty = BigDecimal.valueOf(jh.getJhQty());
                BigDecimal realQty = realQtyMap.get(jhId);
                if (realQty == null) continue;

                boolean isEqual = realQty.compareTo(planQty) == 0;

                if (!isEqual) {
                    // 实到数量小于计划数量，执行扣减 + 设为部分入库（2）
                    gysJhMapper.decreaseJhQtyById(jhId, realQty);
                    if (!needAudit) {
                        gysJhMapper.updateStatusById(jhId, "2"); // 2 = 部分入库
                    }
                } else {
                    // 实到数量等于计划数量，状态设为全部入库（1），不做扣减
                    if (!needAudit) {
                        gysJhMapper.updateStatusById(jhId, "1"); // 1 = 全部入库
                    }
                }
            }
        }

        if (!idsToUpdateStatus.isEmpty()) {
            gysJhMapper.batchUpdateStatusByIds(idsToUpdateStatus);
        }

        // ✅ 第3步：构建 RkInfo 入库实体
        for (RkInfoItemDTO item : dto.getRkList()) {
            if (item.getScanList() == null) continue;

            for (RkInfoScanDTO scan : item.getScanList()) {
                RkInfo entity = new RkInfo();
                entity.setRkType(dto.getRkType());
                entity.setWlType(dto.getWlType());
                entity.setCangku(dto.getCangku());
                entity.setLihuoY(dto.getLihuoY());
                entity.setBillNo(billNo);
                entity.setXj(item.getXj());
                entity.setXmMs(item.getXmMs());
                entity.setXmNo(item.getXmNo());
                entity.setSapNo(item.getSapNo());
                entity.setJhQty(item.getJhQty());
                entity.setHtQty(item.getHtQty());
                entity.setJhAmt(item.getJhAmt());
                entity.setHtDj(item.getHtDj());
                entity.setDw(item.getDw());
                entity.setWlNo(item.getWlNo());
                entity.setWlMs(item.getWlMs());
                entity.setGysNo(item.getGysNo());
                entity.setGysMc(item.getGysMc());
                entity.setPcode(scan.getPcode());
                entity.setPcodeId(CodeConvertUtil.stringToHex(scan.getPcode()));
                entity.setTrayCode(scan.getTrayCode());
                entity.setRealQty(scan.getRealQty());
                entity.setEntityId(scan.getEntityId());
                entity.setRemark(scan.getRemark());
                entity.setGysJhId(scan.getGysJhId());
                entity.setIsDelete("0");
                entity.setRkTime(now);
                entity.setCreateBy(userId);
                entity.setCreateTime(now);

                if (needAudit) {
                    entity.setStatus("0");   // 待审核
                    entity.setIsChuku("2"); // 待入库
                } else {
                    entity.setStatus("1");   // 审核通过
                    entity.setIsChuku("0"); // 已入库
                }

                saveList.add(entity);
            }
        }

        if (saveList.isEmpty()) {
            throw new ServiceException("未提取到任何可保存的数据");
        }

        // ✅ 第4步：批量插入入库记录
        rkInfoMapper.batchInsertRkInfo(saveList);

        // ✅ 第5步：若开启审核，写入签字与照片信息
        if (needAudit) {
            List<AuditSignature> recordList = new ArrayList<>();
            boolean isResubmit = auditSignatureMapper.existsCurrentSigner(billNo, "0");
            if (isResubmit) {
                auditSignatureMapper.updateIsCurrentToZero(billNo, "0");
            }

            // 发起人签字记录
            AuditSignature mainSign = new AuditSignature();
            mainSign.setBillNo(billNo);
            mainSign.setBillType("0");
            mainSign.setSignerId(userId);
            mainSign.setSignerRole("0");
            mainSign.setSignUrl(dto.getSignatureUrl());
            mainSign.setImageType("0");
            mainSign.setSignTime(now);
            mainSign.setApproverId(dto.getApproverId());
            mainSign.setAuditResult("2");
            mainSign.setIsCurrent("1");
            mainSign.setIsDelete("0");
            mainSign.setCreateBy(userId);
            mainSign.setCreateTime(now);
            recordList.add(mainSign);

            // 现场照片记录
            int index = 0;
            for (RkInfoItemDTO item : dto.getRkList()) {
                if (item.getScanList() == null) continue;
                for (RkInfoScanDTO scan : item.getScanList()) {
                    RkInfo rk = saveList.get(index++);
                    if (StringUtils.isNotBlank(scan.getPhotoUrl())) {
                        AuditSignature photo = new AuditSignature();
                        photo.setBillNo(billNo);
                        photo.setBillType("0");
                        photo.setRkId(rk.getId());
                        photo.setSignerId(userId);
                        photo.setSignerRole("2");
                        photo.setSignUrl(scan.getPhotoUrl());
                        photo.setImageType("1");
                        photo.setSignTime(now);
                        photo.setApproverId(dto.getApproverId());
                        photo.setIsCurrent("1");
                        photo.setAuditResult("2");
                        photo.setIsDelete("0");
                        photo.setCreateBy(userId);
                        photo.setCreateTime(now);
                        photo.setPcode(rk.getPcode());
                        photo.setTrayCode(rk.getTrayCode());
                        recordList.add(photo);
                    }
                }
            }

            if (!recordList.isEmpty()) {
                auditSignatureMapper.batchInsert(recordList);
            }
        }

        return saveList.size();
    }

    @Transactional
    public void deleteByBillNo(String billNo) {
        // 1. 查询 sap_no
        List<String> sapNos = rkInfoMapper.selectSapNoByBillNo(billNo);

        // 2. 更新供应计划状态
        if (sapNos != null && !sapNos.isEmpty()) {
            gysJhMapper.resetGysJhStatusBySapNos(sapNos);
        }

        // 3. 删除入库记录
        rkInfoMapper.deleteByBillNo(billNo);
    }

    /**
     * 撤销出库
     * @param
     */
    @Override
    public int deleteByCkBillNo(String billNoCk) {

        return rkInfoMapper.cancelStockOut(billNoCk);

    }

    /**
     * 统计过期库存
     * @return
     */
    @Override
    public int countOverdueStock() {
        return rkInfoMapper.countOverdueStock();
    }

    /**
     * 获取最新20条逾期库存列表
     * @param i
     * @return
     */
    @Override
    public List<RkInfo> selectTopOverdueStock(int i) {
        return rkInfoMapper.selectTopOverdueStock(i);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int batchOutStock(StockOutDTO dto) {

        if (dto.getCkList() == null || dto.getCkList().isEmpty()) {
            throw new ServiceException("出库数据不能为空");
        }

        String userId = SecurityUtils.getUserId().toString();
        Date now = DateUtils.getNowDate();

        // Step 1: 判断是否需要审核
        boolean needAudit = "1".equals(configService.selectConfigByKey("rk.audit.enabled"));

        // Step 2: 生成出库单据号
        String billNo = BillNoUtil.generateTodayBillNo("CK");

        // Step 3: 批量更新 rk_info 出库字段
        for (StockOutItemDTO item : dto.getCkList()) {
            RkInfo update = new RkInfo();
            update.setId(item.getId());
            update.setBillNoCk(billNo);
            update.setIsDelivery(dto.getIsDelivery());
            update.setCkType(dto.getCkType());
            update.setTeamCode(dto.getTeamCode());
            update.setLyTime(now);
            update.setCkLihuoY(dto.getCkLihuoY());
            update.setCkRemark(item.getCkRemark());
            update.setXmNoCk(dto.getXmNoCk());
            update.setXmMsCk(dto.getXmMsCk());
            update.setUpdateBy(userId);
            update.setUpdateTime(now);

            if ("JLCK".equals(dto.getCkType())) {
                update.setIsBorrowed("1");
                update.setBorrowTime(dto.getBorrowTime());
                update.setReturnTime(dto.getReturnTime());
            }

            if (needAudit) {
                update.setStatus("3"); // 出库待审核
                update.setIsChuku("JLCK".equals(dto.getCkType()) ? "3" : "2");
            } else {
                update.setStatus("1"); // 审核通过
                update.setIsChuku("JLCK".equals(dto.getCkType()) ? "3" : "1");
            }

            rkInfoMapper.updateById(update);
        }

        // Step 4: 如果启用审核，写入签字记录
        if (needAudit) {
            List<AuditSignature> recordList = new ArrayList<>();

            boolean hasOldSign = auditSignatureMapper.existsCurrentSigner(billNo, "0");
            if (hasOldSign) {
                auditSignatureMapper.updateIsCurrentToZero(billNo, "0");
            }

            AuditSignature sign = new AuditSignature();
            sign.setBillNo(billNo);
            sign.setBillType("1");
            sign.setSignerId(userId);
            sign.setSignerRole("0");
            sign.setSignUrl(dto.getSignatureUrl());
            sign.setImageType("0");
            sign.setSignTime(now);
            sign.setAuditResult("2");
            sign.setApproverId(dto.getApproverId());
            sign.setIsCurrent("1");
            sign.setIsDelete("0");
            sign.setCreateBy(userId);
            sign.setCreateTime(now);
            recordList.add(sign);

            for (StockOutItemDTO item : dto.getCkList()) {
                if (StringUtils.isBlank(item.getPhotoUrl())) continue;

                AuditSignature photo = new AuditSignature();
                photo.setBillNo(billNo);
                photo.setBillType("1");
                photo.setSignerId(userId);
                photo.setSignerRole("2");
                photo.setSignUrl(item.getPhotoUrl());
                photo.setImageType("1");
                photo.setSignTime(now);
                photo.setApproverId(dto.getApproverId());
                photo.setIsCurrent("1");
                photo.setAuditResult("2");
                photo.setIsDelete("0");
                photo.setCreateBy(userId);
                photo.setCreateTime(now);
                photo.setRkId(item.getId());
                photo.setPcode(item.getPcode());
                photo.setTrayCode(item.getTrayCode());
                recordList.add(photo);
            }

            if (!recordList.isEmpty()) {
                auditSignatureMapper.batchInsert(recordList);
            }
        }

        return dto.getCkList().size();
    }

    @Override
    @Transactional(rollbackFor = Exception.class) // 开启事务，异常时回滚
    public void matchWithStatus(QueryDTO dto) {
        // 从 DTO 获取待比对的库位 ID 列表
        List<String> pcdeIds = dto.getIds();
        if (pcdeIds == null || pcdeIds.isEmpty()) return; // 如果为空，直接返回

        // 通过任务 ID 获取场景 ID（优先取 DTO 中的，若无则从任务表查）
        String sceneId = taskMapper.selectSceneIdById(dto.getTaskId());
        if (sceneId == null || sceneId.trim().isEmpty()) {
            throw new ServiceException("匹配失败：缺少场景ID");
        }

        // 自动盘点必须有设备 ID
        String deviceId = dto.getDeviceId();
        int scanType = dto.getScanType() != null ? dto.getScanType() : 0; // 扫描类型：0=手动盘点，1=自动盘点
        String taskId = dto.getTaskId();

        // 校验：若是自动盘点（scanType=1），但未传设备 ID，则报错
        if (scanType == 1 && (deviceId == null || deviceId.trim().isEmpty())) {
            throw new ServiceException("自动盘点必须传递设备ID");
        }

        // ====================== 分类匹配三类数据 ======================

        // 1. 正常匹配的数据（库位 ID 存在于库存表中，且属于当前场景 sceneId）
        List<RkInfo> matchedAll = rkInfoMapper.getByPcodeIdList(pcdeIds, sceneId);
        matchedAll.forEach(r -> r.setStatus("0")); // 标记状态 0：匹配成功

        // 2. 未盘点的数据（存在于库存表，但未出现在传入的 pcdeIds 中）
        List<RkInfo> missedAll = rkInfoMapper.getMissedPcodeIds(pcdeIds, sceneId);
        missedAll.forEach(r -> r.setStatus("1")); // 标记状态 1：未盘点

        // 3. 异常数据（传入的库位 ID 在库存中不存在）
        List<String> errorIds = pcdeIds.stream()
                .filter(id -> rkInfoMapper.existsByPcodeId(id, sceneId) == 0) // 判断库位是否存在
                .collect(Collectors.toList());

        // 将异常库位 ID 封装成 RkInfo 对象，状态设为 2：错误
        List<RkInfo> errorAll = errorIds.stream().map(id -> {
            RkInfo r = new RkInfo();
            r.setPcodeId(id);
            r.setStatus("2");
            return r;
        }).collect(Collectors.toList());

        // ====================== 扫描结果落库 ======================

        // 当前时间字符串
        String tmeStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        List<InventoryMatchScan> toSave = new ArrayList<>();

        // 将匹配成功的数据封装成扫描记录
        for (RkInfo r : matchedAll) {
            toSave.add(buildScanRecord(taskId, r.getPcode(), "0", deviceId, tmeStr, scanType));
        }
        // 将未盘点的数据封装成扫描记录
        for (RkInfo r : missedAll) {
            toSave.add(buildScanRecord(taskId, r.getPcode(), "1", deviceId, tmeStr, scanType));
        }
        // 将异常数据封装成扫描记录
        for (RkInfo r : errorAll) {
            toSave.add(buildScanRecord(taskId, r.getPcodeId(), "2", deviceId, tmeStr, scanType));
        }

        // 批量插入数据库
        if (!toSave.isEmpty()) {
            matchScanMapper.insertBatch(toSave);
        }

        // 更新任务状态为“已完成”（状态=1）
        if (taskId != null && !taskId.trim().isEmpty()) {
            taskMapper.updateStatus(taskId, "1");
        }
    }

    /**
     * 封装一个盘点扫描记录
     *
     * @param taskId   任务 ID
     * @param pcode    库位编码 ID
     * @param status   盘点状态（0=匹配成功，1=未盘点，2=错误）
     * @param deviceId 设备 ID（自动盘点时才有）
     * @param tme      扫描时间
     * @param scanType 扫描类型（0=手动盘点，1=自动盘点）
     * @return 封装好的 InventoryMatchScan 对象
     */
    private InventoryMatchScan buildScanRecord(String taskId, String pcode, String status,
                                               String deviceId, String tme, int scanType) {
        InventoryMatchScan record = new InventoryMatchScan();
        record.setPcode(pcode);
        record.setTaskId(taskId);
        record.setStatus(status);

        // 自动盘点时才保存设备 ID
        if (scanType == 1 && deviceId != null) {
            record.setDeviceId(deviceId);
        }

        record.setTme(tme);
        record.setScanType(scanType);
        return record;
    }


    @Override
    public int countGetByWh(String warehouse) {
        return rkInfoMapper.countGetByWh(warehouse);
    }

    /**
     * 图表统计：每个库位有多少个货物
     * @param dto
     * @return
     */
    @Override
    public ChartDataVO matchWithAll(QueryDTO dto) {
        List<String> ids = dto.getIds();
        if (ids == null || ids.isEmpty()) {
            return new ChartDataVO(Collections.emptyList(),
                    Collections.emptyList(),
                    0L);
        }

        List<PcdeCntVO> rows = rkInfoMapper.selectPcdeCntFromRkInfo(ids);

        List<String> pcdeList = new ArrayList<>(rows.size());
        List<Long> fycdeCntList = new ArrayList<>(rows.size());

        for (PcdeCntVO r : rows) {
            pcdeList.add(r.getPcde());
            fycdeCntList.add(r.getCnt());
        }

        return new ChartDataVO(pcdeList, fycdeCntList, (long) rows.size());
    }

    /**
     * 还料入库
     * @param dto
     * @return
     */
    @Override
    public int refundMaterial(RefundRequestDTO dto) {
        Long originalId = dto.getOriginalId();
        String newPcode = dto.getPcode();

        // 1. 查原出库记录
        RkInfo original = rkInfoMapper.selectRkInfoById(originalId);
        if (original == null || "1".equals(original.getIsDelete())) {
            throw new ServiceException("原出库记录不存在或已删除");
        }

        // 2. 构造新的入库记录
        RkInfo newEntry = new RkInfo();
        BeanUtils.copyProperties(original, newEntry);
        newEntry.setId(null);
        newEntry.setIsChuku("0");
        newEntry.setPcode(newPcode);
        newEntry.setRkType(dto.getRkType());
        newEntry.setRkTime(DateUtils.getNowDate());
        newEntry.setBillNo(BillNoUtil.generateTodayBillNo("RK"));
        newEntry.setCreateBy(SecurityUtils.getUserId().toString());
        newEntry.setCreateTime(DateUtils.getNowDate());
        newEntry.setUpdateBy(null);
        newEntry.setUpdateTime(null);
        newEntry.setIsBorrowed("0");

        // 3. 插入新入库记录
        int rows = rkInfoMapper.insertRkInfo(newEntry);

        // ✅ 4. 更新原记录的 is_borrowed = '2'
        RkInfo update = new RkInfo();
        update.setId(originalId);
        update.setIsBorrowed("2");
        update.setReturnTime(DateUtils.getNowDate());
        update.setUpdateBy(SecurityUtils.getUsername());
        update.setUpdateTime(DateUtils.getNowDate());
        rkInfoMapper.updateById(update);

        return rows;
    }

    @Override
    public void cancelOutStockById(Long id) {
        // 构造只更新 is_chuku 字段的对象
        RkInfo update = new RkInfo();
        update.setId(id);
        update.setIsChuku("5");
        update.setUpdateBy(SecurityUtils.getUsername());
        update.setUpdateTime(DateUtils.getNowDate());

        // 执行更新
        rkInfoMapper.updateRkInfo(update);
    }

    /**
     * 根据 IDs 恢复入库或出库状态（撤销撤销操作）
     *
     * 说明：
     * - 如果 isChuku = 4（撤销入库），改为 0（已入库）；
     * - 如果 isChuku = 5（撤销出库），改为 1（已出库）；
     * - 其他状态不处理；
     *
     * @param ids 单据 ID 数组
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void revertByIds(Long[] ids) {
        Date now = new Date();
        String userId = SecurityUtils.getUserId().toString();

        for (Long id : ids) {
            RkInfo info = rkInfoMapper.selectRkInfoById(id);
            if (info == null) continue;

            String isChuku = info.getIsChuku();

            if ("4".equals(isChuku)) {
                // 撤销入库 -> 恢复为已入库
                info.setIsChuku("0");
                info.setUpdateBy(userId);
                info.setUpdateTime(now);
                rkInfoMapper.updateRkInfo(info);
            } else if ("5".equals(isChuku)) {
                // 撤销出库 -> 恢复为已出库
                info.setIsChuku("1");
                info.setUpdateBy(userId);
                info.setUpdateTime(now);
                rkInfoMapper.updateRkInfo(info);
            }
        }
    }

    @Override
    public List<RkInfo> listRkInfoByPcode(String pcode) {
        return rkInfoMapper.listRkInfoByPcode(pcode);
    }

    /**
     * 获取需要配送的出库列表
     * @param query
     * @return
     */
    @Override
    public List<RkInfo> selectDeliveryAll(RkInfo query) {
        return rkInfoMapper.selectDeliveryAll(query);
    }
}
