package com.kingdee.eas.custom.perfweb.service;

import com.kingdee.bos.BOSException;
import com.kingdee.bos.Context;
import com.kingdee.bos.bsf.service.app.IHRMsfService;
import com.kingdee.bos.metadata.entity.EntityViewInfo;
import com.kingdee.bos.metadata.entity.FilterInfo;
import com.kingdee.bos.metadata.entity.FilterItemInfo;
import com.kingdee.bos.metadata.entity.SelectorItemCollection;
import com.kingdee.bos.metadata.entity.SelectorItemInfo;
import com.kingdee.bos.metadata.query.util.CompareType;
import com.kingdee.eas.basedata.org.AdminOrgUnitInfo;
import com.kingdee.eas.common.EASBizException;
import com.kingdee.eas.custom.perfweb.AssessmentResultDescriptionCollection;
import com.kingdee.eas.custom.perfweb.AssessmentResultDescriptionFactory;
import com.kingdee.eas.custom.perfweb.AssessmentResultDescriptionInfo;
import com.kingdee.eas.custom.perfweb.IAssessmentResultDescription;
import com.kingdee.eas.custom.perfweb.bizEnum.AutoStatusEnum;
import com.kingdee.eas.custom.perfweb.utils.CenterOrgUtil;
import com.kingdee.shr.perfweb.app.base.evalplan.EvaResultSummaryCollection;
import com.kingdee.shr.perfweb.app.base.evalplan.EvaResultSummaryFactory;
import com.kingdee.shr.perfweb.app.base.evalplan.EvaResultSummaryInfo;
import com.kingdee.shr.perfweb.app.base.evalplan.IEvaResultSummary;
import com.kingdee.shr.perfweb.app.base.evalplan.EvaPeriodStatusEnum;
import com.kingdee.shr.perfweb.app.base.evalplan.IPerfPeriod;
import com.kingdee.shr.perfweb.app.base.evalplan.PerfEvaObjectInfo;
import com.kingdee.shr.perfweb.app.base.evalplan.PerfPeriodCollection;
import com.kingdee.shr.perfweb.app.base.evalplan.PerfPeriodFactory;
import com.kingdee.shr.perfweb.app.base.evalplan.PerfPeriodInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 绩效考核结果报表 MSF/OSF 查询服务。
 * <p>
 * 以「考核结果说明」({@code AssessmentResultDescription}) 为主表，一个中心组织对应一条报表数据；
 * 各等级推荐/实际人数从考核结果汇总按<strong>中心及其全部下级组织</strong>范围内员工统计。
 * 审批、飞书推送由列表页 Handler 处理，不在本服务内实现。
 * <p>
 * 入参：periodId（选填，空则取考核中最新考核周期）、orgId（选填，按组织过滤）
 * <p>
 * 作    者: 青梧
 * 创建日期: 2026-06-17
 */
public class CenterPerfStatService implements IHRMsfService {

    private static final Logger logger = Logger.getLogger(CenterPerfStatService.class);

    /**
     * MSF 入参：考核周期 ID
     */
    private static final String PARAM_PERIOD_ID = "periodId";
    /**
     * MSF 入参：行政组织 ID
     */
    private static final String PARAM_ORG_ID = "orgId";

    /**
     * 绩效等级顺序（与报表原型一致）
     */
    private static final List<String> GRADE_ORDER = Arrays.asList("S", "A+", "A", "B", "C");

    /**
     * 各等级分布占比（%）：S=5, A+=25, A=45, B=20, C=5。
     * A 等级标准人数不直接按比例四舍五入，见 {@link #calcStandardRecommendCounts(int)}。
     */
    private static final Map<String, Integer> GRADE_PERCENT = new LinkedHashMap<>();

    /**
     * S/A+/B/C 按占比四舍五入取标准人数；A 用剩余人数兜底
     */
    private static final List<String> ROUNDED_GRADE_ORDER = Arrays.asList("S", "A+", "B", "C");

