package com.kingdee.eas.custom.compensation.osf;

import com.kingdee.bos.BOSException;
import com.kingdee.bos.Context;
import com.kingdee.bos.bsf.service.app.IHRMsfService;
import com.kingdee.bos.dao.ormapping.ObjectUuidPK;
import com.kingdee.bos.util.BOSUuid;
import com.kingdee.eas.basedata.org.AdminOrgUnitInfo;
import com.kingdee.eas.basedata.person.PersonInfo;
import com.kingdee.eas.common.EASBizException;
import com.kingdee.eas.custom.compensation.*;
import com.kingdee.eas.framework.CoreBaseCollection;
import com.kingdee.eas.util.app.DbUtil;
import com.kingdee.jdbc.rowset.IRowSet;
import com.kingdee.shr.base.syssetting.app.osf.OSFAppUtil;
import com.kingdee.shr.compensation.CalSchemeCollection;
import com.kingdee.shr.compensation.CalSchemeFactory;
import com.kingdee.shr.compensation.CalSchemeInfo;
import com.kingdee.shr.compensation.ICalScheme;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 手机押金单据OSF服务
 * <p>
 * 实现 {@link IHRMsfService} 接口，作为薪酬计算的OSF（Open Service Framework）后处理节点，
 * 负责根据薪酬计算结果自动生成、更新或删除员工的手机押金收退单据。
 * </p>
 *
 * <h3>核心业务流程</h3>
 * <ol>
 *   <li>从薪酬计算表（T_HR_SCMPCALTABLE）读取计算完成的手机押金收取/退回金额。</li>
 *   <li>按人员匹配已有押金单据：存在则更新，不存在则新建。</li>
 *   <li>支持反审批（mack="deleteDeposit"）：先将对应来源的押金字段清空，若单据全部为空则删除整条记录。</li>
 *   <li>支持跨月补算：当薪酬方案的上次同步标记与实际期间不一致时，先补算上月数据再算本月。</li>
 * </ol>
 *
 * <h3>动态字段说明</h3>
 * <p>s204、s205 为薪酬计算表中的动态字段名，分别对应手机押金收取金额和退回金额。
 * 使用动态字段名的原因是不同客户的薪酬方案中这两个字段的列名可能不同。</p>
 *
 * @see IHRMsfService
 * @see HandMacDepositFactory
 */
public class HandMacDepositOSFService implements IHRMsfService {

    /**
     * 反审批操作标记
     * <p>当 map 中的 mack 参数等于此值时，在生成单据之前会先执行反审批清理逻辑，
     * 将对应来源的押金字段置空或删除整个单据。</p>
     */
    private static String UN_DEPOSIT = "deleteDeposit";

