package com.sundot.airport.ledger.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.sundot.airport.common.core.domain.entity.SysDept;
import com.sundot.airport.common.core.domain.entity.SysUser;
import com.sundot.airport.system.domain.SysPost;
import com.sundot.airport.system.domain.BasePosition;
import com.sundot.airport.ledger.domain.*;
import com.sundot.airport.ledger.domain.LedgerDailyTraining;
import com.sundot.airport.ledger.domain.LedgerLeaderDuty;
import com.sundot.airport.ledger.domain.LedgerHealthSoldier;
import com.sundot.airport.ledger.domain.LedgerDormFireSafety;
import com.sundot.airport.ledger.domain.LedgerTrainingIssue;
import com.sundot.airport.ledger.service.*;
import com.sundot.airport.ledger.service.ILedgerDailyTrainingService;
import com.sundot.airport.ledger.service.ILedgerLeaderDutyService;
import com.sundot.airport.ledger.service.ILedgerHealthSoldierService;
import com.sundot.airport.ledger.service.ILedgerDormFireSafetyService;
import com.sundot.airport.ledger.service.ILedgerTrainingIssueService;
import com.sundot.airport.system.mapper.SysDeptMapper;
import com.sundot.airport.system.mapper.SysUserMapper;
import com.sundot.airport.system.mapper.SysPostMapper;
import com.sundot.airport.system.mapper.BasePositionMapper;
import org.apache.poi.ss.usermodel.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 台账合并Excel一键全量导入实现
 * 列索引映射完全复刻 excel_to_sql.py 逻辑
 */
@Service
public class LedgerCombinedImportServiceImpl implements ILedgerCombinedImportService {

    @Autowired private ILedgerSupervisionProblemService supervisionProblemService;
    @Autowired private ILedgerPatrolInspectionService patrolInspectionService;
    @Autowired private ILedgerRealtimeInterceptionService realtimeInterceptionService;
    @Autowired private ILedgerServicePatrolService servicePatrolService;
    @Autowired private ILedgerComplaintService complaintService;
    @Autowired private ILedgerSecurityTestService securityTestService;
    @Autowired private ILedgerChannelPassRateService channelPassRateService;
    @Autowired private ILedgerUnsafeEventService unsafeEventService;
    @Autowired private ILedgerSeizureStatsService seizureStatsService;
    @Autowired private ILedgerTerminalBonusService terminalBonusService;
    @Autowired private ILedgerExamScoreService examScoreService;
    @Autowired private ILedgerRewardApprovalService rewardApprovalService;
    @Autowired private ILedgerRewardPenaltyService rewardPenaltyService;
    @Autowired private ILedgerLeaveSpecialService leaveSpecialService;
    @Autowired private ILedgerBannerLetterService bannerLetterService;
    @Autowired private ILedgerDailyTrainingService dailyTrainingService;
    @Autowired private ILedgerLeaderDutyService leaderDutyService;
    @Autowired private ILedgerHealthSoldierService healthSoldierService;
    @Autowired private ILedgerDormFireSafetyService dormFireSafetyService;
    @Autowired private ILedgerTrainingIssueService trainingIssueService;
    @Autowired private IScoreEventService scoreEventService;
    @Autowired private SysUserMapper sysUserMapper;
    @Autowired private SysDeptMapper sysDeptMapper;
    @Autowired private SysPostMapper sysPostMapper;
    @Autowired private BasePositionMapper basePositionMapper;

    // 导入缓存（在导入开始时构建，导入结束后释放）
    private ImportCache importCache;

    // ── Sheet 名称 → 处理方法路由表 ──────────────────────────────────
    private static final Map<String, String> SHEET_ROUTE = new LinkedHashMap<>();
    static {
        SHEET_ROUTE.put("部门监察问题记录表",        "supervisionProblem");
        SHEET_ROUTE.put("队室三级质控巡查记录表",     "patrolInspection");
        SHEET_ROUTE.put("部门实时质控拦截情况记录表", "realtimeInterception");
        SHEET_ROUTE.put("服务巡查",                  "servicePatrol");
        SHEET_ROUTE.put("投诉情况",                  "complaint");
        SHEET_ROUTE.put("安保测试记录表（部门）",      "securityTest");
        SHEET_ROUTE.put("通道过检率",                "channelPassRate");
        SHEET_ROUTE.put("不安全事件",                "unsafeEvent");
        SHEET_ROUTE.put("2026查获违规品统计",        "seizureStats");
        SHEET_ROUTE.put("航站楼加分",               "terminalBonus");
        SHEET_ROUTE.put("成绩收集",                 "examScore");
        SHEET_ROUTE.put("小额奖励审批单-安检-质控",   "rewardApproval");
        SHEET_ROUTE.put("部门奖惩记录表",            "rewardPenalty");
        SHEET_ROUTE.put("请、休假记录表（特殊）",     "leaveSpecial");
        SHEET_ROUTE.put("锦旗及感谢信",              "bannerLetter");
        SHEET_ROUTE.put("日常培训记录",              "dailyTraining");
        SHEET_ROUTE.put("组长履职情况记录表",         "leaderDuty");
        SHEET_ROUTE.put("健康锐兵（2026_3）",        "healthSoldier");
        SHEET_ROUTE.put("宿舍消防安全专项自查表（部门）", "dormFireSafety");
        SHEET_ROUTE.put("培训台账问题通报",           "trainingIssue");
    }

    private final DataFormatter formatter = new DataFormatter();