    /**
     * 考核状态枚举值 → 单据字段展示名
     */
    private static final Map<String, String> AUTO_STATUS_LABELS = new LinkedHashMap<>();

    /**
     * 考核状态 → 报表「单据状态」列展示名（未提交 / 审批中 / 已通过）
     */
    private static final Map<String, String> BILL_STATUS_LABELS = new LinkedHashMap<>();

    static {
        GRADE_PERCENT.put("S", 5);
        GRADE_PERCENT.put("A+", 25);
        GRADE_PERCENT.put("A", 45);
        GRADE_PERCENT.put("B", 20);
        GRADE_PERCENT.put("C", 5);

        AUTO_STATUS_LABELS.put(AutoStatusEnum.WFQ_VALUE, "未发起");
        AUTO_STATUS_LABELS.put(AutoStatusEnum.ZJSPZ_VALUE, "总监审批中");
        AUTO_STATUS_LABELS.put(AutoStatusEnum.ZJLSPZ_VALUE, "总经理审批中");
        AUTO_STATUS_LABELS.put(AutoStatusEnum.SPTG_VALUE, "审批通过");

        BILL_STATUS_LABELS.put(AutoStatusEnum.WFQ_VALUE, "未提交");
        BILL_STATUS_LABELS.put(AutoStatusEnum.ZJSPZ_VALUE, "审批中");
        BILL_STATUS_LABELS.put(AutoStatusEnum.ZJLSPZ_VALUE, "审批中");
        BILL_STATUS_LABELS.put(AutoStatusEnum.SPTG_VALUE, "已通过");
    }

    public CenterPerfStatService() {
    }

    /**
     * MSF 统一入口：按 periodId、orgId 查询报表 JSON。
     */
    @Override
    public Object process(Context context, Map<String, Object> params) throws EASBizException, BOSException {
        if (params == null) {
            params = new HashMap<>();
        }
        String periodId = getStringParam(params, PARAM_PERIOD_ID);
        String orgId = getStringParam(params, PARAM_ORG_ID);
        logger.info("CenterPerfStatService.process periodId=" + periodId + "，orgId=" + orgId);
        return queryReport(context, periodId, orgId);
    }

    /**
     * 查询绩效考核结果报表：主表为考核结果说明，一组织一行。
     *
     * @param ctx      上下文
     * @param periodId 考核周期 ID（选填，空则取默认周期）
     * @param orgId    行政组织 ID（选填，快速过滤手工选择）
     */
    public static Map<String, Object> queryReport(Context ctx, String periodId, String orgId) {
        return queryReport(ctx, periodId, orgId, null);
    }

    /**
     * 查询绩效考核结果报表：主表为考核结果说明，一组织一行。
     *
     * @param ctx              上下文
     * @param periodId         考核周期 ID（选填，空则取默认周期）
     * @param orgId            行政组织 ID（选填，快速过滤手工选择）
     * @param adminOrgRangeIds 当前用户行政组织范围（未选手工组织时由 Handler 传入，对标品 getCurrentRequestAdminOrgRangeIdSet）
     */
    public static Map<String, Object> queryReport(Context ctx, String periodId, String orgId, Set<String> adminOrgRangeIds) {
        Map<String, Object> result = baseResult();
        try {
            if (StringUtils.isBlank(periodId)) {
                periodId = resolveDefaultPeriodId(ctx);
            }
            if (StringUtils.isBlank(periodId)) {
                return fail(result, "未找到状态为考核中的考核周期");
            }
            PerfPeriodInfo period = loadPeriod(ctx, periodId);
            if (period == null) {
                return fail(result, "未找到考核周期，periodId=" + periodId);
            }
            // 主表：考核结果说明；orgId 选手工过滤，否则按 adminOrgRangeIds 系统组织范围过滤
            AssessmentResultDescriptionCollection collection = loadDescriptionByPeriod(ctx, periodId);
            List<CenterOrgContext> centerContexts = buildCenterContexts(ctx, collection, orgId, adminOrgRangeIds);
            // 按中心组织树汇总各等级人数（中心 + 全部下级部门员工）
            Map<String, OrgGradeAgg> gradeAggMap = aggregateGradeByCenter(ctx, periodId, centerContexts);

            List<Map<String, Object>> rows = new ArrayList<>();
            for (CenterOrgContext centerCtx : centerContexts) {
                OrgGradeAgg gradeAgg = gradeAggMap.get(centerCtx.orgId);
                if (gradeAgg == null) {
                    gradeAgg = new OrgGradeAgg();
                }
                rows.add(buildReportRow(centerCtx.description, gradeAgg, period));
            }

            Map<String, Object> data = new LinkedHashMap<>();
            data.put("periodId", periodId);
            data.put("periodName", StringUtils.defaultString(period.getName(), ""));
            data.put("orgId", StringUtils.defaultString(orgId, ""));
            data.put("gradePercents", copyGradePercents());
            data.put("rows", rows);
            result.put("code", "200");
            result.put("msg", "success");
            result.put("data", data);
            return result;
        } catch (Exception e) {
            logger.error("queryReport failed, periodId=" + periodId + ", orgId=" + orgId, e);
            return fail(result, "查询失败：" + e.getMessage());
        }
    }

