package thyyxxk.webserver.service.medicalinsurance;

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 thyyxxk.webserver.config.exception.ExceptionEnum;
import thyyxxk.webserver.constants.sidicts.*;
import thyyxxk.webserver.constants.YesOrNo;
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.manage.InsSetlDetlChkRslt;
import thyyxxk.webserver.entity.medicalinsurance.manage.InstStlLdgChk;
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.*;

/**
 * @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 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 SiManageService manageService;
    @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, SiManageService manageService) {
        this.dao = dao;
        this.patientDao = patientDao;
        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.manageService = manageService;
    }

    public ResultVo<String> admissRegister(ZyPatientInfo p) {
        AdmMdtrtinfo admMdtrtinfo = dao.selectAdmMdtrtinfo(p.getInpatientNo(), p.getLedgerSn());
        if (null == admMdtrtinfo) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "此患者没有有效的参保信息！");
        }
        Date setlEndTime = dao.selectSetlEndTime(p.getInpatientNo(), p.getAdmissTimes());
        if (null != setlEndTime && setlEndTime.after(admMdtrtinfo.getBegntime())) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "此患者有 " +
                    DateUtil.formatDatetime(setlEndTime, "yyyy-MM-dd") + " 的医保结算条目，不可重复登记。");
        }
        if (StringUtil.isBlank(admMdtrtinfo.getChfpdrName())) {
            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.setDiseDorNo(admMdtrtinfo.getAtddrNo());
            item.setDiseDorName(admMdtrtinfo.getChfpdrName());
        });
        ReadCardBizType readCardBizType = ReadCardBizType.get(p.getReadCardBizType());
        if (readCardBizType != ReadCardBizType.ADMISSION && !p.getInsuplcAdmdvs().startsWith("43")) {
            return ResultVoUtil.fail(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())) {
                    return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "社保卡姓名与HIS姓名不一致，请确认是否人证相符。");
                }
                admMdtrtinfo.setMdtrtCertNo(out[2]);
                admMdtrtinfo.setCardSn(out[3]);
            } else if (mdtrtCertType.getCode().equals(MdtrtCertType.MEDICAL_INSURANCE_ELECTRONIC_VOUCHER.getCode())) {
                JSONObject qrinfo = JSONObject.parseObject(p.getReadCardResult());
                if (!p.getName().trim().equals(qrinfo.getString("userName").trim())) {
                    return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "电子医保凭证姓名与HIS姓名不一致，请确认是否人证相符。");
                }
                if (!p.getSocialNo().trim().equals(qrinfo.getString("idNo").trim())) {
                    return ResultVoUtil.fail(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);
        log.info("【操作员：{}】，医保入院登记：\n参数：{}，\n结果：{}", TokenUtil.getInstance().getTokenUserId(), input, result);
        if (null == result) {
            return ResultVoUtil.fail(ExceptionEnum.NETWORK_ERROR);
        }
        Integer infcode = result.getInteger(RESULT_CODE);
        if (null == infcode) {
            return ResultVoUtil.fail(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 ResultVoUtil.success("入院登记成功，就诊号为：" + mdtrtId);
        }
        return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, result.getString(ERROR_MESSAGE));
    }

    public ResultVo<String> modifyAdmissionInfo(ZyPatientInfo p) {
        SiPatInfo siPatInfo = queryDao.selectSiPatInfoForZy(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);
        log.info("【操作员：{}】，入院信息变更：\n参数：{}，\n结果：{}", TokenUtil.getInstance().getTokenUserId(), input, result);
        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 ResultVo<String> revokeAdmission(ZyPatientInfo p) {
        SiPatInfo siPatInfo = queryDao.selectSiPatInfoForZy(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        if (null == siPatInfo || StringUtil.isBlank(siPatInfo.getMdtrtId())) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "此患者没有有效的医保在院信息！");
        }
        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);
        log.info("【操作员：{}】，取消入院登记：\n参数：{}，\n结果：{}", TokenUtil.getInstance().getTokenUserId(), input, result);
        if (null == result) {
            return ResultVoUtil.fail(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) {
            return ResultVoUtil.fail(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 ResultVoUtil.success("取消入院登记成功。");
        }
        return ResultVoUtil.fail(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);
            }
            o.setStaffId(TokenUtil.getInstance().getTokenUserId());
        }
        return zySrvc.uploadMultiplePatientFees(siZyFeeUrl, overviews);
    }

    private boolean invalidMultipleUploadPatient(String medtype) {
        MedType medType = MedType.get(medtype);
        return null == medType || medType == MedType.INJURY_HOSPITALIZATION
                || medType == MedType.SELF_PAY || medType == MedType.GCP_EXPERIMENT;
    }

    public ResultVo<String> hospitalizationPreSettlement(ZyPatientInfo p) {
        PreSetlmt preSetlmt = dao.selectPreSetlmt(p.getInpatientNo(), p.getAdmissTimes(),
                p.getLedgerSn(), DateUtil.getTodayEndTime());
        if (null == preSetlmt || StringUtil.isBlank(preSetlmt.getMdtrtId())) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "此患者没有有效的医保在院信息！");
        }
        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(SiUtil.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);
        log.info("预结算：\n参数：{}，\n结果：{}", input, result);
        if (null == result) {
            return ResultVoUtil.fail(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) {
            return ResultVoUtil.fail(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);
            String message = "患者【" + p.getName() + "】院内总费用与医保中心总费用一致，医保报销金额为：￥ " + fundPay + "。";
            return ResultVoUtil.success(message);
        }
        return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, result.getString(ERROR_MESSAGE));
    }

    public ResultVo<String> revokeUploadFees(ZyPatientInfo p) {
        SiPatInfo siPatInfo = queryDao.selectSiPatInfoForZy(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        if (null == siPatInfo || StringUtil.isBlank(siPatInfo.getMdtrtId())) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "此患者没有有效的医保在院信息！");
        }
        JSONObject input = exec.makeTradeHeaderWithInsureArea(SiFunction.REVOKE_HOSPITALIZATION_FEE_DETAILS, siPatInfo.getInsuplcAdmdvs());
        JSONArray data = new JSONArray();
        if (null == p.getDetailSns() || p.getDetailSns().isEmpty()) {
            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);
        log.info("【操作员：{}】撤销已上传的费用：\n参数：{}，\n结果：{}", TokenUtil.getInstance().getTokenUserId(), input, result);
        if (null == result) {
            return ResultVoUtil.fail(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) {
            return ResultVoUtil.fail(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 ResultVoUtil.success("撤销费用上传成功。");
        }
        return ResultVoUtil.fail(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.selectSiPatInfoForZy(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);
        log.info("【操作员：{}】退费处理：\n参数：{}，\n结果：{}", TokenUtil.getInstance().getTokenUserId(), input, result);
        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("退费处理成功。");
    }

    public ResultVo<String> revokeSingleCharge(String patNo, int times, int ledgerSn, int detailSn) {
        SiPatInfo siPatInfo = queryDao.selectSiPatInfoForZy(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);
        log.info("【操作员：{}】退费处理：\n参数：{}，\n结果：{}", TokenUtil.getInstance().getTokenUserId(), input, result);
        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);
        }
        return ResultVoUtil.success("处理成功。");
    }

    public ResultVo<String> dischargeProcessing(ZyPatientInfo p) {
        if (null == p.getLedgerSn()) {
            p.setLedgerSn(dao.selectMaxLedgerSn(p.getInpatientNo(), p.getAdmissTimes()));
        }
        Dscginfo dscginfo;
        if (p.getMidSetl()) {
            dscginfo = dao.selectDscginfoForMidSetl(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
            dscginfo.setEndtime(p.getZjdzDatetime());
        } else {
            dscginfo = dao.selectDscginfo(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        }
        if (null == dscginfo || StringUtil.isBlank(dscginfo.getMdtrtId())) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "此患者没有有效的医保在院信息！");
        }
        if (StringUtil.isBlank(dscginfo.getDscgDeptCodg())) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "出院科室【" +
                    dscginfo.getDscgDeptName() + "】编码不符合医保字典规范，请联系医保科匹配。");
        }
        List<SetlDiseinfo> setlDises = dao.selectSetlDises(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        if (null == setlDises || setlDises.isEmpty()) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "患者医保出院诊断不能为空，请联系医生在病案首页填写。");
        }
        if (null == p.getStaffId()) {
            p.setStaffId(TokenUtil.getInstance().getTokenUserId());
        }
        if (StringUtil.isBlank(p.getAcctUsedFlag())) {
            p.setAcctUsedFlag(YesOrNo.NO.getCodeStr());
        }

//        if (p.getAge() >= 7 && p.getAge() < 70) {
//            ReadCardBizType readCardBizType = ReadCardBizType.get(p.getReadCardBizType());
//            if (readCardBizType != ReadCardBizType.SETTLEMENT) {
//                return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "请先读卡再做出院结算。");
//            }
//        }

        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);
        log.info("【操作员：{}】，医保出院办理：\n参数：{}，\n结果：{}", p.getStaffId(), input, result);
        if (null == result) {
            return ResultVoUtil.fail(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) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "医保中心报错：" + result.getString("message"));
        }
        if (infcode == 0) {
            if (p.getStopHere()) {
                return ResultVoUtil.success("出院办理成功。");
            }
            p.setDismissDate(dscginfo.getEndtime());
            return inpatientSettlement(p);
        }
        String errMsg = result.getString(ERROR_MESSAGE);
        if (null != errMsg && errMsg.contains("该人员已经是出院状态")) {
            p.setDismissDate(dscginfo.getEndtime());
            return inpatientSettlement(p);
        }
        return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, errMsg);
    }

    public ResultVo<String> revokeDischarge(ZyPatientInfo p) {
        SiPatInfo siPatInfo = queryDao.selectSiPatInfoForZy(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        if (null == siPatInfo || StringUtil.isBlank(siPatInfo.getMdtrtId())) {
            transbackTables(p);
            return ResultVoUtil.success("取消出院办理成功。");
        }
        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);
        log.info("【操作员：{}】取消医保出院办理：\n参数：{}，\n结果：{}", TokenUtil.getInstance().getTokenUserId(), input, result);
        if (null == result) {
            return ResultVoUtil.fail(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) {
            return ResultVoUtil.fail(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 ResultVoUtil.success("取消医保出院办理成功。");
        }
        return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, result.getString(ERROR_MESSAGE));
    }

    private ResultVo<String> inpatientSettlement(ZyPatientInfo p) {
        PreSetlmt preSetlmt;
        Date begntime = p.getYbRegisterDate();
        if (null == begntime) {
            begntime = dismissService.getBegntime(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), "zy_actpatient");
        }
        if (p.getMidSetl()) {
            preSetlmt = dao.selectPreSetlmtForMidSetl(p.getInpatientNo(), p.getAdmissTimes(),
                    p.getLedgerSn(), begntime, p.getZjdzDatetime());
        } else {
            preSetlmt = dao.selectPreSetlmt(p.getInpatientNo(), p.getAdmissTimes(),
                    p.getLedgerSn(), "2999-12-31 23:59:59.999");
        }
        if (null == preSetlmt || StringUtil.isBlank(preSetlmt.getMdtrtId())) {
            revokeDischarge(p);
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "此患者没有有效的医保在院信息！");
        }
        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);
                    return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "社保卡姓名与HIS姓名不一致，请确认是否人证相符。");
                }
                preSetlmt.setMdtrtCertNo(out[2]);
                preSetlmt.setCardSn(out[3]);
            } else if (mdtrtCertType.getCode().equals(MdtrtCertType.MEDICAL_INSURANCE_ELECTRONIC_VOUCHER.getCode())) {
                JSONObject qrinfo = JSONObject.parseObject(p.getReadCardResult());
                if (!p.getName().trim().equals(qrinfo.getString("userName").trim())) {
                    revokeDischarge(p);
                    return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "电子医保凭证姓名与HIS姓名不一致，请确认是否人证相符。");
                }
                if (!p.getSocialNo().trim().equals(qrinfo.getString("idNo").trim())) {
                    revokeDischarge(p);
                    return ResultVoUtil.fail(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);
        log.info("【操作员：{}】，医保出院结算：\n参数：{}，\n结果：{}", p.getStaffId(), input, result);
        if (null == result) {
            revokeDischarge(p);
            return ResultVoUtil.fail(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);
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "医保中心报错：" + result.getString("message"));
        }
        if (infcode == 0) {
            JSONObject setlinfo = result.getJSONObject(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(preSetlmt.getInsuplcAdmdvs());
            setlinfoEntity.setBegntime(begntime);
            setlinfoEntity.setEndtime(p.getDismissDate());
            setlinfoEntity.setHiPaymtd(CommonUtil.calcHiPaymtd(setlinfoEntity.getInsuplcAdmdvs(),setlinfoEntity.getMedType(),setlinfoEntity.getInsutype(),setlinfoEntity.getSetlTime()));
            setlinfoEntity.setMedinsType(MedInsTypeUtil.getMedInsType(setlinfoEntity));
            JSONArray setldetail = result.getJSONObject(OUTPUT).getJSONArray("setldetail");
            BigDecimal hospitalPart = BigDecimal.ZERO;
            for (int i = 0; i < setldetail.size(); i++) {
                SiSetldetail setldetailEntity = JSONObject.parseObject(setldetail.getJSONObject(i).toJSONString(), SiSetldetail.class);
                setldetailEntity.setPatNo(p.getInpatientNo());
                setldetailEntity.setTimes(p.getAdmissTimes());
                setldetailEntity.setLedgerSn(p.getLedgerSn());
                if (setldetailEntity.getFundPayType().equals("999996") || setldetailEntity.getSetlProcInfo().equals("999996")) {
                    hospitalPart = hospitalPart.add(setldetailEntity.getFundPayamt());
                }
                setldetldao.insert(setldetailEntity);
            }
            setlinfoEntity.setHospPartAmt(hospitalPart.doubleValue());
            setlinfodao.insert(setlinfoEntity);
            setlinfodao.updateSiZyInfoSetlId(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(),
                    setlinfoEntity.getSetlId(), setlinfoEntity.getMedinsSetlId(), input.getString("msgid"));
            setlinfodao.updateApplySettled(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn(), YesOrNo.YES.getCode());

            // 跨省异地数据不参与对账，对总账时无需统计进去。
            if (setlinfoEntity.getFundPaySumamt() > 0 && setlinfoEntity.getInsuplcAdmdvs().startsWith("43")) {
                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);
                        return ResultVoUtil.fail(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);
                                return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, insSetlDetlChkRslt.getMemo());
                            }
                            break;
                        }
                    }
                }
            }
            queryService.saveCumInfo(setlinfoEntity);
            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(setlinfo.getString("psn_cash_pay"));
            settleFee.setTable(p.getTable());
            return dismissService.executeDischarging(settleFee);
        }
        revokeDischarge(p);
        return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, result.getString(ERROR_MESSAGE));
    }

    public ResultVo<String> revokeSettlement(ZyPatientInfo p) {
        SiPatInfo siPatInfo = queryDao.selectSiPatInfoForZy(p.getInpatientNo(), p.getAdmissTimes(), p.getLedgerSn());
        if (null == p.getStaffId()) {
            p.setStaffId(TokenUtil.getInstance().getTokenUserId());
        }
        if (null == p.getLedgerSn()) {
            p.setLedgerSn(dao.selectMaxLedgerSn(p.getInpatientNo(), p.getAdmissTimes()));
        }
        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);
        log.info("【操作员：{}】取消医保结算：\n参数：{}，\n结果：{}", p.getStaffId(), input, result);
        if (null == result) {
            return ResultVoUtil.fail(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) {
            return ResultVoUtil.fail(ExceptionEnum.NETWORK_ERROR);
        }
        String errMsg = null;
        if (infcode != 0) {
            errMsg = result.getString("err_msg");
        }
        ResultVo<String> rvkDischrgRslt = revokeDischarge(p);
        if (rvkDischrgRslt.getCode() == ExceptionEnum.SUCCESS.getCode()) {
            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;
        }
        return null == errMsg ? rvkDischrgRslt : ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, errMsg);
    }

    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());
    }
}
