package thyyxxk.webserver.service.medicalinsurance;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.yaml.snakeyaml.util.UriEncoder;
import thyyxxk.webserver.config.envionment.MedinsurConfig;
import thyyxxk.webserver.config.exception.BizException;
import thyyxxk.webserver.config.exception.ExceptionEnum;
import thyyxxk.webserver.constants.sidicts.*;
import thyyxxk.webserver.constants.YesOrNo;
import thyyxxk.webserver.dao.his.inpatient.DoctorsAdviseDao;
import thyyxxk.webserver.dao.his.inpatient.PatientDao;
import thyyxxk.webserver.dao.his.medicalinsurance.*;
import thyyxxk.webserver.entity.ResultVo;
import thyyxxk.webserver.entity.inpatient.patient.NotUploadedFee;
import thyyxxk.webserver.entity.medicalinsurance.log.SiLog;
import thyyxxk.webserver.entity.medicalinsurance.inpatient.*;
import thyyxxk.webserver.entity.medicalinsurance.query.SiPatInfo;
import thyyxxk.webserver.entity.medicalinsurance.setlinfo.SiSetldetail;
import thyyxxk.webserver.entity.medicalinsurance.setlinfo.SiSetlinfo;
import thyyxxk.webserver.entity.inpatient.dismiss.MedinsSettleFee;
import thyyxxk.webserver.entity.inpatient.patient.Overview;
import thyyxxk.webserver.service.externalhttp.SiZySrvc;
import thyyxxk.webserver.service.inpatient.DismissService;
import thyyxxk.webserver.utils.*;

import java.math.BigDecimal;
import java.util.*;
import java.util.ArrayList;

/**
 * @description: 住院医保交易
 * @author: DingJie
 * @create: 2021-05-14 16:28:16
 **/
@Slf4j
@Service
public class SiZyService {
    private static final String RESULT_CODE = "infcode";
    private static final String ERROR_MESSAGE = "err_msg";
    private static final String OUTPUT = "output";
    private final SiZyDao dao;
    private final DoctorsAdviseDao doctorsAdviseDao;
    private final PatientDao patientDao;
    private final SiQueryService queryService;
    private final SiQueryDao queryDao;
    private final SiSetlinfoDao setlinfodao;
    private final SiSetldetailDao setldetldao;
    private final SiChargeTempDao chrgtmpdao;
    private final SiLogDao logDao;
    private final ExecService exec;
    private final DismissService dismissService;
    private final SiZySrvc zySrvc;
    private final MedinsurConfig cfg;
    @Value("${si-zy-fee-url}")
    private String siZyFeeUrl;

    @Autowired
    public SiZyService(SiZyDao dao, PatientDao patientDao, SiQueryService queryService, SiQueryDao queryDao, SiSetlinfoDao setlinfoDao,
                       SiSetldetailDao setldetldao, SiChargeTempDao chrgtmpdao, SiLogDao logDao, ExecService exec,
                       DismissService dismissService, SiZySrvc zySrvc, MedinsurConfig cfg, DoctorsAdviseDao doctorsAdviseDao) {
        this.dao = dao;
        this.patientDao = patientDao;
        this.doctorsAdviseDao = doctorsAdviseDao;
        this.queryService = queryService;
        this.queryDao = queryDao;
        this.setlinfodao = setlinfoDao;
        this.setldetldao = setldetldao;
        this.chrgtmpdao = chrgtmpdao;
        this.logDao = logDao;
        this.exec = exec;
        this.dismissService = dismissService;
        this.zySrvc = zySrvc;
        this.cfg = cfg;
    }