    /**
     * 发送飞书公示前校验：指定考核周期下全部「考核结果说明」须已审批通过（autoStatus = SPTG）。
     *
     * @return 可发送时返回 null；不可发送时返回前端提示文案
     */
    public static String validateAllDescriptionApprovedForNotify(Context ctx, String periodId) throws BOSException {
        if (StringUtils.isBlank(periodId)) {
            return "无法确定考核周期，无法发送飞书公示通知";
        }
        String periodName = resolvePeriodDisplayName(ctx, periodId);
        AssessmentResultDescriptionCollection collection = loadDescriptionByPeriod(ctx, periodId);
        if (collection == null || collection.isEmpty()) {
            return "【" + periodName + "】绩效结果暂未审批，无法发送飞书公示通知";
        }
        int total = collection.size();
        int approvedCount = 0;
        for (int i = 0; i < collection.size(); i++) {
            AssessmentResultDescriptionInfo info = collection.get(i);
            if (info != null && AutoStatusEnum.SPTG.equals(info.getAutoStatus())) {
                approvedCount++;
            }
        }
        if (approvedCount < total) {
            return "【" + periodName + "】绩效结果暂未审批，无法发送飞书公示通知";
        }
        return null;
    }

    /**
     * HR 发起前校验：当前查询条件下待发起中心，各等级（S/A+/A/B/C）实际人数之和须等于总人数。
     * 组织快速过滤选中某中心时，仅校验该中心；未选组织时校验权限范围内全部待发起中心。
     *
     * @return 可发起时返回 null；不可发起时返回前端提示文案
     */
    public static String validateGradeActualEqualsTotalForHrStart(Context ctx, String periodId, String orgId,
            Set<String> adminOrgRangeIds, String expectStatusValue) {
        Map<String, Object> queryResult = queryReport(ctx, periodId, orgId, adminOrgRangeIds);
        if (!"200".equals(String.valueOf(queryResult.get("code")))) {
            return String.valueOf(queryResult.get("msg"));
        }
        Map<String, Object> data = (Map<String, Object>) queryResult.get("data");
        List<Map<String, Object>> rows = data != null && data.get("rows") != null
                ? (List<Map<String, Object>>) data.get("rows") : new ArrayList<>();
        List<String> invalidOrgs = new ArrayList<>();
        for (Map<String, Object> row : rows) {
            if (row == null || !expectStatusValue.equals(String.valueOf(row.get("autoStatus")))) {
                continue;
            }
            int totalCount = toIntValue(row.get("totalCount"));
            int actualSum = sumGradeActualCount(row);
            if (actualSum != totalCount) {
                invalidOrgs.add(formatOrgGradeCountMismatch(row, actualSum, totalCount));
            }
        }
        if (invalidOrgs.isEmpty()) {
            return null;
        }
        return String.join("", invalidOrgs);
    }

