package com.ywt.outpatient.rpc.provider;

import com.google.protobuf.TextFormat;
import com.ywt.biz.common.constant.GlobalConstants;
import com.ywt.biz.common.constant.YwtCommonRespCode;
import com.ywt.biz.common.enums.*;
import com.ywt.biz.common.exception.YwtCommonException;
import com.ywt.biz.common.util.Checker;
import com.ywt.biz.common.util.DateUtil;
import com.ywt.biz.common.util.HttpUtil;
import com.ywt.biz.common.util.StringHelper;
import com.ywt.gapi.Result;
import com.ywt.gapi.ResultCode;
import com.ywt.gapi.base.idCard.IdCardService;
import com.ywt.gapi.base.idCard.ValidateIdCardRequest;
import com.ywt.gapi.base.idCard.ValidateIdCardResponse;
import com.ywt.gapi.doctor.DoctorInfoResponse;
import com.ywt.gapi.doctor.DoctorRequest;
import com.ywt.gapi.doctor.DoctorService;
import com.ywt.gapi.ordercenter.OrderCenterService;
import com.ywt.gapi.ordercenter.RefundRequest;
import com.ywt.gapi.ordercenter.RefundResponse;
import com.ywt.gapi.system.*;
import com.ywt.gapi.taihe.register.RegisterResponse;
import com.ywt.gapi.taihe.register.*;
import com.ywt.gapi.taihe.register.RegisteredOrder;
import com.ywt.gapi.third.taihe.*;
import com.ywt.gapi.user.*;
import com.ywt.gapi.user.User;
import com.ywt.outpatient.core.PagedList;
import com.ywt.outpatient.core.SqlHelper;
import com.ywt.outpatient.core.utils.BizUtil;
import com.ywt.outpatient.core.utils.CheckUtil;
import com.ywt.outpatient.core.utils.IdCardUtil;
import com.ywt.outpatient.domain.entity.center.*;
import com.ywt.outpatient.domain.entity.center.MedicalCard;
import com.ywt.outpatient.domain.models.HisMedCardConfig;
import com.ywt.outpatient.domain.models.enums.*;
import com.ywt.outpatient.rpc.handler.RestCallerApiProvider;
import com.ywt.outpatient.service.rpc.Constants;
import com.ywt.outpatient.service.rpc.IdGenerator;
import com.ywt.outpatient.service.rpc.VerifyCreateRegisteredResult;
import com.ywt.outpatient.service.rpc.VerifyRegisterWithoutMedicalCardResult;
import com.ywt.outpatient.service.rpc.common.*;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;

import java.math.BigDecimal;
import java.sql.Types;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

import static com.ywt.biz.common.util.DateUtil.convertToLocalDate;


/**
 * 太和医院预约挂号业务类
 *
 * @author johnson lin
 * @date 2019-04-30 2:55 PM
 */
@DubboService
public class TaiheRegisterProvider extends DubboTaiheRegisterServiceTriple.TaiheRegisterServiceImplBase {
    private static Logger logger = LoggerFactory.getLogger(TaiheRegisterProvider.class);

    @DubboReference(url = GlobalConstants.CENTRAL_CONTROL_SERVICE_NFYY_SERVICE_DUBBO_REFERENCE_URL, protocol = GlobalConstants.DUBBO_REFERENCE_PROTOCOL)
    private TaiheService taiheServiceBlockingStub;

    @DubboReference
    private OrderCenterService orderCenterServiceBlockingStub;

    @DubboReference
    private SystemService systemServiceBlockingStub;

    @Autowired
    private MedicalCardRepository medicalCardRepository;

    @Autowired
    private IdGenerator idGenerator;

    @Autowired
    private RegisteredOrderRepository registeredOrderRepository;

    @Autowired
    private DoctorHisCodeRepository doctorHisCodeRepository;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private TaiheWeChatMsgService taiheWeChatMsgService;

    @Autowired
    private UserUnionsRepository userUnionsRepository;

    @Autowired
    private OrderPaymentRepository orderPaymentRepository;

    @Autowired
    private OfflineConsultationRepository offlineConsultationRepository;

    @Autowired
    private SqlHelper sqlHelper;

    @Autowired
    private RegisterRecordRepository registerRecordRepository;

    @Autowired
    private NfYwtWeChatMsgService nfYwtWeChatMsgService;

    @DubboReference
    private DoctorService doctorServiceBlockingStub;

    @DubboReference
    private UserService userServiceBlockingStub;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private ConfigProvider configProvider;

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private PatientRepository patientRepository;

    @DubboReference
    IdCardService idCardServiceBlockingStub;

    @Autowired
    RefundLogRepository refundLogRepository;

    @Autowired
    private RestCallerApiProvider restCallerApiProvider ;

    @Autowired
    private WeChatMsgProvider weChatMsgProvider;

    private static int OFFICAIL = 21;
    private static int MIN = 20;