    /**
     * 执行押金单据处理（同步方法）
     * <p>
     * 核心调度逻辑：
     * <ol>
     *   <li>若未指定薪酬方案（calSchemeID为空），则遍历所有启用状态的方案，逐个处理。</li>
     *   <li>若方案的简单名称标记与实际期间不一致（说明存在跨月未同步），则先补算标记中的历史期间数据。</li>
     *   <li>处理完成后更新方案的简单名称为当前期间，作为下次比对的基准。</li>
     *   <li>若 mack 等于 "deleteDeposit"，在每个方案处理前先执行反审批清理。</li>
     * </ol>
     *
     * @param ctx          BOS上下文
     * @param param        OSF传入的原始参数Map
     * @param calSchemeID  薪酬计算方案ID，为空时遍历所有启用方案
     * @param beginDate    手动指定的开始日期，为空则从方案配置或历史标记中读取
     * @param endDate      手动指定的结束日期，为空则从方案配置或历史标记中读取
     * @param personIds    人员ID列表（逗号分隔），用于限定处理范围
     * @param calTimeStr   手动指定的计算次数，为空则从方案配置或历史标记中读取
     * @param s204         薪酬计算表中"手机押金收取金额"的动态字段名
     * @param s205         薪酬计算表中"手机押金退回金额"的动态字段名
     * @param mack         操作标记，{@link #UN_DEPOSIT} 表示反审批
     * @return 处理结果："ok" 表示成功，否则返回错误信息
     * @throws BOSException BOS框架异常
     */
    protected synchronized String _exceDeposit(Context ctx, Map param, String calSchemeID, String beginDate, String endDate, String personIds, String calTimeStr, String s204, String s205, String mack) throws BOSException {
        String result = null;

        try {
            IHandMacDeposit handMacDeposit = HandMacDepositFactory.getLocalInstance(ctx);
            // 未指定方案ID：遍历所有启用状态的薪酬计算方案
            if (StringUtils.isBlank(calSchemeID)) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                // 查询所有启用状态的薪酬计算方案
                IRowSet rs = DbUtil.executeQuery(ctx, "select * from T_HR_SCalScheme where Fstate=1");
                while (rs.next()) {
                    String desc = rs.getString("FsimpleName");
                    String fid = rs.getString("fid");
                    String beginDateStr = sdf.format(rs.getDate("FbeginDate"));
                    String endDateStr = sdf.format(rs.getDate("FendDate"));
                    int calTime = rs.getInt("FcalTime");
                    // 将当前方案的期间信息拼接为标记字符串，用于判断是否需要跨月补算
                    String desc1 = beginDateStr + "," + endDateStr + "," + calTime;
                    Map<String, Object> map = new HashMap<String, Object>();
                    map.put("personIds", personIds);
                    map.put("calSchemeID", fid);

                    // 跨月补算检测：FsimpleName 存储了上次处理时的"开始日期,结束日期,计算次数"标记。
                    // 若标记与实际方案期间不一致，说明中间有月份未同步，需先补算历史期间数据。
                    if (null != desc && desc.indexOf(",") > 0) {
                        String[] descArr = desc.split(",");
                        if (descArr.length < 3 || desc.equals(desc1)) {
                            // 历史标记格式不完整或与当前方案一致，跳过补算
                        } else {
                            // --- 补算历史期间数据 ---
                            Map<String, Object> map1 = new HashMap<String, Object>();
                            map1.put("personIds", personIds);
                            map1.put("calSchemeID", fid);

                            // 优先使用手动指定的日期/次数，否则使用历史标记中的值
                            if (beginDate != null) {
                                map1.put("beginDate", beginDate);
                            } else {
                                map1.put("beginDate", descArr[0]);
                            }
                            if (endDate != null) {
                                map1.put("endDate", endDate);
                            } else {
                                map1.put("endDate", descArr[1]);
                            }
                            if (calTimeStr != null) {
                                map1.put("calTime", calTimeStr);
                            } else {
                                map1.put("calTime", descArr[2]);
                            }
                            // 反审批时先清理历史数据
                            if (null != mack && mack.equals(UN_DEPOSIT)) {
                                deleteDeposit(ctx, param, (String) map1.get("calSchemeID"), (String) map1.get("beginDate"), (String) map1.get("endDate"), (String) map1.get("calTime"), s204, s205, handMacDeposit);
                            }
                            // 生成/更新押金单据
                            Object obj = saveDeposit(ctx, param, (String) map1.get("calSchemeID"), (String) map1.get("beginDate"), (String) map1.get("endDate"), (String) map1.get("calTime"), s204, s205, handMacDeposit);
                        }
                    }

                    // --- 处理当前期间数据 ---
                    // 构建当前期间的查询参数：优先使用手动指定的日期/次数，否则使用方案配置的默认值
                    if (beginDate != null) {
                        map.put("beginDate", beginDate);
                    } else {
                        map.put("beginDate", beginDateStr);
                        map.put("calTime", String.valueOf(calTime));
                    }
                    if (endDate != null) {
                        map.put("endDate", endDate);
                    } else {
                        map.put("endDate", endDateStr);
                        map.put("calTime", String.valueOf(calTime));
                    }
                    if (calTimeStr != null) {
                        map.put("calTime", calTimeStr);
                    } else {
                        map.put("calTime", String.valueOf(calTime));
                    }
                    // 反审批时先清理数据
                    if (null != mack && mack.equals(UN_DEPOSIT)) {
                        deleteDeposit(ctx, param, (String) map.get("calSchemeID"), (String) map.get("beginDate"), (String) map.get("endDate"), (String) map.get("calTime"), s204, s205, handMacDeposit);
                    }
                    // 生成/更新押金单据
                    Object obj = saveDeposit(ctx, param, (String) map.get("calSchemeID"), (String) map.get("beginDate"), (String) map.get("endDate"), (String) map.get("calTime"), s204, s205, handMacDeposit);
                    result = (null != obj) ? obj.toString() : null;
                    // 更新方案的简单名称标记，记录本次处理的期间，供下次比对使用
                    DbUtil.execute(ctx, "update T_HR_SCalScheme set FsimpleName=? where fid= ? ", new Object[]{desc1, fid});
                }
            } else {
                // 指定了方案ID：直接处理该方案，无需遍历
                if (null != mack && mack.equals(UN_DEPOSIT)) {
                    deleteDeposit(ctx, param, calSchemeID, beginDate, endDate, calTimeStr, s204, s205, handMacDeposit);
                }
                Object obj = saveDeposit(ctx, param, calSchemeID, beginDate, endDate, calTimeStr, s204, s205, handMacDeposit);
                result = (null != obj) ? obj.toString() : null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            result = e.getMessage();
        }
        return result;
    }
    /**
     * OSF服务入口方法
     * <p>
     * 由薪酬计算引擎在计算完成后回调，从 map 中提取参数并委托给 {@link #_exceDeposit} 执行。
     * </p>
     *
     * @param context BOS上下文
     * @param map     参数Map，支持以下键：
     *                <ul>
     *                  <li><b>calSchemeID</b> - 薪酬计算方案ID（可选，为空则处理所有启用方案）</li>
     *                  <li><b>beginDate</b>   - 开始日期（可选）</li>
     *                  <li><b>endDate</b>     - 结束日期（可选）</li>
     *                  <li><b>calTime</b>     - 计算次数（可选）</li>
     *                  <li><b>personIds</b>   - 人员ID列表，逗号分隔（可选）</li>
     *                  <li><b>s204</b>        - 手机押金收取金额字段名（必填）</li>
     *                  <li><b>s205</b>        - 手机押金退回金额字段名（必填）</li>
     *                  <li><b>mack</b>        - 操作标记，"deleteDeposit"表示反审批（可选）</li>
     *                </ul>
     * @return 处理结果字符串
     * @throws EASBizException 业务异常
     * @throws BOSException    BOS框架异常
     */
    @Override
    public Object process(Context context, Map map) throws EASBizException, BOSException {

        String calSchemeID = (String) map.get("calSchemeID");
        String beginDate = (String) map.get("beginDate");
        String endDate = (String) map.get("endDate");
        String calTime = (String) map.get("calTime");
        String personIds = (String) map.get("personIds");
        String s204 = (String) map.get("s204");
        String s205 = (String) map.get("s205");
        String mack = (String) map.get("mack");

        return _exceDeposit(context, map, calSchemeID, beginDate, endDate, personIds, calTime, s204, s205, mack);
    }