    private static String formatOrgGradeCountMismatch(Map<String, Object> row, int actualSum, int totalCount) {
        return  resolveOrgDisplayName(row) + "待评定人员绩效等级未完成结果审核（"
                + actualSum + "/" + totalCount + "），不能发起结果审批。";
    }

    private static String resolveOrgDisplayName(Map<String, Object> row) {
        if (row == null) {
            return "未知组织";
        }
        String centerName = StringUtils.trimToNull(String.valueOf(row.get("centerName")));
        if (centerName != null && !"null".equalsIgnoreCase(centerName)) {
            return centerName;
        }
        String orgName = StringUtils.trimToNull(String.valueOf(row.get("orgName")));
        if (orgName != null && !"null".equalsIgnoreCase(orgName)) {
            return orgName;
        }
        String number = StringUtils.trimToNull(String.valueOf(row.get("number")));
        if (number != null && !"null".equalsIgnoreCase(number)) {
            return number;
        }
        return "未知组织";
    }

    private static int sumGradeActualCount(Map<String, Object> row) {
        int sum = 0;
        for (String grade : GRADE_ORDER) {
            sum += toIntValue(row.get(toFieldPrefix(grade) + "Actual"));
        }
        return sum;
    }

    private static int toIntValue(Object value) {
        if (value == null) {
            return 0;
        }
        if (value instanceof Number) {
            return ((Number) value).intValue();
        }
        try {
            return Integer.parseInt(String.valueOf(value).trim());
        } catch (NumberFormatException e) {
            return 0;
        }
    }

    private static String resolvePeriodDisplayName(Context ctx, String periodId) {
        try {
            PerfPeriodInfo period = PerfPeriodFactory.getLocalInstance(ctx).getPerfPeriodInfo("where id = '" + periodId + "'");
            if (period != null && StringUtils.isNotBlank(period.getName())) {
                return period.getName();
            }
        } catch (Exception e) {
            logger.error("resolvePeriodDisplayName failed, periodId=" + periodId, e);
        }
        return periodId;
    }

    /**
     * 按考核周期加载全部考核结果说明。
     */
    private static AssessmentResultDescriptionCollection loadDescriptionByPeriod(Context ctx, String periodId) throws BOSException {
        IAssessmentResultDescription iBill = AssessmentResultDescriptionFactory.getLocalInstance(ctx);
        FilterInfo filter = new FilterInfo();
        filter.getFilterItems().add(new FilterItemInfo("period.id", periodId, CompareType.EQUALS));
        SelectorItemCollection sic = new SelectorItemCollection();
        sic.add(new SelectorItemInfo("*"));
        sic.add(new SelectorItemInfo("period.*"));
        sic.add(new SelectorItemInfo("adminOrg.*"));
        EntityViewInfo evi = new EntityViewInfo();
        evi.setFilter(filter);
        evi.setSelector(sic);
        return iBill.getAssessmentResultDescriptionCollection(evi);
    }

    /**
     * 从主表构建中心上下文列表。
     * orgId 有值时按手工组织过滤；否则按 adminOrgRangeIds（系统组织范围）过滤。
     */
    private static List<CenterOrgContext> buildCenterContexts(Context ctx, AssessmentResultDescriptionCollection collection, String orgId, Set<String> adminOrgRangeIds) throws BOSException {
        List<CenterOrgContext> contexts = new ArrayList<>();
        if (collection == null) {
            return contexts;
        }
        AdminOrgUnitInfo filterOrg = StringUtils.isNotBlank(orgId) ? CenterOrgUtil.loadOrg(ctx, orgId) : null;
        for (int i = 0; i < collection.size(); i++) {
            AssessmentResultDescriptionInfo info = collection.get(i);
            AdminOrgUnitInfo centerOrg = info.getAdminOrg();
            if (centerOrg == null || centerOrg.getId() == null) {
                continue;
            }
            if (filterOrg != null) {
                if (!matchesOrgFilter(ctx, filterOrg, centerOrg, orgId)) {
                    continue;
                }
            } else if (!matchesAdminOrgRange(ctx, centerOrg, adminOrgRangeIds)) {
                continue;
            }
            CenterOrgContext centerCtx = new CenterOrgContext();
            centerCtx.orgId = centerOrg.getId().toString();
            centerCtx.centerOrg = centerOrg;
            centerCtx.description = info;
            contexts.add(centerCtx);
        }
        return contexts;
    }