    public String admissRegister(ZyPatientInfo p, ZyOrJzYbData ybData) {
        AdmMdtrtinfo admMdtrtinfo = ybData.getAdmMdtrtinfo(p);

        // 当医生有医保姓名时，优先使用医保姓名
        try {
            String ybName = doctorsAdviseDao.getYbName(admMdtrtinfo.getChfpdrName());
            if(StringUtil.isNotEmpty(ybName)) {
                log.info("门诊挂号医生{}，使用医保姓名：{}", admMdtrtinfo.getChfpdrName(), ybName);
                admMdtrtinfo.setChfpdrName(ybName);
            }
        } catch (Exception e) {
            log.error(e.getMessage());
            log.error("获取医保姓名出错：{}", admMdtrtinfo.getAtddrNo());
        }

        List<AdmDiseinfo> admDiseinfos = ybData.getAdmDiseinfo(p.getInpatientNo(), p.getAdmissTimes());
        admDiseinfos.forEach(item -> {
            item.setDiseDorNo(admMdtrtinfo.getAtddrNo());
            item.setDiseDorName(admMdtrtinfo.getChfpdrName());
        });
        ReadCardBizType readCardBizType = ReadCardBizType.get(p.getReadCardBizType());
        if (readCardBizType != ReadCardBizType.ADMISSION) {
            String bizarea = cfg.getHospArea();
            if (StringUtil.notBlank(bizarea) && bizarea.length() > 2) {
                String areaHeader = bizarea.substring(0, 2);
                if (!p.getInsuplcAdmdvs().startsWith(areaHeader)) {
                    throw new BizException(ExceptionEnum.LOGICAL_ERROR, "省外异地的患者请读社保卡登记！");
                }
            }
        }
        if (readCardBizType == ReadCardBizType.ADMISSION) {
            MdtrtCertType mdtrtCertType = MdtrtCertType.getByLabel(p.getMdtrtCertType());
            admMdtrtinfo.setMdtrtCertType(mdtrtCertType.getCode());
            if (mdtrtCertType.getCode().equals(MdtrtCertType.SOCIAL_SECURITY_CARD.getCode())) {
                String[] out = p.getReadCardResult().split("\\|");
                if (!p.getName().trim().equals(out[4].trim())) {
                    throw new BizException(ExceptionEnum.LOGICAL_ERROR, "社保卡姓名与HIS姓名不一致，请确认是否人证相符。");
                }
                admMdtrtinfo.setMdtrtCertNo(out[2] + "|" + p.getBusCardInfo());
                admMdtrtinfo.setCardSn(out[3]);
            } else if (mdtrtCertType.getCode().equals(MdtrtCertType.MEDICAL_INSURANCE_ELECTRONIC_VOUCHER.getCode())) {
                JSONObject qrinfo = JSONObject.parseObject(p.getReadCardResult());
                String userName = UriEncoder.decode(qrinfo.getString("userName"));
                if (!p.getName().trim().equals(userName.trim())) {
                    throw new BizException(ExceptionEnum.LOGICAL_ERROR, "电子医保凭证姓名与HIS姓名不一致，请确认是否人证相符。");
                }
                if (!p.getSocialNo().trim().equals(qrinfo.getString("idNo").trim())) {
                    throw new BizException(ExceptionEnum.LOGICAL_ERROR, "电子医保凭证身份证与HIS身份证不一致，请确认是否人证相符。");
                }
                admMdtrtinfo.setMdtrtCertNo(qrinfo.getString("ecToken"));
            }
        }
        if (StringUtil.isBlank(admMdtrtinfo.getDiseCodg())) {
            admMdtrtinfo.setDiseCodg(admDiseinfos.get(0).getDiagCode());
            admMdtrtinfo.setDiseName(admDiseinfos.get(0).getDiagName());
        }
        admMdtrtinfo.setInsutype(p.getInsutype());
        admMdtrtinfo.setAdmDiagDscr(admDiseinfos.get(0).getDiagName());
        admMdtrtinfo.setDscgMaindiagCode(admDiseinfos.get(0).getDiagCode());
        admMdtrtinfo.setDscgMaindiagName(admDiseinfos.get(0).getDiagName());
        admMdtrtinfo.setMatnType(p.getMatnType());
        admMdtrtinfo.setLatechbFlag(p.getLatechbFlag());
        admMdtrtinfo.setPretFlag(p.getPretFlag());
        JSONObject input = exec.makeTradeHeaderWithInsureArea(SiFunction.ADMISSION_REGISTRATION, p.getInsuplcAdmdvs());
        String ref = JSONObject.toJSONStringWithDateFormat(admMdtrtinfo, "yyyy-MM-dd HH:mm:ss");
        input.getJSONObject("input").put("mdtrtinfo", JSONObject.parseObject(ref));
        String diseRef = JSONArray.toJSONStringWithDateFormat(admDiseinfos, "yyyy-MM-dd HH:mm:ss");
        input.getJSONObject("input").put("diseinfo", JSONArray.parse(diseRef));
        JSONObject result = exec.executeTrade(input, SiFunction.ADMISSION_REGISTRATION);
        if (null == result) {
            throw new BizException(ExceptionEnum.NETWORK_ERROR, "网络异常");
        }
        Integer infcode = result.getInteger(RESULT_CODE);
        if (null == infcode) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "医保中心报错：" + result.getString("message"));
        }
        logDao.insert(new SiLog(input, result, p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), infcode, admMdtrtinfo.getPsnNo()));
        if (result.getIntValue(RESULT_CODE) == 0) {
            JSONObject output = result.getJSONObject(OUTPUT);
            String mdtrtId = output.getJSONObject("result").getString("mdtrt_id");
            p.setMdtrtId(mdtrtId);
            p.setAdmRegMsgid(input.getString("msgid"));
            dao.afterAdmissRegister(p);
            String restype = dao.selectResponceType(admMdtrtinfo.getMedType());
            dao.updateResponceType(restype, admMdtrtinfo.getMedType(), p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
            return "入院登记成功，就诊号为：" + mdtrtId;
        }
        throw new BizException(ExceptionEnum.LOGICAL_ERROR, result.getString(ERROR_MESSAGE));
    }

    public ResultVo<String> modifyAdmissionInfo(ZyPatientInfo p) {
        SiPatInfo siPatInfo = queryDao.selectSiPatInfo(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        if (null == siPatInfo || StringUtil.isBlank(siPatInfo.getMdtrtId())) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "患者医保不在院！");
        }
        List<AdmDiseinfo> admDiseinfos = dao.selectAdmDiags(p.getInpatientNo(), p.getAdmissTimes());
        if (null == admDiseinfos || admDiseinfos.isEmpty()) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "请填写医保入院诊断！");
        }
        admDiseinfos.forEach(item -> item.setMdtrtId(siPatInfo.getMdtrtId()));
        JSONObject input = exec.makeTradeHeaderWithInsureArea(SiFunction.MODIFY_ADMISSION_INFO, siPatInfo.getInsuplcAdmdvs());
        JSONObject adminfo = new JSONObject();
        adminfo.put("mdtrt_id", siPatInfo.getMdtrtId());
        adminfo.put("psn_no", siPatInfo.getPsnNo());
        adminfo.put("begntime", DateUtil.formatDatetime(p.getYbRegisterDate()));
        adminfo.put("mdtrt_cert_type", MdtrtCertType.RESIDENT_IDENTITY_CARD.getCode());
        adminfo.put("med_type", siPatInfo.getMedType());
        adminfo.put("ipt_otp_no", p.getInpatientNo());
        adminfo.put("atddr_no", p.getReferPhysician());
        adminfo.put("chfpdr_name", p.getReferPhysicianName());
        adminfo.put("adm_diag_dscr", admDiseinfos.get(0).getDiagCode());
        adminfo.put("adm_dept_codg", p.getSmallDept());
        adminfo.put("adm_dept_name", p.getSmallDeptName());
        adminfo.put("adm_bed", p.getBedNo());
        adminfo.put("dscg_maindiag_code", admDiseinfos.get(0).getDiagCode());
        adminfo.put("dscg_maindiag_name", admDiseinfos.get(0).getDiagName());
        input.getJSONObject("input").put("adminfo", adminfo);
        String diseRef = JSONArray.toJSONStringWithDateFormat(admDiseinfos, "yyyy-MM-dd HH:mm:ss");
        input.getJSONObject("input").put("diseinfo", JSONArray.parse(diseRef));
        JSONObject result = exec.executeTrade(input, SiFunction.MODIFY_ADMISSION_INFO);
        Integer infcode = result.getInteger(RESULT_CODE);
        logDao.insert(new SiLog(input, result, p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), infcode, siPatInfo.getPsnNo()));
        return SiUtil.makeReturnWithoutOutput(result, "变更住院信息成功。");
    }

    public String revokeAdmission(ZyPatientInfo p) {
        SiPatInfo siPatInfo = queryDao.selectSiPatInfo(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        if (null == siPatInfo || StringUtil.isBlank(siPatInfo.getMdtrtId())) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "此患者没有有效的医保在院信息！");
        }
        int uploadedFees = queryDao.getUploadedCount(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        if (uploadedFees > 0) {
            revokeUploadFees(p);
        }
        JSONObject input = exec.makeTradeHeaderWithInsureArea(SiFunction.REVOKE_ADMISSION, siPatInfo.getInsuplcAdmdvs());
        JSONObject data = new JSONObject();
        data.put("mdtrt_id", siPatInfo.getMdtrtId());
        data.put("psn_no", siPatInfo.getPsnNo());
        input.getJSONObject("input").put("data", data);
        JSONObject result = exec.executeTrade(input, SiFunction.REVOKE_ADMISSION);
        if (null == result) {
            throw new BizException(ExceptionEnum.NETWORK_ERROR);
        }
        Integer infcode = result.getInteger(RESULT_CODE);
        logDao.insert(new SiLog(input, result, p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), infcode, siPatInfo.getPsnNo()));
        if (null == infcode) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "医保中心报错：" + result.getString("message"));
        }
        if (infcode == 0) {
            queryDao.clearMdtrtIdForZy(siPatInfo.getPatNo(), siPatInfo.getTimes(), siPatInfo.getLedgerSn(), null);
            dao.updateResponceType("01", null, p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
            return "取消入院登记成功。";
        }
        throw new BizException(ExceptionEnum.LOGICAL_ERROR, result.getString(ERROR_MESSAGE));
    }

    public ResultVo<String> uploadFeeDetail(Overview o) {
        o.setStaffId(TokenUtil.getInstance().getTokenUserId());
        return zySrvc.uploadFeeDetail(siZyFeeUrl, o);
    }

    public ResultVo<String> multipleUpload(List<Overview> overviews) {
        for (Overview o : overviews) {
            if (o.getStatus() == 0) {
                String message = String.format("【%s床，%s，%s】未进行医保登记，无法进行费用上传。",
                        o.getBedNo(), o.getInpatientNo(), o.getName());
                return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, message);
            }
            if (invalidMultipleUploadPatient(o.getMedType())) {
                String message = String.format("【%s床，%s，%s】是工伤患者，不支持批量上传。",
                        o.getBedNo(), o.getInpatientNo(), o.getName());
                return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, message);
            }
            if (isSelfPayPatient(o.getMedType())) {
                String message = String.format("【%s床，%s，%s】是自费患者，不支持批量上传。",
                        o.getBedNo(), o.getInpatientNo(), o.getName());
                return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, message);
            }
            o.setStaffId(TokenUtil.getInstance().getTokenUserId());
        }
        return zySrvc.uploadMultiplePatientFees(siZyFeeUrl, overviews);
    }

    public ResultVo<String> injuryMultipleUpload(List<Overview> overviews) {
        for (Overview o : overviews) {
            if (o.getStatus() == 0) {
                String message = String.format("【%s床，%s，%s】未进行工伤登记，无法进行费用上传。",
                        o.getBedNo(), o.getInpatientNo(), o.getName());
                return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, message);
            }
            if (!invalidMultipleUploadPatient(o.getMedType())) {
                String message = String.format("【%s床，%s，%s】是非工伤患者，不支持批量上传。",
                        o.getBedNo(), o.getInpatientNo(), o.getName());
                return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, message);
            }
            o.setStaffId(TokenUtil.getInstance().getTokenUserId());
        }
        return zySrvc.uploadWorkInjuryFees(siZyFeeUrl, overviews);
    }

    private boolean invalidMultipleUploadPatient(String medtype) {
        MedType medType = MedType.get(medtype);
        return null == medType || medType == MedType.ZHUYUAN_ZHITI_CAN || medType == MedType.ZHUYUAN_ZHIYE_BING || medType == MedType.ZHUYUAN_KANGFU;
    }

    private boolean isSelfPayPatient(String medtype) {
        MedType medType = MedType.get(medtype);
        return medType == MedType.SELF_PAY;
    }

    public String hospitalizationPreSettlement(ZyPatientInfo p, ZyOrJzYbData ybData) {
        PreSetlmt preSetlmt = ybData.getPreSetlmt(p);
        preSetlmt.setMdtrtCertType(MdtrtCertType.RESIDENT_IDENTITY_CARD.getCode());
        preSetlmt.setPsnSetlway(p.getDbg() ? PsnSetlWay.SETTLE_BY_QUOTA.getCode() : PsnSetlWay.SETTLE_BY_ITEMS.getCode());
        preSetlmt.setAcctUsedFlag(YesOrNo.NO.getCodeStr());
        preSetlmt.setMidSetlFlag(YesOrNo.NO.getCodeStr());
        preSetlmt.setMdtrtareaAdmvs(exec.getInstitutionArea(preSetlmt.getInsuplcAdmdvs()));
        preSetlmt.setDscgTime(dao.selectActOrderDisDate(p.getInpatientNo(), p.getAdmissTimes()));

        JSONObject input = exec.makeTradeHeaderWithInsureArea(SiFunction.HOSPITALIZATION_PRE_SETTLEMENT, preSetlmt.getInsuplcAdmdvs());
        String ref = JSONObject.toJSONStringWithDateFormat(preSetlmt, "yyyy-MM-dd");
        input.getJSONObject("input").put("data", JSONObject.parseObject(ref));
        JSONObject result = exec.executeTrade(input, SiFunction.HOSPITALIZATION_PRE_SETTLEMENT);
        if (null == result) {
            throw new BizException(ExceptionEnum.NETWORK_ERROR);
        }
        Integer infcode = result.getInteger(RESULT_CODE);
        logDao.insert(new SiLog(input, result, p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), infcode, preSetlmt.getPsnNo()));
        if (null == infcode) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "医保中心报错：" + result.getString("message"));
        }
        if (infcode == 0) {
            JSONObject setlinfo = result.getJSONObject(OUTPUT).getJSONObject("setlinfo");
            String fundPay = setlinfo.getString("fund_pay_sumamt");
            dao.updateFundPay(fundPay, p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
            patientDao.recountDeposit(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
            patientDao.updateBalance(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
            String balance = patientDao.selectLedgerBalance(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
            if (StringUtil.isBlank(balance)) {
                balance = "0";
            }
            patientDao.updateZyActPatientBalance(p.getInpatientNo(), balance);
            dao.solidifyPatFees(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
            return "患者【" + p.getName() + "】院内总费用与医保中心总费用一致，医保报销金额为：￥ " + fundPay + "。";
        }
        throw new BizException(ExceptionEnum.LOGICAL_ERROR, result.getString(ERROR_MESSAGE));
    }

    public String revokeUploadFees(ZyPatientInfo p) {
        SiPatInfo siPatInfo = queryDao.selectSiPatInfo(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        if (null == siPatInfo || StringUtil.isBlank(siPatInfo.getMdtrtId())) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "此患者没有有效的医保在院信息！");
        }
        JSONObject input = exec.makeTradeHeaderWithInsureArea(SiFunction.REVOKE_HOSPITALIZATION_FEE_DETAILS, siPatInfo.getInsuplcAdmdvs());
        JSONArray data = new JSONArray();
        Integer dbUploadedSize = queryDao.getDbUploadedSize(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        if (ListUtil.isBlank(p.getDetailSns())) {
            List<Integer> unsolidSns = queryDao.getUnsolidSn(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
            if (Objects.equals(dbUploadedSize, unsolidSns.size())) {
                unsolidSns = null;
            }
            p.setDetailSns(unsolidSns);
        }
        Integer dbSolidSize = queryDao.getDbSolidSize(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        if (ListUtil.isBlank(p.getDetailSns()) || Objects.equals(dbSolidSize, dbUploadedSize)) {
            p.setDetailSns(null);
            JSONObject item = new JSONObject();
            item.put("feedetl_sn", "0000");
            item.put("mdtrt_id", siPatInfo.getMdtrtId());
            item.put("psn_no", siPatInfo.getPsnNo());
            data.add(item);
        } else {
            p.getDetailSns().forEach(detailSn -> {
                JSONObject item = new JSONObject();
                item.put("feedetl_sn", detailSn);
                item.put("mdtrt_id", siPatInfo.getMdtrtId());
                item.put("psn_no", siPatInfo.getPsnNo());
                data.add(item);
            });
        }
        input.getJSONObject("input").put("data", data);
        JSONObject result = exec.executeTrade(input, SiFunction.REVOKE_HOSPITALIZATION_FEE_DETAILS);
        if (null == result) {
            throw new BizException(ExceptionEnum.NETWORK_ERROR);
        }
        Integer infcode = result.getInteger(RESULT_CODE);
        logDao.insert(new SiLog(input, result, p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), infcode, siPatInfo.getPsnNo()));
        if (null == infcode) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "医保中心报错：" + result.getString("message"));
        }
        if (infcode == 0) {
            QueryWrapper<SiChargeTemp> wrapper = new QueryWrapper<>();
            wrapper.eq("pat_no", p.getInpatientNo());
            wrapper.eq("times", p.getAdmissTimes());
            wrapper.eq("ledger_sn", p.getLedgerSn());
            if (null == p.getDetailSns() || p.getDetailSns().isEmpty()) {
                dao.revokeAllUploadFee(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
                dao.resetFundpayAmt(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
                chrgtmpdao.delete(wrapper);
            } else {
                wrapper.in("feedetl_sn", p.getDetailSns());
                chrgtmpdao.delete(wrapper);
                dao.revokePartUploadFee(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), p.getDetailSns());
            }
            return "撤销费用上传成功。";
        }
        throw new BizException(ExceptionEnum.LOGICAL_ERROR, result.getString(ERROR_MESSAGE));
    }

    public ResultVo<String> pairNegativeFee(NotUploadedFee fee) {
        if (null == fee.getOriDetailSn()) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "请先处理违规退费。");
        }
        SiPatInfo siPatInfo = queryDao.selectSiPatInfo(fee.getPatNo(), fee.getTimes(), fee.getLedgerSn());
        JSONObject input = exec.makeTradeHeaderWithInsureArea(SiFunction.REVOKE_HOSPITALIZATION_FEE_DETAILS,
                siPatInfo.getInsuplcAdmdvs());
        JSONArray data = new JSONArray();
        JSONObject item = new JSONObject();
        item.put("feedetl_sn", fee.getOriDetailSn());
        item.put("mdtrt_id", siPatInfo.getMdtrtId());
        item.put("psn_no", siPatInfo.getPsnNo());
        data.add(item);
        input.getJSONObject("input").put("data", data);
        JSONObject result = exec.executeTrade(input, SiFunction.REVOKE_HOSPITALIZATION_FEE_DETAILS);
        if (null == result) {
            return ResultVoUtil.fail(ExceptionEnum.NETWORK_ERROR);
        }
        Integer infcode = result.getInteger(RESULT_CODE);
        logDao.insert(new SiLog(input, result, fee.getPatNo(), fee.getTimes(), fee.getLedgerSn(), infcode, siPatInfo.getPsnNo()));
        if (null == infcode) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "医保中心报错：" + result.getString("message"));
        }
        if (infcode == 0) {
            QueryWrapper<SiChargeTemp> wrapper = new QueryWrapper<>();
            wrapper.eq("pat_no", fee.getPatNo());
            wrapper.eq("times", fee.getTimes());
            wrapper.eq("ledger_sn", fee.getLedgerSn());
            wrapper.eq("feedetl_sn", fee.getOriDetailSn());
            dao.updateTransFlag(fee.getPatNo(), fee.getTimes(), fee.getDetailSn(), fee.getOriDetailSn());
            chrgtmpdao.delete(wrapper);
            return ResultVoUtil.success("退费处理成功。");
        }
        return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, result.getString(ERROR_MESSAGE));
    }

    public ResultVo<String> revokeSingleCharge(String patNo, int times, int ledgerSn, int detailSn) {
        SiPatInfo siPatInfo = queryDao.selectSiPatInfo(patNo, times, ledgerSn);
        JSONObject input = exec.makeTradeHeaderWithInsureArea(SiFunction.REVOKE_HOSPITALIZATION_FEE_DETAILS,
                siPatInfo.getInsuplcAdmdvs());
        JSONArray data = new JSONArray();
        JSONObject item = new JSONObject();
        item.put("feedetl_sn", detailSn);
        item.put("mdtrt_id", siPatInfo.getMdtrtId());
        item.put("psn_no", siPatInfo.getPsnNo());
        data.add(item);
        input.getJSONObject("input").put("data", data);
        JSONObject result = exec.executeTrade(input, SiFunction.REVOKE_HOSPITALIZATION_FEE_DETAILS);
        if (null == result) {
            return ResultVoUtil.fail(ExceptionEnum.NETWORK_ERROR);
        }
        Integer infcode = result.getInteger(RESULT_CODE);
        logDao.insert(new SiLog(input, result, patNo, times, ledgerSn, infcode, siPatInfo.getPsnNo()));
        if (null == infcode) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "医保中心报错：" + result.getString("message"));
        }
        if (infcode == 0) {
            QueryWrapper<SiChargeTemp> wrapper = new QueryWrapper<>();
            wrapper.eq("pat_no", patNo);
            wrapper.eq("times", times);
            wrapper.eq("ledger_sn", ledgerSn);
            wrapper.eq("feedetl_sn", detailSn);
            chrgtmpdao.delete(wrapper);
            dao.updateTransFlagToZero(patNo, times, detailSn);
        }
        return ResultVoUtil.success("处理成功。");
    }

    public String dischargeProcessing(ZyPatientInfo p, ZyOrJzYbData ybData) {
        if (null == p.getLedgerSn()) {
            p.setLedgerSn(dao.selectMaxLedgerSn(p.getInpatientNo(), p.getAdmissTimes()));
        }
        Dscginfo dscginfo = ybData.getDscginfo(p);
        List<SetlDiseinfo> setlDises = ybData.getSetlDiseinfo(p);
        if (null == p.getStaffId()) {
            p.setStaffId(TokenUtil.getInstance().getTokenUserId());
        }
        if (StringUtil.isBlank(p.getAcctUsedFlag())) {
            p.setAcctUsedFlag(YesOrNo.NO.getCodeStr());
        }

        dscginfo.setDiseCodg(setlDises.get(0).getDiagCode());
        dscginfo.setDiseName(setlDises.get(0).getDiagName());
        dscginfo.setDscgWay(DscgWay.ORDER_TO_LEAVE_HOSPITAL.getCode());
        String ref = JSONObject.toJSONStringWithDateFormat(dscginfo, "yyyy-MM-dd HH:mm:ss");
        JSONObject input = exec.makeTradeHeaderWithInsureArea(SiFunction.DISCHARGE_PROCESSING,
                dao.selectAdmdvs(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn()));
        input.getJSONObject("input").put("dscginfo", JSONObject.parseObject(ref));
        String diseRef = JSONArray.toJSONStringWithDateFormat(setlDises, "yyyy-MM-dd HH:mm:ss");
        input.getJSONObject("input").put("diseinfo", JSONArray.parse(diseRef));
        JSONObject result = exec.executeTrade(input, SiFunction.DISCHARGE_PROCESSING);
        if (null == result) {
            throw new BizException(ExceptionEnum.NETWORK_ERROR);
        }
        Integer infcode = result.getInteger(RESULT_CODE);
        logDao.insert(new SiLog(input, result, p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), infcode, dscginfo.getPsnNo()));
        if (null == infcode) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "医保中心报错：" + result.getString("message"));
        }
        if (infcode == 0) {
            if (p.getStopHere()) {
                return "出院办理成功。";
            }
            p.setDismissDate(dscginfo.getEndtime());
            return inpatientSettlement(p, ybData);
        }
        String errMsg = result.getString(ERROR_MESSAGE);
        if (null != errMsg && errMsg.contains("该人员已经是出院状态")) {
            p.setDismissDate(dscginfo.getEndtime());
            return inpatientSettlement(p, ybData);
        }
        throw new BizException(ExceptionEnum.LOGICAL_ERROR, errMsg);
    }

    public String revokeDischarge(ZyPatientInfo p) {
        SiPatInfo siPatInfo = queryDao.selectSiPatInfo(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        if (null == siPatInfo || StringUtil.isBlank(siPatInfo.getMdtrtId())) {
            throw new BizException("此患者没有在院的医保业务。");
        }
        p.setMdtrtId(siPatInfo.getMdtrtId());
        JSONObject input = exec.makeTradeHeaderWithInsureArea(SiFunction.REVOKE_DISCHARGE, siPatInfo.getInsuplcAdmdvs());
        JSONObject data = new JSONObject();
        data.put("mdtrt_id", siPatInfo.getMdtrtId());
        data.put("psn_no", siPatInfo.getPsnNo());
        input.getJSONObject("input").put("data", data);
        JSONObject result = exec.executeTrade(input, SiFunction.REVOKE_DISCHARGE);
        if (null == result) {
            throw new BizException(ExceptionEnum.NETWORK_ERROR);
        }
        Integer infcode = result.getInteger(RESULT_CODE);
        logDao.insert(new SiLog(input, result, p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), infcode, siPatInfo.getPsnNo()));
        if (null == infcode) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "医保中心报错：" + result.getString("message"));
        }
        if (infcode == 0) {
            dao.deleteSetlInfo(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
            dao.deleteSetlDetail(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
            transbackTables(p);
            return "取消医保出院办理成功。";
        }
        throw new BizException(ExceptionEnum.LOGICAL_ERROR, result.getString(ERROR_MESSAGE));
    }

    private void fillBeginTimeAndEndTime(ZyPatientInfo p) {
        if (null == p.getYbRegisterDate()) {
            Date beginTime = dismissService.getBegntime(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), "zy_actpatient");
            p.setYbRegisterDate(beginTime);
        }
        if (null == p.getDismissDate()) {
            Date disdate = dao.selectDisdate(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
            if (null == disdate) {
                throw new BizException(ExceptionEnum.LOGICAL_ERROR, "请先提交出院申请！");
            }
            p.setDismissDate(disdate);
        }
    }

    private String inpatientSettlement(ZyPatientInfo p, ZyOrJzYbData ybData) {
        fillBeginTimeAndEndTime(p);
        PreSetlmt preSetlmt = ybData.getSetlmt(p);
        ReadCardBizType readCardBizType = ReadCardBizType.get(p.getReadCardBizType());
        if (readCardBizType == ReadCardBizType.SETTLEMENT) {
            MdtrtCertType mdtrtCertType = MdtrtCertType.getByLabel(p.getMdtrtCertType());
            preSetlmt.setMdtrtCertType(mdtrtCertType.getCode());
            if (mdtrtCertType.getCode().equals(MdtrtCertType.SOCIAL_SECURITY_CARD.getCode())) {
                String[] out = p.getReadCardResult().split("\\|");
                if (!p.getName().trim().equals(out[4].trim())) {
                    revokeDischarge(p);
                    throw new BizException(ExceptionEnum.LOGICAL_ERROR, "社保卡姓名与HIS姓名不一致，请确认是否人证相符。");
                }
                preSetlmt.setMdtrtCertNo(out[2] + "|" + p.getBusCardInfo());
                preSetlmt.setCardSn(out[3]);
            } else if (mdtrtCertType.getCode().equals(MdtrtCertType.MEDICAL_INSURANCE_ELECTRONIC_VOUCHER.getCode())) {
                JSONObject qrinfo = JSONObject.parseObject(p.getReadCardResult());
                String userName = UriEncoder.decode(qrinfo.getString("userName"));
                if (!p.getName().trim().equals(userName.trim())) {
                    revokeDischarge(p);
                    throw new BizException(ExceptionEnum.LOGICAL_ERROR, "电子医保凭证姓名与HIS姓名不一致，请确认是否人证相符。");
                }
                if (!p.getSocialNo().trim().equals(qrinfo.getString("idNo").trim())) {
                    revokeDischarge(p);
                    throw new BizException(ExceptionEnum.LOGICAL_ERROR, "电子医保凭证身份证与HIS身份证不一致，请确认是否人证相符。");
                }
                preSetlmt.setMdtrtCertNo(qrinfo.getString("ecToken"));
            }
        }
        preSetlmt.setPsnSetlway(p.getDbg() ? PsnSetlWay.SETTLE_BY_QUOTA.getCode() : PsnSetlWay.SETTLE_BY_ITEMS.getCode());
        preSetlmt.setAcctUsedFlag(p.getAcctUsedFlag());
        preSetlmt.setMidSetlFlag(YesOrNo.NO.getCodeStr());
        preSetlmt.setInvono(SnowFlakeId.instance().nextId());
        JSONObject input = exec.makeTradeHeaderWithInsureArea(SiFunction.HOSPITALIZATION_SETTLEMENT, preSetlmt.getInsuplcAdmdvs());
        String ref = JSONObject.toJSONString(preSetlmt);
        input.getJSONObject("input").put("data", JSONObject.parseObject(ref));
        JSONObject result = exec.executeTrade(input, SiFunction.HOSPITALIZATION_SETTLEMENT);
        if (null == result) {
            revokeDischarge(p);
            throw new BizException(ExceptionEnum.NETWORK_ERROR);
        }
        Integer infcode = result.getInteger(RESULT_CODE);
        logDao.insert(new SiLog(input, result, p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), infcode, preSetlmt.getPsnNo()));
        if (null == infcode) {
            revokeDischarge(p);
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "医保中心报错：" + result.getString("message"));
        }
        if (infcode == 0) {
            p.setInsuplcAdmdvs(preSetlmt.getInsuplcAdmdvs());
            p.setInsuplcAdmdvsName(queryDao.getInsuplcAdmdvsName(p));
            return executeHisSettlement(result.getJSONObject(OUTPUT), p);
        }
        String message = result.getString(ERROR_MESSAGE);
        if (message.contains("服务提供者后端服务响应超时")) {
            return timeoutSettlement(p);
        }
        revokeDischarge(p);
        throw new BizException(ExceptionEnum.LOGICAL_ERROR, message);
    }

    public String timeoutSettlement(ZyPatientInfo p) {
        SiPatInfo siPatInfo = queryDao.selectSiPatInfo(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        if (null == siPatInfo) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "[t_si_pat_info]没有此患者的就诊信息。");
        }
        fillBeginTimeAndEndTime(p);
        p.setInsuplcAdmdvs(siPatInfo.getInsuplcAdmdvs());
        p.setInsuplcAdmdvsName(siPatInfo.getInsuplcAdmdvsName());
        JSONObject output = queryService.execute5203(siPatInfo);
        return executeHisSettlement(output, p);
    }

    private String executeHisSettlement(JSONObject output, ZyPatientInfo p) {
        dao.deleteSetlDetail(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        JSONObject setlinfo = output.getJSONObject("setlinfo");
        SiSetlinfo setlinfoEntity = JSONObject.parseObject(setlinfo.toJSONString(), SiSetlinfo.class);
        setlinfoEntity.setPatNo(p.getInpatientNo());
        setlinfoEntity.setTimes(p.getAdmissTimes());
        setlinfoEntity.setLedgerSn(p.getLedgerSn());
        setlinfoEntity.setStaffId(TokenUtil.getInstance().getTokenUserId());
        setlinfoEntity.setRevoked(YesOrNo.NO.getCode());
        setlinfoEntity.setSetlType(ClrType.INPATIENT.getCode());
        setlinfoEntity.setInsuplcAdmdvs(p.getInsuplcAdmdvs());
        setlinfoEntity.setInsuplcAdmdvsName(p.getInsuplcAdmdvsName());
        setlinfoEntity.setBegntime(p.getYbRegisterDate());
        setlinfoEntity.setEndtime(p.getDismissDate());
        setlinfoEntity.setHiPaymtd(CommonUtil.calcHiPaymtd(setlinfoEntity.getInsuplcAdmdvs(),setlinfoEntity.getMedType(),setlinfoEntity.getInsutype(),setlinfoEntity.getSetlTime()));
        setlinfoEntity.setMedinsType(MedInsTypeUtil.getMedInsType(setlinfoEntity));
        if (null == setlinfoEntity.getPsnPartAmt()) {
            Double psnPay = setlinfo.getDouble("psn_pay");
            setlinfoEntity.setPsnPartAmt(psnPay);
        }
        if (null == setlinfoEntity.getPsnCashPay()) {
            Double cashPay = setlinfo.getDouble("cash_payamt");
            setlinfoEntity.setPsnCashPay(cashPay);
        }
        JSONArray setldetail = output.getJSONArray("setldetail");
        BigDecimal hospitalPart = BigDecimal.ZERO;
        int setlCount = setlinfodao.getSetlCount(setlinfoEntity.getSetlId());
        for (int i = 0; i < setldetail.size(); i++) {
            SiSetldetail setldetailEntity = JSONObject.parseObject(setldetail.getJSONObject(i).toJSONString(), SiSetldetail.class);
            setldetailEntity.setSetlId(setlinfoEntity.getSetlId());
            setldetailEntity.setSortNo(i + 1);
            setldetailEntity.setPatNo(p.getInpatientNo());
            setldetailEntity.setTimes(p.getAdmissTimes());
            setldetailEntity.setLedgerSn(p.getLedgerSn());
            if ("999996".equals(setldetailEntity.getFundPayType()) || "999996".equals(setldetailEntity.getSetlProcInfo())) {
                hospitalPart = hospitalPart.add(setldetailEntity.getFundPayamt());
            }
            if (setlCount == 0) {
                setldetldao.insert(setldetailEntity);
            }
        }
        setlinfoEntity.setHospPartAmt(hospitalPart.doubleValue());
        if (setlCount == 0) {
            setlinfodao.insert(setlinfoEntity);
        }
        setlinfodao.updateSiZyInfoSetlId(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(),
                setlinfoEntity.getSetlId(), setlinfoEntity.getMedinsSetlId(), setlinfoEntity.getMedinsSetlId());
        setlinfodao.updateApplySettled(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), YesOrNo.YES.getCode());

        // 跨省异地数据不参与对账，对总账时无需统计进去。
//        String hospArea = cfg.getHospArea();
//        if (null != hospArea && hospArea.length() > 2) {
//            String areaHeader = hospArea.substring(0, 2);
//            if (setlinfoEntity.getFundPaySumamt() > 0 && setlinfoEntity.getInsuplcAdmdvs().startsWith(areaHeader)) {
//                InstStlLdgChk ldgChk = new InstStlLdgChk();
//                ldgChk.setInsutype(setlinfoEntity.getInsutype());
//                ldgChk.setClrType(setlinfoEntity.getClrType());
//                ldgChk.setSetlOptins(setlinfoEntity.getClrOptins());
//                ldgChk.setStmtBegndate(setlinfoEntity.getSetlTime());
//                ldgChk.setStmtEnddate(setlinfoEntity.getSetlTime());
//                ResultVo<String> totalLedgerCheck = manageService.institutionSettlementLedgerCheck(ldgChk);
//                if (totalLedgerCheck.getCode() != ExceptionEnum.SUCCESS.getCode()) {
//                    InstStlLdgChk detlChk = new InstStlLdgChk();
//                    detlChk.setSetlOptins(ldgChk.getSetlOptins());
//                    detlChk.setStmtBegndate(ldgChk.getStmtBegndate());
//                    detlChk.setStmtEnddate(ldgChk.getStmtEnddate());
//                    detlChk.setClrType(ldgChk.getClrType());
//                    detlChk.setRefdSetlFlag(YesOrNo.NO.getCodeStr());
//                    ResultVo<List<InsSetlDetlChkRslt>> detailLedgerCheck = manageService.institutionSettlementDetailCheck(detlChk);
//                    if (detailLedgerCheck.getCode() != ExceptionEnum.SUCCESS.getCode()) {
//                        revokeSettlement(p);
//                        throw new BizException(ExceptionEnum.LOGICAL_ERROR, detailLedgerCheck.getMessage());
//                    }
//                    for (InsSetlDetlChkRslt insSetlDetlChkRslt : detailLedgerCheck.getData()) {
//                        if (Objects.equals(insSetlDetlChkRslt.getSetlId(), setlinfoEntity.getSetlId())) {
//                            if (!Objects.equals(insSetlDetlChkRslt.getStmtRslt(), "0")) {
//                                revokeSettlement(p);
//                                throw new BizException(ExceptionEnum.LOGICAL_ERROR, insSetlDetlChkRslt.getMemo());
//                            }
//                            break;
//                        }
//                    }
//                }
//            }
//        }

        queryService.saveCumInfo(setlinfoEntity);
        if (p.getLedgerSn() == 0) {
            return StrUtil.format("出院结算成功。患者总费用：{}。其中基金支付：{}；个账支付：{}；现金支付：{}",
                    setlinfoEntity.getMedfeeSumamt(), setlinfoEntity.getFundPaySumamt(), setlinfoEntity.getAcctPay(), setlinfoEntity.getPsnCashPay());
        }

        MedinsSettleFee settleFee = new MedinsSettleFee();
        if (p.getMidSetl()) {
            settleFee.setZjdzDatetime(p.getZjdzDatetime());
        }
        settleFee.setMidSetl(p.getMidSetl());
        settleFee.setStaffId(TokenUtil.getInstance().getTokenUserId());
        settleFee.setInpatientNo(p.getInpatientNo());
        settleFee.setAdmissTimes(p.getAdmissTimes());
        settleFee.setBedNo(p.getBedNo());
        settleFee.setWardCode(p.getAdmissWard());
        settleFee.setDeptCode(p.getAdmissDept());
        settleFee.setTotalCost(setlinfo.getString("medfee_sumamt"));
        settleFee.setTczf(setlinfo.getString("fund_pay_sumamt"));
        settleFee.setHospitalPay(setlinfo.getString("hosp_part_amt"));
        settleFee.setGrzhzf(setlinfo.getString("acct_pay"));
        settleFee.setDbzf(setlinfo.getString("hifmi_pay"));
        settleFee.setXjzf(String.valueOf(setlinfoEntity.getPsnCashPay()));
        settleFee.setTable(p.getTable());
        return dismissService.executeDischarging(settleFee);
    }

    public String revokeSettlement(ZyPatientInfo p) {
        if (null == p.getStaffId()) {
            p.setStaffId(TokenUtil.getInstance().getTokenUserId());
        }
        if (null == p.getLedgerSn()) {
            p.setLedgerSn(dao.selectMaxLedgerSn(p.getInpatientNo(), p.getAdmissTimes()));
        }
        SiPatInfo siPatInfo = queryDao.selectSiPatInfo(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        if (null == siPatInfo || StringUtil.isBlank(siPatInfo.getSetlId())) {
            return revokeDischarge(p);
        }
        p.setMdtrtId(siPatInfo.getMdtrtId());
        JSONObject input = exec.makeTradeHeaderWithInsureArea(SiFunction.REVOKE_HOSPITALIZATION_SETTLEMENT, siPatInfo.getInsuplcAdmdvs());
        JSONObject data = new JSONObject();
        data.put("mdtrt_id", siPatInfo.getMdtrtId());
        data.put("setl_id", siPatInfo.getSetlId());
        data.put("psn_no", siPatInfo.getPsnNo());
        input.getJSONObject("input").put("data", data);
        JSONObject result = exec.executeTrade(input, SiFunction.REVOKE_HOSPITALIZATION_SETTLEMENT);
        if (null == result) {
            throw new BizException(ExceptionEnum.NETWORK_ERROR);
        }
        Integer infcode = result.getInteger(RESULT_CODE);
        logDao.insert(new SiLog(input, result, p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), infcode, siPatInfo.getPsnNo()));
        if (null == infcode) {
            throw new BizException(ExceptionEnum.NETWORK_ERROR);
        }
        if (infcode != 0) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, result.getString("err_msg"));
        }
        String rvkDischrgRslt = revokeDischarge(p);
        dao.updateRvkSetlMsgid(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), input.getString("msgid"));
        setlinfodao.updateApplySettled(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), YesOrNo.NO.getCode());
        transbackTables(p);
        return rvkDischrgRslt;
    }

    private void transbackTables(ZyPatientInfo p) {
        dao.deleteSiSetlFeeDetl(p.getMdtrtId());
        dao.deleteZyLedgerFileYb(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        dao.deleteZyReceipt(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        dao.updateSettleType(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        dao.updateChargeStatus(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
    }

    public String revokeWorkInjuryUploadFees(ZyPatientInfo p) {
        SiPatInfo siPatInfo = queryDao.selectSiPatInfo(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        if (null == siPatInfo || StringUtil.isBlank(siPatInfo.getMdtrtId())) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "此患者没有有效的工伤在院信息！");
        }
        
        // 根据传入的明细号决定撤销方式
        if (null == p.getDetailSns() || p.getDetailSns().isEmpty()) {
            // 撤销全部：只传门诊/住院流水号，处方号和处方流水号为空
            return revokeAllWorkInjuryFees(p, siPatInfo);
        } else {
            // 撤销指定明细：需要遍历每个明细号
            return revokeSpecificWorkInjuryFees(p, siPatInfo);
        }
    }
    
    /**
     * 撤销全部工伤费用
     */
    private String revokeAllWorkInjuryFees(ZyPatientInfo p, SiPatInfo siPatInfo) {
        // 构建工伤撤销请求（处方明细撤销）
        JSONObject input = new JSONObject();
        input.put("action", "transaction");
        input.put("transactionName", "2205");
        
        JSONObject businessParams = new JSONObject();
        businessParams.put("ipt_otp_no", siPatInfo.getMdtrtId());  // 门诊/住院流水号
        businessParams.put("rxno", "");  // 处方号为空
        businessParams.put("feedetl_sn", "");  // 处方流水号为空
        
        input.put("businessParams", businessParams);
        
        // 调用工伤撤销接口
        JSONObject result = exec.executeWorkInjuryTrade(input);
        
        if (null == result) {
            throw new BizException(ExceptionEnum.NETWORK_ERROR, "工伤撤销接口调用失败");
        }
        
        // 工伤接口返回结果处理（支持模拟接口和真实接口切换）
        Integer infcode = extractWorkInjuryResultCode(result);
        
        if (null == infcode) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "工伤撤销接口返回结果解析失败");
        }
        
        // 构造工伤日志input（参考工伤预结算写法）
        JSONObject logInput = new JSONObject();
        if (result != null && result.containsKey("data")) {
            JSONObject data = result.getJSONObject("data");
            if (data.containsKey("transformed_parameters")) {
                JSONObject transformedParams = data.getJSONObject("transformed_parameters");
                logInput.put("infno", transformedParams.getString("infno"));
                logInput.put("insuplc_admdvs", "");
                logInput.put("msgid", transformedParams.getString("msgid"));
                logInput.put("opter", transformedParams.getString("opter"));
            } else {
                logInput.put("infno", "2205"); // 工伤撤销接口号
                logInput.put("insuplc_admdvs", "");
                logInput.put("msgid", "WORK_INJURY_" + System.currentTimeMillis());
                logInput.put("opter", p.getStaffId());
            }
        } else {
            logInput.put("infno", "2205");
            logInput.put("insuplc_admdvs", "");
            logInput.put("msgid", "WORK_INJURY_" + System.currentTimeMillis());
            logInput.put("opter", p.getStaffId());
        }
        logDao.insert(new SiLog(logInput, result, p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), infcode, siPatInfo.getPsnNo()));
        
        if (infcode == 0) {
            // 撤销成功，更新数据库状态
            dao.revokeAllUploadFee(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
            dao.resetFundpayAmt(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
            
            QueryWrapper<SiChargeTemp> wrapper = new QueryWrapper<>();
            wrapper.eq("pat_no", p.getInpatientNo());
            wrapper.eq("times", p.getAdmissTimes());
            wrapper.eq("ledger_sn", p.getLedgerSn());
            chrgtmpdao.delete(wrapper);
            
            return "工伤撤销费用成功。";
        } else {
            String errorMsg = extractWorkInjuryErrorMessage(result);
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "工伤撤销失败：" + errorMsg);
        }
    }
    
    /**
     * 撤销指定工伤费用明细
     */
    private String revokeSpecificWorkInjuryFees(ZyPatientInfo p, SiPatInfo siPatInfo) {
        List<Integer> successDetailSns = new ArrayList<>();
        List<Integer> failedDetailSns = new ArrayList<>();
        
        // 遍历每个明细号，逐个撤销
        for (Integer detailSn : p.getDetailSns()) {
            // 构建工伤撤销请求（处方明细撤销）
            JSONObject input = new JSONObject();
            input.put("action", "transaction");
            input.put("transactionName", "2205");
            
            JSONObject businessParams = new JSONObject();
            businessParams.put("ipt_otp_no", siPatInfo.getMdtrtId());  // 门诊/住院流水号
            
            // 获取对应的处方号
            String rxno = dao.selectRxnoByDetailSn(p.getInpatientNo(), p.getAdmissTimes(), detailSn);
            
            if (StringUtil.isBlank(rxno)) {
                // 如果处方号为空，只传门诊/住院流水号，不传处方号和处方流水号
                // 这样会撤销该门诊/住院流水号下所有未结算处方
                businessParams.put("rxno", "");
                businessParams.put("feedetl_sn", "");
            } else {
                // 如果处方号有值，传完整的参数
                businessParams.put("rxno", rxno);
                businessParams.put("feedetl_sn", detailSn.toString());
            }
            
            input.put("businessParams", businessParams);
            
            // 调用工伤撤销接口
            JSONObject result = exec.executeWorkInjuryTrade(input);
            
            if (null == result) {
                log.error("工伤撤销接口调用失败，明细：{}", detailSn);
                failedDetailSns.add(detailSn);
                continue; // 继续撤销下一个，不中断整个流程
            }
            
            // 工伤接口返回结果处理（支持模拟接口和真实接口切换）
            Integer infcode = extractWorkInjuryResultCode(result);
            
            if (null == infcode) {
                log.error("工伤撤销接口返回结果解析失败，明细：{}", detailSn);
                failedDetailSns.add(detailSn);
                continue;
            }
            
            // 构造工伤日志input（参考工伤预结算写法）
            JSONObject logInput = new JSONObject();
            if (result != null && result.containsKey("data")) {
                JSONObject data = result.getJSONObject("data");
                if (data.containsKey("transformed_parameters")) {
                    JSONObject transformedParams = data.getJSONObject("transformed_parameters");
                    logInput.put("infno", transformedParams.getString("infno"));
                    logInput.put("insuplc_admdvs", "");
                    logInput.put("msgid", transformedParams.getString("msgid"));
                    logInput.put("opter", transformedParams.getString("opter"));
                } else {
                    logInput.put("infno", "2205"); // 工伤撤销接口号
                    logInput.put("insuplc_admdvs", "");
                    logInput.put("msgid", "WORK_INJURY_" + System.currentTimeMillis());
                    logInput.put("opter", p.getStaffId());
                }
            } else {
                logInput.put("infno", "2205");
                logInput.put("insuplc_admdvs", "");
                logInput.put("msgid", "WORK_INJURY_" + System.currentTimeMillis());
                logInput.put("opter", p.getStaffId());
            }
            logDao.insert(new SiLog(logInput, result, p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), infcode, siPatInfo.getPsnNo()));
            
            if (infcode == 0) {
                log.info("工伤撤销成功，明细：{}", detailSn);
                successDetailSns.add(detailSn);
            } else {
                String errorMsg = extractWorkInjuryErrorMessage(result);
                log.error("工伤撤销失败，明细：{}，错误：{}", detailSn, errorMsg);
                failedDetailSns.add(detailSn);
            }
        }
        
        // 根据撤销结果分别更新数据库状态
        if (successDetailSns.isEmpty()) {
            log.warn("工伤撤销全部失败，患者：{}，明细数：{}", p.getInpatientNo(), p.getDetailSns().size());
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "工伤撤销全部失败，请检查网络连接和接口状态。");
        }
        
        // 更新数据库
        if (successDetailSns.size() == p.getDetailSns().size()) {
            // 全部撤销成功
            log.info("工伤撤销全部成功，患者：{}，明细数：{}", p.getInpatientNo(), successDetailSns.size());
            dao.revokePartUploadFee(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), successDetailSns);
            
            QueryWrapper<SiChargeTemp> wrapper = new QueryWrapper<>();
            wrapper.eq("pat_no", p.getInpatientNo());
            wrapper.eq("times", p.getAdmissTimes());
            wrapper.eq("ledger_sn", p.getLedgerSn());
            wrapper.in("feedetl_sn", successDetailSns);
            chrgtmpdao.delete(wrapper);
        } else {
            // 部分撤销成功，只更新成功撤销的明细
            log.info("工伤撤销部分成功，患者：{}，成功：{}，失败：{}", 
                    p.getInpatientNo(), successDetailSns.size(), failedDetailSns.size());
            dao.revokePartUploadFee(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), successDetailSns);
            
            QueryWrapper<SiChargeTemp> wrapper = new QueryWrapper<>();
            wrapper.eq("pat_no", p.getInpatientNo());
            wrapper.eq("times", p.getAdmissTimes());
            wrapper.eq("ledger_sn", p.getLedgerSn());
            wrapper.in("feedetl_sn", successDetailSns);
            chrgtmpdao.delete(wrapper);
        }
        
        log.info("工伤撤销流程完成，患者：{}，成功撤销：{}，失败撤销：{}", 
                p.getInpatientNo(), successDetailSns.size(), failedDetailSns.size());
        
        if (failedDetailSns.isEmpty()) {
            return "工伤撤销费用成功。";
        } else {
            return String.format("工伤撤销部分成功。成功：%d，失败：%d", successDetailSns.size(), failedDetailSns.size());
        }
    }

    /**
     * 工伤接口返回结果代码提取（支持模拟接口和真实接口切换）
     */
    private Integer extractWorkInjuryResultCode(JSONObject result) {
        if (result == null) {
            return null;
        }
        
        // 模拟接口返回结构：result.getJSONObject("data").getJSONObject("data").getString("infcode")
        // 真实接口返回结构：result.getJSONObject("data").getString("infcode")
        
        // 模拟接口处理（当前使用）
        if (result.containsKey("data") && result.getJSONObject("data").containsKey("data")) {
            JSONObject innerData = result.getJSONObject("data").getJSONObject("data");
            return innerData != null ? innerData.getInteger(RESULT_CODE) : null;
        }
        // 真实接口处理（注释掉，需要时手动切换）
        // else if (result.containsKey("data")) {
        //     JSONObject data = result.getJSONObject("data");
        //     return data != null ? data.getInteger(RESULT_CODE) : null;
        // }
        
        return null;
    }

    /**
     * 工伤接口错误信息提取（支持模拟接口和真实接口切换）
     */
    private String extractWorkInjuryErrorMessage(JSONObject result) {
        if (result == null) {
            return "未知错误";
        }
        
        // 模拟接口返回结构：result.getJSONObject("data").getJSONObject("data").getString("err_msg")
        // 真实接口返回结构：result.getJSONObject("data").getString("err_msg")
        
        // 模拟接口处理（当前使用）
        if (result.containsKey("data") && result.getJSONObject("data").containsKey("data")) {
            JSONObject innerData = result.getJSONObject("data").getJSONObject("data");
            if (innerData != null) {
                String errorMsg = innerData.getString(ERROR_MESSAGE);
                return StringUtil.isBlank(errorMsg) ? "未知错误" : errorMsg;
            }
        }
        // 真实接口处理（注释掉，需要时手动切换）
        // else if (result.containsKey("data")) {
        //     JSONObject data = result.getJSONObject("data");
        //     if (data != null) {
        //         String errorMsg = data.getString(ERROR_MESSAGE);
        //         return StringUtil.isBlank(errorMsg) ? "未知错误" : errorMsg;
        //     }
        // }
        
        return "未知错误";
    }
}