    /**
     * 挂号
     * Update: 接入东华 HIS 后业务流程变更：锁号 --> 支付 --> HIS 挂号
     *
     * @param request {@link CreateRegisteredRequest}
     */
    @Override
    public CreateRegisteredResponse createRegistered(CreateRegisteredRequest request) {
        CreateRegisteredResponse.Builder builder = CreateRegisteredResponse.newBuilder();
        try {
            VerifyCreateRegisteredResult verifyCreateRegisteredResult = verifyCreateRegisteredRequest(request);
            if (!verifyCreateRegisteredResult.isSuccess()) {
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("校验未通过：%s", verifyCreateRegisteredResult.getMsg()));
            }
            com.ywt.outpatient.domain.entity.center.MedicalCard medicalCard = verifyCreateRegisteredResult.getMedicalCard();
            String doctorTitle = verifyCreateRegisteredResult.getDoctorTitle();
            String doctorSessTypeCode = request.getDoctorSessTypeCode(); // 出诊级别code
            String doctorSessTypeName = request.getDoctorSessTypeName(); // 出诊级别name
            int paymentChannel = Checker.getIntegerValue(request.getPaymentChannel(), PaymentChannelEnum.WeChat.getValue());
            int paymentStatus = PaymentStatusEnum.Pending.getValue();

            if (request.getTotal() == 0) {
                paymentChannel = PaymentChannelEnum.FREE.getValue();
                paymentStatus = PaymentStatusEnum.Success.getValue();
            }
            // 默认太和，兼容
            int hospitalId = request.getHospitalId() > 0 ? request.getHospitalId() : Constants.TAIHE_HOSPITAL_ID;
            //获取医生ID
            int doctorId = 0;
            DoctorHisCode doctorHisCode = doctorHisCodeRepository.getDoctorByHisCode(hospitalId, request.getDoctorCode());
            if (doctorHisCode != null && doctorHisCode.getDoctorId() > 0) {
                doctorId = doctorHisCode.getDoctorId();
            }
            // 需求变动：白云医院的夜诊或午间诊，无论是公医还是 6 岁以下，都统一按照诊查费收费（即全收费）
            // 判断是否全收费（统一按照诊查费收费）
            // 2023年7月6日，需求变动：太和也使用这个规则
            boolean isFullChargeReg = checkIsFullChargeReg(hospitalId, request.getPeriod(), doctorSessTypeName);

            // 判断是否是公医，公医挂号费 0 元
            boolean isFreeMedicalTreat = false;
            com.ywt.outpatient.domain.entity.center.MedicalCard mc = medicalCardRepository.getById(request.getMedicalCardId());
            if (!isFullChargeReg) {
                if (mc != null) {
                    isFreeMedicalTreat = configProvider.getFmtFlagList(Checker.getIntegerValue(request.getHospitalId())).contains(Checker.getStringValue(mc.getPatientType()));
                }
                if (isFreeMedicalTreat) {
                    paymentChannel = PaymentChannelEnum.FREE.getValue();
                    paymentStatus = PaymentStatusEnum.Success.getValue();
                }
            } else {
                logger.info("TaiheRegisterService#createRegistered(): 白云夜诊");
            }
            com.ywt.outpatient.domain.entity.center.RegisteredOrder registeredOrder = new com.ywt.outpatient.domain.entity.center.RegisteredOrder();
            registeredOrder.setUserId(request.getUserId());
            registeredOrder.setDoctorId(doctorId);
            registeredOrder.setDoctorTitle(doctorTitle);
            registeredOrder.setRegisteredDate(DateUtil.convertTimestampToDate(request.getRegisteredDate()));
            registeredOrder.setRegisteredPeriod(request.getPeriod());
            registeredOrder.setStartTime(request.getStartTime());
            registeredOrder.setEndTime(request.getEndTime());
            registeredOrder.setHisPatientId(medicalCard.getHisPatientId());
            registeredOrder.setMedicalCardId(request.getMedicalCardId());
            registeredOrder.setPatientSex(medicalCard.getSex());
            registeredOrder.setPatientName(medicalCard.getPatientName());
            registeredOrder.setPaymentChannel(paymentChannel);
            registeredOrder.setScheduleItemCode(request.getScheduleItemCode());
            registeredOrder.setDoctorCode(request.getDoctorCode());
            registeredOrder.setDoctorName(request.getDoctorName());
            registeredOrder.setDeptId(request.getDeptId());
            registeredOrder.setDeptCode(request.getDeptCode());
            registeredOrder.setDeptName(request.getDeptName());
            int medicalFee = Checker.getIntegerValue(request.getMedicalFee());
            registeredOrder.setSubHospitalId(request.getSubHospitalId());

            // 6 岁以下加收 30% 的挂号费
            if (!isFullChargeReg) {
                int curAge = IdCardUtil.calcAgeByIdNoAndBirthday(medicalCard.getIdNo(), medicalCard.getBirthday());
                if (curAge < 6) {
                    medicalFee = (new BigDecimal("1.3")).multiply((new BigDecimal(String.valueOf(medicalFee)))).intValue();
                    logger.info("TaiheRegisterService#createRegistered : 6 岁以下加收 30% 的挂号费，原挂号费：{}，重新计算得挂号费：{}",
                            request.getMedicalFee(), medicalFee);
                }
                if (isFreeMedicalTreat) {
                    logger.info("TaiheRegisterService#createRegistered(): 公医，cardId: {}", request.getMedicalCardId());
                    medicalFee = 0;
                }
            }
            int regFee = request.getRegisteredFee();
            registeredOrder.setRegisteredFee(regFee);
            registeredOrder.setTotal(medicalFee + regFee);
            registeredOrder.setMedicalFee(medicalFee);
            registeredOrder.setPaymentStatus(paymentStatus);
            registeredOrder.setSource(request.getSource());
            registeredOrder.setMedicalCardNo(medicalCard.getCardNo());
            registeredOrder.setRegisteredType(RegisteredTypeEnum.GENERAL.getValue());

            // 锁号
            // 需求改动：
            // 2020年8月12日：锁号使用病人 ID, 不再使用卡号
            LockOrderRequest lockOrderRequest = LockOrderRequest.newBuilder()
//                    .setCardNo(medicalCard.getCardNo())
                    .setPatID(medicalCard.getHisPatientId())
                    .setEndTime(Checker.getStringValue(request.getEndTime()))
                    .setStartTime(Checker.getStringValue(request.getStartTime()))
                    .setScheduleItemCode(Checker.getStringValue(request.getScheduleItemCode()))
                    .setAvailableNumStr(Checker.getStringValue(request.getAvailableNumStr()))
                    .setLockQueueNo(Checker.getStringValue(request.getLockQueueNo()))
                    .setHospitalId(hospitalId)
                    .build();
            LockOrderResponse lockOrderResponse = taiheServiceBlockingStub.lockOrder(lockOrderRequest);
            if (lockOrderResponse.getCode() != ResultCode.SUCCEED_VALUE) {
                String errMsg = StringHelper.isNullOrEmpty(lockOrderResponse.getMsg()) ? "号源不足，请选择其他医生！" : lockOrderResponse.getMsg();
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, errMsg);
            }
            // 存入 orderCode
            registeredOrder.setHisOrderCode(Checker.getStringValue(lockOrderResponse.getOrderCode()));
            registeredOrder.setHospitalId(hospitalId);
            registeredOrder.setHospitalName(BizUtil.getHospitalNameByIdSimply(hospitalId));
            registeredOrder.setRemark(BizUtil.getHospitalNameByIdSimply(hospitalId));
            //创建订单中心订单
            ServiceResult serviceResult = createOrder(registeredOrder);
            if (!serviceResult.isSuccessful()) {
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("创建订单出错：%s", serviceResult.getMsg()));
            }

            // 非名医挂号，如果挂号费是 0 元的订单或公医订单，不用支付，直接去 HIS 挂号然后修改订单状态
            // 名医挂号还是要等线下就诊订单支付成功后再去 HIS 挂号
            if (!checkIsNetDept(request.getDeptCode(), hospitalId) && (isFreeMedicalTreat || request.getTotal() == 0)) {
                payFreeReg(registeredOrder.getId(), paymentChannel);
            }

            builder.setCode(ResultCode.SUCCEED_VALUE)
                    .setOrderId(registeredOrder.getOrderId())
                    .setOrderType(OrderTypeEnum.REGISTERED.getValue())
                    .setHisClinicCode(0)
                    .setIdNo(Checker.getStringValue(medicalCard.getIdNo()))
                    .setName(Checker.getStringValue(medicalCard.getPatientName()))
                    .setMobile(Checker.getStringValue(medicalCard.getMobile()))
                    .setDoctorId(registeredOrder.getDoctorId())
                    .setOrderNo(registeredOrder.getOrderNo())
                    .setRegId(registeredOrder.getId())
                    .build();
            logger.info("createRegistered(): \nrequest - {}\nresponse - {}", TextFormat.printToUnicodeString(request),
                    TextFormat.printToUnicodeString(builder.build()));
            return builder.build();

        } catch (YwtCommonException ame) {
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setMsg(ame.getMessage());
            return builder.build();
        } catch (Exception e) {
            logger.error("createRegistered({}): {}", TextFormat.printToUnicodeString(request), e.getMessage(), e);
            builder.setCode(ResultCode.APP_EXCEPTION_VALUE).setMsg(Constants.ERROR_INFO);
            return builder.build();
        }
    }

    /**
     * 免费挂号，0 元挂号费的订单（如公医）调用 HIS 挂号并修改订单状态
     */
    private void payFreeReg(int regId, int paymentChannel) throws YwtCommonException {
        com.ywt.outpatient.domain.entity.center.RegisteredOrder registeredOrder = registeredOrderRepository.getById(regId);
        if (registeredOrder == null) {
            throw new YwtCommonException(YwtCommonRespCode.P_ERR, "0 元挂号：挂号订单不存在");
        }
        int orderId = Checker.getIntegerValue(registeredOrder.getOrderId());
        String payMode = Constants.PAY_MODE_WECHAT;
        if (paymentChannel == PaymentChannelEnum.AliPay.getValue()) {
            payMode = Constants.PAY_MODE_ALI;
        }
        // 调用HIS接口，进行挂号同时把支付状态回写到HIS系统
        String hisOrderCode = Checker.getStringValue(registeredOrder.getHisOrderCode());
        PayRequest payReq = PayRequest.newBuilder()
                .setOrderCode(hisOrderCode)
                .setTransactionId("")// 不支付没有流水
                .setPatientId(Checker.getStringValue(registeredOrder.getHisPatientId()))
                .setCardNo(Checker.getStringValue(registeredOrder.getMedicalCardNo()))
                .setPayAmount(0)
                .setPayMode(payMode)
                .setPayTime(DateUtil.convertToString(new Date()))
                .setHospitalId(Checker.getIntegerValue(registeredOrder.getHospitalId()))
                .build();
        PayResponse payRes = taiheServiceBlockingStub.pay(payReq);
        //若挂号不成功，记录失败原因，进行退费处理，资金原路退回
        if (payRes.getCode() != ResultCode.SUCCEED_VALUE) {
            logger.info("payFreeReg({}):支付状态回写到HIS系统失败：{}", regId, payRes.getMsg());
            registeredOrder.setRemark(payRes.getMsg());
            // 挂号失败，修改挂号订单状态为初始状态
            registeredOrder.setRegisteredStatus(0);
            registeredOrderRepository.save(registeredOrder);
            throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("call HIS pay:%s", payRes.getMsg()));
        } else {
            //挂号成功，更新挂号订单信息
            registeredOrderRepository.updateRegisterInfoById(payRes.getSeqCode(), String.valueOf(payRes.getRegFee())
                    , payRes.getAdmitRange(), payRes.getAdmitAddress(), "", payRes.getTransactionId()
                    , hisOrderCode, RegisteredStatusEnum.Booked.getValue(), registeredOrder.getId(), payRes.getAdmNo());
            // 更新订单信息，暂不需要
//            Order order = orderRepository.getOrderById(orderId);
//            order.setPaymentStatus(PaymentStatusEnum.Success.getValue());
//            order.setPaymentNo(request.getOutTradeNo());
//            order.setPayTime(new Date());
//            orderRepository.save(order);

            // 发送消息
            registeredOrder.setHisAdmitAddress(Checker.getStringValue(payRes.getAdmitAddress()));
            registeredOrder.setHisSeqCode(payRes.getSeqCode());
            registeredOrder.setHisAdmitRange(payRes.getAdmitRange());
            // 根据订单下单来源判断是哪个终端的订单
            int source = Checker.getIntegerValue(registeredOrder.getSource());
            if (!BizUtil.isAlipayMp(source)) {
                // 微信体系才发送微信消息
                nfYwtWeChatMsgService.sendMsgForRegister(registeredOrder);
                taiheWeChatMsgService.sendMsgForRegister(registeredOrder);
            }
        }
    }

    /**
     * 校验 CreateRegisteredRequest 参数是否合法
     *
     * @param request {@link CreateRegisteredRequest}
     */
    private VerifyCreateRegisteredResult verifyCreateRegisteredRequest(CreateRegisteredRequest request) throws YwtCommonException {
        VerifyCreateRegisteredResult result = new VerifyCreateRegisteredResult();
        long minTimestamp = DateUtil.convertToTimestamp(LocalDate.now());
        long maxTimestamp = DateUtil.convertToTimestamp(LocalDate.now().plusDays(8));
        if (request.getRegisteredDate() < minTimestamp || request.getRegisteredDate() > maxTimestamp) {
            //只能预约8天内的号源
            return result.failed("预约日期格式不正确");
        }
        if (StringHelper.isNullOrWhiteSpace(request.getDeptCode()) || StringHelper.isNullOrWhiteSpace(request.getDeptName())) {
            return result.failed("科室不存在");
        }
        if (StringHelper.isNullOrWhiteSpace(request.getDoctorCode()) || StringHelper.isNullOrWhiteSpace(request.getDoctorName())) {
            return result.failed("医生不存在");
        }
        if (StringHelper.isNullOrWhiteSpace(request.getStartTime())) {
            return result.failed("预约开始时间不存在");
        }
        if (StringHelper.isNullOrWhiteSpace(request.getEndTime())) {
            return result.failed("预约结束时间不存在");
        }
        if (request.getMedicalCardId() < 1) {
            return result.failed("请选择就诊人");
        }
        if (StringHelper.isNullOrWhiteSpace(request.getScheduleItemCode())) {
            return result.failed("门诊排班项记录标识不能为空");
        }
        if (RegisteredDatePeriod.valueOf(request.getPeriod()) == null) {
            return result.failed("所选择的预约时段不存在！");
        }
        //判断门诊排班项记录标识是否合法
        String serviceDate = DateUtil.convertTimestampToDateString(request.getRegisteredDate(), "yyyy-MM-dd");
//        GetScheduleTimeInfoRequest req = GetScheduleTimeInfoRequest.newBuilder()
//                .setDeptCode(request.getDeptCode())
//                .setDoctorCode(request.getDoctorCode())
//                .setServiceDate(serviceDate)
//                .setDatePeriod(request.getPeriod())
//                .build();
//        GetScheduleTimeInfoResponse res = taiheServiceBlockingStub.getScheduleTimeInfo(req);
//        if (res.getCode() != ResultCode.SUCCEED_VALUE) {
//            return result.failed(res.getMsg());
//        }
//        if (res.getScheduleTimeInfosList().stream().noneMatch(p -> request.getScheduleItemCode().equals(p.getScheduleItemCode()))) {
//            return result.failed("门诊排班项记录标识不存在");
//        }
        //判断医生编码是否合法
        GetScheduleListRequest getScheduleListRequest = GetScheduleListRequest.newBuilder()
                .setDeptCode(request.getDeptCode())
                .setDoctorCode(request.getDoctorCode())
                .setServiceDate(serviceDate)
                .setHospitalId(request.getHospitalId())
                .build();
        GetScheduleListResponse getScheduleListResponse = taiheServiceBlockingStub.getScheduleList(getScheduleListRequest);
        if (getScheduleListResponse.getCode() != ResultCode.SUCCEED_VALUE || getScheduleListResponse.getSchedulesList().size() == 0) {
            return result.failed("医生不存在");
        }
        Schedule schedule = getScheduleListResponse.getSchedulesList().stream()
                .filter(s -> s.getDatePeriod() == Checker.getIntegerValue(request.getPeriod()))
                .findFirst()
                .orElse(null);
        if (schedule == null) {
            return result.failed("当前时间段没有匹配的排班数据！");
        }
        //校验传入的价格
        if (checkIsNetDept(request.getDeptCode(), request.getHospitalId())) {
            if (request.getMedicalFee() != 0 || request.getRegisteredFee() != 0 || request.getTotal() != 0) {
                //名医门诊科室的挂号费为 0
                return result.failed("挂号费不正确");
            }
        } else {
            if (request.getMedicalFee() != schedule.getCheckupFee() || request.getRegisteredFee() != schedule.getRegFee()) {
                return result.failed("诊查费不正确");
            }
            if (request.getTotal() != request.getMedicalFee() + request.getRegisteredFee()) {
                return result.failed("费用不正确");
            }
        }
        //诊疗卡是否合法
        // 2020年5月21日20点44分：暂时移除 userId 的限制
//        MedicalCard medicalCard = medicalCardRepository.getByIdAndStatusAndUserId(request.getMedicalCardId(),
//                MedicalCardStatus.NORMAL.getValue(), request.getUserId());
        com.ywt.outpatient.domain.entity.center.MedicalCard medicalCard = medicalCardRepository.getByIdAndStatus(request.getMedicalCardId(),
                MedicalCardStatusEnum.NORMAL.getValue());
        if (medicalCard == null) {
            logger.info(String.format("诊疗卡不存在！cardId: %d, userId: %d", request.getMedicalCardId(),
                    request.getUserId()));
            return result.failed("诊疗卡不存在！");
        }
        // 小于 14 岁挂内科的号，需要提示去儿科挂号
        if (checkIsInternalMedicineDept(request.getDeptCode(), request.getHospitalId())) {
            boolean isAgeLt14 = IdCardUtil.calcAgeByIdNoAndBirthday(medicalCard.getIdNo(), medicalCard.getBirthday()) < 14;
            int hospitalId = request.getHospitalId() > 0 ? request.getHospitalId() : Constants.TAIHE_HOSPITAL_ID;
            if (isAgeLt14) {
                // 呼吸内科改造
                if (Constants.NFYYBYFY_INTERNAL_BREATHE_MEDICINE_DEPT_CODE.equals(request.getDeptCode()) && hospitalId == Constants.NFYYBYFY_HOSPITAL_ID) {
                    return result.failed("该患者挂号年龄不在挂号范围：≤14岁患者请挂儿科号就诊");
                } else {
                    return result.failed("就诊人少于14岁，请预约儿科医生就诊！");
                }
            }
        }
        result.setSuccess(true);
        result.setDoctorTitle(schedule.getDoctorTitle());
        result.setMedicalCard(medicalCard);
        return result;
    }

    /**
     * 创建预约挂号订单
     *
     * @param registeredOrder {@link RegisteredOrder}
     * @return {@link ServiceResult}
     */
    private ServiceResult createOrder(com.ywt.outpatient.domain.entity.center.RegisteredOrder registeredOrder) {
        ServiceResult serviceResult = new ServiceResult();
        Date now = new Date();
        int id = idGenerator.genRegisteredOrderId();
        registeredOrder.setId(id);
        registeredOrder.setDeleted(false);
        registeredOrder.setUpdateTime(now);
        registeredOrder.setCreateTime(now);
        registeredOrder.setRegisteredStatus(0);
        registeredOrderRepository.save(registeredOrder);

        //创建订单中心订单
        int orderType = OrderTypeEnum.REGISTERED.getValue();
        com.ywt.gapi.ordercenter.CreateOrderRequest req = com.ywt.gapi.ordercenter.CreateOrderRequest.newBuilder()
                .setOrderType(orderType)
                .setBizId(registeredOrder.getId())
                .setPaymentCallbackMethod("/com.ywt.gapi.taihe.register.TaiheRegisterService/payCallback")
                .setPayCode(getHospPayCodeByHospId(registeredOrder.getHospitalId(), Checker.getIntegerValue(registeredOrder.getSource())))
                .setAmount(registeredOrder.getTotal())
                .setPayBody(OrderTypeEnum.REGISTERED.getDisplayName())
                .setUserId(registeredOrder.getUserId())
                .setPatientMobile("")
                .setDoctorId(registeredOrder.getDoctorId())
                .build();
        com.ywt.gapi.ordercenter.CreateOrderResponse res = orderCenterServiceBlockingStub.createOrder(req);

        if (res.getCode() != ResultCode.SUCCEED_VALUE) {
            return serviceResult.failed("创建订单不成功，原因：" + res.getInfo());
        }

        //更新订单ID、订单编码
        registeredOrderRepository.updateOrderIdAndOrderNoById(res.getOrderId(), res.getOrderNo(), registeredOrder.getId());

        registeredOrder.setOrderId(res.getOrderId());
        registeredOrder.setOrderNo(res.getOrderNo());
        return serviceResult.succeed();
    }

    /**
     * 根据医院 id 获取对应医院的支付代码，目前只支持太和和白云分院
     * v1.6.5_nfby 开始，白云小程序支持太和医院的挂号，订单的支付代码根据创建订单的客户端来源判断
     * @param hospitalId 医院 id
     * @return 支付代码
     */
    public static String getHospPayCodeByHospId(int hospitalId, int source) {
        switch (hospitalId) {
            case com.ywt.outpatient.core.Constants.TAIHE_HOSPITAL_ID:
                if (BizUtil.isAlipayMp(source)) return com.ywt.outpatient.core.Constants.TH_PAY_CODE_ALIPAY;
                String nfyybyfyTaihePayCode = StringHelper.isNullOrEmpty(BizUtil.payCode4NfyybyfyTest) ? Constants.NFYYBYFY_TAIHE_REG_PAY_CODE : BizUtil.payCode4NfyybyfyTest;
                return source == OfflineConsultationSourceEnum.NFYYBYFY_WXAPP.getValue() ? nfyybyfyTaihePayCode : Constants.TAIHE_PAY_CODE;
            case com.ywt.outpatient.core.Constants.NFYYBYFY_HOSPITAL_ID:
                if (BizUtil.isAlipayMp(source)) return com.ywt.outpatient.core.Constants.NFYYBYFY_PAY_CODE_ALIPAY;
                if (!StringHelper.isNullOrEmpty(BizUtil.payCode4NfyybyfyTest)) return BizUtil.payCode4NfyybyfyTest;
                return com.ywt.outpatient.core.Constants.NFYYBYFY_PAY_CODE;
            default:
                return "";
        }
    }
    /**
     * 取消预约，若用户已支付挂号费，则挂号费原路退还给用户
     *
     * @param request          {@link CancelRegisteredRequest}
     */
    @Override
    public CancelRegisteredResponse cancelRegistered(CancelRegisteredRequest request) {
        CancelRegisteredResponse.Builder builder = CancelRegisteredResponse.newBuilder();

        try {
            // 默认太和，兼容旧接口
            int hospitalId = request.getHospitalId() > 0 ? request.getHospitalId() : Constants.TAIHE_HOSPITAL_ID;
            com.ywt.outpatient.domain.entity.center.RegisteredOrder registeredOrder = registeredOrderRepository.getFirstByOrderIdAndHospitalIdAndDeletedFalse(
                    request.getOrderId(), hospitalId);
            if (registeredOrder == null) {
                builder.setCode(ResultCode.APP_ERROR_VALUE).setMsg("订单不存在");
                return builder.build();
            }
            if (registeredOrder.getUserId() != request.getUserId()) {
                builder.setCode(ResultCode.APP_ERROR_VALUE).setMsg("您没有权限取消该预约");
                return builder.build();
            }
            if (StringHelper.isNullOrWhiteSpace(registeredOrder.getHisOrderCode())) {
                builder.setCode(ResultCode.APP_ERROR_VALUE).setMsg("订单未预约成功，无法取消");
                return builder.build();
            }
            // 从 v2.1.9_nfby 开始，白云分院的取消预约按钮显示规则修改：（太和等医院逻辑不变）
            // ①就诊当天00：00后，“取消预约”按钮不显示。
            // ②预约挂号订单有诊断的（通过his接口判断），“取消预约”按钮不显示。
            // 点击“取消预约”按钮，提示：
            //    a、若出现①的情况，提示“就诊当天00：00后不可线上取消！”，然后“取消预约”按钮不显示；
            //    b、若出现②的情况，提示“已就诊，不可线上取消！”，然后“取消预约”按钮不显示；
            if (hospitalId == Constants.NFYYBYFY_HOSPITAL_ID) {
                if (!canTodayCancelReg(hospitalId, registeredOrder.getRegisteredDate())) {
                    builder.setCode(ResultCode.APP_ERROR_VALUE).setMsg("就诊当天00：00后不可线上取消！");
                    return builder.build();
                }
                boolean hasDiagnose = hasDiagnose(Checker.getStringValue(registeredOrder.getHisClinicCode()),
                        Checker.getIntegerValue(registeredOrder.getHospitalId()));
                if (hasDiagnose) {
                    builder.setCode(ResultCode.APP_ERROR_VALUE).setMsg("已就诊，不可线上取消！");
                    return builder.build();
                }
            }
            String payMode = Constants.PAY_MODE_WECHAT;
            boolean isAlipay = Checker.getIntegerValue(registeredOrder.getPaymentChannel()) == PaymentChannelEnum.AliPay.getValue();
            boolean isWechatPay = Checker.getIntegerValue(registeredOrder.getPaymentChannel()) == PaymentChannelEnum.WeChat.getValue();
            if (isAlipay) {
                payMode = Constants.PAY_MODE_ALI;
            }
            String transactionId = Checker.getStringValue(registeredOrder.getWxTransactionId());
            CancelRequest cancelReq = CancelRequest.newBuilder()
                    .setOrderCode(registeredOrder.getHisOrderCode())
                    .setCardNo(Checker.getStringValue(registeredOrder.getMedicalCardNo()))
                    .setPatId(Checker.getStringValue(registeredOrder.getHisPatientId()))
                    .setClinicCode(Checker.getStringValue(registeredOrder.getHisClinicCode()))
                    .setHospitalId(hospitalId)
                    .setTransactionId(transactionId)
                    .setPayMode(payMode)
                    .build();
            CancelResponse cancelRes = taiheServiceBlockingStub.cancel(cancelReq);

            if (cancelRes.getCode() != ResultCode.SUCCEED_VALUE) {
                logger.error("TaiheRegisterService#cancelRegistered(): {}", cancelRes.getMsg());
                builder.setCode(ResultCode.APP_ERROR_VALUE).setMsg("该预约挂号不能取消！");
                return builder.build();
            }

            //取消成功，更新挂号订单状态
            registeredOrderRepository.updateRegisteredStatus(RegisteredStatusEnum.Back.getValue(), registeredOrder.getId());

            //若用户已支付挂号费，则挂号费原路退还给用户
            if (registeredOrder.getTotal() > 0 && registeredOrder.getPaymentStatus() == PaymentStatusEnum.Success.getValue()) {
                refund(registeredOrder.getOrderId(), registeredOrder.getOrderNo(), registeredOrder.getId());
            }

            //发送模板消息
            switch (hospitalId) {
                case Constants.TAIHE_HOSPITAL_ID:
                    if (isWechatPay) {
                        //《南方医务通》公众号
                        nfYwtWeChatMsgService.sendMsgForCancel(registeredOrder);
                        //《南方医院太和分院》公众号
                        taiheWeChatMsgService.sendMsgForCancel(registeredOrder);
                    }
                    if (isAlipay) {
                        String orderNo = Checker.getStringValue(registeredOrder.getOrderNo());
                        String createTime = DateUtil.convertToString(registeredOrder.getCreateTime());
                        String paymentNo = Checker.getStringValue(registeredOrder.getPaymentNo());
                        String regTime = DateUtil.convertToString(registeredOrder.getRegisteredDate(), "yyyy-MM-dd") +
                                " " + Checker.getStringValue(registeredOrder.getStartTime()) + ":00";
                        String admAddr = registeredOrder.getHisAdmitAddress();
                        OrderPayment payment = orderPaymentRepository.getOrderPaymentByOrderNo(orderNo);
                        String alipayUid = "";
                        if (payment != null) {
                            alipayUid = Checker.getStringValue(payment.getAlipayUid());
                        }
                        sendAlipayMpMsgForReg(Checker.getIntegerValue(registeredOrder.getOrderId()), orderNo, createTime,
                                String.format("%.2f", Checker.getIntegerValue(registeredOrder.getTotal()) / 100d),
                                paymentNo, Checker.getStringValue(registeredOrder.getHospitalName()),
                                Checker.getStringValue(registeredOrder.getDeptName()), Checker.getStringValue(registeredOrder.getDoctorName()),
                                String.valueOf(Checker.getIntegerValue(registeredOrder.getDoctorId())), Checker.getStringValue(registeredOrder.getPatientName()),
                                regTime, (StringHelper.isNullOrEmpty(admAddr) ? "-" : admAddr), alipayUid,
                                Checker.getIntegerValue(registeredOrder.getHospitalId()), "MERCHANT_CLOSED");
                    }
                    break;
                case Constants.NFYYBYFY_HOSPITAL_ID:
                    if (isWechatPay) {
                        taiheWeChatMsgService.notifyCancel4NFYYBYFY(Checker.getIntegerValue(registeredOrder.getUserId()),
                                BizUtil.getHospitalNameByIdSimply(hospitalId), Checker.getStringValue(registeredOrder.getPatientName()),
                                Checker.getStringValue(registeredOrder.getDoctorName()),
                                String.format("%s %s-%s", DateUtil.convertToString(registeredOrder.getRegisteredDate(), "MM月dd日"),
                                        Checker.getStringValue(registeredOrder.getStartTime()),
                                        Checker.getStringValue(registeredOrder.getEndTime())));
                    }
                    if (isAlipay) {
                        String orderNo = Checker.getStringValue(registeredOrder.getOrderNo());
                        String createTime = DateUtil.convertToString(registeredOrder.getCreateTime());
                        String paymentNo = Checker.getStringValue(registeredOrder.getPaymentNo());
                        String regTime = DateUtil.convertToString(registeredOrder.getRegisteredDate(), "yyyy-MM-dd") +
                                " " + Checker.getStringValue(registeredOrder.getStartTime()) + ":00";
                        String admAddr = registeredOrder.getHisAdmitAddress();
                        OrderPayment payment = orderPaymentRepository.getOrderPaymentByOrderNo(orderNo);
                        String alipayUid = "";
                        if (payment != null) {
                            alipayUid = Checker.getStringValue(payment.getAlipayUid());
                        }
                        sendAlipayMpMsgForReg(Checker.getIntegerValue(registeredOrder.getOrderId()), orderNo, createTime,
                                String.format("%.2f", Checker.getIntegerValue(registeredOrder.getTotal()) / 100d),
                                paymentNo, Checker.getStringValue(registeredOrder.getHospitalName()),
                                Checker.getStringValue(registeredOrder.getDeptName()), Checker.getStringValue(registeredOrder.getDoctorName()),
                                String.valueOf(Checker.getIntegerValue(registeredOrder.getDoctorId())), Checker.getStringValue(registeredOrder.getPatientName()),
                                regTime, (StringHelper.isNullOrEmpty(admAddr) ? "-" : admAddr), alipayUid,
                                Checker.getIntegerValue(registeredOrder.getHospitalId()), "MERCHANT_CLOSED");
                    }
                    break;
                default:
                    break;
            }

            builder.setCode(ResultCode.SUCCEED_VALUE)
                    .setMsg("取消成功")
                    //预约挂号单支付状态：1-订单未支付 2-订单已支付
                    .setStatus(2);
        } catch (Exception e) {
            logger.error("cancelRegistered({}): {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_EXCEPTION_VALUE).setMsg(Constants.ERROR_INFO);
        } finally {
            return builder.build();
        }
    }

    /**
     * 订单支付成功通知
     *
     * @param request {@link PayCallbackRequest}
     */
    @Override
    public PayCallbackResponse payCallback(PayCallbackRequest request) {
        PayCallbackResponse.Builder builder = PayCallbackResponse.newBuilder();
        com.ywt.outpatient.domain.entity.center.RegisteredOrder registeredOrder = null;
        try {
            logger.info("TaiheRegisterService#payCallback(): bizId={}, orderId={}, orderNo={}, terminal={}",
                    request.getBizId(), request.getOrderId(), request.getOrderNo(), request.getTerminal());
            int paymentChannel = Checker.getIntegerValue(request.getPaymentChannel());
            String paymentNo = request.getPaymentNo(); // 支付宝支付时，等于 tradeNo
            // 支付流水号，支付方式是微信，表示 wxTrarsactionId，支付方式是支付宝，表示 tradeNo
            String wxTransactionId = request.getWxTransactionId();
            OrderPayment payment = null;
            if (paymentChannel == PaymentChannelEnum.WeChat.getValue() && StringHelper.isNullOrEmpty(wxTransactionId)) {
                //订单中心未返回 TransactionId（微信交易流水号），则从表 OrderPayment 读取。 2019-04-28 20:43:58
                payment = orderPaymentRepository.getFirstByOrderIdAndPaymentStatusAndDeletedFalseOrderByCreateTimeDesc(
                        request.getBizId(), PaymentStatusEnum.Success.getValue());
                if (payment != null) {
                    wxTransactionId = payment.getTransactionId();
                    logger.info("TaiheRegisterService#payCallback(): orderPayment terminal = {}", payment.getTerminal());
                }
            }
            String sql = "", payMode = "";
            Object[] args = null;
            int[] argTypes = null;
            switch (paymentChannel) {
                // 支付宝
                case 2:
                    sql = "update registered_order " +
                            "set payment_status = ?" +
                            ", payment_no = ?" +
                            ", payment_time = ?" +
                            ", update_time = now() " +
                            "where id = ? and payment_status != ?";
                    args = new Object[]{
                            PaymentStatusEnum.Success.getValue(),
                            paymentNo,
                            //request.getPayFinishDate()单位：秒，转成Date格式需要转换为毫秒。
                            new Date(request.getPayFinishDate() * 1000),
                            request.getBizId(),
                            PaymentStatusEnum.Success.getValue()
                    };
                    argTypes = new int[]{Types.INTEGER, Types.VARCHAR, Types.TIMESTAMP, Types.INTEGER, Types.INTEGER};
                    payMode = Constants.PAY_MODE_ALI;
                    break;
                // 默认微信
                default:
                    sql = "update registered_order " +
                            "set payment_status = ?" +
                            ", payment_no = ?" +
                            ", wx_transaction_id = ?" +
                            ", payment_time = ?" +
                            ", update_time = now() " +
                            "where id = ? and payment_status != ?";
                    args = new Object[]{
                            PaymentStatusEnum.Success.getValue(),
                            paymentNo,
                            wxTransactionId,
                            //request.getPayFinishDate()单位：秒，转成Date格式需要转换为毫秒。
                            new Date(request.getPayFinishDate() * 1000),
                            request.getBizId(),
                            PaymentStatusEnum.Success.getValue()
                    };
                    argTypes = new int[]{Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.TIMESTAMP, Types.INTEGER, Types.INTEGER};
                    payMode = Constants.PAY_MODE_WECHAT;
                    break;
            }

            int count = jdbcTemplate.update(sql, args, argTypes);
            if (count < 1) {
                builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo("订单不存在或重复回调");
                return builder.build();
            }
            registeredOrder = registeredOrderRepository.getById(request.getBizId());
            String orderNo = Checker.getStringValue(registeredOrder.getOrderNo());
            if (paymentChannel == PaymentChannelEnum.AliPay.getValue()) {
                wxTransactionId = paymentNo;
            }
            int payAmount = Checker.getIntegerValue(registeredOrder.getTotal());
            // 调用HIS接口，进行挂号同时把支付状态回写到HIS系统
            String hisOrderCode = Checker.getStringValue(registeredOrder.getHisOrderCode());
            PayRequest payReq = PayRequest.newBuilder()
                    .setOrderCode(hisOrderCode)
                    .setTransactionId(wxTransactionId)
                    .setPatientId(registeredOrder.getHisPatientId())
                    .setCardNo(registeredOrder.getMedicalCardNo())
                    .setPayAmount(payAmount)
                    //支付方式
                    .setPayMode(payMode)
                    .setPayTime(DateUtil.formatDate(registeredOrder.getPaymentTime(), "yyyy-MM-dd HH:mm:ss"))
                    .setHospitalId(Checker.getIntegerValue(registeredOrder.getHospitalId()))
                    .build();
            PayResponse payRes = taiheServiceBlockingStub.pay(payReq);
            //若挂号不成功，记录失败原因，进行退费处理，资金原路退回
            if (payRes.getCode() != ResultCode.SUCCEED_VALUE) {
                logger.info("payCallback({}):支付状态回写到HIS系统失败：{}", request, payRes.getMsg());
                registeredOrder.setRemark(payRes.getMsg());
                // 挂号失败，修改挂号订单状态为初始状态
                registeredOrder.setRegisteredStatus(0);
                registeredOrderRepository.save(registeredOrder);
                refund(registeredOrder.getOrderId(), registeredOrder.getOrderNo(), registeredOrder.getId());
            } else {
                // 东华 HIS 流程是 pay 方法包括了原来的挂号和支付状态回写，下面是处理挂号成功之后的逻辑

                //挂号成功，更新挂号信息
                registeredOrderRepository.updateRegisterInfoById(payRes.getSeqCode(), String.valueOf(payRes.getRegFee())
                        , payRes.getAdmitRange(), payRes.getAdmitAddress(), "", payRes.getTransactionId()
                        , hisOrderCode, RegisteredStatusEnum.Booked.getValue(), registeredOrder.getId(), payRes.getAdmNo());

                //发送挂号成功模板消息
                //《南方医务通》公众号
                // 先赋值一下就诊地址
                registeredOrder.setHisAdmitAddress(Checker.getStringValue(payRes.getAdmitAddress()));
                registeredOrder.setHisSeqCode(payRes.getSeqCode());
                registeredOrder.setHisAdmitRange(payRes.getAdmitRange());
                if (paymentChannel == PaymentChannelEnum.WeChat.getValue()) {


                    nfYwtWeChatMsgService.sendMsgForRegister(registeredOrder);
                    //《南方医院太和分院》公众号
                    taiheWeChatMsgService.sendMsgForRegister(registeredOrder);
                }
                if (paymentChannel == PaymentChannelEnum.AliPay.getValue()) {
                    String createTime = DateUtil.convertToString(registeredOrder.getCreateTime());
                    String regTime = DateUtil.convertToString(registeredOrder.getRegisteredDate(), "yyyy-MM-dd") +
                            " " + Checker.getStringValue(registeredOrder.getStartTime()) + ":00";
                    String admAddr = registeredOrder.getHisAdmitAddress();
                    payment = orderPaymentRepository.getOrderPaymentByOrderNo(orderNo);
                    String alipayUid = "";
                    if (payment != null) {
                        alipayUid = Checker.getStringValue(payment.getAlipayUid());
                    }
                    sendAlipayMpMsgForReg(Checker.getIntegerValue(registeredOrder.getOrderId()), orderNo, createTime,
                            String.format("%.2f", Checker.getIntegerValue(registeredOrder.getTotal()) / 100d),
                            paymentNo, Checker.getStringValue(registeredOrder.getHospitalName()),
                            Checker.getStringValue(registeredOrder.getDeptName()), Checker.getStringValue(registeredOrder.getDoctorName()),
                            String.valueOf(Checker.getIntegerValue(registeredOrder.getDoctorId())), Checker.getStringValue(registeredOrder.getPatientName()),
                            regTime, (StringHelper.isNullOrEmpty(admAddr) ? "-" : admAddr), alipayUid,
                            Checker.getIntegerValue(registeredOrder.getHospitalId()), "MERCHANT_PREORDER_SUCCESS");
                }

                // 挂号成功后，更新线下就诊订单的 his_clinic_code 和 patient mobile
                OfflineConsultation offlineConsultation = offlineConsultationRepository.getByRegId(registeredOrder.getId());
                if (offlineConsultation != null) {
                    try {
                        offlineConsultation.setHisClinicCode(Integer.valueOf(payRes.getAdmNo()));
                        String mobile = offlineConsultation.getMobile();
                        if (Checker.isNone(mobile)) {
                            // 如果医院登记的手机号没有，尝试用患者授权的手机号
                            int userId = offlineConsultation.getUserId();
                            UserRequest req = UserRequest.newBuilder().setUserid(userId).build();
                            UserResponse resp = userServiceBlockingStub.findByUserId(req);
                            Result result = resp.getResult();
                            if (result.getCode() == ResultCode.SUCCEED) {
                                User user = resp.getUser();
                                if (Checker.isNone(user.getMobile())) {
                                    offlineConsultation.setMobile(user.getMobile());
                                }
                            }
                        }
                        offlineConsultationRepository.save(offlineConsultation);
                    } catch (NumberFormatException e) {
                        logger.error("TaiheRegisterService#payCallback(request={} ) Failed to convert number:\n {}", request,  e.getMessage(), e);
                    }
                }

                if (registeredOrder.getDoctorId() > 0) {
                    //关注医生
                    followDoctor(registeredOrder.getUserId(), registeredOrder.getDoctorId());
                }
            }

            builder.setCode(ResultCode.SUCCEED_VALUE);
        } catch (Exception e) {
            logger.error("payCallback():{}, {}", request, e.getMessage(), e);
            // 挂号失败，需要做退费处理
            if (registeredOrder != null) {
                logger.error("TaiheRegisterService#payCallback(): 挂号失败，退费处理");
                String errMsg = Checker.getStringValue(e.getMessage(), "挂号失败，未知异常");
                int maxMsgLength = 100;
                registeredOrder.setRemark(errMsg.length() > maxMsgLength ? errMsg.substring(0, maxMsgLength) : errMsg);
                // 挂号失败，修改挂号订单状态为初始状态
                registeredOrder.setRegisteredStatus(0);
                registeredOrderRepository.save(registeredOrder);
                refund(registeredOrder.getOrderId(), registeredOrder.getOrderNo(), registeredOrder.getId());
            }
            builder.setCode(ResultCode.APP_EXCEPTION_VALUE).setInfo(Constants.ERROR_INFO);
        } finally {
            return builder.build();
        }
    }

    /**
     * 预约挂号业务，系统发起退款成功后自动向 HIS 发起退号申请，目的是尽可能保证双方的状态一致
     */
    private void cancelRegAfterRefunded(String refundNo) {
        try {
            com.ywt.outpatient.domain.entity.center.RegisteredOrder registeredOrder = registeredOrderRepository.getFirstByRefundNoAndDeletedFalse(refundNo);
            if (registeredOrder == null) {
                logger.warn("TaiheRegisterService#cancelRegAfterRefunded(refundNo = {}): 找不到订单信息", refundNo);
                return ;
            }
            String hisOrderCode = Checker.getStringValue(registeredOrder.getHisOrderCode());
            String medCardNo = Checker.getStringValue(registeredOrder.getMedicalCardNo());
            String hisPatientId = Checker.getStringValue(registeredOrder.getHisPatientId());
            String hisClinicCode = Checker.getStringValue(registeredOrder.getHisClinicCode());
            String payMode = Constants.PAY_MODE_WECHAT;
            boolean isAlipay = Checker.getIntegerValue(registeredOrder.getPaymentChannel()) == PaymentChannelEnum.AliPay.getValue();
            if (isAlipay) {
                payMode = Constants.PAY_MODE_ALI;
            }
            String transactionId = Checker.getStringValue(registeredOrder.getWxTransactionId());
            int hospitalId = Checker.getIntegerValue(registeredOrder.getHospitalId());

            CancelRequest cancelReq = CancelRequest.newBuilder()
                    .setOrderCode(hisOrderCode)
                    .setCardNo(Checker.getStringValue(medCardNo))
                    .setPatId(Checker.getStringValue(hisPatientId))
                    .setClinicCode(Checker.getStringValue(hisClinicCode))
                    .setHospitalId(hospitalId)
                    .setTransactionId(transactionId)
                    .setPayMode(payMode)
                    .build();
            CancelResponse cancelRes = taiheServiceBlockingStub.cancel(cancelReq);

            if (cancelRes.getCode() != ResultCode.SUCCEED_VALUE) {
                logger.error("TaiheRegisterService#cancelRegAfterRefunded(hisOrderCode={} , medCardNo={} , hisPatientId={} , " +
                                "hisClinicCode={} , hospitalId={} , transactionId={} , payMode={} ):退号失败：{}",
                        hisOrderCode, medCardNo, hisPatientId, hisClinicCode, hospitalId, transactionId, payMode, cancelRes.getMsg());
            } else {
                logger.info("TaiheRegisterService#cancelRegAfterRefunded(hisOrderCode={} , medCardNo={} , hisPatientId={} , " +
                                "hisClinicCode={} , hospitalId={} , transactionId={} , payMode={} ):退号成功",
                        hisOrderCode, medCardNo, hisPatientId, hisClinicCode, hospitalId, transactionId, payMode);
            }
        } catch (Exception e) {
            logger.error("TaiheRegisterService#cancelRegAfterRefunded(refundNo={}):\n {}", refundNo, e.getMessage(), e);
        }
    }

    /**
     * 支付宝挂号单状态消息
     * <a href="https://gw.alipayobjects.com/os/bmw-prod/05064ae2-e3c7-4e6c-93ed-ad4740b66000.pdf">merchantOrderStatus 状态文档</a>
     */
    private void sendAlipayMpMsgForReg(int orderId, String orderNo, String orderCreateTime, String orderAmountStr,
                                       String tradeNo, String hospitalName, String deptName, String doctorName,
                                       String doctorId, String patientName, String regDate, String deptLoc,
                                       String alipayUid, int hospitalId, String merchantOrderStatus) {
        String path = "/msg/sendRegMsg";
        Map<String, Object> params = new HashMap<>();
        params.put("orderId", orderId);
        params.put("orderNo", orderNo);
        params.put("orderCreateTime", orderCreateTime);
        params.put("orderAmountStr", orderAmountStr);
        params.put("tradeNo", tradeNo);
        params.put("hospitalName", hospitalName);
        params.put("deptName", deptName);
        params.put("doctorName", doctorName);
        params.put("doctorId", doctorId);
        params.put("patientName", patientName);
        params.put("regDate", regDate);
        params.put("deptLoc", deptLoc);
        params.put("alipayUid", alipayUid);
        params.put("hospitalId", hospitalId);
        params.put("merchantOrderStatus", merchantOrderStatus);
        try {
            HttpUtil.Resp resp = restCallerApiProvider.postAlipayMpRestApi(path, params);
        } catch (Exception e) {
            logger.error("TaiheRegisterService#sendAlipayMpMsgForReg(orderId={} , orderNo={} , orderCreateTime={} , " +
                            "orderAmountStr={} , tradeNo={} , hospitalName={} , deptName={} , doctorName={} , doctorId={} , " +
                            "patientName={} , regDate={} , deptLoc={} , alipayUid={} , hospitalId={}, merchantOrderStatus={} ):\n {}",
                    orderId, orderNo, orderCreateTime, orderAmountStr, tradeNo, hospitalName, deptName, doctorName,
                    doctorId, patientName, regDate, deptLoc, alipayUid, hospitalId, merchantOrderStatus, e.getMessage(), e);
        }
    }

    /**
     * 退款回调
     *
     * @param request {@link RefundCallbackRequest}
     */
    @Override
    public RefundCallbackResponse refundCallback(RefundCallbackRequest request) {
        RefundCallbackResponse.Builder builder = RefundCallbackResponse.newBuilder();
        try {
            String sql =
                    "update registered_order " +
                            "set refund_status = ?, refund_callback_time = now() " +
                            "where refund_no = ? and refund_status = ?;";
            Object[] args = new Object[]{
                    request.getRefundStatus(),
                    request.getRefundNo(),
                    RefundStatusEnum.PENDING.getValue()
            };
            int[] types = new int[]{
                    Types.INTEGER,
                    Types.VARCHAR,
                    Types.INTEGER
            };
            int count = jdbcTemplate.update(sql, args, types);

            if (count > 0) {
                builder.setCode(ResultCode.SUCCEED_VALUE);
                cancelRegAfterRefunded(request.getRefundNo());
            } else {
                builder.setCode(ResultCode.APP_ERROR_VALUE).setMsg("流水号不存在或重复回调");
            }
        } catch (Exception e) {
            logger.error("refundCallback({}): {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_EXCEPTION_VALUE).setMsg(Constants.ERROR_INFO);
        } finally {
            return builder.build();
        }
    }

    /**
     * 挂号（线下就诊挂号，挂号信息已先保存至数据表中）
     *
     * @param request {@link com.ywt.gapi.taihe.register.RegisterRequest}
     * @deprecated 接入东华 HIS 不再调用这个方法
     */
    @Deprecated
    @Override
    public RegisterResponse register(com.ywt.gapi.taihe.register.RegisterRequest request) {
        RegisterResponse.Builder builder =
                RegisterResponse.newBuilder();
        try {
            com.ywt.outpatient.domain.entity.center.RegisteredOrder registeredOrder = registeredOrderRepository.getById(request.getRegId());
            if (registeredOrder == null) {
                builder.setCode(ResultCode.APP_ERROR_VALUE).setMsg("regId not exist");
                return builder.build();
            }

            com.ywt.gapi.third.taihe.RegisterResponse registerResponse = register(registeredOrder);
            if (registerResponse.getCode() != ResultCode.SUCCEED_VALUE) {
                builder.setCode(ResultCode.APP_ERROR_VALUE).setMsg(registerResponse.getMsg());
                return builder.build();
            }

            //0元号的支付状态回写到HIS系统
            payFree(registerResponse.getOrderCode(), registeredOrder.getHisPatientId(), Checker.getIntegerValue(registeredOrder.getHospitalId()));

            builder.setCode(ResultCode.SUCCEED_VALUE);
        } catch (Exception e) {
            logger.error("register({}): {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_EXCEPTION_VALUE).setMsg(Constants.ERROR_INFO);
        } finally {
            return builder.build();
        }
    }


    /**
     * 取号
     *
     * @param request          {@link com.ywt.gapi.taihe.register.TakeRequest}
     */
    @Override
    public com.ywt.gapi.taihe.register.TakeResponse take(com.ywt.gapi.taihe.register.TakeRequest request) {
        com.ywt.gapi.taihe.register.TakeResponse.Builder builder = com.ywt.gapi.taihe.register.TakeResponse.newBuilder();
        try {
            // 默认太和，兼容
            int hospitalId = request.getHospitalId() > 0 ? request.getHospitalId() : Constants.TAIHE_HOSPITAL_ID;
            com.ywt.outpatient.domain.entity.center.RegisteredOrder registeredOrder = registeredOrderRepository.getFirstByOrderIdAndHospitalIdAndDeletedFalse(
                    request.getOrderId(), hospitalId);

            if (registeredOrder == null) {
                builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setMsg("挂号订单不存在");
                return builder.build();
            }
            if (registeredOrder.getUserId() != request.getUserId()) {
                builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setMsg("非法请求");
                return builder.build();
            }
            if (StringHelper.isNullOrWhiteSpace(registeredOrder.getHisOrderCode())) {
                builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setMsg("订单未预约成功");
                return builder.build();
            }

            //进行取号处理
            com.ywt.gapi.third.taihe.TakeRequest req = com.ywt.gapi.third.taihe.TakeRequest.newBuilder()
                    .setOrderCode(registeredOrder.getHisOrderCode())
                    .setPatientId(registeredOrder.getHisPatientId())
                    .setHospitalId(hospitalId)
                    .build();
            com.ywt.gapi.third.taihe.TakeResponse res = taiheServiceBlockingStub.take(req);

            if (res.getCode() != ResultCode.SUCCEED_VALUE) {
                builder.setCode(ResultCode.APP_ERROR_VALUE).setMsg(res.getMsg());
                return builder.build();
            }

            registeredOrder.setRegisteredStatus(registeredOrder.getRegisteredStatus() | RegisteredStatusEnum.Took.getValue());
            registeredOrder.setHisClinicCode(res.getRegistrationId());
            registeredOrderRepository.save(registeredOrder);

            //发送模板消息
            taiheWeChatMsgService.sendMsgForTook(registeredOrder);

            builder.setCode(ResultCode.SUCCEED_VALUE).setHisClinicCode(Checker.getStringValue(registeredOrder.getHisClinicCode()));
        } catch (Exception e) {
            logger.error("take({}): {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_EXCEPTION_VALUE).setMsg(Constants.ERROR_INFO);
        } finally {
            return builder.build();
        }
    }

    /**
     * 获取候诊订单列表
     *
     * @param request          {@link GetQueueListRequest}
     */
    @Override
    public GetQueueListResponse getQueueList(GetQueueListRequest request) {
        GetQueueListResponse.Builder builder = GetQueueListResponse.newBuilder();

        try {
            if (request.getUserId() < 1) {
                builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setMsg("用户不存在");
                return builder.build();
            }
            int hospitalId = request.getHospitalId() > 0 ? request.getHospitalId() : Constants.TAIHE_HOSPITAL_ID;
            List<RegisteredOrder> lst = new LinkedList<>();
            String currentDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-M-d"));
            List<com.ywt.outpatient.domain.entity.center.RegisteredOrder> orderList = registeredOrderRepository.listQueueOrders(request.getUserId(), currentDate,
                    hospitalId);

            if (orderList != null) {
                for (com.ywt.outpatient.domain.entity.center.RegisteredOrder order : orderList) {
                    Date paymentDate;
                    if (order.getPaymentTime() != null) {
                        paymentDate = order.getPaymentTime();
                    } else {
                        paymentDate = order.getCreateTime();
                    }

                    int status = -1;
                    String statusStr = "";
                    Map<String, Integer> map = getRegisteredStatusName(order);
                    for (Map.Entry<String, Integer> me : map.entrySet()) {
                        //根据键值对对象获得键和值
                        status = me.getValue();
                        statusStr = me.getKey();
                    }

                    RegisteredOrder item = RegisteredOrder.newBuilder()
                            .setOrderId(order.getOrderId())
                            .setOrderNo(order.getOrderNo())
                            .setRegisteredId(order.getId())
                            .setUserId(order.getUserId())
                            .setHospitalId(order.getHospitalId())
                            .setHospitalName(BizUtil.getHospitalNameByIdSimply(order.getHospitalId()))
                            .setRegisteredDate(DateUtil.convertToTimestamp(order.getRegisteredDate()))
                            .setRegisteredPeriod(order.getRegisteredPeriod())
                            .setStartTime(Checker.getStringValue(order.getStartTime()))
                            .setEndTime(Checker.getStringValue(order.getEndTime()))
                            .setMedicalCardId(order.getMedicalCardId())
                            .setPatientName(order.getPatientName())
                            .setPaymentChannel(order.getPaymentChannel())
                            .setCreateTime(DateUtil.convertToTimestamp(order.getCreateTime()))
                            .setDoctorCode(Checker.getStringValue(order.getDoctorCode()))
                            .setDeptCode(Checker.getStringValue(order.getDeptCode()))
                            .setDeptName(Checker.getStringValue(order.getDeptName()))
                            .setRegisteredFee(order.getRegisteredFee())
                            .setMedicalFee(order.getMedicalFee())
                            .setPaymentStatus(order.getPaymentStatus())
                            .setRegisteredStatus(order.getRegisteredStatus())
                            .setTotal(order.getTotal())
                            .setDoctorName(Checker.getStringValue(order.getDoctorName()))
                            .setSex(Checker.getIntegerValue(order.getPatientSex()))
                            .setHisClinicCode(Checker.getStringValue(order.getHisClinicCode()))
                            .setAddress(Checker.getStringValue(order.getHisAdmitAddress()))
                            .setPaymentDate(DateUtil.convertToTimestamp(paymentDate))
                            .setStatusName(statusStr)
                            .setStatus(status)
                            .setHisSeqCode(Checker.getStringValue(order.getHisSeqCode()))
                            .build();
                    lst.add(item);
                }
            }

            builder.setCode(ResultCode.SUCCEED_VALUE).addAllOrder(lst);
        } catch (Exception e) {
            logger.error("getQueueList({}): {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_EXCEPTION_VALUE).setMsg(Constants.ERROR_INFO);
        } finally {
            return builder.build();
        }
    }

    /**
     * 获取太和门诊记录
     *
     * @param request          {@link GetCommonOrderListRequest}
     */
    @Override
    public GetCommonOrderListResponse getCommonOrderList(GetCommonOrderListRequest request) {
        GetCommonOrderListResponse.Builder builder = GetCommonOrderListResponse.newBuilder();
        try {
            List<Object> paramList = new LinkedList<>();
            List<Integer> typeList = new LinkedList<>();
            StringBuilder whereSql = new StringBuilder("user_id = ? and (order_type = ? or order_type = 13) and deleted = 0");
            //v2.2.2版本，白云不展示线下就诊
            if (request.getTerminal() == TerminalEnum.NFYYBYFY_WXAPP.getValue()) {
                whereSql = new StringBuilder("user_id = ? and (order_type = ?) and deleted = 0");
            }

            paramList.add(request.getUserId());
            paramList.add(OrderTypeEnum.REGISTERED.getValue());
//            paramList.add(OrderType.OFFLINE_CONSULT.getValue());
//            paramList.add(request.getTerminal());
//            typeList.add(Types.INTEGER);
            typeList.add(Types.INTEGER);
            typeList.add(Types.INTEGER);

            if (request.getStartDate() > 0L && request.getEndDate() > 0L && request.getStartDate() < request.getEndDate()) {
                whereSql.append(" and create_time >= ? and create_time < ?");
                paramList.add(new Date(request.getStartDate()));
                paramList.add(new Date(request.getEndDate()));
                typeList.add(Types.TIMESTAMP);
                typeList.add(Types.TIMESTAMP);
            }

            Object[] args = paramList.toArray();
            int[] argTypes = typeList.stream().mapToInt(i -> i).toArray();
            String orderBy = "id desc";
            String fields = "*";
            PagedList<Orders> pagedList = sqlHelper.getPagedList(
                    request.getPageIndex(), request.getPageSize(), fields, whereSql.toString(), orderBy, Orders.class, args, argTypes);
            CommonOrder commonOrder;
            List<CommonOrder> lst = new LinkedList<>();
            if (pagedList.getItems() != null && pagedList.getItems().size() > 0) {
                for (Orders item : pagedList.getItems()) {
                    int hospitalId = 0;
                    String hospitalName = "";
                    String deptName = "";
                    String doctorName = "";
                    String doctorTitle = "";
                    String patientName = "";
                    int amount = 0;
                    String statusName = "";
                    String patientSex = "";
                    String registeredDate = "";
                    String startTime = "";
                    String endTime = "";
                    //订单是否可以取消，1-是，0-否
                    int cancel = 0;
                    String deptCode = "";
                    int registeredType = 0;
                    String hisAdmAddr = "";// HIS 就诊地点
                    String hisPatientId = "";
                    int subHospitalId = 0;
                    String seqCode = "";
                    String addr = "";

                    if (item.getOrderType() == OrderTypeEnum.REGISTERED.getValue()) {
                        com.ywt.outpatient.domain.entity.center.RegisteredOrder registeredOrder = registeredOrderRepository.getById(item.getBusinessId());
                        if (registeredOrder == null || registeredOrder.getDeleted() || registeredOrder.getRegisteredStatus() <= 0
                                || checkIsNetDept(registeredOrder.getDeptCode(), registeredOrder.getHospitalId())) {
                            continue;
                        }

                        hospitalId = registeredOrder.getHospitalId();
                        hospitalName = registeredOrder.getHospitalName();
                        deptName = registeredOrder.getDeptName();
                        doctorName = registeredOrder.getDoctorName();
                        doctorTitle = registeredOrder.getDoctorTitle();
                        patientName = registeredOrder.getPatientName();
                        patientSex = Gender.getDisplayName(registeredOrder.getPatientSex());
                        amount = registeredOrder.getTotal();
                        deptCode = registeredOrder.getDeptCode();
                        hisPatientId = registeredOrder.getHisPatientId();
                        registeredType = Checker.getIntegerValue(registeredOrder.getRegisteredType(), RegisteredTypeEnum.GENERAL.getValue());
                        startTime = registeredOrder.getStartTime();
                        endTime = registeredOrder.getEndTime();
                        seqCode = Checker.getStringValue(registeredOrder.getHisSeqCode());
                        registeredDate = String.format("%s %s~%s"
                                , DateUtil.formatDate(registeredOrder.getRegisteredDate() ,"yyyy-MM-dd")
                                , registeredOrder.getStartTime()
                                , registeredOrder.getEndTime());
                        hisAdmAddr = Checker.getStringValue(registeredOrder.getHisAdmitAddress());
                        if (hospitalId == Constants.NFYY_HOSPITAL_ID) {
                            if (!StringHelper.isNullOrWhiteSpace(registeredOrder.getHisRegFee())) {
                                //南方医院诊查费以挂号接口返回的价格为主，若HIS系统返回的 HisRegFee 字段不为空，则以该价格为主
                                amount = (int) (Checker.parseDouble(registeredOrder.getHisRegFee()) * 100d);
                            }
                            statusName = getNfyyRegisteredStatusName(registeredOrder);
                        } else if (hospitalId == Constants.TAIHE_HOSPITAL_ID || hospitalId == Constants.NFYYBYFY_HOSPITAL_ID) {
                            statusName = getTaiheRegisteredStatusName(registeredOrder);
                        }

                        if (registeredOrder.getRegisteredStatus() == 1 || registeredOrder.getRegisteredStatus() == 5) {
                            // 已预约或已取号状态
                            // 从 v2.1.9_nfby 开始，白云分院的取消预约按钮显示规则修改：（太和等医院逻辑不变）
                            // ①就诊当天00：00后，“取消预约”按钮不显示。
                            // ②预约挂号订单有诊断的（通过his接口判断），“取消预约”按钮不显示。
                            boolean hasDiagnose = hasDiagnose(Checker.getStringValue(registeredOrder.getHisClinicCode()),
                                    Checker.getIntegerValue(registeredOrder.getHospitalId()));
                            // 是否超过退号时间，规则：白云-预约日期及之后不可退号；太和等其他医院-预约日期后不可退号；
                            boolean canTodayCancelReg = canTodayCancelReg(hospitalId, registeredOrder.getRegisteredDate());
                            if (canTodayCancelReg && !hasDiagnose) {
                                cancel = 1;
                            }
                        }
                        subHospitalId = Checker.getIntegerValue(registeredOrder.getSubHospitalId());
                        addr = Checker.getStringValue(registeredOrder.getHisAdmitAddress());
                    } else if (item.getOrderType() == OrderTypeEnum.OFFLINE_CONSULT.getValue()) {
                        OfflineConsultation consultation = offlineConsultationRepository.getById(item.getBusinessId());
                        if (consultation == null || consultation.getDeleted()
                                || consultation.getPaymentStatus() != PaymentStatusEnum.Success.getValue()) {
                            continue;
                        }
                        if (consultation.getRefundStatus() != null &&
                                consultation.getRefundStatus() == RefundStatusEnum.SUCCESS.getValue()) {
                            statusName = "已退款";
                        } else {
                            statusName = "已支付";
                            // 从 v2.1.9_nfby 开始，白云分院的取消预约按钮显示规则修改：（太和等医院逻辑不变）
                            // ①就诊当天00：00后，“取消预约”按钮不显示。
                            // ②预约挂号订单有诊断的（通过his接口判断），“取消预约”按钮不显示。
                            int regId = Checker.getIntegerValue(consultation.getRegId());
                            if (regId > 0) {
                                com.ywt.outpatient.domain.entity.center.RegisteredOrder registeredOrder = registeredOrderRepository.getById(regId);
                                boolean hasDiagnose = hasDiagnose(Checker.getStringValue(registeredOrder.getHisClinicCode()),
                                        Checker.getIntegerValue(registeredOrder.getHospitalId()));
                                // 是否超过退号时间，规则：白云-预约日期及之后不可退号；太和等其他医院-预约日期后不可退号；
                                boolean canTodayCancelReg = canTodayCancelReg(hospitalId, registeredOrder.getRegisteredDate());
                                if (canTodayCancelReg && !hasDiagnose) {
                                    cancel = 1;
                                }
                            } else {
                                logger.info("TaiheRegisterService#getCommonOrderList(bizId={}): regId 有误", item.getBusinessId());
                            }
                        }

                        hospitalId = Checker.getIntegerValue(consultation.getHospitalId());
                        hospitalName = Checker.getStringValue(consultation.getHospitalName());
                        doctorName = consultation.getDoctorName();
                        patientName = consultation.getRealName();
                        patientSex = Gender.getDisplayName(consultation.getSex());
                        amount = consultation.getTotal();

                        deptName = "名医门诊";
                        registeredDate = consultation.getRegisteredTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
                        if (consultation.getRegId() != null && consultation.getRegId() > 0) {
                            com.ywt.outpatient.domain.entity.center.RegisteredOrder registeredOrder = registeredOrderRepository.getById(consultation.getRegId());
                            if (registeredOrder != null && !registeredOrder.getDeleted()) {
                                if (registeredOrder.getDeptId() > 0) {
                                    GetDeptRequest req = GetDeptRequest.newBuilder().setDeptId(registeredOrder.getDeptId()).build();
                                    GetDeptResponse res = systemServiceBlockingStub.getDept(req);
                                    if (res.getCode() == ResultCode.SUCCEED_VALUE) {
                                        deptName = res.getDept().getDeptName() + "（名医门诊）";
                                    }
                                }
                                registeredDate = String.format("%s %s~%s"
                                        , DateUtil.formatDate(registeredOrder.getRegisteredDate(), "yyyy-MM-dd")
                                        , registeredOrder.getStartTime()
                                        , registeredOrder.getEndTime());
                            }
                        }
                    }

                    commonOrder = CommonOrder.newBuilder()
                            .setOrderId(item.getId())
                            .setOrderNo(item.getOrderNo())
                            .setOrderType(item.getOrderType())
                            //统一显示为 预约挂号
                            .setOrderTitle(OrderTypeEnum.REGISTERED.getDisplayName())
                            .setHospitalId(hospitalId)
                            .setHospitalName(Checker.getStringValue(hospitalName))
                            .setDeptName(Checker.getStringValue(deptName))
                            .setDoctorName(Checker.getStringValue(doctorName))
                            .setDoctorTitle(Checker.getStringValue(doctorTitle))
                            .setDeptCode(deptCode)
                            .setPatientName(patientName)
                            .setPatientSex(patientSex)
                            .setAmount(amount)
                            .setRegisteredType(registeredType)
                            .setRegisteredDate(registeredDate)
                            .setStartTime(startTime)
                            .setEndTime(endTime)
                            .setStatusName(statusName)
                            .setCancel(cancel)
                            .setHisAdmAddr(hisAdmAddr)
                            .setHisPatientId(hisPatientId)
                            .setSubHospitalId(subHospitalId)
                            .setSeqCode(seqCode)
                            .setHisAdmAddr(addr)
                            .build();
                    lst.add(commonOrder);
                }
            }
            builder.setCode(ResultCode.SUCCEED_VALUE)
                    .addAllOrder(lst)
                    .setTotalPage(pagedList.getTotalPage())
                    .setCount(pagedList.getTotal());
        } catch (Exception e) {
            logger.error("getCommonOrderList({}): {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_EXCEPTION_VALUE).setMsg(Constants.ERROR_INFO);
        } finally {
            return builder.build();
        }
    }

    /**
     * 判断 HIS 是否已有诊断
     */
    private boolean hasDiagnose(String hisClinicCode, int hospitalId) {
        GetDiagnoseRecordRequest getDiagnoseRecordRequest = GetDiagnoseRecordRequest.newBuilder()
                .setClinicCode(hisClinicCode)
                .setHospitalId(hospitalId)
                .build();
        GetDiagnoseRecordResponse getDiagnoseRecordResponse = taiheServiceBlockingStub.getDiagnoseRecord(getDiagnoseRecordRequest);
        if (getDiagnoseRecordResponse.getCode() == ResultCode.SUCCEED_VALUE) {
            // 请求成功，视为已出诊断
            return true;
        } else {
            logger.info("TaiheRegisterService#hasDiagnose(hisClinicCode={} , hospitalId={} ): 获取诊断失败：{}", hisClinicCode, hospitalId, getDiagnoseRecordResponse.getInfo());
            return false;
        }
    }

    /**
     * 判断当前日期是否超过可退号日期
     * <p>
     * 规则：白云-预约日期及之后不可退号；太和等其他医院-预约日期后不可退号；
     *
     * @param hospitalId     医院 id
     * @param registeredDate 挂号日期
     * @return 是否超过可退号日期
     */
    private boolean canTodayCancelReg(int hospitalId, Date registeredDate) {
        Date currentDate = new Date();
        return hospitalId == Constants.NFYYBYFY_HOSPITAL_ID ? currentDate.before(registeredDate) : !currentDate.after(registeredDate);
    }


    /**
     * 获取挂号订单列表
     *
     * @param request          {@link GetRegisteredListRequest}
     */
    @Override
    public GetRegisteredListResponse getRegisteredList(GetRegisteredListRequest request) {
        GetRegisteredListResponse.Builder builder = GetRegisteredListResponse.newBuilder();
        try {
            List<RegisteredOrder> lst = new LinkedList<>();
            PagedList<com.ywt.outpatient.domain.entity.center.RegisteredOrder> pagedList = listRegisteredOrder(
                    request.getPageIndex(), request.getPageSize(), request.getUserId(), 0L, 0L,
                    request.getHospitalId(), request.getRegisteredType());

            if (pagedList.getItems() != null && pagedList.getItems().size() > 0) {
                for (com.ywt.outpatient.domain.entity.center.RegisteredOrder item : pagedList.getItems()) {
                    int total = item.getTotal();
                    String statusName = "";
                    String deptName = item.getDeptName();

                    if (item.getHospitalId() == Constants.TAIHE_HOSPITAL_ID || item.getHospitalId() == Constants.NFYYBYFY_HOSPITAL_ID) {
                        statusName = getTaiheRegisteredStatusName(item);

                        if (checkIsNetDept(item.getDeptCode(), item.getHospitalId())) {
                            deptName = "名医门诊";
                            if (item.getDeptId() > 0) {
                                GetDeptRequest req = GetDeptRequest.newBuilder().setDeptId(item.getDeptId()).build();
                                GetDeptResponse res = systemServiceBlockingStub.getDept(req);
                                if (res.getCode() == ResultCode.SUCCEED_VALUE) {
                                    deptName = res.getDept().getDeptName() + "（名医门诊）";
                                }
                            }
                        }
                    } else {
                        statusName = getNfyyRegisteredStatusName(item);
                        if (!StringHelper.isNullOrWhiteSpace(item.getHisRegFee())) {
                            //南方医院诊查费以挂号接口返回的价格为主，若HIS系统返回的 HisRegFee 字段不为空，则以该价格为主
                            total = (int) (Checker.parseDouble(item.getHisRegFee()) * 100d);
                        }
                    }
                    RegisteredOrder registeredOrder = RegisteredOrder.newBuilder()
                            .setOrderId(item.getOrderId())
                            .setOrderNo(item.getOrderNo())
                            .setHospitalName(Checker.getStringValue(item.getHospitalName()))
                            .setHospitalId(Checker.getIntegerValue(item.getHospitalId()))
                            .setRegisteredId(item.getId())
                            .setUserId(item.getUserId())
                            .setRegisteredDate(DateUtil.convertToTimestamp(item.getRegisteredDate()))
                            .setRegisteredPeriod(item.getRegisteredPeriod())
                            .setStartTime(Checker.getStringValue(item.getStartTime()))
                            .setEndTime(Checker.getStringValue(item.getEndTime()))
                            .setMedicalCardId(item.getMedicalCardId())
                            .setPaymentChannel(item.getPaymentChannel())
                            .setCreateTime(DateUtil.convertToTimestamp(item.getCreateTime()))
                            .setDoctorCode(Checker.getStringValue(item.getDoctorCode()))
                            .setDoctorName(item.getDoctorName())
                            .setDeptCode(item.getDeptCode())
                            .setDeptName(deptName)
                            .setRegisteredFee(item.getRegisteredFee())
                            .setRegisteredType(item.getRegisteredType())
                            .setMedicalFee(item.getMedicalFee())
                            .setPaymentStatus(item.getPaymentStatus())
                            .setRegisteredStatus(item.getRegisteredStatus())
                            .setTotal(total)
                            .setDoctorName(item.getDoctorName())
                            .setSex(item.getPatientSex())
                            .setPatientName(item.getPatientName())
                            .setStatusName(statusName)
                            .build();
                    lst.add(registeredOrder);
                }
            }
            builder.setCode(ResultCode.SUCCEED_VALUE)
                    .addAllOrder(lst)
                    .setTotalPage(pagedList.getTotalPage())
                    .setCount(pagedList.getTotal());
        } catch (Exception e) {
            logger.error("getRegisteredList({}): {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_EXCEPTION_VALUE).setMsg(Constants.ERROR_INFO);
        } finally {
            return builder.build();
        }
    }

    /**
     * 南方医院预约挂号订单状态
     *
     * @param registeredOrder
     * @return
     */
    private String getNfyyRegisteredStatusName(com.ywt.outpatient.domain.entity.center.RegisteredOrder registeredOrder) {
        if ((registeredOrder.getRegisteredStatus() & RegisteredStatusEnum.Cancel.getValue()) == RegisteredStatusEnum.Cancel.getValue() ||
                (registeredOrder.getRegisteredStatus() & RegisteredStatusEnum.Back.getValue()) == RegisteredStatusEnum.Back.getValue()) {
            return "已取消";
        }
        if (registeredOrder.getPaymentStatus() == PaymentStatusEnum.Pending.getValue()) {
            return "待支付";
        }
        if (registeredOrder.getPaymentStatus() == PaymentStatusEnum.Success.getValue()) {
            String regDateStr = DateUtil.formatDate(registeredOrder.getRegisteredDate(), "yyyy-MM-dd");
            if (registeredOrder.getEndTime() != null) {
                regDateStr = regDateStr + " " + registeredOrder.getEndTime() + ":00";
            } else {
                regDateStr = regDateStr + " " + "00:00:00";
            }
            if (DateUtil.hourBetween(regDateStr) <= 0) {
                return "已完成";
            }
            return "待就诊";
        }
        return "";
    }

    /**
     * 太和分院预约挂号订单状态
     *
     * @param registeredOrder
     * @return
     */
    private String getTaiheRegisteredStatusName(com.ywt.outpatient.domain.entity.center.RegisteredOrder registeredOrder) {
        String statusName = "";
        if (registeredOrder.getRefundStatus() != null && registeredOrder.getRefundStatus() == RefundStatusEnum.SUCCESS.getValue()) {
            statusName = "已退款";
        } else if ((registeredOrder.getRegisteredStatus() & RegisteredStatusEnum.Cancel.getValue()) == RegisteredStatusEnum.Cancel.getValue() ||
                (registeredOrder.getRegisteredStatus() & RegisteredStatusEnum.Back.getValue()) == RegisteredStatusEnum.Back.getValue()) {
            statusName = "已取消";
        } else if (registeredOrder.getPaymentStatus() == PaymentStatusEnum.Pending.getValue()) {
            statusName = "待支付";
        } else if (registeredOrder.getPaymentStatus() == PaymentStatusEnum.Success.getValue()) {
            statusName = "待就诊";

            String regDateStr = DateUtil.formatDate(registeredOrder.getRegisteredDate(), "yyyy-MM-dd");
            if (registeredOrder.getEndTime() != null) {
                regDateStr = regDateStr + " " + registeredOrder.getEndTime() + ":00";
            } else {
                regDateStr = regDateStr + " " + "00:00:00";
            }

            LocalDateTime now = LocalDateTime.now();
            LocalDateTime registeredTime = LocalDateTime.parse(regDateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

            if (now.isAfter(registeredTime)) {
                statusName = "已完成";
            }
        }
        return statusName;
    }

    /**
     * 获取预约挂号订单状态
     *
     * @param registeredOrder
     * @return
     */
    public Map<String, Integer> getRegisteredStatusName(com.ywt.outpatient.domain.entity.center.RegisteredOrder registeredOrder) {
        Map<String, Integer> map = new HashMap<>(1);
        int registeredStatus = registeredOrder.getRegisteredStatus();
        if ((registeredStatus & RegisteredStatusEnum.Back.getValue()) == RegisteredStatusEnum.Back.getValue()) {
            map.put("已退号", 5);
            return map;
        }
        if ((registeredStatus & RegisteredStatusEnum.Expired.getValue()) == RegisteredStatusEnum.Expired.getValue()) {
            map.put("已过期", 4);
            return map;
        }
        if ((registeredStatus & RegisteredStatusEnum.Cancel.getValue()) == RegisteredStatusEnum.Cancel.getValue()) {
            map.put("已取消", 2);
            return map;
        }
        if ((registeredStatus & RegisteredStatusEnum.Took.getValue()) == RegisteredStatusEnum.Took.getValue()) {
            map.put("已取号", 3);
            return map;
        }
        if ((registeredStatus & RegisteredStatusEnum.Booked.getValue()) == RegisteredStatusEnum.Booked.getValue()) {
            map.put("已预约", 1);
            return map;
        }

        return map;
    }


    /**
     * 发起退款
     *
     * @param orderId    订单Id
     * @param orderNo    订单编号
     * @param registerId 预约挂号表主键
     */
    private void refund(int orderId, String orderNo, int registerId) {
        String sql =
                "update registered_order " +
                        "set refund_status = ?, refund_time = now() " +
                        "where id = ? and (refund_status is null or refund_status = ? or refund_status = ?);";
        Object[] args = new Object[]
                {
                        RefundStatusEnum.PENDING.getValue(),
                        registerId,
                        RefundStatusEnum.EXCEPTION.getValue(),
                        RefundStatusEnum.CLOSED.getValue()
                };
        int[] types = new int[]
                {
                        Types.INTEGER,
                        Types.INTEGER,
                        Types.INTEGER,
                        Types.INTEGER
                };
        int count = jdbcTemplate.update(sql, args, types);

        if (count < 1) {
            logger.info("refund({}, {}, {}): registered order not exist or refund repeatedly.", orderId, orderNo, registerId);
            return ;
        }

        RefundRequest req = RefundRequest.newBuilder()
                //退款金额(单位：分), 当为0时，默认该订单总金额
                .setAmount(0)
                .setOrderId(orderId)
                .setOrderNo(orderNo)
                //退款方式，1: 人工, 2: 系统
                .setRefundType(2)
                .setRefundNotifyMethod("/com.ywt.gapi.taihe.register.TaiheRegisterService/refundCallback")
                .build();
        RefundResponse res = orderCenterServiceBlockingStub.refund(req);

        if (res.getCode() == ResultCode.SUCCEED_VALUE) {
            sql = "update registered_order set refund_no = ? where id = ?;";
            args = new Object[]{res.getRefundNo(), registerId};
            types = new int[]{Types.VARCHAR, Types.INTEGER};
            count = jdbcTemplate.update(sql, args, types);

            if (count < 1) {
                logger.info("refund({}, {}, {}): 退款成功，更新状态失败", orderId, orderNo, registerId);
            }
            return ;
        }

        //发起退款失败，还原状态
        sql = "update registered_order set refund_status = null, refund_time = null where id = ?;";
        args = new Object[]{registerId};
        types = new int[]{Types.INTEGER};
        count = jdbcTemplate.update(sql, args, types);

        if (count < 1) {
            logger.info("refund({}, {}, {}): 还原状态失败", orderId, orderNo, registerId);
        }
        logger.info("refund({}, {}, {}): refund failed. req: {}; res: {}", orderId, orderNo, registerId, req, res);
    }

    /**
     * 调用HIS接口，进行挂号
     * 若挂号成功，更新挂号信息，并且如果系统存在该挂号医生的信息，则患者会自动关注该医生
     *
     * @param registeredOrder 挂号订单
     * @return {@link RegisterResponse}
     * @deprecated 接入东华 HIS 不再调用这个方法
     */
    @Deprecated
    private com.ywt.gapi.third.taihe.RegisterResponse register(com.ywt.outpatient.domain.entity.center.RegisteredOrder registeredOrder) {
        com.ywt.gapi.third.taihe.RegisterRequest req = com.ywt.gapi.third.taihe.RegisterRequest.newBuilder()
                .setScheduleItemCode(registeredOrder.getScheduleItemCode())
                .setPatientId(registeredOrder.getHisPatientId())
                .setCardNo(registeredOrder.getMedicalCardNo())
                .setStartTime(registeredOrder.getStartTime())
                .setEndTime(registeredOrder.getEndTime())
                .build();
        com.ywt.gapi.third.taihe.RegisterResponse res = taiheServiceBlockingStub.register(req);

        if (res.getCode() == ResultCode.SUCCEED_VALUE) {
            //挂号成功，更新挂号信息
//            registeredOrderRepository.updateRegisterInfoById(res.getSeqCode(), String.valueOf(res.getRegFee())
//                    , res.getAdmitRange(), res.getAdmitAddress(), res.getOrderContent(), ""
//                    , res.getOrderCode(), RegisteredStatusEnum.Booked.getValue(), registeredOrder.getId());

            if (registeredOrder.getDoctorId() > 0) {
                //关注医生
                followDoctor(registeredOrder.getUserId(), registeredOrder.getDoctorId());
            }
        }
        return res;
    }

    /**
     * 0元号的支付状态回写到HIS系统
     *
     * @param hisOrderCode HIS系统订单编码
     * @param hisPatientId HIS系统患者ID（门诊ID）
     */
    private void payFree(String hisOrderCode, String hisPatientId, int hospitalId) {
        PayFreeRequest req = PayFreeRequest.newBuilder()
                .setOrderCode(hisOrderCode)
                .setPatientId(hisPatientId)
                .setHospitalId(hospitalId)
                .build();
        PayFreeResponse res = taiheServiceBlockingStub.payFree(req);
        if (res.getCode() != ResultCode.SUCCEED_VALUE) {
            logger.info("payFree({}, {})：0元号的支付状态回写到HIS系统失败：{}", hisOrderCode, hisPatientId, res.getMsg());
        }
    }

    /**
     * 关注医生（临时方法，后续该方法需迁至 UserService RPC）
     *
     * @param userId   用户ID
     * @param doctorId 医生ID
     */
    private void followDoctor(int userId, int doctorId) {
        UserUnions unions = userUnionsRepository.getFirstByUserIdAndUnionUserIdAndTypeAndDeletedFalse(userId, doctorId, 4);

        if (unions != null) {
            if (unions.getStatus() != 0) {
                unions.setStatus(0);
                unions.setUpdateTime(new Date());
            }
        } else {
            unions = new UserUnions();
            int unionsId = idGenerator.genUserUnionsId();
            unions.setCreateTime(new Date());
            unions.setUpdateTime(new Date());
            unions.setDeleted(false);
            unions.setId(unionsId);
            // 0-启用，1-禁用(此时表示0-关注，1-不关注)
            unions.setStatus(0);
            unions.setType(4);
            unions.setUserId(userId);
            unions.setUnionUserId(doctorId);
            unions.setHasPresc(0);
        }
        userUnionsRepository.save(unions);
    }

    /**
     * 预约挂号订单列表
     *
     * @param pageIndex
     * @param pageSize
     * @param userId
     * @param startDate
     * @param endDate
     * @param registeredType
     * @param hospitalId
     * @return
     */
    private PagedList<com.ywt.outpatient.domain.entity.center.RegisteredOrder> listRegisteredOrder(
            int pageIndex, int pageSize, int userId, long startDate, long endDate, int hospitalId, int registeredType) {
        List<Object> paramList = new LinkedList<>();
        List<Integer> typeList = new LinkedList<>();
        StringBuilder whereSql = new StringBuilder("user_id = ? and registered_status > 0 and deleted = 0");
        paramList.add(userId);
        typeList.add(Types.INTEGER);

        if (hospitalId > 0) {
            whereSql.append(" and hospital_id = ?");
            paramList.add(hospitalId);
            typeList.add(Types.INTEGER);
        }
        if (RegisteredTypeEnum.isDefined(registeredType)) {
            whereSql.append(" and registered_type = ?");
            paramList.add(registeredType);
            typeList.add(Types.INTEGER);
        }
        if (startDate > 0L && endDate > 0L && startDate < endDate) {
            whereSql.append(" and create_time >= ? and create_time < ?");
            paramList.add(new Date(startDate));
            paramList.add(new Date(endDate));
            typeList.add(Types.TIMESTAMP);
            typeList.add(Types.TIMESTAMP);
        }

        Object[] args = paramList.toArray();
        int[] argTypes = typeList.stream().mapToInt(i -> i).toArray();
        String orderBy = "id desc";
        String fields = "*";
        return sqlHelper.getPagedList(pageIndex, pageSize, fields, whereSql.toString(), orderBy, com.ywt.outpatient.domain.entity.center.RegisteredOrder.class, args, argTypes);
    }

    /**
     * 无诊疗卡预约挂号
     *
     * @param request          {@link RegisterWithoutMedicalCardRequest}
     */
    @Override
    public RegisterWithoutMedicalCardResponse registerWithoutMedicalCard(RegisterWithoutMedicalCardRequest request) {
        RegisterWithoutMedicalCardResponse.Builder builder = RegisterWithoutMedicalCardResponse.newBuilder();

        try {
            // 默认太和，兼容
            int hospitalId = request.getHospitalId() > 0 ? request.getHospitalId() : Constants.TAIHE_HOSPITAL_ID;
            VerifyRegisterWithoutMedicalCardResult result = verifyCreateRegisteredRequest(request);
            if (!result.isSuccess()) {
                builder.setCode(ResultCode.APP_ERROR_VALUE).setMsg(result.getMsg());
                return builder.build();
            }
            RegisterRecord record = new RegisterRecord();
            record.setHospitalId(hospitalId);
            record.setHospitalName(BizUtil.getHospitalNameByIdSimply(hospitalId));
            record.setDeptId(0);
            record.setDeptCode(request.getDeptCode());
            record.setDeptName(request.getDeptName());
            record.setDoctorId(0);
            record.setDoctorCode(request.getDoctorCode());
            record.setDoctorName(request.getDoctorName());
            record.setDoctorTitle(result.getDoctorTitle());
            record.setMedicalFee(request.getMedicalFee());
            record.setRegisteredFee(request.getRegisteredFee());
            record.setTotal(request.getMedicalFee() + request.getRegisteredFee());
            record.setRegisteredDate(DateUtil.convertToLocalDate(request.getRegisteredDate()));
            record.setRegisteredPeriod(request.getPeriod());
            record.setStartTime(request.getStartTime());
            record.setEndTime(request.getEndTime());
            record.setIdType(request.getIdType());
            record.setIdNo(request.getIdNo());
            record.setPatientName(request.getPatientName());
            record.setBirthday(DateUtil.convertToLocalDate(request.getBirthday()));
            record.setPatientSex(request.getPatientSex());
            record.setPatientMobile(request.getPatientMobile());
            record.setUserId(request.getUserId());
            record.setScheduleItemCode(request.getScheduleItemCode());
            record.setDeleted(false);
            record.setCreateTime(LocalDateTime.now());
            registerRecordRepository.save(record);
            builder.setCode(ResultCode.SUCCEED_VALUE);

            //发送模板消息
            taiheWeChatMsgService.sendMsgForRegisterWithoutMedicalCard(record);
        } catch (Exception e) {
            logger.error("registerWithoutMedicalCard({}): {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_EXCEPTION_VALUE).setMsg(Constants.ERROR_INFO);
        } finally {
            return builder.build();
        }
    }

    /**
     * 校验 RegisterWithoutMedicalCardRequest 参数是否合法
     *
     * @param request {@link RegisterWithoutMedicalCardRequest}
     */
    private VerifyRegisterWithoutMedicalCardResult verifyCreateRegisteredRequest(RegisterWithoutMedicalCardRequest request) {
        VerifyRegisterWithoutMedicalCardResult result = new VerifyRegisterWithoutMedicalCardResult();
        long minTimestamp = DateUtil.convertToTimestamp(LocalDate.now());
        long maxTimestamp = DateUtil.convertToTimestamp(LocalDate.now().plusDays(8));
        if (request.getRegisteredDate() < minTimestamp || request.getRegisteredDate() > maxTimestamp) {
            //只能预约8天内的号源
            return result.failed("预约日期格式不正确");
        }
        if (StringHelper.isNullOrWhiteSpace(request.getDeptCode()) || StringHelper.isNullOrWhiteSpace(request.getDeptName())) {
            return result.failed("科室不存在");
        }
        if (StringHelper.isNullOrWhiteSpace(request.getDoctorCode()) || StringHelper.isNullOrWhiteSpace(request.getDoctorName())) {
            return result.failed("医生不存在");
        }
        if (StringHelper.isNullOrWhiteSpace(request.getStartTime())) {
            return result.failed("预约开始时间不存在");
        }
        if (StringHelper.isNullOrWhiteSpace(request.getEndTime())) {
            return result.failed("预约结束时间不存在");
        }
        if (StringHelper.isNullOrWhiteSpace(request.getScheduleItemCode())) {
            return result.failed("门诊排班项记录标识不能为空");
        }
        if (StringHelper.isNullOrWhiteSpace(request.getIdType())) {
            return result.failed("请选择证件类型");
        }
        if (StringHelper.isNullOrWhiteSpace(request.getIdNo())) {
            return result.failed("请输入证件号码");
        }
        if (StringHelper.isNullOrWhiteSpace(request.getPatientName())) {
            return result.failed("姓名不能为空");
        }
        if (StringHelper.isNullOrWhiteSpace(request.getPatientMobile())) {
            return result.failed("手机号不能为空");
        }
        if (request.getPatientSex() != Gender.FEMALE.getValue() && request.getPatientSex() != Gender.MALE.getValue()) {
            return result.failed("请选择性别");
        }
        if (request.getPeriod() != RegisteredDatePeriod.MORNING.getValue() &&
                request.getPeriod() != RegisteredDatePeriod.AFTERNOON.getValue() &&
                request.getPeriod() != RegisteredDatePeriod.NIGHT.getValue()) {
            return result.failed("所选择的预约时段不存在！");
        }
        //判断门诊排班项记录标识是否合法
        String serviceDate = DateUtil.convertTimestampToDateString(request.getRegisteredDate(), "yyyy-MM-dd");
//        GetScheduleTimeInfoRequest req = GetScheduleTimeInfoRequest.newBuilder()
//                .setDeptCode(request.getDeptCode())
//                .setDoctorCode(request.getDoctorCode())
//                .setServiceDate(serviceDate)
//                .setDatePeriod(request.getPeriod())
//                .build();
//        GetScheduleTimeInfoResponse res = taiheServiceBlockingStub.getScheduleTimeInfo(req);
//        if (res.getCode() != ResultCode.SUCCEED_VALUE) {
//            return result.failed(res.getMsg());
//        }
//        if (res.getScheduleTimeInfosList().stream().noneMatch(p -> request.getScheduleItemCode().equals(p.getScheduleItemCode()))) {
//            return result.failed("门诊排班项记录标识不存在");
//        }
        //判断医生编码是否合法
        GetScheduleListRequest getScheduleListRequest = GetScheduleListRequest.newBuilder()
                .setDeptCode(request.getDeptCode())
                .setDoctorCode(request.getDoctorCode())
                .setServiceDate(serviceDate)
                .setHospitalId(request.getHospitalId())
                .build();
        GetScheduleListResponse getScheduleListResponse = taiheServiceBlockingStub.getScheduleList(getScheduleListRequest);
        if (getScheduleListResponse.getCode() != ResultCode.SUCCEED_VALUE || getScheduleListResponse.getSchedulesList().size() == 0) {
            return result.failed("医生不存在");
        }
        Schedule schedule = getScheduleListResponse.getSchedulesList().stream()
                .filter(s -> s.getDatePeriod() == Checker.getIntegerValue(request.getPeriod()))
                .findFirst()
                .orElse(null);
        if (schedule == null) {
            return result.failed("当前时间段没有匹配的排班数据！");
        }
        //校验传入的价格
        if (checkIsNetDept(request.getDeptCode(), request.getHospitalId())) {
            if (request.getMedicalFee() != 0 || request.getRegisteredFee() != 0 || request.getTotal() != 0) {
                //名医门诊科室的挂号费为 0
                return result.failed("问诊费不正确");
            }
        } else {
            if (request.getMedicalFee() != schedule.getCheckupFee() || request.getRegisteredFee() != schedule.getRegFee()) {
                return result.failed("诊查费不正确");
            }
            if (request.getTotal() != request.getMedicalFee() + request.getRegisteredFee()) {
                return result.failed("费用不正确");
            }
        }
        result.setSuccess(true);
        result.setDoctorTitle(schedule.getDoctorTitle());
        return result;
    }


    /**
     * 判断该科室是否属于名医诊区（包括一级二级）
     * TODO: 可以把 {@link Constants#TAIHE_NET_DEPT_CODE} 做成配置，这样以后改动可以减少代码层面的修改。
     *
     * @param deptCode 待判断科室
     * @return true or false
     */
    public boolean checkIsNetDept(String deptCode, Integer hospitalId) {
        // 默认太和，兼容
        int hid = Checker.getIntegerValue(hospitalId) > 0 ? hospitalId : Constants.TAIHE_HOSPITAL_ID;
        if (getNetDeptByHospitalId(hid).contains(deptCode)) return true;
        HospitalDeptListRequest req = HospitalDeptListRequest.newBuilder()
                .setHospitalId(hid)
                .build();
        HospitalDeptListResponse res = systemServiceBlockingStub.getHospitalDeptList(req);
        if (res.getResult().getCode() == ResultCode.SUCCEED) {
            List<HospitalDept> hospitalDepts = res.getHospitalDeptListList();
            List<String> netDeptIds = new LinkedList<>();
            if (!Checker.isNone(hospitalDepts)) {
                List<HospitalDept> hdList = hospitalDepts.stream()
                        .filter(hospitalDept -> getNetDeptByHospitalId(hid).contains(hospitalDept.getDeptCode())).collect(Collectors.toList());
                for (HospitalDept hd : hdList) {
                    netDeptIds.add(hd.getDeptCode());
                    hd.getDeptListList().forEach(sd -> {
                        netDeptIds.add(sd.getDeptCode());
                    });
                }
                return netDeptIds.stream().anyMatch(id -> id.equals(deptCode));
            }
        }
        return false;
    }

    /**
     * 判断该科室是否属于儿科（包括一级二级）
     *
     * @param deptCode 待判断科室
     * @return true or false
     */
    public boolean checkIsPediatricDept(String deptCode, Integer hospitalId) {
        // 默认太和，兼容
        int hid = Checker.getIntegerValue(hospitalId) > 0 ? hospitalId : Constants.TAIHE_HOSPITAL_ID;
        if (getPediatricDeptByHospitalId(hid).equals(deptCode)) return true;
        HospitalDeptListRequest req = HospitalDeptListRequest.newBuilder()
                .setHospitalId(hid)
                .build();
        HospitalDeptListResponse res = systemServiceBlockingStub.getHospitalDeptList(req);
        if (res.getResult().getCode() == ResultCode.SUCCEED) {
            List<HospitalDept> hospitalDepts = res.getHospitalDeptListList();
            List<String> netDeptIds = new LinkedList<>();
            if (!Checker.isNone(hospitalDepts)) {
                HospitalDept hd = hospitalDepts.stream()
                        .filter(hospitalDept -> getPediatricDeptByHospitalId(hid).equals(hospitalDept.getDeptCode())).findFirst().orElse(null);
                if (hd != null) {
                    netDeptIds.add(hd.getDeptCode());
                    hd.getDeptListList().forEach(sd -> {
                        netDeptIds.add(sd.getDeptCode());
                    });
                    return netDeptIds.stream().anyMatch(id -> id.equals(deptCode));
                }
            }
        }
        return false;
    }

    private String getPediatricDeptByHospitalId(int hospitalId) {
        switch (hospitalId) {
            case Constants.TAIHE_HOSPITAL_ID:
                return Constants.TAIHE_PEDIATRIC_DEPT_CODE;
            case Constants.NFYYBYFY_HOSPITAL_ID:
                return Constants.NFYYBYFY_PEDIATRIC_DEPT_CODE;
            default:
                return "";
        }
    }

    private String getInternalMedicineDeptByHospitalId(int hospitalId) {
        switch (hospitalId) {
            case Constants.TAIHE_HOSPITAL_ID:
                return Constants.TAIHE_INTERNAL_MEDICINE_DEPT_CODE;
            case Constants.NFYYBYFY_HOSPITAL_ID:
                return Constants.NFYYBYFY_INTERNAL_MEDICINE_DEPT_CODE;
            default:
                return "";
        }
    }

    private List<String> getNetDeptByHospitalId(int hospitalId) {
        List<String> list = new ArrayList<>();
        switch (hospitalId) {
            case Constants.TAIHE_HOSPITAL_ID:
                list.add(Constants.TAIHE_NET_DEPT_CODE);
//                list.add(Constants.TAIHE_NET_DEPT_CODE_NIGHT_DIAGNOSIS);
                break;
            case Constants.NFYYBYFY_HOSPITAL_ID:
                list.add(Constants.NFYYBYFY_NET_DEPT_CODE);
                break;
            default:
                list.add("");
                break;
        }
        return list;
    }

    /**
     * 判断该科室是否属于内科（包括一级二级）
     *
     * @param deptCode 待判断科室
     * @return true or false
     */
    public boolean checkIsInternalMedicineDept(String deptCode, Integer hospitalId) {
        // 默认太和，兼容
        int hid = Checker.getIntegerValue(hospitalId) > 0 ? hospitalId : Constants.TAIHE_HOSPITAL_ID;
        List<String> deptCodes = Arrays.asList(getInternalMedicineDeptByHospitalId(hid).split(","));
        if (deptCodes.contains(deptCode)) return true;
        HospitalDeptListRequest req = HospitalDeptListRequest.newBuilder()
                .setHospitalId(hid)
                .build();
        HospitalDeptListResponse res = systemServiceBlockingStub.getHospitalDeptList(req);
        if (res.getResult().getCode() == ResultCode.SUCCEED) {
            List<HospitalDept> hospitalDepts = res.getHospitalDeptListList();
            List<String> netDeptIds = new LinkedList<>();
            if (!Checker.isNone(hospitalDepts)) {
                HospitalDept hd = hospitalDepts.stream()
                        .filter(hospitalDept -> deptCodes.contains(hospitalDept.getDeptCode())).findFirst().orElse(null);
                if (hd != null) {
                    netDeptIds.add(hd.getDeptCode());
                    hd.getDeptListList().forEach(sd -> {
                        netDeptIds.add(sd.getDeptCode());
                    });
                    return netDeptIds.stream().anyMatch(id -> id.equals(deptCode));
                }
            }
        }
        return false;
    }

    /**
     * 根据 deptCode 获取科室信息 （二级）
     *
     * @param deptCode
     * @param hospitalId
     * @return
     */
    public HospitalDept getDepartmentByDeptCodeAndHospitalId(String deptCode, int hospitalId) {
        if (Checker.isNone(deptCode)) return null;
        HospitalDeptListRequest req = HospitalDeptListRequest.newBuilder()
                .setHospitalId(hospitalId)
                .build();
        HospitalDeptListResponse res = systemServiceBlockingStub.getHospitalDeptList(req);
        if (res.getResult().getCode() == ResultCode.SUCCEED) {
            List<HospitalDept> hospitalDepts = res.getHospitalDeptListList();
            if (!Checker.isNone(hospitalDepts)) {
                // 返回的是一级嵌套二级列表，需要处理
                return hospitalDepts.stream()
                        .flatMap(d -> d.getDeptListList().stream())
                        .map(d -> HospitalDept.newBuilder()
                                .setDeptCode(d.getDeptCode())
                                .setDeptId(d.getDeptId())
                                .setDeptName(d.getDeptName())
                                .build())
                        .filter(hospitalDept -> deptCode.equals(hospitalDept.getDeptCode()))
                        .findFirst()
                        .orElse(null);
            }
        }
        return null;
    }

    @Override
    public GetOfflineConsultInfoByDoctorIdResponse getOfflineConsultInfoByDoctorId(GetOfflineConsultInfoByDoctorIdRequest request) {
        GetOfflineConsultInfoByDoctorIdResponse.Builder builder = GetOfflineConsultInfoByDoctorIdResponse.newBuilder();
        try {
            int doctorId = request.getDoctorId();
            // 默认太和，兼容处理
            int hospitalId = request.getHospitalId() > 0 ? request.getHospitalId() : Constants.TAIHE_HOSPITAL_ID;
            List<DoctorHisCode> doctorHisCodeList = doctorHisCodeRepository.getListByDoctorIdAndHospitalId(doctorId,
                    hospitalId);
            if (Checker.isNone(doctorHisCodeList)) {
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, "医生 id 有误");
            }
            DoctorHisCode netRelated = doctorHisCodeList.stream()
                    .filter(d -> checkIsNetDept(d.getDeptCode(), hospitalId))
                    .findFirst()
                    .orElse(null);
            if (netRelated == null) {
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, "找不到医生对应的科室排班信息");
            }
            String deptCode = netRelated.getDeptCode();
            String doctorName = netRelated.getDoctorName();
            String doctorHisCode = netRelated.getHisCode();
            // 获取 deptId & deptName
            HospitalDept hospitalDept = getDepartmentByDeptCodeAndHospitalId(deptCode, hospitalId);
            if (hospitalDept == null) {
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, "找不到科室信息");
            }
            int deptId = hospitalDept.getDeptId();
            String deptName = hospitalDept.getDeptName();
            // 获取医生问诊费
            DoctorRequest doctorRequest = DoctorRequest.newBuilder()
                    .setUserid(doctorId)
                    .build();
            DoctorInfoResponse response = doctorServiceBlockingStub.getDoctorInfo(doctorRequest);
            if (response.getResult().getCode() != ResultCode.SUCCEED) {
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("获取不到医生信息: %s", response.getResult().getInfo()));
            }
            // v3.2.0 线下就诊费用跟问诊费分开，线下就诊费用读取 offline_fee 字段
            int consultFee = Checker.getIntegerValue(response.getInfo().getOfflineFee());
            builder.setCode(ResultCode.SUCCEED_VALUE)
                    .setInfo("")
                    .setDeptCode(deptCode)
                    .setDoctorName(doctorName)
                    .setDoctorHisCode(doctorHisCode)
                    .setDeptId(deptId)
                    .setDeptName(deptName)
                    .setConsultFee(consultFee);

            return builder.build();
        } catch (YwtCommonException e) {
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        } catch (Exception e) {
            logger.error("TaiheRegisterService#getOfflineConsultInfoByDoctorId(request={}):\n {}", request, e.getMessage(), e);
            return builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage()).build();
        }
    }

    @Override
    public GetLatestRegOrderResponse getLatestRegOrder(GetLatestRegOrderRequest request) {
        GetLatestRegOrderResponse.Builder builder = GetLatestRegOrderResponse.newBuilder();
        int doctorId = request.getDoctorId();
        int userId = request.getUserId();
        int hospitalId = request.getHospitalId();
        try {
            // 支付成功的都算
            String endDate = DateUtil.convertTimestampToDateString(System.currentTimeMillis(), "yyyy-MM-dd");
            com.ywt.outpatient.domain.entity.center.RegisteredOrder order = registeredOrderRepository.getLatestRegOrderUntilDate(doctorId, userId, endDate, hospitalId);
            if (order != null) {
                String regDate = DateUtil.formatDate(order.getRegisteredDate(), "yyyy-MM-dd");
                String regDateEnd = regDate + " 18:00:00";// 挂号从就诊当天 18 点（发送消息的时间点）过后开始算
                logger.info("regDateEnd " + regDateEnd);
                builder.setRegisterDate(DateUtil.stringToDate(regDateEnd, "yyyy-MM-dd HH:mm:ss").getTime());
                builder.setCode(ResultCode.SUCCEED_VALUE);
                builder.setInfo("操作成功");
            } else {
                builder.setCode(ResultCode.APP_ERROR_VALUE);
                builder.setInfo("找不到记录");
            }
            return builder.build();
        } catch (Exception e) {
            logger.error("TaiheRegisterService#getLatestRegOrder(request={}):\n {}",request, e.getMessage(), e);
            return builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage()).build();
        }
    }

    @Override
    public CalAdditionalFeeResponse calAdditionalFee(CalAdditionalFeeRequest request) {
        int medicalCardId = Checker.getIntegerValue(request.getMedicalCardId());
        int medicalFee = Checker.getIntegerValue(request.getMedicalFee());
        int registeredFee = Checker.getIntegerValue(request.getRegisteredFee());
        int userId = Checker.getIntegerValue(request.getUserId());
        int hospitalId = Checker.getIntegerValue(request.getHospitalId());
        String doctorSessTypeName = Checker.getStringValue(request.getDoctorSessTypeName());
        CalAdditionalFeeResponse.Builder builder = CalAdditionalFeeResponse.newBuilder();
        try {

            com.ywt.outpatient.domain.entity.center.MedicalCard medicalCard = medicalCardRepository.getByIdAndStatus(medicalCardId,
                    MedicalCardStatusEnum.NORMAL.getValue());
            logger.error("TaiheRegisterService#calAdditionalFee medicalCardId {} userId {}", medicalCardId, userId);
            if (medicalCard == null) {
                builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setInfo("诊疗卡不存在");
            } else {
                String idNo = medicalCard.getIdNo();
                String birthday = medicalCard.getBirthday();
                boolean isFullChargeReg = checkIsFullChargeReg(hospitalId, request.getPeriod(), doctorSessTypeName);
                // 判断是否是公医，公医挂号费 0 元
                boolean isFreeMedicalTreat = false;
                if (!isFullChargeReg) {
                    // 6 岁以下加收 30% 的挂号费
                    int curAge = 0;
                    try {
                        curAge = IdCardUtil.calcAgeByIdNoAndBirthday(idNo, birthday);
                    } catch (Exception e) {
                        logger.error("TaiheRegisterService#calAdditionalFee(idNo={} , birthday={} ): 无法获取年龄，不加收费用，由 HIS 判断", idNo, birthday);
                    }
                    if (curAge < 6) {
                        logger.info("TaiheRegisterService#calAdditionalFee : 挂号费 {} 诊查费 {}", registeredFee, medicalFee);
                        medicalFee = (new BigDecimal("1.3")).multiply((new BigDecimal(String.valueOf(medicalFee)))).intValue();
                    }
                    isFreeMedicalTreat = configProvider.getFmtFlagList(hospitalId).contains(Checker.getStringValue(medicalCard.getPatientType()));
                    if (isFreeMedicalTreat) {
                        logger.info("TaiheRegisterService#createRegistered(): 公医，cardId: {}", request.getMedicalCardId());
                        medicalFee = 0;
                    }
                } else {
                    logger.info("TaiheRegisterService#createRegistered(): 白云夜诊");
                }
                int payAmount = medicalFee + registeredFee;
                logger.info("TaiheRegisterService#calAdditionalFee :重新计算总费用：{}", payAmount);
                builder.setCode(ResultCode.SUCCEED_VALUE).setInfo("").setPayMedicalFee(medicalFee).setPayTotal(payAmount).setIsFreeMedicalTreat(isFreeMedicalTreat);
            }
            return builder.build();
        } catch (Exception e) {
            logger.error("TaiheRegisterService#calAdditionalFee(request={}):\n {}", request, e.getMessage(), e);
            return builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage()).build();
        }
    }

    /**
     * 根据 userId & mobile 获取 hisPatientId, 如果没有记录，会调用 HIS 接口进行建档，然后新增诊疗卡记录并返回
     */
    @Override
    public GetHisPatientIdByUserIdAndMobileResponse getHisPatientIdByUserIdAndMobile(GetHisPatientIdByUserIdAndMobileRequest request) {
        int userId = request.getUserId();
        String mobile = request.getMobile();
        String idCardNo = request.getCardNo();
        String cardType = Checker.getStringValue(request.getCardType(), "1");
        GetHisPatientIdByUserIdAndMobileResponse.Builder builder = GetHisPatientIdByUserIdAndMobileResponse.newBuilder();
        try {
            // 默认太和，兼容
            int hospitalId = request.getHospitalId() > 0 ? request.getHospitalId() : Constants.TAIHE_HOSPITAL_ID;
            String hisPatientId = "";
            com.ywt.outpatient.domain.entity.center.MedicalCard medicalCard = medicalCardRepository.getByUserIdAndIdNoAndStatusAndHospitalIdOrderByUpdateTimeDesc(
                    userId, idCardNo, MedicalCardStatusEnum.NORMAL.getValue(), hospitalId)
                    .stream()
                    .findFirst()
                    .orElse(null);
            if (medicalCard == null) {
                logger.info("userId={}, idCardNo={}, mobile={}查询不到结果", userId, idCardNo, mobile);
                com.ywt.outpatient.domain.entity.center.User user = userRepository.getByIdAndIsDeletedFalse(userId);
                if (user == null) {
                    throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("用户不存在，id: %d", userId));
                }
                // 不能直接用 userName, 要使用 patientName, 因为用户可以添加其他患者
                String patientName = request.getName();
                String userName = Checker.isNone(patientName) ? Checker.getStringValue(user.getRealName()) : patientName;
                // 查询 HIS 是否已有 姓名+身份证 绑定的卡
                GetPatientInfoRequest getPatientInfoRequest = GetPatientInfoRequest.newBuilder()
                        .setPatientName(userName)
                        .setIdNo(idCardNo)
                        .setHospitalId(hospitalId)
                        .setCardType(cardType)
                        .build();
                GetPatientInfoResponse getPatientInfoResponse = taiheServiceBlockingStub.getPatientInfo(getPatientInfoRequest);
                com.ywt.outpatient.domain.entity.center.MedicalCard mc;
                if (getPatientInfoResponse.getCode() == ResultCode.SUCCEED_VALUE) {
                    // 能获取到信息，插入数据
                    PatientInfo patientInfo = getPatientInfoResponse.getPatientInfo();
                    if (patientInfo != null) {
                        // HIS 查询到信息，插入数据
                        mc = addNewMedicalCard(userId, userName, mobile, Checker.getIntegerValue(user.getSex()),
                                patientInfo.getCardNo(), patientInfo.getPatientId(),
                                idCardNo, patientInfo.getPatientType(), request.getAddress(), hospitalId, 0, cardType);
                    } else {
                        if (!"1".equals(cardType)) {
                            throw new YwtCommonException(YwtCommonRespCode.P_ERR, "港澳台患者需先到院线下建档。");
                        }
                        // 建档
                        mc = createNewMedicalCard(request.getAddress(), request.getCardNo(), userId, user, userName, hospitalId, cardType);
                    }
                } else {
                    if (!"1".equals(cardType)) {
                        throw new YwtCommonException(YwtCommonRespCode.P_ERR, "港澳台患者需先到院线下建档。");
                    }
                    logger.error("获取就诊卡信息失败，patientName: {}, idCardNo: {}: {}", userName, idCardNo, getPatientInfoResponse.getMsg());
                    // 建档
                    mc = createNewMedicalCard(request.getAddress(), request.getCardNo(), userId, user, userName, hospitalId, cardType);
                }
                hisPatientId = mc.getHisPatientId();
            } else {
                hisPatientId = Checker.getStringValue(medicalCard.getHisPatientId());
            }
            // 更新 patient 的 idCardNo & address
            try {
                int patId = Integer.parseInt(request.getPatientNo());
                Patient patient = patientRepository.getById(patId);
                if (patient != null) {
                    if (StringHelper.isNullOrEmpty(patient.getAddress())) {
                        patient.setAddress(request.getAddress());
                    }
                    if (StringHelper.isNullOrEmpty(patient.getIdCardNo())) {
                        patient.setIdCardNo(request.getCardNo());
                    }
                    patientRepository.save(patient);
                } else {
                    logger.warn("TaiheRegisterService#getHisPatientIdByUserIdAndMobile(): 找不到该患者 patId: {}", patId);
                }
            } catch (NumberFormatException e) {
                logger.error("TaiheRegisterService#getHisPatientIdByUserIdAndMobile(): 解析 patientNo 出错 patientNo: {}", request.getPatientNo());
            }
            builder.setCode(ResultCode.SUCCEED_VALUE)
                    .setHisPatientId(hisPatientId);
            return builder.build();
        } catch (YwtCommonException ame) {
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE)
                    .setInfo(Checker.getStringValue(ame.getMessage()));
            return builder.build();
        } catch (Exception e) {
            logger.error("TaiheRegisterService#getHisPatientIdByUserIdAndMobile(request={}):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_ERROR_VALUE)
                    .setInfo(e.getMessage());
            return builder.build();
        }
    }

    /**
     * 调用 HIS 接口建档，建档成功后新建就诊卡
     */
    private com.ywt.outpatient.domain.entity.center.MedicalCard createNewMedicalCard4HIS(String address, String cardNo, String city, String userName, String mobile,
                                                                                         String isInsu, int hospitalId, int userId, int sex, int relationship, String cardType) throws YwtCommonException {
        com.ywt.outpatient.domain.entity.center.MedicalCard mc;
        CreatePatInfoRequest createPatInfoRequest = CreatePatInfoRequest.newBuilder()
                .setAddress(Checker.getStringValue(address))
                .setIdNo(Checker.getStringValue(cardNo))
                .setPatientName(userName)
                .setCity(city)
                .setPhoneNo(mobile)
                .setIsInsu(isInsu)
                .setHospitalId(hospitalId)
                .build();
        CreatePatInfoResponse createPatInfoResponse = taiheServiceBlockingStub.createPatInfo(createPatInfoRequest);
        if (createPatInfoResponse.getCode() != ResultCode.SUCCEED_VALUE) {
//            throw new AppMessageException(String.format("HIS 建档：%s", createPatInfoResponse.getInfo()));
            throw new YwtCommonException(YwtCommonRespCode.P_ERR, createPatInfoResponse.getInfo());
        }
        // 建档成功，新增诊疗卡记录
        mc = addNewMedicalCard(userId, userName, mobile, sex, createPatInfoResponse.getPatientCard(),
                createPatInfoResponse.getPatientId(), cardNo, "", address, hospitalId, relationship, cardType);// TODO: 2020/6/18 patientType 没有返回
        return mc;
    }

    /**
     * 一键挂号调用的建档方法，具体实现已重写到 createNewMedicalCard4HIS
     */
    private com.ywt.outpatient.domain.entity.center.MedicalCard createNewMedicalCard(String address, String cardNo, int userId,
                                                                                     com.ywt.outpatient.domain.entity.center.User user, String userName,
                                                                                     int hospitalId, String cardType) throws YwtCommonException {
        return createNewMedicalCard4HIS(Checker.getStringValue(address), Checker.getStringValue(cardNo),
                Checker.getStringValue(user.getCity()), userName, Checker.getStringValue(user.getMobile()), "",
                hospitalId, userId, Checker.getIntegerValue(user.getSex()), 0, cardType);
    }

    private com.ywt.outpatient.domain.entity.center.MedicalCard addNewMedicalCard(int userId, String patientName, String mobile, int sex, String cardNo,
                                                                                  String hisPatientId, String idCardNo, String patientType, String address,
                                                                                  int hospitalId, int relationship, String cardType) {

        GetPatientInfoRequest getPatientInfoRequest = GetPatientInfoRequest.newBuilder()
                .setPatientName(patientName)
                .setIdNo(idCardNo)
                .setHospitalId(hospitalId)
                .setCardType(cardType)
                .build();
        GetPatientInfoResponse getPatientInfoResponse = taiheServiceBlockingStub.getPatientInfo(getPatientInfoRequest);
        if (getPatientInfoResponse.getCode() == ResultCode.SUCCEED_VALUE) {
            PatientInfo patientInfo = getPatientInfoResponse.getPatientInfo();
            patientType = Checker.getStringValue(patientInfo.getPatientType());
        }

        com.ywt.outpatient.domain.entity.center.MedicalCard mc = new com.ywt.outpatient.domain.entity.center.MedicalCard();
        mc.setId(idGenerator.genMedicalCardId());
        mc.setUserId(userId);
        mc.setHospitalId(hospitalId);
        mc.setCardNo(cardNo);
        mc.setIdNo(idCardNo);
        mc.setAddress(address);
        mc.setHisPatientId(hisPatientId);
        mc.setCardType(cardType);
        mc.setPatientName(patientName);
        mc.setMobile(mobile);
        mc.setSex(Checker.getIntegerValue(sex));
        mc.setBalance(0);
        mc.setStatus(MedicalCardStatusEnum.NORMAL.getValue());
        mc.setCreateTime(new Date());
        mc.setPatientType(patientType);
        if (relationship > 0) {
            mc.setRelationship(relationship);
        }
        medicalCardRepository.save(mc);
        return mc;

    }

    /**
     * 调用 HIS 接口建档，建档成功后新建就诊卡
     */
    @Override
    public CreateMedicalCardResponse createMedicalCard(CreateMedicalCardRequest request) {
        CreateMedicalCardResponse.Builder builder = CreateMedicalCardResponse.newBuilder();
        String phoneNo = request.getPhoneNo();
        String idNo = request.getIdNo();
        String patientName = request.getPatientName();
        String city = request.getCity();
        String address = request.getAddress();
        String isInsu = request.getIsInsu();
        int relationship = request.getRelationship();
        int userId = request.getUserId();
        int hospitalId = request.getHospitalId();
        int terminal = request.getTerminal();
        String idCardNoType = "1";
        String cardType = Checker.getStringValue(request.getCardType(), idCardNoType);
        try {
            logger.info("createMedicalCard:phoneNo={}, idNo={}, patientName={}, city={}, address={}, isInsu={}, relationship={}, userId={}, hospitalId={}, cardType={}",
                    phoneNo, idNo, patientName, city, address, isInsu, relationship, userId, hospitalId, cardType);
            // 如果不是身份证
            if (!idCardNoType.equals(cardType)) {
                // 港澳台身份证
                // 如果前端传入的是大陆身份证，但提交的类型是港澳台类型，这里需要返回错误提示
                if (IdCardUtil.verifyMainlandIdCard(idNo)) {
                    throw new YwtCommonException(YwtCommonRespCode.P_ERR, "“身份证号”或“证件类型”有误，建档不成功，请修改！");
                }
                int sex = IdCardUtil.getSexIntWithGAT(idNo);
                // 查询 HIS 是否已有 姓名+身份证 绑定的卡
                // 先查一次数据库有没有存在建卡记录
                com.ywt.outpatient.domain.entity.center.MedicalCard card = medicalCardRepository.getByUserIdAndPatientNameAndIdNoAndCardTypeAndHospitalIdAndStatus(userId,
                        patientName, idNo, cardType, hospitalId, MedicalCardStatusEnum.NORMAL.getValue());
                if (card != null) {
                    if (relationship > 0) {
                        card.setRelationship(relationship);
                        medicalCardRepository.save(card);

                    }
                    builder.setCode(ResultCode.SUCCEED_VALUE)
                            .setInfo("已有诊疗卡")
                            .setMedicalCardId(card.getId());
                } else {
                    GetPatientInfoRequest getPatientInfoRequest = GetPatientInfoRequest.newBuilder()
                            .setPatientName(patientName)
                            .setIdNo(idNo)
                            .setHospitalId(hospitalId)
                            .setCardType(cardType)
                            .build();
                    GetPatientInfoResponse getPatientInfoResponse = taiheServiceBlockingStub.getPatientInfo(getPatientInfoRequest);
                    if (getPatientInfoResponse.getCode() == ResultCode.SUCCEED_VALUE) {
                        PatientInfo patientInfo = getPatientInfoResponse.getPatientInfo();
                        String rMobile = patientInfo.getMobile();
                        String rPatientName = patientInfo.getPatientName();
                        String rIdNo = patientInfo.getIdNo();
                        String patientType = Checker.getStringValue(patientInfo.getPatientType());
                        if (phoneNo.equals(rMobile) && patientName.equals(rPatientName) && idNo.equals(rIdNo)) {
                            // 用户提交的手机号、姓名、身份证跟 HIS 查询到的信息一致，自动创建诊疗卡
                            com.ywt.outpatient.domain.entity.center.MedicalCard mc = addNewMedicalCard(userId, patientName, phoneNo, sex, patientInfo.getCardNo(),
                                    patientInfo.getPatientId(), idNo, patientType, address, hospitalId, relationship, cardType);
                            builder.setCode(ResultCode.SUCCEED_VALUE)
                                    .setInfo("已有诊疗卡，绑定成功")
                                    .setMedicalCardId(mc.getId());
                            sendMsgForCreateCard(userId, hospitalId, mc, terminal);

                        } else {
                            // 信息不一致，不建卡，直接返回 HIS 的错误提示
                            logger.error("TaiheRegisterService#createMedicalCard(): 用户信息与 HIS 信息不一致（{}-{}, {}-{}, {}-{}, {}）",
                                    phoneNo, rMobile, patientName, rPatientName, idNo, rIdNo, cardType);
                            builder.setCode(ResultCode.APP_ERROR_VALUE)
                                    .setInfo("个人信息与预留在院系统信息不一致");
                        }
                    } else {
                        logger.error("TaiheRegisterService#createMedicalCard({}, {}, {}, {}): 无法获取患者信息：{}",
                                patientName, idNo, hospitalId, cardType, getPatientInfoResponse.getMsg());
                        builder.setCode(ResultCode.APP_ERROR_VALUE)
                                .setInfo("港澳台患者需先到院线下建档。");
                    }
                }
            } else {
                // 大陆身份证
                if (!IdCardUtil.verifyMainlandIdCard(idNo)) {
                    throw new YwtCommonException(YwtCommonRespCode.P_ERR, "“身份证号”或“证件类型”有误，建档不成功，请修改！");
                }
                // 根据配置做实名校验
                HisMedCardConfig config = configProvider.getHisMedCardConfig(hospitalId);
                if (config != null && Checker.getBooleanValue(config.getNameAuth())) {
                    ValidateIdCardResponse validateIdCardResponse = idCardServiceBlockingStub.validateIdCard(ValidateIdCardRequest.newBuilder()
                            .setIdCardNo(idNo)
                            .setRealName(patientName)
                            .build());
                    if (validateIdCardResponse.getCode() != ResultCode.SUCCEED_VALUE)
                        throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("实名校验：%s", validateIdCardResponse.getInfo()));
                    if (!validateIdCardResponse.getValid())
                        throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("实名校验：%s(^%s^%s^)", validateIdCardResponse.getInfo(),
                                idNo, patientName));
                }
                CheckUtil.ensureNotEmpty(phoneNo, "手机号不能为空");
                CheckUtil.ensureNotEmpty(patientName, "患者姓名不能为空");
                CheckUtil.ensureNotEmpty(city, "在线建卡市区不能为空");
                CheckUtil.ensureNotEmpty(address, "详细地址不能为空");
                CheckUtil.ensureLargerThanZero(hospitalId, "请指定医院");
                CheckUtil.ensureLargerThanZero(userId, "请登录后操作");
                com.ywt.outpatient.domain.entity.center.User user = new com.ywt.outpatient.domain.entity.center.User();
                user.setCity(city);
                user.setMobile(phoneNo);
                int sex = IdCardUtil.getSexInt(idNo);
                user.setSex(sex);
                try {
                    com.ywt.outpatient.domain.entity.center.MedicalCard medicalCard = createNewMedicalCard4HIS(address, idNo, city, patientName, phoneNo, isInsu, hospitalId,
                            userId, IdCardUtil.getSexInt(idNo), relationship, cardType);
                    builder.setCode(ResultCode.SUCCEED_VALUE)
                            .setMedicalCardId(medicalCard.getId())
                            .setMedicalCardNo(medicalCard.getCardNo());
                    sendMsgForCreateCard(userId, hospitalId, medicalCard, terminal);
                } catch (YwtCommonException e) {
                    // 建卡 HIS 返回失败，查询 HIS 是否已有 姓名+身份证 绑定的卡
                    // 先查一次数据库有没有存在建卡记录
                    com.ywt.outpatient.domain.entity.center.MedicalCard card = medicalCardRepository.getByUserIdAndPatientNameAndIdNoAndCardTypeAndHospitalIdAndStatus(userId,
                            patientName, idNo, cardType, hospitalId, MedicalCardStatusEnum.NORMAL.getValue());
                    if (card != null) {
                        if (relationship > 0) {
                            card.setRelationship(relationship);
                            medicalCardRepository.save(card);
                        }
                        builder.setCode(ResultCode.SUCCEED_VALUE)
                                .setInfo("已有诊疗卡")
                                .setMedicalCardId(card.getId());
                    } else {
                        GetPatientInfoRequest getPatientInfoRequest = GetPatientInfoRequest.newBuilder()
                                .setPatientName(patientName)
                                .setIdNo(idNo)
                                .setHospitalId(hospitalId)
                                .setCardType(cardType)
                                .build();
                        GetPatientInfoResponse getPatientInfoResponse = taiheServiceBlockingStub.getPatientInfo(getPatientInfoRequest);
                        if (getPatientInfoResponse.getCode() == ResultCode.SUCCEED_VALUE) {
                            PatientInfo patientInfo = getPatientInfoResponse.getPatientInfo();
                            String rMobile = patientInfo.getMobile();
                            String rPatientName = patientInfo.getPatientName();
                            String rIdNo = patientInfo.getIdNo();
                            String patientType = Checker.getStringValue(patientInfo.getPatientType());
                            if (phoneNo.equals(rMobile) && patientName.equals(rPatientName) && idNo.equals(rIdNo)) {
                                // 用户提交的手机号、姓名、身份证跟 HIS 查询到的信息一致，自动创建诊疗卡
                                MedicalCard mc = addNewMedicalCard(userId, patientName, phoneNo, sex, patientInfo.getCardNo(),
                                        patientInfo.getPatientId(), idNo, patientType, address, hospitalId, relationship, cardType);
                                builder.setCode(ResultCode.SUCCEED_VALUE)
                                        .setInfo("已有诊疗卡，绑定成功")
                                        .setMedicalCardId(mc.getId());
                                sendMsgForCreateCard(userId, hospitalId, mc, terminal);
                            } else {
                                // 信息不一致，不建卡，直接返回 HIS 的错误提示
                                logger.error("TaiheRegisterService#createMedicalCard(): 用户信息与 HIS 信息不一致（{}-{}, {}-{}, {}-{}, {}）",
                                        phoneNo, rMobile, patientName, rPatientName, idNo, rIdNo, cardType);
                                builder.setCode(ResultCode.APP_ERROR_VALUE)
                                        .setInfo("个人信息与预留在院系统信息不一致");
                            }
                        } else {
                            logger.error("TaiheRegisterService#createMedicalCard({}, {}, {}, {}): 无法获取患者信息：{}",
                                    patientName, idNo, hospitalId, cardType, getPatientInfoResponse.getMsg());
                            builder.setCode(ResultCode.APP_ERROR_VALUE)
                                    .setInfo(getPatientInfoResponse.getMsg());
                        }
                    }
                }
            }
            return builder.build();
        } catch (YwtCommonException e) {
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        } catch (Exception e) {
            logger.error("TaiheRegisterService#createMedicalCard(request={}):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_EXCEPTION_VALUE).setInfo(e.getMessage());
            return builder.build();
        }
    }

    public void sendMsgForCreateCard(int userId, int hospital, com.ywt.outpatient.domain.entity.center.MedicalCard card, int terminal) {

        int age = 0;
        String realAge = "";
        String idNo = Checker.getStringValue(card.getIdNo());
        String birthday = Checker.getStringValue(card.getBirthday());
        try {
            age = IdCardUtil.calcAgeByIdNoAndBirthday(idNo, birthday);
            realAge = String.valueOf(age);
        } catch (YwtCommonException e) {
            e.printStackTrace();
            logger.error("TaiheRegisterService#calAdditionalFee(idNo={} , birthday={} ): 无法获取身份证与生日", idNo, birthday);
        }
        String sexStr = "";
        String cardNo = card.getCardNo() + "（ID号：" + card.getHisPatientId() + "）";
        sexStr = card.getSex() == 1 ? "男" : "女";
        taiheWeChatMsgService.sendMsgForCreateMedicalCard(userId, cardNo, card.getPatientName(), sexStr, realAge, hospital);
        int terminalHosp = getHospByTerminal(terminal);
        if (terminalHosp != hospital && terminalHosp > 0) {
            taiheWeChatMsgService.sendMsgForCreateMedicalCard(userId, cardNo, card.getPatientName(), sexStr, realAge, terminalHosp);
        }

    }

    public int getHospByTerminal(int terminal) {
        int hospital = 0;
        if (terminal == TerminalEnum.TaiheWxOfficial.getValue()) {
            hospital = Constants.TAIHE_HOSPITAL_ID;
        }
        if (terminal == TerminalEnum.NFYYBYFY_WXAPP.getValue()) {
            hospital = Constants.NFYYBYFY_HOSPITAL_ID;
        }
        return hospital;
    }

    @Override
    public RegisterCheckInMessageResponse registerCheckInMessage(RegisterCheckInMessageRequest request) {
        RegisterCheckInMessageResponse.Builder builder = RegisterCheckInMessageResponse.newBuilder();
        int userId = request.getUserId();
        int hospitalId = request.getHospitalId();
        String name = request.getPatName();
        String deptName = request.getAdmLoc();
        String regDate = request.getAdmArriveDate();
        String queueNo = Checker.getStringValue(request.getQueueNo());
        String areaId = request.getAreaCode();
        String cardNo = request.getCardNo();
        String admBoroughDesc = Checker.getStringValue(request.getAdmBoroughDesc());
        String rangeTime = Checker.getStringValue(request.getRangTime());
        String doctorName = Checker.getStringValue(request.getAdmDoc());
        try {
//                   String doctorName = order.getDoctorName();
//                   String regTime = regDate + "    " + order.getStartTime() + "-" + order.getEndTime();
            String date = getCurrentDate();
            String regTime = date + "    " + rangeTime;

            logger.info("TaiheWeChatMsgService#sendMsgForRegister(): 白云小程序挂太和的号再发一条消息到白云公众号");

            if (hospitalId == 41) {
                GetSnsUserInfoResponse response = userServiceBlockingStub.getSnsUserInfo(GetSnsUserInfoRequest.newBuilder()
                        .setUserId(userId)
                        .setTerminal(OFFICAIL)
                        .build());
                String openId = "";
                String unionId = "";
                if (response.getCode() != 0 || (response.getCode() == 0 && Checker.isNone(response.getSnsUserInfo().getOpenId()))) {

                    String templateId = "6-WClPs06KY0CJWGHoLqgkq0W7mwXsdpMJtWhCD31Qs";
                    String page = String.format(Constants.CHECK_IN_DETAIL, areaId, cardNo, "false");
                    Map<String, String> keywordMap = new HashMap();
                    keywordMap.put("thing3", "南方医科大学南方医院白云分院");
                    keywordMap.put("thing4", deptName);
                    keywordMap.put("thing5", doctorName);
                    String thing1 = "您已报道成功，序列号为" + queueNo + "，请在候诊区等候，，叫号后请到" + admBoroughDesc + "就诊。门诊开诊时间：周一到周五8:00-12:00,13:00-16:00，周六周日：8:00-12:00。";
                    keywordMap.put("thing1", thing1);

                    GetSnsUserInfoResponse snsResponse = userServiceBlockingStub.getSnsUserInfo(GetSnsUserInfoRequest.newBuilder()
                            .setUserId(userId)
                            .setTerminal(MIN)
                            .build());
                    if (snsResponse.getCode() == 0) {
                        openId = snsResponse.getSnsUserInfo().getOpenId();
                        weChatMsgProvider.sendMPSubscribeMessage(MIN, openId, templateId, page, keywordMap);
                    }
                }
            }
            nfYwtWeChatMsgService.sendMsgForCheckIn(name, queueNo, deptName, areaId, cardNo, doctorName, regTime, admBoroughDesc, userId, hospitalId);
            builder.setCode(ResultCode.SUCCEED_VALUE);
            builder.setInfo("操作成功");
            return builder.build();
        } catch (Exception e) {
            logger.error("TaiheRegisterService#registerCheckInMessage(request={}):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_EXCEPTION_VALUE).setInfo(e.getMessage());
            return builder.build();
        }
    }

    private String getCurrentDate() {
        try {
            String value = DateUtil.formatDate(new Date(), DateUtil.DADE_FROMAT_YMD);
            return value;
        } catch (Exception e) {
            logger.error("TaiheRegisterService#getCurrentDate({}): {}", e.getMessage(), e);
        }
        return "";
    }

    private boolean checkIsFullChargeReg(int hospitalId, int period, String doctorSessTypeName) {
        boolean isFullChargeReg = (hospitalId == Constants.NFYYBYFY_HOSPITAL_ID || hospitalId == Constants.TAIHE_HOSPITAL_ID) &&
                (RegisteredDatePeriod.NIGHT == RegisteredDatePeriod.valueOf(period)
                        || RegisteredDatePeriod.MIDNOON == RegisteredDatePeriod.valueOf(period))
                || (configProvider.getHisRegisteredFeeFlagList(hospitalId).contains(doctorSessTypeName));
        return isFullChargeReg;
    }

}