    /**
     * 系统组织范围过滤：中心自身在范围内，或范围内任一组织落在该中心树下。
     * 对标品 ListHandler.getCurrentRequestAdminOrgRangeIdSet 的 INCLUDE 语义。
     */
    private static boolean matchesAdminOrgRange(Context ctx, AdminOrgUnitInfo centerOrg, Set<String> adminOrgRangeIds) throws BOSException {
        if (adminOrgRangeIds == null || adminOrgRangeIds.isEmpty()) {
            return true;
        }
        String centerId = centerOrg.getId().toString();
        if (adminOrgRangeIds.contains(centerId)) {
            return true;
        }
        for (String rangeOrgId : adminOrgRangeIds) {
            if (StringUtils.isBlank(rangeOrgId)) {
                continue;
            }
            AdminOrgUnitInfo rangeOrg = CenterOrgUtil.loadOrg(ctx, rangeOrgId);
            if (rangeOrg != null && CenterOrgUtil.isUnderCenterOrg(ctx, rangeOrg, centerOrg)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 组织过滤：orgId 为中心时精确匹配；为下级部门时匹配其所属中心单据。
     */
    private static boolean matchesOrgFilter(Context ctx, AdminOrgUnitInfo filterOrg, AdminOrgUnitInfo centerOrg, String orgId) throws BOSException {
        if (filterOrg == null || centerOrg == null || centerOrg.getId() == null) {
            return true;
        }
        if (orgId.equals(centerOrg.getId().toString())) {
            return true;
        }
        // filterOrg 在中心组织树下（含中心自身）
        return CenterOrgUtil.isUnderCenterOrg(ctx, filterOrg, centerOrg);
    }

    /**
     * 按中心组织（含全部下级）汇总考核周期内各等级人数，并计算推荐人数。
     */
    private static Map<String, OrgGradeAgg> aggregateGradeByCenter(Context ctx, String periodId, List<CenterOrgContext> centerContexts) throws BOSException {
        Map<String, OrgGradeAgg> aggMap = new LinkedHashMap<>();
        for (CenterOrgContext centerCtx : centerContexts) {
            aggMap.put(centerCtx.orgId, new OrgGradeAgg());
        }
        if (centerContexts.isEmpty()) {
            return aggMap;
        }
        EvaResultSummaryCollection coll = loadSummaryByPeriod(ctx, periodId);
        if (coll == null) {
            fillRecommendCounts(aggMap);
            return aggMap;
        }
        for (int i = 0; i < coll.size(); i++) {
            EvaResultSummaryInfo info = coll.get(i);
            PerfEvaObjectInfo evaObj = info.getEvaObj();
            if (evaObj == null || evaObj.getAdminOrgUnit() == null) {
                continue;
            }
            AdminOrgUnitInfo empOrg = evaObj.getAdminOrgUnit();
            // 员工落在哪个中心组织树下，就计入该中心
            String matchedCenterId = matchCenterForEmployee(ctx, empOrg, centerContexts);
            if (matchedCenterId == null) {
                continue;
            }
            OrgGradeAgg agg = aggMap.get(matchedCenterId);
            if (agg == null) {
                continue;
            }
            agg.totalCount++;
            String grade = resolveGradeLevel(info);
            if (StringUtils.isNotBlank(grade)) {
                agg.actualMap.put(grade, agg.actualMap.getOrDefault(grade, 0) + 1);
            }
        }
        fillRecommendCounts(aggMap);
        return aggMap;
    }

    /**
     * 判断员工所属组织属于哪个中心（中心 + 下级范围），返回中心 orgId。
     */
    private static String matchCenterForEmployee(Context ctx, AdminOrgUnitInfo empOrg, List<CenterOrgContext> centerContexts) throws BOSException {
        for (CenterOrgContext centerCtx : centerContexts) {
            if (CenterOrgUtil.isUnderCenterOrg(ctx, empOrg, centerCtx.centerOrg)) {
                return centerCtx.orgId;
            }
        }
        return null;
    }

    /**
     * 按等级分布规则批量计算各中心标准（推荐）人数
     */
    private static void fillRecommendCounts(Map<String, OrgGradeAgg> aggMap) {
        for (OrgGradeAgg agg : aggMap.values()) {
            Map<String, Integer> standardMap = calcStandardRecommendCounts(agg.totalCount);
            agg.recommendMap.putAll(standardMap);
        }
    }

    /**
     * 计算各等级标准（推荐）人数。
     * <ul>
     *   <li>S、A+、B、C：总人数 × 占比，四舍五入取整</li>
     *   <li>A：剩余人数 = 总人数 ? (S + A+ + B + C)，保证各等级标准人数之和等于总人数</li>
     * </ul>
     * 示例：总人数 10 → S=1, A+=3, A=3, B=2, C=1
     */
    private static Map<String, Integer> calcStandardRecommendCounts(int total) {
        Map<String, Integer> result = new LinkedHashMap<>();
        if (total <= 0) {
            for (String grade : GRADE_ORDER) {
                result.put(grade, 0);
            }
            return result;
        }
        int otherSum = 0;
        for (String grade : ROUNDED_GRADE_ORDER) {
            int count = calcRoundedRecommend(total, GRADE_PERCENT.get(grade));
            result.put(grade, count);
            otherSum += count;
        }
        // A 等级承接四舍五入后的剩余人数
        int aCount = total - otherSum;
        if (aCount < 0) {
            aCount = 0;
        }
        result.put("A", aCount);
        return result;
    }

    /**
     * 组装报表一行：中心、考核周期、人数合计、各等级推荐/实际、备注、单据状态。
     * 字段命名与前端报表列一一对应，一组织一条记录。
     */
    private static Map<String, Object> buildReportRow(AssessmentResultDescriptionInfo info, OrgGradeAgg gradeAgg, PerfPeriodInfo period) {
        Map<String, Object> row = new LinkedHashMap<>();
        row.put("id", info.getId() != null ? info.getId().toString() : "");
        row.put("number", StringUtils.defaultString(info.getNumber(), ""));

        PerfPeriodInfo rowPeriod = info.getPeriod() != null ? info.getPeriod() : period;
        if (rowPeriod != null && rowPeriod.getId() != null) {
            row.put("periodId", rowPeriod.getId().toString());
            row.put("periodName", StringUtils.defaultString(rowPeriod.getName(), ""));
        } else if (period != null && period.getId() != null) {
            row.put("periodId", period.getId().toString());
            row.put("periodName", StringUtils.defaultString(period.getName(), ""));
        } else {
            row.put("periodId", "");
            row.put("periodName", "");
        }

        AdminOrgUnitInfo adminOrg = info.getAdminOrg();
        if (adminOrg != null) {
            row.put("orgId", adminOrg.getId() != null ? adminOrg.getId().toString() : "");
            row.put("orgName", StringUtils.defaultString(adminOrg.getName(), ""));
            row.put("centerName", StringUtils.defaultString(adminOrg.getName(), ""));
        } else {
            row.put("orgId", "");
            row.put("orgName", "");
            row.put("centerName", "");
        }

        row.put("totalCount", gradeAgg.totalCount);

        // 各等级推荐/实际，平铺字段便于前端直接绑定列
        putGradeFields(row, "S", gradeAgg);
        putGradeFields(row, "A+", gradeAgg);
        putGradeFields(row, "A", gradeAgg);
        putGradeFields(row, "B", gradeAgg);
        putGradeFields(row, "C", gradeAgg);

        // 同时提供 grades 数组，便于循环渲染
        row.put("grades", buildGradesList(gradeAgg));

        row.put("remark", StringUtils.defaultString(info.getRemarks(), ""));
        row.put("remarks", StringUtils.defaultString(info.getRemarks(), ""));

        AutoStatusEnum autoStatus = info.getAutoStatus();
        String statusValue = autoStatus != null ? autoStatus.getValue() : "";
        row.put("autoStatus", statusValue);
        row.put("autoStatusName", resolveAutoStatusName(autoStatus));
        row.put("billStatus", statusValue);
        row.put("billStatusName", resolveBillStatusName(autoStatus));
        return row;
    }

    /**
     * 将单个等级的标准（推荐）/实际人数写入行 Map。
     * recommend=标准人数（按比例计算），actual=实际人数（考核结果真实统计）。
     */
    private static void putGradeFields(Map<String, Object> row, String grade, OrgGradeAgg gradeAgg) {
        int recommend = gradeAgg.recommendMap.getOrDefault(grade, 0);
        int actual = gradeAgg.actualMap.getOrDefault(grade, 0);
        String prefix = toFieldPrefix(grade);
        row.put(prefix + "Recommend", recommend);
        row.put(prefix + "Actual", actual);
        row.put(prefix + "Exceed", actual > recommend);
    }

    private static List<Map<String, Object>> buildGradesList(OrgGradeAgg gradeAgg) {
        List<Map<String, Object>> grades = new ArrayList<>();
        for (String grade : GRADE_ORDER) {
            int recommend = gradeAgg.recommendMap.getOrDefault(grade, 0);
            int actual = gradeAgg.actualMap.getOrDefault(grade, 0);
            Map<String, Object> item = new LinkedHashMap<>();
            item.put("grade", grade);
            item.put("recommend", recommend);
            item.put("actual", actual);
            item.put("exceed", actual > recommend);
            grades.add(item);
        }
        return grades;
    }

    /**
     * 等级 → JSON 字段前缀：S→s, A+→aPlus, A→a, B→b, C→c
     */
    private static String toFieldPrefix(String grade) {
        if ("S".equals(grade)) {
            return "s";
        }
        if ("A+".equals(grade)) {
            return "aPlus";
        }
        if ("A".equals(grade)) {
            return "a";
        }
        if ("B".equals(grade)) {
            return "b";
        }
        if ("C".equals(grade)) {
            return "c";
        }
        return grade.toLowerCase();
    }

    /**
     * 中心组织上下文：主表单据 + 中心组织信息
     */
    private static class CenterOrgContext {
        private String orgId;
        private AdminOrgUnitInfo centerOrg;
        private AssessmentResultDescriptionInfo description;
    }

    private static EvaResultSummaryCollection loadSummaryByPeriod(Context ctx, String periodId) throws BOSException {
        IEvaResultSummary iEvaResultSummary = EvaResultSummaryFactory.getLocalInstance(ctx);
        FilterInfo filter = new FilterInfo();
        filter.getFilterItems().add(new FilterItemInfo("period.id", periodId, CompareType.EQUALS));
        SelectorItemCollection sic = new SelectorItemCollection();
        sic.add(new SelectorItemInfo("*"));
        sic.add(new SelectorItemInfo("evaObj.*"));
        sic.add(new SelectorItemInfo("evaObj.adminOrgUnit.*"));
        sic.add(new SelectorItemInfo("evaObj.adminOrgUnit.parent.*"));
        EntityViewInfo evi = new EntityViewInfo();
        evi.setFilter(filter);
        evi.setSelector(sic);
        return iEvaResultSummary.getEvaResultSummaryCollection(evi);
    }

    /**
     * 解析最终绩效等级：审定 &gt; 修订 &gt; 系统等级。
     */
    private static String resolveGradeLevel(EvaResultSummaryInfo info) {
        if (info == null) {
            return "";
        }
        if (StringUtils.isNotBlank(info.getAuditGradeLevel())) {
            return normalizeGrade(info.getAuditGradeLevel());
        }
        if (StringUtils.isNotBlank(info.getReviseGradeLevel())) {
            return normalizeGrade(info.getReviseGradeLevel());
        }
        return normalizeGrade(info.getGradeLevel());
    }

    private static String normalizeGrade(String grade) {
        if (StringUtils.isBlank(grade)) {
            return "";
        }
        String trimmed = grade.trim();
        if ("A＋".equals(trimmed) || "A+".equalsIgnoreCase(trimmed)) {
            return "A+";
        }
        for (String item : GRADE_ORDER) {
            if (item.equalsIgnoreCase(trimmed)) {
                return item;
            }
        }
        return trimmed;
    }

    /**
     * S/A+/B/C 标准人数：总人数 × 占比，四舍五入
     */
    private static int calcRoundedRecommend(int total, int percent) {
        if (total <= 0) {
            return 0;
        }
        return (int) Math.round(total * percent / 100.0);
    }

    private static List<Map<String, Object>> copyGradePercents() {
        List<Map<String, Object>> list = new ArrayList<>();
        for (Map.Entry<String, Integer> entry : GRADE_PERCENT.entrySet()) {
            Map<String, Object> item = new LinkedHashMap<>();
            item.put("grade", entry.getKey());
            item.put("percent", entry.getValue());
            list.add(item);
        }
        return list;
    }

    private static String resolveAutoStatusName(AutoStatusEnum autoStatus) {
        if (autoStatus == null) {
            return "";
        }
        String label = AUTO_STATUS_LABELS.get(autoStatus.getValue());
        return StringUtils.isNotBlank(label) ? label : autoStatus.getName();
    }

    /**
     * 映射为报表「单据状态」列：未提交 / 审批中 / 已通过
     */
    private static String resolveBillStatusName(AutoStatusEnum autoStatus) {
        if (autoStatus == null) {
            return BILL_STATUS_LABELS.get(AutoStatusEnum.WFQ_VALUE);
        }
        String label = BILL_STATUS_LABELS.get(autoStatus.getValue());
        return StringUtils.isNotBlank(label) ? label : resolveAutoStatusName(autoStatus);
    }

    private static PerfPeriodInfo loadPeriod(Context ctx, String periodId) throws BOSException, EASBizException {
        IPerfPeriod iPerfPeriod = PerfPeriodFactory.getLocalInstance(ctx);
        return iPerfPeriod.getPerfPeriodInfo("where id = '" + periodId + "'");
    }

    /**
     * periodId 为空时：直接查 PerfPeriod，状态 {@link EvaPeriodStatusEnum#EVALUATING 考核中}，
     * 按 createTime 倒序取最新一条。
     */
    public static String resolveDefaultPeriodId(Context ctx) throws BOSException {
        IPerfPeriod iPerfPeriod = PerfPeriodFactory.getLocalInstance(ctx);
        String oql = "where evaPeriodStatus = " + EvaPeriodStatusEnum.EVALUATING.getValue() + " order by createTime desc";
        PerfPeriodCollection coll = iPerfPeriod.getPerfPeriodCollection(oql);
        if (coll == null || coll.isEmpty()) {
            return null;
        }
        PerfPeriodInfo period = coll.get(0);
        return period != null && period.getId() != null ? period.getId().toString() : null;
    }

    private static Map<String, Object> baseResult() {
        return new LinkedHashMap<>();
    }

    private static Map<String, Object> fail(Map<String, Object> result, String msg) {
        result.put("code", "500");
        result.put("msg", msg);
        return result;
    }

    private static String getStringParam(Map<String, Object> params, String key) {
        if (params == null || !params.containsKey(key) || params.get(key) == null) {
            return null;
        }
        return String.valueOf(params.get(key)).trim();
    }

    /**
     * 中心组织等级汇总中间结构
     */
    private static class OrgGradeAgg {
        private int totalCount;
        private final Map<String, Integer> recommendMap = new HashMap<>();
        private final Map<String, Integer> actualMap = new HashMap<>();
    }
}