    /**
     * 审批通过处理：根据薪酬计算结果生成或更新手机押金单据
     * <p>
     * 核心逻辑：
     * <ol>
     *   <li>从薪酬计算表查询计算完成的手机押金收退数据（FCalState in 12,20）。</li>
     *   <li>按人员查找已有生效押金单据：
     *     <ul>
     *       <li><b>有历史单据且来源匹配</b>：直接更新已有单据的押金收退字段。</li>
     *       <li><b>有历史单据但来源不匹配</b>：旧单据失效，创建新单据（合并旧数据防止丢失）。</li>
     *       <li><b>无历史单据</b>：直接创建新单据。</li>
     *     </ul>
     *   </li>
     *   <li>清理多余的生效单据（同一人员只保留一条生效记录）。</li>
     * </ol>
     *
     * @param context         BOS上下文
     * @param map             OSF原始参数Map
     * @param calSchemeID     薪酬计算方案ID
     * @param beginDate       开始日期
     * @param endDate         结束日期
     * @param calTime         计算次数
     * @param s204            手机押金收取金额的动态字段名
     * @param s205            手机押金退回金额的动态字段名
     * @param handMacDeposit  手机押金单据操作接口
     * @return "ok" 表示成功，"err" 表示异常，其他字符串为具体错误信息
     */
    private String saveDeposit(Context context, Map map, String calSchemeID, String beginDate, String endDate, String calTime, String s204, String s205, IHandMacDeposit handMacDeposit) {
        try {
            System.out.println("calSchemeID:" + calSchemeID);
            System.out.println("beginDate:" + beginDate);
            System.out.println("endDate:" + endDate);
            System.out.println("calTime:" + calTime);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            // 参数兜底：calSchemeID 为空时尝试从 map 中取 calSchemeId 键
            if (null == calSchemeID) {
                calSchemeID = (String) map.get("calSchemeId");
            }
            // 必填参数校验
            if (null == calSchemeID) {
                return "calSchemeID为空";
            }
            if (null == s204) {
                return "s204为空";
            }
            if (null == s205) {
                return "s205为空";
            }
            String personIds = (String) map.get("personIds");
            // 构建查询SQL：从薪酬计算表读取已计算完成的押金收退数据
            // FCalState=12 表示审批通过，20 表示已锁定，均为有效计算状态
            StringBuilder sql = new StringBuilder();
            List<Object> sqlParams = new ArrayList<Object>();
            sql.append(" select ");
            sql.append(" b.fid, ");
            sql.append(" b.fnumber, ");
            sql.append(" b.FAdminOrgUnitID, ");
            sql.append(" b.FCostBearOrgID, ");
            sql.append(" b.fpersonid, ");
            sql.append(" costBearOrg.Fname_l2 costBearOrgName, ");
            sql.append(" b.FBeginDate, ");
            sql.append(" b." + s204 + ", ");
            sql.append(" b." + s205 + " ");
            sql.append(" from T_HR_SCMPCALTABLE b ");
            sql.append(" left join t_org_admin costBearOrg on b.FCostBearOrgID = costBearOrg.fid ");
            sql.append(" where b.FCalSchemeID=? and b.FCalState in(12,20) and (b." + s204 + " is not null or b." + s205 + " is not null) ");
            sqlParams.add(calSchemeID);
            // personIds 改用参数化查询，防止SQL注入
            // 将逗号分隔的人员ID列表展开为参数化占位符，避免直接拼接SQL
            if (StringUtils.isNotBlank(personIds)) {
                String[] ids = personIds.split(",");
                sql.append(" and b.fpersonid in (");
                for (int i = 0; i < ids.length; i++) {
                    if (i > 0) {
                        sql.append(",");
                    }
                    sql.append("?");
                    sqlParams.add(ids[i].trim());
                }
                sql.append(") ");
            }
            // 未指定日期范围时，从方案配置中读取默认的开始/结束日期
            if (null == beginDate || null == endDate) {
                ICalScheme scheme = CalSchemeFactory.getLocalInstance(context);
                CalSchemeInfo calSchemeCon = scheme.getCalSchemeInfo(new ObjectUuidPK(calSchemeID));
                beginDate = sdf.format(calSchemeCon.getBeginDate());
                endDate = sdf.format(calSchemeCon.getEndDate());
            }
            sql.append(" and b.FBeginDate >= ? ");
            sqlParams.add(beginDate);
            sql.append(" and b.FEndDate <= ? ");
            sqlParams.add(endDate);

            // 指定计算次数时增加过滤条件（同一方案可能有多次计算）
            if (null != calTime) {
                sql.append(" and b.FcalTime = ? ");
                sqlParams.add(calTime);
            }
            sql.append(" order by b.FPeriodYear,b.FPeriodMonth,b.FCalTime ");
            IRowSet rs = DbUtil.executeQuery(context, sql.toString(), sqlParams.toArray());
            while (rs.next()) {
                String id = rs.getString("fid");
                String fnumber = rs.getString("fnumber");
                String FAdminOrgUnitID = rs.getString("FAdminOrgUnitID");
                String FCostBearOrgID = rs.getString("FCostBearOrgID");
                String costBearOrgName = rs.getString("costBearOrgName");
                String personId = rs.getString("fpersonid");

                Date FBeginDate = rs.getDate("FBeginDate");
                BigDecimal Fs204 = rs.getBigDecimal(s204);
                BigDecimal Fs205 = rs.getBigDecimal(s205);

                // 查询该人员所有生效的押金单据（同一人员理论上只有一条生效记录）
                HandMacDepositCollection existingDocs = handMacDeposit.getHandMacDepositCollection(
                        "where person.id ='" + personId + "' and isEffected=1 ");
                CoreBaseCollection docsToSave = new CoreBaseCollection();
                CoreBaseCollection docsToAddNew = new CoreBaseCollection();
                HandMacDepositInfo oldDoc = null;
                if (existingDocs != null && !existingDocs.isEmpty()) {
                    // 只取第一条作为有效历史单据，多余的置为失效（清理脏数据，保证一人一单）
                    oldDoc = existingDocs.get(0);
                    for (int i = 1; i < existingDocs.size(); i++) {
                        HandMacDepositInfo extraDoc = existingDocs.get(i);
                        extraDoc.setIsEffected(false);
                        extraDoc.setLastUpdateTime(new Timestamp(System.currentTimeMillis()));
                        docsToSave.add(extraDoc);
                    }

                    // 判断当前行与历史单据是否同一来源，决定更新还是新建
                    // 收取押金场景（Fs204不为空）：用 sourceBillId 与当前薪酬行ID匹配
                    // 仅退回押金场景（Fs204为空、Fs205不为空）：用 sourceFunction 匹配
                    boolean matched = false;
                    if (null != Fs204 && id.equals(oldDoc.getSourceBillId())) {
                        matched = true;
                    } else if (null == Fs204 && null != Fs205 && (null == oldDoc.getSourceFunction() || id.equals(oldDoc.getSourceFunction()))) {
                        matched = true;
                    }

                    if (matched) {
                        // 同一来源：直接更新已有单据的收退字段
                        if (null != Fs204) {
                            AdminOrgUnitInfo CostBearOrg = new AdminOrgUnitInfo();
                            CostBearOrg.setId(BOSUuid.read(FCostBearOrgID));
                            oldDoc.setCollectUnit(CostBearOrg);
                            oldDoc.setDepositCollectUnit(costBearOrgName);
                            oldDoc.setDepositCollectCycle(FBeginDate);
                            oldDoc.setDepositAmount(Fs204);
                            oldDoc.setSourceBillId(id);
                            oldDoc.setIsEffected(true);
                        }
                        if (null != Fs205) {
                            AdminOrgUnitInfo CostBearOrg = new AdminOrgUnitInfo();
                            CostBearOrg.setId(BOSUuid.read(FCostBearOrgID));
                            oldDoc.setRefundUnit(CostBearOrg);
                            oldDoc.setDepositRefundUnit(costBearOrgName);
                            oldDoc.setDepositRefundCycle(FBeginDate);
                            oldDoc.setDepositRefundAmount(Fs205);
                            oldDoc.setSourceFunction(id);
                        }
                        docsToSave.add(oldDoc);
                    } else {
                        // 来源不匹配：旧单据置为失效，创建新单据并合并旧数据，防止因来源变更导致押金信息丢失
                        oldDoc.setIsEffected(false);
                        docsToSave.add(oldDoc);

                        HandMacDepositInfo newDoc = new HandMacDepositInfo();
                        newDoc.setIsEffected(true);
                        newDoc.setCreateTime(new Timestamp(System.currentTimeMillis()));
                        newDoc.setLastUpdateTime(new Timestamp(System.currentTimeMillis()));
                        // 设置表头信息：管理组织、人员、编号等基础字段
                        AdminOrgUnitInfo adminOrgUnitInfo = new AdminOrgUnitInfo();
                        adminOrgUnitInfo.setId(BOSUuid.read(FAdminOrgUnitID));
                        newDoc.setAdminOrgUnit(adminOrgUnitInfo);
                        newDoc.setAdminOrg(adminOrgUnitInfo);
                        PersonInfo personInfo = new PersonInfo();
                        personInfo.setId(BOSUuid.read(personId));
                        newDoc.setPerson(personInfo);
                        newDoc.setPersonNumber(fnumber);
                        newDoc.setNumber(id);
                        newDoc.setDescription(calSchemeID);

                        // 设置收取押金字段：当前行有值则用新值，否则从旧单据继承（防止数据丢失）
                        if (null != Fs204) {
                            AdminOrgUnitInfo CostBearOrg = new AdminOrgUnitInfo();
                            CostBearOrg.setId(BOSUuid.read(FCostBearOrgID));
                            newDoc.setCollectUnit(CostBearOrg);
                            newDoc.setDepositCollectUnit(costBearOrgName);
                            newDoc.setDepositCollectCycle(FBeginDate);
                            newDoc.setDepositAmount(Fs204);
                            newDoc.setSourceBillId(id);
                        } else {
                            // 当前行无收取押金数据，从旧单据继承，防止因薪酬方案变更导致历史押金信息丢失
                            if (oldDoc.getDepositAmount() != null) {
                                newDoc.setDepositAmount(oldDoc.getDepositAmount());
                                newDoc.setDepositCollectCycle(oldDoc.getDepositCollectCycle());
                                newDoc.setDepositCollectUnit(oldDoc.getDepositCollectUnit());
                                newDoc.setCollectUnit(oldDoc.getCollectUnit());
                                newDoc.setSourceBillId(oldDoc.getSourceBillId());
                            }
                        }

                        // 设置退回押金字段：当前行有值则用新值，否则从旧单据继承（防止数据丢失）
                        if (null != Fs205) {
                            AdminOrgUnitInfo CostBearOrg = new AdminOrgUnitInfo();
                            CostBearOrg.setId(BOSUuid.read(FCostBearOrgID));
                            newDoc.setRefundUnit(CostBearOrg);
                            newDoc.setDepositRefundUnit(costBearOrgName);
                            newDoc.setDepositRefundCycle(FBeginDate);
                            newDoc.setDepositRefundAmount(Fs205);
                            newDoc.setSourceFunction(id);
                        } else {
                            // 当前行无退回押金数据，从旧单据继承，防止因薪酬方案变更导致历史押金信息丢失
                            if (oldDoc.getDepositRefundAmount() != null) {
                                newDoc.setDepositRefundAmount(oldDoc.getDepositRefundAmount());
                                newDoc.setDepositRefundCycle(oldDoc.getDepositRefundCycle());
                                newDoc.setDepositRefundUnit(oldDoc.getDepositRefundUnit());
                                newDoc.setRefundUnit(oldDoc.getRefundUnit());
                                newDoc.setSourceFunction(oldDoc.getSourceFunction());
                            }
                        }
                        docsToAddNew.add(newDoc);
                    }
                }
                else {
                    // 无历史单据：直接创建新的押金单据
                    System.out.println("HandMacDepositInfo newDoc");
                    HandMacDepositInfo newDoc = new HandMacDepositInfo();

                    newDoc.setCreateTime(new Timestamp(System.currentTimeMillis()));
                    newDoc.setLastUpdateTime(new Timestamp(System.currentTimeMillis()));
                    newDoc.setIsEffected(true);
                    AdminOrgUnitInfo adminOrgUnitInfo = new AdminOrgUnitInfo();
                    adminOrgUnitInfo.setId(BOSUuid.read(FAdminOrgUnitID));
                    newDoc.setAdminOrgUnit(adminOrgUnitInfo);
                    newDoc.setAdminOrg(adminOrgUnitInfo);
                    PersonInfo personInfo = new PersonInfo();
                    personInfo.setId(BOSUuid.read(personId));
                    newDoc.setPerson(personInfo);
                    newDoc.setPersonNumber(fnumber);
                    newDoc.setNumber(id);
                    newDoc.setDescription(calSchemeID);

                    if (null != Fs204) {
                        AdminOrgUnitInfo CostBearOrg = new AdminOrgUnitInfo();
                        CostBearOrg.setId(BOSUuid.read(FCostBearOrgID));
                        newDoc.setCollectUnit(CostBearOrg);
                        newDoc.setDepositCollectUnit(costBearOrgName);
                        newDoc.setDepositCollectCycle(FBeginDate);
                        newDoc.setDepositAmount(Fs204);
                        newDoc.setSourceBillId(id);
                    }
                    if (null != Fs205) {
                        AdminOrgUnitInfo CostBearOrg = new AdminOrgUnitInfo();
                        CostBearOrg.setId(BOSUuid.read(FCostBearOrgID));
                        newDoc.setRefundUnit(CostBearOrg);
                        newDoc.setDepositRefundUnit(costBearOrgName);
                        newDoc.setDepositRefundCycle(FBeginDate);
                        newDoc.setDepositRefundAmount(Fs205);
                        newDoc.setSourceFunction(id);
                    }
                    docsToAddNew.add(newDoc);
                    System.out.println("HandMacDepositInfo newDoc:"+docsToAddNew.size());
                }
                // 批量保存：更新已有单据（失效、修改）
                if (!docsToSave.isEmpty()) {
                    handMacDeposit.updateBatchData(docsToSave);
                }
                // 批量新增：创建新单据
                if (!docsToAddNew.isEmpty()) {
                    handMacDeposit.addnewBatchData(docsToAddNew);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "err";
        }
        return "ok";
    }

    /**
     * 反审批处理：清除或删除薪酬计算中反审批的押金数据
     * <p>
     * 与 {@link #saveDeposit} 互为逆操作，核心逻辑：
     * <ol>
     *   <li>从薪酬计算表查询状态≤8（反审批）的押金收退数据。</li>
     *   <li>按人员查找已有生效押金单据：
     *     <ul>
     *       <li><b>来源匹配</b>：将对应来源的押金字段清空（置为null）。
     *           若收取和退回字段均为空，则删除整条单据记录。</li>
     *       <li><b>来源不匹配</b>：旧单据置为失效，不做删除（可能是其他来源产生的数据）。</li>
     *     </ul>
     *   </li>
     *   <li>清理多余的生效单据（同一人员只保留一条生效记录）。</li>
     * </ol>
     *
     * @param context         BOS上下文
     * @param map             OSF原始参数Map
     * @param calSchemeID     薪酬计算方案ID
     * @param beginDate       开始日期
     * @param endDate         结束日期
     * @param calTime         计算次数
     * @param s204            手机押金收取金额的动态字段名
     * @param s205            手机押金退回金额的动态字段名
     * @param handMacDeposit  手机押金单据操作接口
     * @return "ok" 表示成功，"err" 表示异常，其他字符串为具体错误信息
     */
    private String deleteDeposit(Context context, Map map, String calSchemeID, String beginDate, String endDate, String calTime, String s204, String s205, IHandMacDeposit handMacDeposit) {
        try {
            System.out.println("calSchemeID:" + calSchemeID);
            System.out.println("beginDate:" + beginDate);
            System.out.println("endDate:" + endDate);
            System.out.println("calTime:" + calTime);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            // 参数兜底：calSchemeID 为空时尝试从 map 中取 calSchemeId 键
            if (null == calSchemeID) {
                calSchemeID = (String) map.get("calSchemeId");
            }
            // 必填参数校验
            if (null == calSchemeID) {
                return "calSchemeID为空";
            }
            if (null == s204) {
                return "s204为空";
            }
            if (null == s205) {
                return "s205为空";
            }
            String personIds = (String) map.get("personIds");
            StringBuilder sql = new StringBuilder();
            List<Object> sqlParams = new ArrayList<Object>();
            sql.append(" select ");
            sql.append(" b.fid, ");
            sql.append(" b.fnumber, ");
            sql.append(" b.FAdminOrgUnitID, ");
            sql.append(" b.FCostBearOrgID, ");
            sql.append(" b.fpersonid, ");
            sql.append(" costBearOrg.Fname_l2 costBearOrgName, ");
            sql.append(" b.FBeginDate, ");
            sql.append(" b." + s204 + ", ");
            sql.append(" b." + s205 + " ");
            sql.append(" from T_HR_SCMPCALTABLE b ");
            sql.append(" left join t_org_admin costBearOrg on b.FCostBearOrgID = costBearOrg.fid ");
            // 反审批查询：状态≤8 表示反审批中或之前的状态，需要清除对应的押金数据
            sql.append(" where b.FCalSchemeID=? and b.FCalState <= 8  and (b." + s204 + " is not null or b." + s205 + " is not null) ");
            sqlParams.add(calSchemeID);
            // personIds 改用参数化查询，防止SQL注入
            // 将逗号分隔的人员ID列表展开为参数化占位符，避免直接拼接SQL
            if (StringUtils.isNotBlank(personIds)) {
                String[] ids = personIds.split(",");
                sql.append(" and b.fpersonid in (");
                for (int i = 0; i < ids.length; i++) {
                    if (i > 0) {
                        sql.append(",");
                    }
                    sql.append("?");
                    sqlParams.add(ids[i].trim());
                }
                sql.append(") ");
            }
            // 未指定日期范围时，从方案配置中读取默认的开始/结束日期
            if (null == beginDate || null == endDate) {
                ICalScheme scheme = CalSchemeFactory.getLocalInstance(context);
                CalSchemeInfo calSchemeCon = scheme.getCalSchemeInfo(new ObjectUuidPK(calSchemeID));
                beginDate = sdf.format(calSchemeCon.getBeginDate());
                endDate = sdf.format(calSchemeCon.getEndDate());
            }
            sql.append(" and b.FBeginDate >= ? ");
            sqlParams.add(beginDate);
            sql.append(" and b.FEndDate <= ? ");
            sqlParams.add(endDate);

            // 指定计算次数时增加过滤条件（同一方案可能有多次计算）
            if (null != calTime) {
                sql.append(" and b.FcalTime = ? ");
                sqlParams.add(calTime);
            }
            sql.append(" order by b.FPeriodYear,b.FPeriodMonth,b.FCalTime ");
            IRowSet rs = DbUtil.executeQuery(context, sql.toString(), sqlParams.toArray());
            while (rs.next()) {
                String id = rs.getString("fid");
                String fnumber = rs.getString("fnumber");
                String FAdminOrgUnitID = rs.getString("FAdminOrgUnitID");
                String FCostBearOrgID = rs.getString("FCostBearOrgID");
                String costBearOrgName = rs.getString("costBearOrgName");
                String personId = rs.getString("fpersonid");

                Date FBeginDate = rs.getDate("FBeginDate");
                BigDecimal Fs204 = rs.getBigDecimal(s204);
                BigDecimal Fs205 = rs.getBigDecimal(s205);

                // 查询该人员所有生效的押金单据（同一人员理论上只有一条生效记录）
                HandMacDepositCollection existingDocs = handMacDeposit.getHandMacDepositCollection(
                        "where person.id ='" + personId + "' and isEffected=1 ");
                CoreBaseCollection docsToSave = new CoreBaseCollection();
                List<ObjectUuidPK> docsToDelete = new ArrayList<>();
                HandMacDepositInfo oldDoc = null;
                if (existingDocs != null && !existingDocs.isEmpty()) {
                    // 只取第一条作为有效历史单据，多余的置为失效（清理脏数据，保证一人一单）
                    oldDoc = existingDocs.get(0);
                    for (int i = 1; i < existingDocs.size(); i++) {
                        HandMacDepositInfo extraDoc = existingDocs.get(i);
                        extraDoc.setIsEffected(false);
                        extraDoc.setLastUpdateTime(new Timestamp(System.currentTimeMillis()));
                        docsToSave.add(extraDoc);
                    }

                    // 判断当前行与历史单据是否同一来源，决定清除还是仅标记失效
                    // 收取押金场景（Fs204不为空）：用 sourceBillId 与当前薪酬行ID匹配
                    // 仅退回押金场景（Fs204为空、Fs205不为空）：用 sourceFunction 匹配
                    boolean matched = false;
                    if (null != Fs204 && id.equals(oldDoc.getSourceBillId())) {
                        matched = true;
                    } else if (null == Fs204 && null != Fs205 && (null == oldDoc.getSourceFunction() || id.equals(oldDoc.getSourceFunction()))) {
                        matched = true;
                    }

                    if (matched) {
                        // 同一来源：将对应字段清空（置null），若全部为空则删除整条记录
                        if (null != Fs204) {
                            AdminOrgUnitInfo CostBearOrg = new AdminOrgUnitInfo();
                            CostBearOrg.setId(BOSUuid.read(FCostBearOrgID));
                            oldDoc.setCollectUnit(null);
                            oldDoc.setDepositCollectUnit(null);
                            oldDoc.setDepositCollectCycle(null);
                            oldDoc.setDepositAmount(null);
                            oldDoc.setSourceBillId(null);
                            oldDoc.setIsEffected(true);
                        }
                        if (null != Fs205) {
                            AdminOrgUnitInfo CostBearOrg = new AdminOrgUnitInfo();
                            CostBearOrg.setId(BOSUuid.read(FCostBearOrgID));
                            oldDoc.setRefundUnit(null);
                            oldDoc.setDepositRefundUnit(null);
                            oldDoc.setDepositRefundCycle(null);
                            oldDoc.setDepositRefundAmount(null);
                            oldDoc.setSourceFunction(null);
                        }
                        // 收取和退回金额均为空时，该单据已无实际数据，直接删除
                        if (null == oldDoc.getDepositAmount() && null == oldDoc.getDepositRefundAmount()) {
                            docsToDelete.add(new ObjectUuidPK(oldDoc.getId()));
                        } else {
                            docsToSave.add(oldDoc);
                        }
                    } else {
                        // 来源不匹配：该单据可能由其他薪酬方案产生，仅置为失效，不做删除
                        oldDoc.setIsEffected(false);
                        docsToSave.add(oldDoc);
                    }
                }
                // 批量保存：更新已有单据（失效、字段清空）
                if (!docsToSave.isEmpty()) {
                    handMacDeposit.updateBatchData(docsToSave);
                }
                // 批量删除：所有字段均被清空的单据
                if (!docsToDelete.isEmpty()) {
                    handMacDeposit.deleteBatchData(docsToDelete.toArray(new ObjectUuidPK[0]));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "err";
        }
        return "ok";
    }

}