    // ════════════════════════════════════════════════════════════════
    //  入口
    // ════════════════════════════════════════════════════════════════

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, String> importAll(MultipartFile file, String batchNo, String username) throws Exception {
        Map<String, String> result = new LinkedHashMap<>();
        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
            // 构建导入缓存
            buildImportCache();
            
            for (Map.Entry<String, String> entry : SHEET_ROUTE.entrySet()) {
                String sheetName = entry.getKey();
                String handler   = entry.getValue();
                Sheet sheet = wb.getSheet(sheetName);
                if (sheet == null) {
                    result.put(sheetName, "未找到该Sheet，跳过");
                    continue;
                }
                try {
                    int count = dispatch(sheet, handler, batchNo, username);
                    result.put(sheetName, "导入 " + count + " 条");
                } catch (Exception e) {
                    result.put(sheetName, "错误: " + e.getMessage());
                }
            }
        } finally {
            // 导入完成后释放缓存
            clearImportCache();
        }
        return result;
    }

    private int dispatch(Sheet sheet, String handler, String batchNo, String username) {
        switch (handler) {
            case "supervisionProblem":    return doSupervisionProblem(sheet, batchNo, username);
            case "patrolInspection":      return doPatrolInspection(sheet, batchNo, username);
            case "realtimeInterception":  return doRealtimeInterception(sheet, batchNo, username);
            case "servicePatrol":         return doServicePatrol(sheet, batchNo, username);
            case "complaint":             return doComplaint(sheet, batchNo, username);
            case "securityTest":          return doSecurityTest(sheet, batchNo, username);
            case "channelPassRate":       return doChannelPassRate(sheet, batchNo, username);
            case "unsafeEvent":           return doUnsafeEvent(sheet, batchNo, username);
            case "seizureStats":          return doSeizureStats(sheet, batchNo, username);
            case "terminalBonus":         return doTerminalBonus(sheet, batchNo, username);
            case "examScore":             return doExamScore(sheet, batchNo, username);
            case "rewardApproval":        return doRewardApproval(sheet, batchNo, username);
            case "rewardPenalty":         return doRewardPenalty(sheet, batchNo, username);
            case "leaveSpecial":          return doLeaveSpecial(sheet, batchNo, username);
            case "bannerLetter":          return doBannerLetter(sheet, batchNo, username);
            case "dailyTraining":         return doDailyTraining(sheet, batchNo, username);
            case "leaderDuty":            return doLeaderDuty(sheet, batchNo, username);
            case "healthSoldier":         return doHealthSoldier(sheet, batchNo, username);
            case "dormFireSafety":        return doDormFireSafety(sheet, batchNo, username);
            case "trainingIssue":         return doTrainingIssue(sheet, batchNo, username);
            default: return 0;
        }
    }

    // ════════════════════════════════════════════════════════════════
    //  各 Sheet 处理方法
    // ════════════════════════════════════════════════════════════════

    /** 1. 部门监察问题记录表 → ledger_supervision_problem
     * 导入顺序：时间(0) 区域(1) 工作点(2) 岗位(3) 责任人(4) 问题描述(5)
     *          问题类型(6) 整改措施(7) 依据(8) 问题层级(9) 班组(10)
     *          队室质控员(11) 质控推送队室负责人(12) 附件(13) 整改情况(14) 佐证材料(15)
     *          发送至相关人员(16) 本月内发生问题次数(17) 上月质控问题超过三次人员(18)
     *          检查人员(19) 分管质控经理(20) 录入时间(21) 部门培训教员(22) 队室内勤(23) 队室负责人(24)
     */
    private int doSupervisionProblem(Sheet sheet, String batchNo, String username) {
        List<LedgerSupervisionProblem> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerSupervisionProblem o = new LedgerSupervisionProblem();
            
            // 按导入顺序映射字段
            o.setRecordDate(date(c, 0));        // 时间
            o.setLocation(str(c, 1));           // 区域
            o.setChannelNo(str(c, 2));          // 工作点
            o.setPosition(str(c, 3));           // 岗位
            String inspectedName = str(c, 4);   // 责任人
            o.setInspectedName(inspectedName);
            o.setProblemDesc(str(c, 5));        // 问题描述
            o.setProblemType(str(c, 6));        // 问题类型
            o.setResultHandling(str(c, 7));     // 整改措施
            o.setBasis(str(c, 8));              // 依据
            o.setProblemLevel(str(c, 9));       // 问题层级
            o.setTeamName(str(c, 10));          // 班组
            o.setQualityOfficer(str(c, 11));    // 队室质控员
            o.setQualityPushLeader(str(c, 12)); // 质控推送队室负责人
            o.setEvidenceFile(str(c, 13));      // 附件
            o.setRectificationStatus(str(c, 14)); // 整改情况
            o.setEvidenceMaterial(str(c, 15));  // 佐证材料
            o.setSendToRelevant(str(c, 16));    // 发送至相关人员
            o.setMonthlyProblemCount(integer(c, 17)); // 本月内发生问题次数
            o.setLastMonthExceedThree(str(c, 18)); // 上月质控问题超过三次人员
            o.setCheckPersonnel(str(c, 19));    // 检查人员
            o.setManageQcManager(str(c, 20));   // 分管质控经理
            o.setInputTime(date(c, 21));        // 录入时间
            o.setDeptTrainingInstructor(str(c, 22)); // 部门培训教员
            o.setTeamInternalDuty(str(c, 23));  // 队室内勤
            o.setTeamLeader(str(c, 24));        // 队室负责人
            
            // 根据责任人名称查找组织ID和名称
            if (inspectedName != null && !inspectedName.trim().isEmpty()) {
                Map<String, Object> orgInfo = resolveOrgInfoByNameWithCache(inspectedName);
                if (!orgInfo.isEmpty()) {
                    o.setInspectedId((Long) orgInfo.get("userId"));
                    o.setDeptId((Long) orgInfo.get("deptId"));
                    o.setDeptName((String) orgInfo.get("deptName"));
                    o.setTeamId((Long) orgInfo.get("teamId"));
                    o.setTeamName((String) orgInfo.get("teamName"));
                    o.setGroupId((Long) orgInfo.get("groupId"));
                    o.setGroupName((String) orgInfo.get("groupName"));
                }
            }
            
            // 根据岗位和区域查找对应ID
            if (importCache != null) {
                // 岗位ID
                String positionName = o.getPosition();
                if (positionName != null && !positionName.trim().isEmpty()) {
                    Long positionId = importCache.getPositionIdByName(positionName.trim());
                    if (positionId != null) {
                        o.setPositionId(positionId);
                    }
                }
                
                // 区域ID
                String areaName = o.getLocation();
                if (areaName != null && !areaName.trim().isEmpty()) {
                    Long areaId = importCache.getAreaIdByName(areaName.trim());
                    if (areaId != null) {
                        o.setAreaId(areaId);
                    }
                }
            }
            
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        supervisionProblemService.batchInsert(list);
        return list.size();
    }

    /** 2. 队室三级质控巡查记录表 → ledger_patrol_inspection
     * 导入顺序：巡查日期(0) 巡查区域(1) 巡查工作点(2) 巡查时间段(3) 巡查岗位(4)
     *          被检查人员(5) 有无问题(6) 检查情况描述(7) 类型(8) 整改措施(9)
     *          附件(10) 整改情况(11) 佐证材料(12) 班组(13) 责任组长(14)
     *          队室质控员(15) 队室负责人(16) 填报人(17) 队室内勤(18) 填报时间(19)
     *          发送至相关人员(20)
     */
    private int doPatrolInspection(Sheet sheet, String batchNo, String username) {
        List<LedgerPatrolInspection> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerPatrolInspection o = new LedgerPatrolInspection();
            
            // 按照新的导入顺序映射字段
            o.setRecordDate(date(c, 0));           // 巡查日期
            o.setLocation(str(c, 1));              // 巡查区域
            o.setChannelNo(str(c, 2));             // 巡查工作点
            o.setPatrolTimePeriod(str(c, 3));      // 巡查时间段
            String positionName = str(c, 4);       // 巡查岗位
            o.setPatrolItem(positionName);
            String inspectedName = str(c, 5);      // 被检查人员
            o.setInspectedName(inspectedName);
            o.setHasProblem(str(c, 6));            // 有无问题
            o.setProblemDesc(str(c, 7));           // 检查情况描述
            o.setPatrolType(str(c, 8));            // 类型
            o.setResultHandling(str(c, 9));        // 整改措施
            o.setEvidenceFile(str(c, 10));         // 附件
            o.setRectificationStatus(str(c, 11));  // 整改情况
            o.setEvidenceMaterial(str(c, 12));     // 佐证材料
            o.setTeamName(str(c, 13));             // 班组
            o.setResponsibleLeader(str(c, 14));    // 责任组长
            o.setQualityOfficer(str(c, 15));       // 队室质控员
            o.setTeamLeader(str(c, 16));           // 队室负责人
            o.setReporter(str(c, 17));             // 填报人
            o.setTeamInternalDuty(str(c, 18));     // 队室内勤
            o.setReportTime(date(c, 19));          // 填报时间
            o.setSendToRelevant(str(c, 20));       // 发送至相关人员
            
            // 根据被查人名称查找组织ID和名称
            if (inspectedName != null && !inspectedName.trim().isEmpty()) {
                Map<String, Object> orgInfo = resolveOrgInfoByNameWithCache(inspectedName);
                if (!orgInfo.isEmpty()) {
                    o.setInspectedId((Long) orgInfo.get("userId"));
                    o.setDeptId((Long) orgInfo.get("deptId"));
                    o.setDeptName((String) orgInfo.get("deptName"));
                    o.setTeamId((Long) orgInfo.get("teamId"));
                    o.setTeamName((String) orgInfo.get("teamName"));
                    o.setGroupId((Long) orgInfo.get("groupId"));
                    o.setGroupName((String) orgInfo.get("groupName"));
                }
            }
            
            // 根据岗位和区域查找对应ID
            if (importCache != null) {
                // 岗位ID
                if (positionName != null && !positionName.trim().isEmpty()) {
                    Long positionId = importCache.getPositionIdByName(positionName.trim());
                    if (positionId != null) {
                        o.setPositionId(positionId);
                    }
                }
                
                // 区域ID
                String areaName = o.getLocation();
                if (areaName != null && !areaName.trim().isEmpty()) {
                    Long areaId = importCache.getAreaIdByName(areaName.trim());
                    if (areaId != null) {
                        o.setAreaId(areaId);
                    }
                }
            }
            
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        patrolInspectionService.batchInsert(list);
        return list.size();
    }

    /** 3. 部门实时质控拦截情况记录表 → ledger_realtime_interception
     * 导入顺序：时间(0) 区域(1) 工作点(2) 岗位(3) 责任人(4) 实时质控拦截物品(5) 个数(6)
     *          问题类型(7) 整改措施(8) 问题层级(9) 班组(10) 附件(11) 问题类别(12)
     *          个人复盘(13) 队室复盘(14) 队室质控员(15) 质控推送队室负责人(16)
     *          开机年限(17) 责任人开机年龄(18) 发送至相关人员(19) 本月内发生问题次数(20)
     *          上月质控问题发生次数(21) 实时图像漏检难易度(22) 检查人员(23) 部门培训教员(24)
     *          分管质控经理(25) 队室内勤(26) 备注(27) 队室负责人(28)
     */
    private int doRealtimeInterception(Sheet sheet, String batchNo, String username) {
        List<LedgerRealtimeInterception> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerRealtimeInterception o = new LedgerRealtimeInterception();
            
            // 按照新的导入顺序映射字段
            o.setRecordDate(date(c, 0));        // 时间
            o.setLocation(str(c, 1));           // 区域
            o.setPosition(str(c, 3));           // 岗位
            o.setChannelNo(str(c, 2));          // 工作点
            String inspectorName = str(c, 4);   // 责任人
            o.setInspectorName(inspectorName);
            o.setItemName(str(c, 5));           // 实时质控拦截物品
            o.setItemQuantity(integer(c, 6));   // 个数
            o.setProblemType(str(c, 7));        // 问题类型
            o.setRectificationMeasures(str(c, 8)); // 整改措施
            o.setProblemLevel(str(c, 9));       // 问题层级
            o.setTeamName(str(c, 10));          // 班组
            o.setAttachment(str(c, 11));        // 附件
            o.setProblemCategory(str(c, 12));   // 问题类别
            o.setPersonalReview(str(c, 13));    // 个人复盘
            o.setTeamReview(str(c, 14));        // 队室复盘
            o.setQualityOfficer(str(c, 15));    // 队室质控员
            o.setQualityPushLeader(str(c, 16)); // 质控推送队室负责人
            o.setOperationYears(str(c, 17));    // 开机年限
            o.setResponsibleOperationAge(str(c, 18)); // 责任人开机年龄
            o.setSendToRelevant(str(c, 19));    // 发送至相关人员
            o.setMonthlyProblemCount(integer(c, 20)); // 本月内发生问题次数
            o.setLastMonthProblemCount(integer(c, 21)); // 上月质控问题发生次数
            o.setImageMissDifficulty(str(c, 22)); // 实时图像漏检难易度
            o.setCheckPersonnel(str(c, 23));    // 检查人员
            o.setDeptTrainingInstructor(str(c, 24)); // 部门培训教员
            o.setManageQcManager(str(c, 25));   // 分管质控经理
            o.setTeamInternalDuty(str(c, 26));  // 队室内勤
            o.setRemark(str(c, 27));            // 备注
            o.setTeamLeader(str(c, 28));        // 队室负责人
            
            // 根据责任人名称查找组织ID和名称
            if (inspectorName != null && !inspectorName.trim().isEmpty()) {
                Map<String, Object> orgInfo = resolveOrgInfoByNameWithCache(inspectorName);
                if (!orgInfo.isEmpty()) {
                    o.setInspectorId((Long) orgInfo.get("userId"));
                    o.setDeptId((Long) orgInfo.get("deptId"));
                    o.setDeptName((String) orgInfo.get("deptName"));
                    o.setTeamId((Long) orgInfo.get("teamId"));
                    o.setTeamName((String) orgInfo.get("teamName"));
                    o.setGroupId((Long) orgInfo.get("groupId"));
                    o.setGroupName((String) orgInfo.get("groupName"));
                }
            }
            
            // 根据岗位和区域查找对应ID
            if (importCache != null) {
                String positionName = str(c, 3);  // 岗位
                if (positionName != null && !positionName.trim().isEmpty()) {
                    Long positionId = importCache.getPositionIdByName(positionName.trim());
                    if (positionId != null) {
                        o.setPositionId(positionId);
                    }
                }
                
                String areaName = str(c, 1);  // 区域
                if (areaName != null && !areaName.trim().isEmpty()) {
                    Long areaId = importCache.getAreaIdByName(areaName.trim());
                    if (areaId != null) {
                        o.setAreaId(areaId);
                    }
                }
            }
            
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        realtimeInterceptionService.batchInsert(list);
        return list.size();
    }

    /** 4. 服务巡查 → ledger_service_patrol
     * 导入顺序：时间(0) 区域(1) 工作点(2) 岗位(3) 责任人(4) 问题描述(5)
     *          问题类型(6) 整改措施(7) 班组(8) 问题层级(9) 队室服务联络人(10)
     *          服务推送队室负责人(11) 附件(12) 整改情况(13) 佐证材料(14)
     *          发送至相关人员(15) 本月内发生问题次数(16) 检查人员(17)
     *          分管服务经理(18) 队室内勤(19) 队室负责人(20)
     */
    private int doServicePatrol(Sheet sheet, String batchNo, String username) {
        List<LedgerServicePatrol> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerServicePatrol o = new LedgerServicePatrol();
            
            // 按照新的导入顺序映射字段
            o.setRecordDate(date(c, 0));              // 时间
            o.setLocation(str(c, 1));                 // 区域
            o.setChannelNo(str(c, 2));                // 工作点
            String positionName = str(c, 3);          // 岗位
            o.setPosition(positionName);
            String inspectedName = str(c, 4);         // 责任人
            o.setInspectedName(inspectedName);
            o.setProblemDesc(str(c, 5));              // 问题描述
            o.setServiceType(str(c, 6));              // 问题类型
            o.setResultHandling(str(c, 7));           // 整改措施
            o.setTeamName(str(c, 8));                 // 班组
            o.setProblemLevel(str(c, 9));             // 问题层级
            o.setServiceContactPerson(str(c, 10));    // 队室服务联络人
            o.setServicePushLeader(str(c, 11));       // 服务推送队室负责人
            o.setEvidenceFile(str(c, 12));            // 附件
            o.setRectificationStatus(str(c, 13));     // 整改情况
            o.setEvidenceMaterial(str(c, 14));        // 佐证材料
            o.setSendToRelevant(str(c, 15));          // 发送至相关人员
            o.setMonthlyProblemCount(integer(c, 16)); // 本月内发生问题次数
            o.setCheckPersonnel(str(c, 17));          // 检查人员
            o.setManageServiceManager(str(c, 18));    // 分管服务经理
            o.setTeamInternalDuty(str(c, 19));        // 队室内勤
            o.setTeamLeader(str(c, 20));              // 队室负责人
            
            // 根据责任人名称查找组织ID和名称
            if (inspectedName != null && !inspectedName.trim().isEmpty()) {
                Map<String, Object> orgInfo = resolveOrgInfoByNameWithCache(inspectedName);
                if (!orgInfo.isEmpty()) {
                    o.setInspectedId((Long) orgInfo.get("userId"));
                    o.setDeptId((Long) orgInfo.get("deptId"));
                    o.setDeptName((String) orgInfo.get("deptName"));
                    o.setTeamId((Long) orgInfo.get("teamId"));
                    o.setTeamName((String) orgInfo.get("teamName"));
                    o.setGroupId((Long) orgInfo.get("groupId"));
                    o.setGroupName((String) orgInfo.get("groupName"));
                }
            }
            
            // 根据岗位和区域查找对应ID
            if (importCache != null) {
                // 岗位ID
                if (positionName != null && !positionName.trim().isEmpty()) {
                    Long positionId = importCache.getPositionIdByName(positionName.trim());
                    if (positionId != null) {
                        o.setPositionId(positionId);
                    }
                }
                
                // 区域ID
                String areaName = o.getLocation();
                if (areaName != null && !areaName.trim().isEmpty()) {
                    Long areaId = importCache.getAreaIdByName(areaName.trim());
                    if (areaId != null) {
                        o.setAreaId(areaId);
                    }
                }
            }
            
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        servicePatrolService.batchInsert(list);
        return list.size();
    }

    /** 5. 投诉情况 → ledger_complaint
     * R2: 时间(0) 航班号(1) 旅客姓名(2) 班组(3) 责任人(4)
     *     投诉情况(5) 旅客诉求(6) 类别(7) 渠道来源(8) 是否有责(9)
     *     处理进度(10) 责任队长(11) 队室内勤(12)
     */
    private int doComplaint(Sheet sheet, String batchNo, String username) {
        List<LedgerComplaint> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerComplaint o = new LedgerComplaint();
            
            // 按导入顺序映射字段
            o.setRecordDate(date(c, 0));           // 时间
            o.setFlightNo(str(c, 1));              // 航班号
            o.setPassengerName(str(c, 2));         // 旅客姓名
            o.setTeamName(str(c, 3));              // 班组
            String responsibleName = str(c, 4);    // 责任人
            o.setResponsibleName(responsibleName);
            o.setComplaintDesc(str(c, 5));         // 投诉情况
            o.setPassengerDemand(str(c, 6));       // 旅客诉求
            o.setComplaintType(str(c, 7));         // 类别
            o.setChannelSource(str(c, 8));         // 渠道来源
            o.setIsResponsible(str(c, 9));         // 是否有责
            o.setResultHandling(str(c, 10));       // 处理进度
            o.setResponsibleCaptain(str(c, 11));   // 责任队长
            o.setTeamInternalDuty(str(c, 12));     // 队室内勤
            
            // 根据责任人名称查找组织信息和ID
            if (responsibleName != null && !responsibleName.trim().isEmpty()) {
                Map<String, Object> orgInfo = resolveOrgInfoByNameWithCache(responsibleName);
                if (!orgInfo.isEmpty()) {
                    o.setResponsibleId((Long) orgInfo.get("userId"));
                    o.setDeptId((Long) orgInfo.get("deptId"));
                    o.setDeptName((String) orgInfo.get("deptName"));
                    o.setTeamId((Long) orgInfo.get("teamId"));
                    o.setTeamName((String) orgInfo.get("teamName"));
                    o.setGroupId((Long) orgInfo.get("groupId"));
                    o.setGroupName((String) orgInfo.get("groupName"));
                }
            }
            
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        complaintService.batchInsert(list);
        return list.size();
    }

    /** 6. 安保测试记录表（部门）→ ledger_security_test
     * 导入顺序：开展时间(0) 测试区域(1) 测试通道(2) 测试项目(3) 被测试人员(4) 被测试岗位(5)
     *          测试物品(6) 开展项目图片或视频(7) 是否通过(8) 层级(9) 整改措施(10)
     *          复查地点及时间(11) 整改材料(12) 班组(13) 推送队室质控员(14) 推送质控队长(15)
     *          队室内勤(16) 点击推送至相关人员(17) 队室负责人(18)
     */
    private int doSecurityTest(Sheet sheet, String batchNo, String username) {
        List<LedgerSecurityTest> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerSecurityTest o = new LedgerSecurityTest();
            
            // 按照新的导入顺序映射字段
            o.setRecordDate(date(c, 0));  // 开展时间
            o.setRegion(str(c, 1));       // 测试区域
            o.setChannel(str(c, 2));      // 测试通道
            o.setTestItem(str(c, 3));     // 测试项目
            
            // 被测试人员
            String testedName = str(c, 4);
            o.setTestedName(testedName);
            
            // 被测试岗位
            o.setTestType(str(c, 5));
            
            // 测试物品→problemDesc
            o.setProblemDesc(str(c, 6));
            
            // 开展项目图片或视频→evidenceFile
            o.setEvidenceFile(str(c, 7));
            
            // 是否通过→testResult
            o.setTestResult(str(c, 8));
            
            // 层级→scoreIndicator
            o.setScoreIndicator(str(c, 9));
            
            // 整改措施→resultHandling
            o.setResultHandling(str(c, 10));
            
            // 复查地点及时间
            o.setReviewLocationTime(str(c, 11));
            
            // 整改材料
            o.setReviewMaterial(str(c, 12));
            
            // 班组→teamName
            String teamName = str(c, 13);
            o.setTeamName(teamName);
            
            // 推送队室质控员
            o.setPushTeamQc(str(c, 14));
            
            // 推送质控队长
            o.setPushQcCaptain(str(c, 15));
            
            // 队室内勤
            o.setTeamInternalDuty(str(c, 16));
            
            // 点击推送至相关人员
            o.setPushToRelevant(str(c, 17));
            
            // 队室负责人
            o.setTeamLeader(str(c, 18));
            
            // 使用缓存查找组织信息（根据被测人名称）
            if (testedName != null && !testedName.trim().isEmpty()) {
                Map<String, Object> orgInfo = resolveOrgInfoByNameWithCache(testedName);
                if (!orgInfo.isEmpty()) {
                    o.setTestedId((Long) orgInfo.get("userId"));
                    o.setDeptId((Long) orgInfo.get("deptId"));
                    o.setDeptName((String) orgInfo.get("deptName"));
                    o.setTeamId((Long) orgInfo.get("teamId"));
                    o.setTeamName((String) orgInfo.get("teamName"));
                    o.setGroupId((Long) orgInfo.get("groupId"));
                    o.setGroupName((String) orgInfo.get("groupName"));
                }
            }
            
            // 根据岗位名称和区域名称查找ID
            if (importCache != null) {
                String positionName = str(c, 5);  // 被测试岗位
                if (positionName != null && !positionName.trim().isEmpty()) {
                    Long positionId = importCache.getPositionIdByName(positionName.trim());
                    if (positionId != null) {
                        o.setPositionId(positionId);
                    }
                }
                
                String areaName = str(c, 1);  // 测试区域
                if (areaName != null && !areaName.trim().isEmpty()) {
                    Long areaId = importCache.getAreaIdByName(areaName.trim());
                    if (areaId != null) {
                        o.setAreaId(areaId);
                    }
                }
            }
            
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        securityTestService.batchInsert(list);
        return list.size();
    }

    /** 7. 通道过检率（pivot表）→ ledger_channel_pass_rate
     * R1: 大标题行
     * R2: 组长(0) 班组(1) 队室内勤(2) 2026年1月平均过检率(3) 2026年2月...(4) ...
     * 每行展开为多条（每月一条）
     */
    private int doChannelPassRate(Sheet sheet, String batchNo, String username) {
        // 读第2行表头（POI 0-indexed = row index 1）
        Row headerRow = sheet.getRow(1);
        if (headerRow == null) return 0;

        // 找月份列（从列3开始）
        List<int[]> monthCols = new ArrayList<>(); // [colIndex, parsedDateMs]
        Pattern p = Pattern.compile("(\\d{4})年(\\d{1,2})月");
        for (int ci = 3; ci <= headerRow.getLastCellNum(); ci++) {
            Cell cell = headerRow.getCell(ci);
            if (cell == null) continue;
            String label = formatter.formatCellValue(cell).trim();
            Matcher m = p.matcher(label);
            if (m.find()) {
                int year  = Integer.parseInt(m.group(1));
                int month = Integer.parseInt(m.group(2));
                Calendar cal = Calendar.getInstance();
                cal.set(year, month - 1, 1, 0, 0, 0);
                cal.set(Calendar.MILLISECOND, 0);
                monthCols.add(new int[]{ci, (int)(cal.getTimeInMillis() / 1000)});
            }
        }

        List<LedgerChannelPassRate> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            String groupLeader = str(c, 0);      // 组长(0)
            String teamName = str(c, 1);         // 班组(1)
            String teamInternalDuty = str(c, 2); // 队室内勤(2)
            
            // 根据组长名称查找组织信息和ID
            Map<String, Object> orgInfo = new HashMap<>();
            if (groupLeader != null && !groupLeader.trim().isEmpty()) {
                orgInfo = resolveOrgInfoByNameWithCache(groupLeader);
            }
            
            for (int[] mc : monthCols) {
                int ci = mc[0];
                BigDecimal rate = decimal(c, ci);
                if (rate == null) continue;
                LedgerChannelPassRate o = new LedgerChannelPassRate();
                o.setGroupLeader(groupLeader);
                o.setTeamName(teamName);
                o.setTeamInternalDuty(teamInternalDuty);
                o.setPassRate(rate);
                // 用时间戳还原日期
                Calendar cal = Calendar.getInstance();
                cal.setTimeInMillis((long) mc[1] * 1000);
                o.setRecordDate(cal.getTime());
                o.setImportBatch(batchNo);
                o.setSourceType("1");
                o.setCreateBy(username);
                
                // 设置组织ID和名称
                if (!orgInfo.isEmpty()) {
                    o.setResponsibleId((Long) orgInfo.get("userId"));
                    o.setDeptId((Long) orgInfo.get("deptId"));
                    o.setDeptName((String) orgInfo.get("deptName"));
                    o.setTeamId((Long) orgInfo.get("teamId"));
                    o.setTeamName((String) orgInfo.get("teamName"));
                    o.setGroupId((Long) orgInfo.get("groupId"));
                    o.setGroupName((String) orgInfo.get("groupName"));
                }
                
                list.add(o);
            }
        }
        channelPassRateService.batchInsert(list);
        return list.size();
    }

    /** 8. 不安全事件 → ledger_unsafe_event
     * R2: 时间(0) 事件描述(1) 类别(2) 航班号(3) 责任人(4)
     *     涉及班组(5) 涉及物品(6) 岗位(7) 区域(8) 通道号(9) 图像(10)
     */
    private int doUnsafeEvent(Sheet sheet, String batchNo, String username) {
        List<LedgerUnsafeEvent> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerUnsafeEvent o = new LedgerUnsafeEvent();
            o.setRecordDate(date(c, 0));
            o.setEventDesc(str(c, 1));
            o.setEventType(str(c, 2));
            o.setFlightNo(str(c, 3));
            String responsibleName = str(c, 4);
            o.setResponsibleName(responsibleName);
            o.setTeamName(str(c, 5));
            o.setInvolvedItems(str(c, 6));
            o.setPosition(str(c, 7));
            o.setArea(str(c, 8));
            o.setChannelNo(str(c, 9));
            o.setImage(str(c, 10));
            
            // 使用缓存查找组织信息和名称
            Map<String, Object> orgInfo = resolveOrgInfoByNameWithCache(responsibleName);
            if (!orgInfo.isEmpty()) {
                o.setResponsibleId((Long) orgInfo.get("userId"));
                o.setDeptId((Long) orgInfo.get("deptId"));
                o.setDeptName((String) orgInfo.get("deptName"));
                o.setTeamId((Long) orgInfo.get("teamId"));
                o.setTeamName((String) orgInfo.get("teamName"));
                o.setGroupId((Long) orgInfo.get("groupId"));
                o.setGroupName((String) orgInfo.get("groupName"));
            }
            
            // 根据岗位名称和区域名称查找ID
            if (importCache != null) {
                String positionName = str(c, 7);
                if (positionName != null && !positionName.trim().isEmpty()) {
                    Long positionId = importCache.getPositionIdByName(positionName.trim());
                    if (positionId != null) {
                        o.setPositionId(positionId);
                    }
                }
                
                String areaName = str(c, 8);
                if (areaName != null && !areaName.trim().isEmpty()) {
                    Long areaId = importCache.getAreaIdByName(areaName.trim());
                    if (areaId != null) {
                        o.setAreaId(areaId);
                    }
                }
            }
            
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        unsafeEventService.batchInsert(list);
        return list.size();
    }

    /** 9. 2026查获违规品统计 → ledger_seizure_stats
     * R2: 查获时间(0) 信息提取(1) 信息提取2(2) 部门/队室(3) 工作区域(4) 安检员(5)
     *     安检资格证书等级(6) 岗位(7) 航班号(8) 目的地(9) 进/出港(10) 违规主体(11)
     *     旅客姓名(12) 旅客性别(13) 旅客民族(14) 旅客年龄(15) 违规类别(16)
     *     违规物品种类(17) 二级分类(18) 数量(19) 事件简况(20) 查获位置(21)
     *     处理结果(22) 班组(23) 队室内勤(24) 备注(25) 查获数量(26)
     */
    private int doSeizureStats(Sheet sheet, String batchNo, String username) {
        List<LedgerSeizureStats> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerSeizureStats o = new LedgerSeizureStats();
            o.setRecordDate(date(c, 0));
            o.setTeamName(str(c, 3));
            o.setWorkArea(str(c, 4));
            String inspectorName = str(c, 5);
            o.setInspectorName(inspectorName);
            o.setQualificationLevel(str(c, 6));
            o.setPosition(str(c, 7));
            o.setFlightNo(str(c, 8));
            o.setDestination(str(c, 9));
            o.setInOutPort(str(c, 10));
            o.setViolationSubject(str(c, 11));
            o.setPassengerName(str(c, 12));
            o.setPassengerGender(str(c, 13));
            o.setPassengerEthnicity(str(c, 14));
            o.setPassengerAge(integer(c, 15));
            o.setItemCategory(str(c, 16));
            o.setItemName(str(c, 17));  // 违规物品种类
            o.setSecondaryCategory(str(c, 18));
            o.setItemQuantity(integer(c, 19));
            o.setEventBrief(str(c, 20));
            o.setConcealmentPart(str(c, 21));   // 查获位置
            o.setHandlingMethod(str(c, 22));  // 处理结果
            o.setTeamInternalDuty(str(c, 24));
            o.setRemark(str(c, 25));
            o.setSeizureQuantity(integer(c, 26));  // 查获数量
                
            // 使用缓存查找组织信息和名称
            Map<String, Object> orgInfo = resolveOrgInfoByNameWithCache(inspectorName);
            if (!orgInfo.isEmpty()) {
                o.setInspectorId((Long) orgInfo.get("userId"));
                o.setDeptId((Long) orgInfo.get("deptId"));
                o.setDeptName((String) orgInfo.get("deptName"));
                o.setTeamId((Long) orgInfo.get("teamId"));
                o.setTeamName((String) orgInfo.get("teamName"));
                o.setGroupId((Long) orgInfo.get("groupId"));
                o.setGroupName((String) orgInfo.get("groupName"));
            }
            
            // 根据岗位名称和工作区域名称查找ID
            if (importCache != null) {
                String positionName = str(c, 7);
                if (positionName != null && !positionName.trim().isEmpty()) {
                    Long positionId = importCache.getPositionIdByName(positionName.trim());
                    if (positionId != null) {
                        o.setPositionId(positionId);
                    }
                }
                
                String areaName = str(c, 4);
                if (areaName != null && !areaName.trim().isEmpty()) {
                    Long areaId = importCache.getAreaIdByName(areaName.trim());
                    if (areaId != null) {
                        o.setAreaId(areaId);
                    }
                }
            }
                
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        seizureStatsService.batchInsert(list);
        return list.size();
    }

    /** 10. 航站楼加分 → ledger_terminal_bonus
     * R2: 审核日期(0) 姓名(1) 班组(2) 加分分数(3) 队室内勤(4)
     */
    private int doTerminalBonus(Sheet sheet, String batchNo, String username) {
        List<LedgerTerminalBonus> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerTerminalBonus o = new LedgerTerminalBonus();
            o.setApproveDate(date(c, 0) != null ? new java.sql.Date(date(c, 0).getTime()) : null);
            o.setPersonName(str(c, 1));
            o.setTeamName(str(c, 2));
            o.setAddScore(decimal(c, 3) != null ? decimal(c, 3) : BigDecimal.ZERO);
            o.setRemark(str(c, 4));
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        terminalBonusService.batchInsert(list);
        return list.size();
    }

    /** 11. 成绩收集 → ledger_exam_score
     * R2: 类别(0) 期数(1) 考试人员(2) 理论成绩(3) 图像成绩(4) 班组(5) 分类(6) 备注(7)
     */
    private int doExamScore(Sheet sheet, String batchNo, String username) {
        List<LedgerExamScore> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerExamScore o = new LedgerExamScore();
            o.setExamCategory(str(c, 0));
            o.setExamPeriod(str(c, 1));
            o.setPersonName(str(c, 2));
            o.setTheoryScore(decimal(c, 3));
            o.setImageScore(decimal(c, 4));
            o.setTeamName(str(c, 5));
            o.setRemark(str(c, 7));
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        examScoreService.batchInsert(list);
        return list.size();
    }

    /** 12. 小额奖励审批单-安检-质控 → ledger_reward_approval
     * 特殊：只有1行表头（headerRow=1）
     * R1: 员工编码(0) 姓名(1) 查获时间(2) 奖励类别(3) 查获物品(4)
     *     类别(5) 奖励事由(6) 人员类别(7) 主要事由简述(8)
     */
    private int doRewardApproval(Sheet sheet, String batchNo, String username) {
        List<LedgerRewardApproval> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 1)) {  // 只跳1行表头
            LedgerRewardApproval o = new LedgerRewardApproval();
            String personName = str(c, 1);      // 姓名
            o.setPersonName(personName);
            o.setApproveDate(date(c, 2) != null ? new java.sql.Date(date(c, 1).getTime()) : null);  // 查获时间
            o.setRewardType(str(c, 3));         // 奖励类别
            o.setSeizedItem(str(c, 4));         // 查获物品
            o.setApprovalStatus(str(c, 5));     // 类别
            o.setRewardReason(str(c, 6));       // 奖励事由
            o.setPersonCategory(str(c, 7));     // 人员类别
            o.setRemark(str(c, 8));             // 主要事由简述
            
            // 根据姓名查找组织ID和名称
            if (personName != null && !personName.trim().isEmpty()) {
                Map<String, Object> orgInfo = resolveOrgInfoByNameWithCache(personName);
                if (!orgInfo.isEmpty()) {
                    o.setPersonId((Long) orgInfo.get("userId"));
                    o.setDeptId((Long) orgInfo.get("deptId"));
                    o.setDeptName((String) orgInfo.get("deptName"));
                    o.setTeamId((Long) orgInfo.get("teamId"));
                    o.setTeamName((String) orgInfo.get("teamName"));
                }
            }
            
            o.setApprovalStatus("待审批");
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        rewardApprovalService.batchInsert(list);
        return list.size();
    }

    /** 13. 部门奖惩记录表 → ledger_reward_penalty
     * R2: 姓名(0) 班组(1) 事由(2) 扣罚金额(3) 最后更新时间(4)
     */
    private int doRewardPenalty(Sheet sheet, String batchNo, String username) {
        List<LedgerRewardPenalty> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerRewardPenalty o = new LedgerRewardPenalty();
            o.setPersonName(str(c, 0));
            o.setTeamName(str(c, 1));
            o.setEventDesc(str(c, 2));
            BigDecimal amt = decimal(c, 3);
            o.setScoreChange(amt != null ? amt.negate() : null);
            o.setType("2");  // 扣罚
            o.setRecordDate(date(c, 4));
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        rewardPenaltyService.batchInsert(list);
        return list.size();
    }

    /** 14. 请、休假记录表（特殊）→ ledger_leave_special
     * R2: 姓名(0) 班组(1) 时间（起）(2) 时间（止）(3) 休假类别(4) 天数/时长(5)
     * 天数/时长列可能是纯数字（天）或"X小时"格式；小时值按8小时/天换算成天数
     */
    private int doLeaveSpecial(Sheet sheet, String batchNo, String username) {
        List<LedgerLeaveSpecial> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerLeaveSpecial o = new LedgerLeaveSpecial();
            o.setPersonName(str(c, 0));
            o.setTeamName(str(c, 1));
            o.setLeaveType(str(c, 4));
            o.setStartDate(date(c, 2));
            o.setEndDate(date(c, 3));
            o.setDays(hoursOrDays(c, 5));
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        leaveSpecialService.batchInsert(list);
        return list.size();
    }

    /** 15. 锦旗及感谢信 → ledger_banner_letter
     * R2: 时间(0) 姓名(1) 获得感谢信的具体内容(2) 类别(3) 图片和附件(4) 班组(5) 队室内勤(6)
     * 类别: 锦旗→'1', 其他→'2'
     */
    private int doBannerLetter(Sheet sheet, String batchNo, String username) {
        List<LedgerBannerLetter> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerBannerLetter o = new LedgerBannerLetter();
            o.setRecordDate(date(c, 0));                        // 时间
            String personName = str(c, 1);
            o.setPersonName(personName);                         // 姓名
            o.setContentDesc(str(c, 2));                        // 获得感谢信的具体内容
            String typeStr = str(c, 3);
            o.setType("锦旗".equals(typeStr) ? "1" : "2");      // 类别
            o.setEvidenceFile(str(c, 4));                        // 图片和附件
            o.setTeamName(str(c, 5));                           // 班组
            o.setTeamInternalDuty(str(c, 6));                    // 队室内勤
            
            // 根据姓名查找组织ID
            if (personName != null && !personName.trim().isEmpty()) {
                Map<String, Object> orgInfo = resolveOrgInfoByNameWithCache(personName);
                if (!orgInfo.isEmpty()) {
                    o.setPersonUserId((Long) orgInfo.get("userId"));
                    o.setDeptId((Long) orgInfo.get("deptId"));
                    o.setDeptName((String) orgInfo.get("deptName"));
                    o.setTeamId((Long) orgInfo.get("teamId"));
                    o.setTeamName((String) orgInfo.get("teamName"));
                    o.setGroupId((Long) orgInfo.get("groupId"));
                    o.setGroupName((String) orgInfo.get("groupName"));
                }
            }
            
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        bannerLetterService.batchInsert(list);
        return list.size();
    }

    /** 16. 日常培训记录 → ledger_daily_training
     * R2: 月度培训记录(0) 班组(1) 队室负责教员(2) 队室培训负责队长(3) 课时(4)
     *     项目名称(5) 培训内容(6) 培训教员(7) 培训地点(8) 参训人数(9) 是否完成(10) 备注(11)
     */
    private int doDailyTraining(Sheet sheet, String batchNo, String username) {
        List<LedgerDailyTraining> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerDailyTraining o = new LedgerDailyTraining();
            o.setRecordDate(date(c, 0));
            o.setTeamName(str(c, 1));
            o.setResponsibleTeacher(str(c, 2));
            o.setResponsibleLeader(str(c, 3));
            o.setHours(str(c, 4));
            o.setProjectName(str(c, 5));
            o.setTrainingContent(str(c, 6));
            o.setTrainer(str(c, 7));
            o.setTrainingLocation(str(c, 8));
            o.setParticipantCount(integer(c, 9));
            o.setIsCompleted(str(c, 10));
            o.setRemark(str(c, 11));
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        dailyTrainingService.batchInsert(list);
        return list.size();
    }

    /** 17. 组长履职情况记录表 → ledger_leader_duty
     * R2: 本班点评(0) 问题处置及整改措施(1) 工作提示(2) 提交人(3) 队室负责人(4) 队室质控员(5)
     */
    private int doLeaderDuty(Sheet sheet, String batchNo, String username) {
        List<LedgerLeaderDuty> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerLeaderDuty o = new LedgerLeaderDuty();
            o.setClassComment(str(c, 0));
            o.setProblemHandling(str(c, 1));
            o.setWorkTip(str(c, 2));
            o.setSubmitter(str(c, 3));
            o.setTeamLeader(str(c, 4));
            o.setQualityOfficer(str(c, 5));
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        leaderDutyService.batchInsert(list);
        return list.size();
    }

    /** 18. 健康锐兵 → ledger_health_soldier
     * R2(0~20): 慢性病(0) 提交人(1) 近期不适(2) 是否体检(3) 体检异常(4) 重大疾病(5)
     *           作息习惯(6) 心理状态(7) 工作压力(8) 佐证材料(9)
     *           ext_major_disease(10) ext_exam_abnormal(11) ext_recent_symptoms(12)
     *           ext_chronic_mild(13) ext_chronic_severe(14) 既往病史(15) ext_medical_history(16)
     *           提交时间(17) 班组(18) 队室负责人(19) 亚健康人员(20)
     */
    private int doHealthSoldier(Sheet sheet, String batchNo, String username) {
        List<LedgerHealthSoldier> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerHealthSoldier o = new LedgerHealthSoldier();
            o.setChronicDisease(str(c, 0));
            o.setSubmitter(str(c, 1));
            o.setRecentSymptoms(str(c, 2));
            o.setHasPhysicalExam(str(c, 3));
            o.setExamReportAbnormal(str(c, 4));
            o.setMajorDiseaseRecent(str(c, 5));
            o.setDailyHabits(str(c, 6));
            o.setMentalState(str(c, 7));
            o.setWorkPressure(str(c, 8));
            o.setEvidenceFile(str(c, 9));
            o.setExtMajorDisease(str(c, 10));
            o.setExtExamAbnormal(str(c, 11));
            o.setExtRecentSymptoms(str(c, 12));
            o.setExtChronicMild(str(c, 13));
            o.setExtChronicSevere(str(c, 14));
            o.setMedicalHistory(str(c, 15));
            o.setExtMedicalHistory(str(c, 16));
            o.setSubmitTime(date(c, 17));
            o.setTeamName(str(c, 18));
            o.setTeamLeader(str(c, 19));
            o.setSubHealthStatus(str(c, 20));
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        healthSoldierService.batchInsert(list);
        return list.size();
    }

    /** 19. 宿舍消防安全专项自查表 → ledger_dorm_fire_safety
     * R2: 检查日期(0) 寝室所在位置(1) 寝室号(2) 自查覆盖项(3) 风险类型排查情况(4)
     *     发现具体隐患(5) 照片(6) 整改责任人(7) 整改完成时间(8) 提交时间(9) 提交人(10)
     */
    private int doDormFireSafety(Sheet sheet, String batchNo, String username) {
        List<LedgerDormFireSafety> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerDormFireSafety o = new LedgerDormFireSafety();
            o.setCheckDate(date(c, 0));
            o.setDormLocation(str(c, 1));
            o.setDormNo(str(c, 2));
            o.setCheckItems(str(c, 3));
            o.setRiskStatus(str(c, 4));
            o.setFoundRisk(str(c, 5));
            o.setPhotos(str(c, 6));
            o.setRectifyPerson(str(c, 7));
            o.setRectifyTime(date(c, 8));
            o.setSubmitTime(date(c, 9));
            o.setSubmitter(str(c, 10));
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        dormFireSafetyService.batchInsert(list);
        return list.size();
    }

    /** 20. 培训台账问题通报 → ledger_training_issue
     * R2: 问题台账日期(0) 问题台账内容(1) 具体问题(2) 处理人(3) 班组(4) 二次复核问题(5) 是否完成整改(6)
     */
    private int doTrainingIssue(Sheet sheet, String batchNo, String username) {
        List<LedgerTrainingIssue> list = new ArrayList<>();
        for (Object[] c : dataRows(sheet, 2)) {
            LedgerTrainingIssue o = new LedgerTrainingIssue();
            o.setRecordDate(date(c, 0));
            o.setLedgerContent(str(c, 1));
            o.setSpecificProblem(str(c, 2));
            o.setHandler(str(c, 3));
            o.setTeamName(str(c, 4));
            o.setRecheckProblem(str(c, 5));
            o.setIsRectified(str(c, 6));
            o.setImportBatch(batchNo);
            o.setSourceType("1");
            o.setCreateBy(username);
            list.add(o);
        }
        trainingIssueService.batchInsert(list);
        return list.size();
    }

    // ════════════════════════════════════════════════════════════════
    //  POI 工具方法
    // ════════════════════════════════════════════════════════════════

    /**
     * 读取数据行（跳过前 headerRows 行），忽略全空行
     * 返回 Object[]，每个元素是原始 Cell 值（已转 Java 类型）
     */
    private List<Object[]> dataRows(Sheet sheet, int headerRows) {
        List<Object[]> result = new ArrayList<>();
        int lastRow = sheet.getLastRowNum();
        for (int r = headerRows; r <= lastRow; r++) {
            Row row = sheet.getRow(r);
            if (row == null) continue;
            int lastCol = row.getLastCellNum();
            if (lastCol < 0) continue;
            Object[] cells = new Object[lastCol];
            boolean allEmpty = true;
            for (int ci = 0; ci < lastCol; ci++) {
                Cell cell = row.getCell(ci);
                Object val = rawValue(cell);
                cells[ci] = val;
                if (val != null) allEmpty = false;
            }
            if (!allEmpty) result.add(cells);
        }
        return result;
    }

    /** 从 POI Cell 提取原始 Java 值 */
    private Object rawValue(Cell cell) {
        if (cell == null) return null;
        switch (cell.getCellType()) {
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) return cell.getDateCellValue();
                return cell.getNumericCellValue();
            case STRING:
                String s = cell.getStringCellValue().trim();
                return s.isEmpty() ? null : s;
            case BOOLEAN:
                return cell.getBooleanCellValue();
            case FORMULA:
                try {
                    if (DateUtil.isCellDateFormatted(cell)) return cell.getDateCellValue();
                    return cell.getNumericCellValue();
                } catch (Exception e) {
                    String fs = cell.getStringCellValue().trim();
                    return fs.isEmpty() ? null : fs;
                }
            default:
                return null;
        }
    }

    private String str(Object[] c, int idx) {
        if (idx >= c.length || c[idx] == null) return null;
        String s = c[idx].toString().trim();
        return s.isEmpty() ? null : s;
    }

    private Date date(Object[] c, int idx) {
        if (idx >= c.length || c[idx] == null) return null;
        Object v = c[idx];
        if (v instanceof Date) return (Date) v;
        // numeric from POI = Excel serial date
        if (v instanceof Double) return DateUtil.getJavaDate((Double) v);
        // try parse string
        String s = v.toString().trim();
        for (String fmt : new String[]{"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM-dd", "yyyy/MM/dd"}) {
            try {
                return new SimpleDateFormat(fmt).parse(s.substring(0, Math.min(s.length(), fmt.length())));
            } catch (Exception ignore) {}
        }
        return null;
    }

    /** 提取时间部分为 HH:mm:ss 字符串 */
    private String timeStr(Object[] c, int idx) {
        Date d = date(c, idx);
        if (d == null) return null;
        return new SimpleDateFormat("HH:mm:ss").format(d);
    }

    private BigDecimal decimal(Object[] c, int idx) {
        if (idx >= c.length || c[idx] == null) return null;
        Object v = c[idx];
        if (v instanceof Double) {
            double d = (Double) v;
            return BigDecimal.valueOf(d);
        }
        String s = v.toString().trim().replace("¥", "").replace(",", "");
        if (s.isEmpty()) return null;
        try { return new BigDecimal(s); } catch (Exception e) { return null; }
    }

    private Integer integer(Object[] c, int idx) {
        BigDecimal d = decimal(c, idx);
        return d == null ? null : d.intValue();
    }

    /**
     * 解析天数/时长列：
     *   纯数字 → 直接作为天数
     *   "X小时" → X / 8，保留4位小数（如4小时→0.5000，7小时→0.8750）
     *   "X天"   → 去掉"天"后解析
     */
    private BigDecimal hoursOrDays(Object[] c, int idx) {
        if (idx >= c.length || c[idx] == null) return null;
        Object v = c[idx];
        if (v instanceof Double) return BigDecimal.valueOf((Double) v);
        String s = v.toString().trim();
        if (s.isEmpty()) return null;
        if (s.contains("小时")) {
            String num = s.replace("小时", "").trim();
            try {
                return new BigDecimal(num).divide(BigDecimal.valueOf(8), 4, java.math.RoundingMode.HALF_UP);
            } catch (Exception e) { return null; }
        }
        if (s.contains("天")) {
            s = s.replace("天", "").trim();
        }
        try { return new BigDecimal(s); } catch (Exception e) { return null; }
    }

    // ════════════════════════════════════════════════════════════════
    //  按时间范围清理全部台账表
    // ════════════════════════════════════════════════════════════════

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Integer> clearByCreateTime(String beginTime, String endTime) {
        Map<String, Integer> result = new LinkedHashMap<>();
        result.put("部门监察问题记录",   clearOne(supervisionProblemService,    beginTime, endTime));
        result.put("队室三级质控巡查记录", clearOne(patrolInspectionService,      beginTime, endTime));
        result.put("实时质控拦截记录",    clearOne(realtimeInterceptionService,  beginTime, endTime));
        result.put("服务巡查记录",        clearOne(servicePatrolService,         beginTime, endTime));
        result.put("投诉情况",            clearOne(complaintService,             beginTime, endTime));
        result.put("安保测试记录",        clearOne(securityTestService,          beginTime, endTime));
        result.put("通道过检率",          clearOne(channelPassRateService,       beginTime, endTime));
        result.put("不安全事件",          clearOne(unsafeEventService,           beginTime, endTime));
        result.put("查获违规品统计",      clearOne(seizureStatsService,          beginTime, endTime));
        result.put("航站楼加分",          clearOne(terminalBonusService,         beginTime, endTime));
        result.put("成绩收集",            clearOne(examScoreService,             beginTime, endTime));
        result.put("小额奖励审批单",      clearOne(rewardApprovalService,        beginTime, endTime));
        result.put("部门奖惩记录",        clearOne(rewardPenaltyService,         beginTime, endTime));
        result.put("请休假记录（特殊）",  clearOne(leaveSpecialService,          beginTime, endTime));
        result.put("锦旗及感谢信",        clearOne(bannerLetterService,          beginTime, endTime));
        result.put("日常培训记录",        clearOne(dailyTrainingService,         beginTime, endTime));
        result.put("组长履职情况记录",    clearOne(leaderDutyService,            beginTime, endTime));
        result.put("健康锐兵",            clearOne(healthSoldierService,         beginTime, endTime));
        result.put("宿舍消防安全自查",    clearOne(dormFireSafetyService,        beginTime, endTime));
        result.put("培训台账问题通报",    clearOne(trainingIssueService,         beginTime, endTime));
        result.put("台账同步配分事项",    clearScoreEvents(beginTime, endTime));
        return result;
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private int clearOne(IService service, String begin, String end) {
        QueryWrapper qw = new QueryWrapper();
        qw.between("create_time", begin, end);
        int count = (int) service.count(qw);
        if (count > 0) {
            service.remove(qw);
        }
        return count;
    }

    private int clearScoreEvents(String begin, String end) {
        QueryWrapper<ScoreEvent> qw = new QueryWrapper<>();
        qw.eq("source_type", "2").between("create_time", begin, end);
        int count = (int) scoreEventService.count(qw);
        if (count > 0) {
            scoreEventService.remove(qw);
        }
        return count;
    }

    // ════════════════════════════════════════════════════════════════
    //  导入缓存类
    // ════════════════════════════════════════════════════════════════

    /**
     * 导入时的统一缓存，避免重复查询数据库
     */
    private static class ImportCache {
        private final Map<String, SysUser> userCache;      // Map<昵称, 用户>
        private final Map<Long, SysDept> deptCache;        // Map<部门ID, 部门>
        private final Map<String, Long> positionCache;     // Map<岗位名称, 岗位ID>
        private final Map<String, Long> areaCache;         // Map<区域名称, 区域ID>

        public ImportCache(Map<String, SysUser> userCache, Map<Long, SysDept> deptCache,
                          Map<String, Long> positionCache, Map<String, Long> areaCache) {
            this.userCache = userCache;
            this.deptCache = deptCache;
            this.positionCache = positionCache;
            this.areaCache = areaCache;
        }

        public SysUser getUserByNickName(String nickName) {
            return userCache.get(nickName);
        }

        public SysDept getDeptById(Long deptId) {
            return deptCache.get(deptId);
        }

        public Long getPositionIdByName(String positionName) {
            return positionCache.get(positionName);
        }

        public Long getAreaIdByName(String areaName) {
            return areaCache.get(areaName);
        }
    }

    // ════════════════════════════════════════════════════════════════
    //  辅助方法：根据人员名称补充组织信息
    // ════════════════════════════════════════════════════════════════

    /**
     * 根据人员名称查找用户，并补充组织架构ID
     * @param personName 人员名称
     * @return Map包含: userId, deptId(BRIGADE), teamId(MANAGER), groupId(TEAMS)
     */
    private Map<String, Object> resolveOrgInfoByNameWithCache(String personName) {
        Map<String, Object> result = new HashMap<>();
        if (personName == null || personName.trim().isEmpty() || importCache == null) {
            return result;
        }

        String name = personName.trim();
        
        // 1. 从缓存中查找用户
        SysUser user = importCache.getUserByNickName(name);
        if (user == null || user.getDeptId() == null) {
            return result;
        }

        result.put("userId", user.getUserId());

        // 2. 从缓存中获取用户部门信息
        SysDept userDept = importCache.getDeptById(user.getDeptId());
        if (userDept == null) {
            return result;
        }

        String deptType = userDept.getDeptType();

        // 3. 根据部门类型递归获取各级组织ID和名称
        if ("BRIGADE".equals(deptType)) {
            // 用户部门是大队
            result.put("deptId", user.getDeptId());
            result.put("deptName", userDept.getDeptName());
        } else if ("MANAGER".equals(deptType)) {
            // 用户部门是队室/班组，向上找大队
            result.put("teamId", user.getDeptId());
            result.put("teamName", userDept.getDeptName());
            if (userDept.getParentId() != null && userDept.getParentId() != 0) {
                result.put("deptId", userDept.getParentId());
                // 从缓存中获取父级部门（大队）
                SysDept parentDept = importCache.getDeptById(userDept.getParentId());
                if (parentDept != null) {
                    result.put("deptName", parentDept.getDeptName());
                }
            }
        } else if ("TEAMS".equals(deptType)) {
            // 用户部门是小组，向上找队室和大队
            result.put("groupId", user.getDeptId());
            result.put("groupName", userDept.getDeptName());
            if (userDept.getParentId() != null && userDept.getParentId() != 0) {
                result.put("teamId", userDept.getParentId());
                // 从缓存中获取父级部门（队室）
                SysDept parentDept = importCache.getDeptById(userDept.getParentId());
                if (parentDept != null) {
                    result.put("teamName", parentDept.getDeptName());
                    if (parentDept.getParentId() != null && parentDept.getParentId() != 0) {
                        result.put("deptId", parentDept.getParentId());
                        // 从缓存中获取祖父级部门（大队）
                        SysDept grandParentDept = importCache.getDeptById(parentDept.getParentId());
                        if (grandParentDept != null) {
                            result.put("deptName", grandParentDept.getDeptName());
                        }
                    }
                }
            }
        }

        return result;
    }

    /**
     * 构建导入缓存
     */
    private void buildImportCache() {
        // 构建用户缓存：Map<昵称, SysUser>
        Map<String, SysUser> userCache = new HashMap<>();
        List<SysUser> allUsers = sysUserMapper.selectUserList(new SysUser());
        if (allUsers != null) {
            for (SysUser user : allUsers) {
                if (user.getNickName() != null && !user.getNickName().trim().isEmpty()) {
                    userCache.put(user.getNickName().trim(), user);
                }
            }
        }

        // 构建部门缓存：Map<部门ID, SysDept>
        Map<Long, SysDept> deptCache = new HashMap<>();
        SysDept queryDept = new SysDept();
        List<SysDept> allDepts = sysDeptMapper.selectDeptList(queryDept);
        if (allDepts != null) {
            for (SysDept dept : allDepts) {
                deptCache.put(dept.getDeptId(), dept);
            }
        }

        // 构建岗位缓存：Map<岗位名称, 岗位ID>
        Map<String, Long> positionCache = new HashMap<>();
        List<SysPost> allPosts = sysPostMapper.selectPostAll();
        if (allPosts != null) {
            for (SysPost post : allPosts) {
                if (post.getPostName() != null && !post.getPostName().trim().isEmpty()) {
                    positionCache.put(post.getPostName().trim(), post.getPostId());
                }
            }
        }

        // 构建区域缓存：Map<区域名称, 区域ID>
        // 从base_position表查询position_type='REGIONAL'的区域
        Map<String, Long> areaCache = new HashMap<>();
        BasePosition queryPosition = new BasePosition();
        queryPosition.setPositionType("REGIONAL");
        List<BasePosition> allAreas = basePositionMapper.selectBasePositionList(queryPosition);
        if (allAreas != null) {
            for (BasePosition area : allAreas) {
                if (area.getName() != null && !area.getName().trim().isEmpty()) {
                    areaCache.put(area.getName().trim(), area.getId());
                }
            }
        }

        // 创建统一缓存对象
        this.importCache = new ImportCache(userCache, deptCache, positionCache, areaCache);
    }

    /**
     * 释放导入缓存
     */
    private void clearImportCache() {
        this.importCache = null;
    }
}