package com.ywt.outpatient.rpc.provider;

import com.ywt.biz.common.constant.YwtCommonRespCode;
import com.ywt.biz.common.enums.PaymentStatusEnum;
import com.ywt.biz.common.enums.RefundStatusEnum;
import com.ywt.biz.common.enums.TerminalEnum;
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.PhoneNumChecker;
import com.ywt.biz.common.util.StringHelper;
import com.ywt.gapi.ResultCode;
import com.ywt.gapi.med_equip.*;
import com.ywt.gapi.ordercenter.OrderCenterService;
import com.ywt.outpatient.core.PagedList;
import com.ywt.outpatient.core.SqlHelper;
import com.ywt.outpatient.core.utils.*;
import com.ywt.outpatient.domain.entity.center.*;
import com.ywt.outpatient.domain.models.DoctorInfo;
import com.ywt.outpatient.core.Constants;
import com.ywt.outpatient.domain.models.enums.*;
import com.ywt.outpatient.service.rpc.DoctorService;
import com.ywt.outpatient.service.rpc.HospitalService;
import com.ywt.outpatient.service.rpc.UserService;
import com.ywt.outpatient.service.rpc.common.WeChatMsgProvider;
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.sql.Types;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Walker
 * Created on 2021/6/29
 */
@DubboService
public class MedEquipProvider extends DubboMedEquipServiceTriple.MedEquipServiceImplBase {
    private final Logger logger = LoggerFactory.getLogger(MedEquipProvider.class);

    @Autowired
    CheckProjectRepository checkProjectRepository;
    @Autowired
    DoctorService doctorService;
    @Autowired
    ChecklistOrdersRepository checklistOrdersRepository;
    @Autowired
    ChecklistOrdersDetailRepository checklistOrdersDetailRepository;
    @DubboReference
    OrderCenterService orderCenterServiceBlockingStub;
    @DubboReference
    LepuService lepuServiceBlockingStub;
    @Autowired
    SqlHelper sqlHelper;
    @Autowired
    EquipmentPersonnelRepository equipmentPersonnelRepository;
    @Autowired
    UserService userService;
    @Autowired
    HospitalService hospitalService;
    @Autowired
    WeChatMsgProvider weChatMsgProvider;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private ChecklistOrdersDagnosisHistoryRepository checklistOrdersDagnosisHistoryRepository;
    @Autowired
    private MedicalEquipmentRepository medicalEquipmentRepository;
    @Autowired
    private ChecklistOrdersChangeEquipmentLogRepository checklistOrdersChangeEquipmentLogRepository;
    @Autowired
    private SnsUserInfoRepository snsUserInfoRepository;

    @Override
    public CreateOrderResponse createOrder(CreateOrderRequest request) {
        CreateOrderResponse.Builder builder = CreateOrderResponse.newBuilder();
        // int hospitalId = request.getHospitalId();
        int doctorId = request.getDoctorId();
        // int deptId = request.getDeptId();
        String diagnose = request.getDiagnose();
        List<InspectionItem> inspectionItem = request.getInspectionItemList();
        // int totalPrice = request.getTotalPrice();
        int doctorSignId = request.getDoctorSignId();
        String remark = request.getRemark();
        try {
            CheckUtil.ensureLargerThanZero(doctorId, "请登录后操作");
            CheckUtil.ensureNotEmpty(diagnose, "请选择诊断");
            if (Checker.isNone(inspectionItem)) throw new YwtCommonException(YwtCommonRespCode.P_ERR, "请选择检查项目");
            CheckUtil.ensureLargerThanZero(doctorSignId, "请提交签名");
            int totalPrice = 0;
            List<ChecklistOrdersDetail> odList = new LinkedList<>();
            String payCode = "";
            DoctorInfo doctorInfo = doctorService.getDoctorInfoById(doctorId);
            CheckUtil.ensureNotNull(doctorInfo, "当前医生用户不存在");
            int hospitalId = Checker.getIntegerValue(doctorInfo.getHospitalId());
            for (InspectionItem item : inspectionItem) {
                int count = item.getCount();
                if (count <= 0) {
                    throw new YwtCommonException(YwtCommonRespCode.P_ERR, "检查项目数量不能小于 1");
                }
                int id = item.getId();
                CheckProject cp = checkProjectRepository.findFirstByIdAndStatusAndDeleted(id, CommonStatusEnum.NORMAL.getValue(), DeletedEnum.NORMAL.getValue());
                CheckUtil.ensureNotNull(cp, String.format("检查项目不可用(%d)", id));
                String pCode = Checker.getStringValue(cp.getPayCode());
                CheckUtil.ensureNotEmpty(pCode, String.format("检查项目未配置支付代码，请联系管理员(%d)", id));
                if (StringHelper.isNullOrEmpty(payCode)) {
                    payCode = pCode;
                }
                if (!pCode.equals(payCode)) throw new YwtCommonException(YwtCommonRespCode.P_ERR, "同一订单不支持不同支付代码的检查项目");
                int price = Checker.getIntegerValue(cp.getPrice());
                totalPrice += (count * price);
                ChecklistOrdersDetail od = new ChecklistOrdersDetail();
                od.setCheckProjectId(cp.getId());
                od.setCheckName(Checker.getStringValue(cp.getCheckName()));
                od.setSpec(Checker.getStringValue(cp.getSpec()));
                od.setAmount(count);
                od.setPrice(price);
                od.setCreateTime(new Date());
                od.setRemark(Checker.getStringValue(cp.getRemark()));
                od.setDeleted(DeletedEnum.NORMAL.getValue());
                odList.add(od);
            }
            CheckUtil.ensureLargerThanZero(totalPrice, "总金额不能为 0");

            // 生成基础订单信息
            ChecklistOrders o = new ChecklistOrders();
            o.setHospitalId(hospitalId);
            int deptId = Checker.getIntegerValue(doctorInfo.getDeptId());
            o.setDeptId(deptId);
            Department department = hospitalService.getDepartment(deptId);
            o.setDeptName(department == null ? "" : Checker.getStringValue(department.getName()));
            o.setDoctorId(doctorId);
            o.setDoctorName(Checker.getStringValue(doctorInfo.getName()));
            o.setTotalPrice(totalPrice);
            o.setDiagnose(diagnose);
            o.setDoctorSignId(doctorSignId);
            o.setStatus(MedEquipStatusEnum.UNPAID.getValue());
            o.setRemark(remark);
            o.setPayCode(payCode);
            o.setDeleted(DeletedEnum.NORMAL.getValue());
            o.setCreateTime(new Date());
            int leftCount = medicalEquipmentRepository.countAllByHospitalIdAndUseStatusAndStatusAndDeleted(hospitalId,
                    MedEquipOccupyStatusEnum.AVAILABLE.getValue(), CommonStatusEnum.NORMAL.getValue(), DeletedEnum.NORMAL.getValue());
            if (leftCount <= 0) {
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("设备不足(%d)", hospitalId));
            }
            ChecklistOrders os = checklistOrdersRepository.save(o);
            // 创建订单
//            com.ywt.gapi.ordercenter.CreateOrderRequest createOrderRequest = com.ywt.gapi.ordercenter.CreateOrderRequest.newBuilder()
//                    .setOrderType(OrderTypeEnum.MED_EQUIP.getValue())
//                    .setBizId(os.getId())
//                    .setPaymentCallbackMethod("/com.ywt.gapi.med_equip.MedEquipService/payCallback")
//                    .setPayCode(payCode)
//                    .setAmount(totalPrice)
//                    .setPayBody("开检查缴费")
//                    .build();
            com.ywt.gapi.ordercenter.CreateOrderRequest createOrderRequest = com.ywt.gapi.ordercenter.CreateOrderRequest.newBuilder()
                    .setAmount(totalPrice)
                    .setUserId(0)
                    .setOrderType(0)
                    .setBizId(os.getId())
                    .setDoctorId(doctorId)
                    .setPayCode(payCode)
                    .setPayBody("开检查缴费")
                    .setPaymentCallbackMethod("/com.ywt.gapi.med_equip.MedEquipService/payCallback")
                    .build();
            com.ywt.gapi.ordercenter.CreateOrderResponse createOrderResponse = orderCenterServiceBlockingStub.createOrder(createOrderRequest);
            if (createOrderResponse.getCode() != ResultCode.SUCCEED_VALUE) {
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("订单中心创建订单：%s", createOrderResponse.getInfo()));
            }
            //保存订单编号、订单Id
            String orderNo = createOrderResponse.getOrderNo();
            int orderId = createOrderResponse.getOrderId();
            o.setOrderId(orderId);
            o.setOrderNo(orderNo);
            checklistOrdersRepository.save(o);
            // 检查项目详情
            for (ChecklistOrdersDetail od : odList) {
                od.setChecklistOrdersId(os.getId());
                checklistOrdersDetailRepository.save(od);
            }
            builder.setCode(ResultCode.SUCCEED_VALUE)
                    .setOrderId(orderId)
                    .setOrderNo(orderNo)
                    .setEquipOrderId(os.getId());
            return builder.build();
        } catch (YwtCommonException e) {
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#createOrder(request={}):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        }
    }

    @Override
    public QueryOrderResponse queryOrder(QueryOrderRequest request) {
        int orderId = request.getOrderId();
        int userId = request.getUserId();
        QueryOrderResponse.Builder builder = QueryOrderResponse.newBuilder();
        try {
            CheckUtil.ensureLargerThanZero(userId, "请登录后操作");
            ChecklistOrders o = getOrderById(orderId);
            CheckUtil.ensureNotNull(o, "找不到订单信息");
            int hospitalId = getHospitalId(o.getChangeHospitalId(), o.getHospitalId());
            int patientId = Checker.getIntegerValue(o.getUserId());
            int doctorId = Checker.getIntegerValue(o.getDoctorId());
            // 是否有权限查看订单详情
            // 只有设备管理员、与开单医生同医院的医生以及患者本人可以查看订单详情
            if (patientId > 0) {
                boolean visible;
                boolean isUserEquipAdmin = isUserEquipAdmin(userId, hospitalId);
                if (isUserEquipAdmin) {
                    visible = true;
                } else {
                    visible = isUserAnAuthorizedDoctor(userId, hospitalId);
                }
                if (userId == patientId || userId == doctorId) visible = true;
                if (!visible) {
                    throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("当前用户暂无权限查看该订单详情(%d-%d)", userId, hospitalId));
                }
            }
            builder.setCode(ResultCode.SUCCEED_VALUE);
            OrderInfo.Builder b = convert(o, userId);
            int doctorSignId = Checker.getIntegerValue(o.getDoctorSignId());
            b.setDoctorSignUrl(doctorService.getDoctorSignUrlById(doctorSignId));
            String hospitalName = hospitalService.getHospitalNamesByHospitalId(hospitalId);
            b.setHospitalName(hospitalName);
            builder.setOrderInfo(b.build());
            return builder.build();
        } catch (YwtCommonException e) {
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#queryOrder(request={} ):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        }
    }

    private int getHospitalId(Integer changedHospitalId, Integer hospitalId) {
        return Checker.getIntegerValue(changedHospitalId) > 0 ? changedHospitalId : Checker.getIntegerValue(hospitalId);
    }

    private ChecklistOrders getOrderById(int orderId) {
        return checklistOrdersRepository.findFirstByIdAndDeleted(orderId, DeletedEnum.NORMAL.getValue());
    }

    @Override
    public QueryOrderRecordsResponse queryOrderRecords(QueryOrderRecordsRequest request) {
        QueryOrderRecordsResponse.Builder builder = QueryOrderRecordsResponse.newBuilder();
        int status = request.getStatus();
        String startTime = request.getStartTime();
        String endTime = request.getEndTime();
        int userId = request.getUserId();
        int hospitalId = request.getHospitalId();
        int pageIndex = request.getPageIndex();
        int pageSize = request.getPageSize();
        if (pageIndex <= 0) {
            pageIndex = Constants.DEFAULT_PAGE_INDEX;
        }
        if (pageSize <= 0) {
            pageSize = Constants.PAGE_SIZE;
        }
        String keyword = request.getKeyword();
        try {
            int patientId = 0;
            boolean isEquipAdmin = false;
            boolean isEquipAdminOnly = false;// 仅仅是管理员身份，非医生
            if (hospitalId >= 0) {
                // 管理员或医生可以查看所在医院的所有订单
                isEquipAdmin = isUserEquipAdmin(userId, hospitalId);
                Doctor d = doctorService.getNormalDoctorById(userId);
                if (!isEquipAdmin && d == null) {
                    // 非管理员非医生，即患者用户
                    patientId = userId;
                }
                if (isEquipAdmin && d == null) isEquipAdminOnly = true;
            } else {
                // hospitalId = -1，表示患者端查看该患者自己所有医院的订单
                patientId = userId;
            }

            PagedList<ChecklistOrders> orders = getCheckOrders(status, startTime, endTime, hospitalId, pageSize,
                    pageIndex, patientId, isEquipAdminOnly, keyword);

            List<Hospital> hospitals = hospitalService.getHospitalList();

            builder.setCode(ResultCode.SUCCEED_VALUE)
                    .setCount(orders.getTotal())
                    .setTotalPage(orders.getTotalPage())
                    .addAllOrderInfo(Optional.of(orders.getItems()).orElse(new ArrayList<>()).stream()
                            .map(i -> convert(i, userId)
                                    .setHospitalName(hospitals.stream()
                                            .filter(h -> Checker.getIntegerValue(h.getId()) == (getHospitalId(i.getChangeHospitalId(), i.getHospitalId())))
                                            .findFirst()
                                            .map(h2 -> Checker.getStringValue(h2.getName()))
                                            .orElse("")
                                    )
                                    .build())
                            .collect(Collectors.toList())
                    );
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#queryOrderRecords(request={} ):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        }
    }

    private InspectionItem convert(ChecklistOrdersDetail d) {
        if (d == null) {
            return InspectionItem.newBuilder().build();
        }
        return InspectionItem.newBuilder()
                .setId(Checker.getIntegerValue(d.getId()))
                .setCount(Checker.getIntegerValue(d.getAmount()))
                .setName(Checker.getStringValue(d.getCheckName()))
                .setSpec(Checker.getStringValue(d.getSpec()))
                .setPrice(Checker.getIntegerValue(d.getPrice()))
                .setRemark(Checker.getStringValue(d.getRemark()))
                .build();
    }

    private OrderInfo.Builder convert(ChecklistOrders o, int userId) {
        if (o == null) {
            return OrderInfo.newBuilder();
        }
        int refundFlag = 0;
        int allocFlag = 0;
        int recycleFlag = 0;
        int doctorId = Checker.getIntegerValue(o.getDoctorId());
        boolean equipCodeEditable = false;

        int hospitalId = getHospitalId(o.getChangeHospitalId(), o.getHospitalId());
//        int deptId = Checker.getIntegerValue(o.getDeptId());
//        String deptName = depts.stream().filter(d -> d.getId() == deptId).findFirst().map(Department::getName).orElse("");
        int status = Checker.getIntegerValue(o.getStatus());
        // 只有已支付的订单才有可能显示“确认领取”
        if (MedEquipStatusEnum.valueOf(status) == MedEquipStatusEnum.PAID) {
            // 开单医生、企业后台设置了设备管理员的医生、开单医生同医院的医生和设备管理员才显示
            if (isUserTheCreateOrderDoctor(userId, doctorId) || isUserAnAuthorizedDoctor(userId, hospitalId) || isUserEquipAdmin(userId, hospitalId)) {
                allocFlag = 1;
            }
        }
        // 已支付、已领取和已回收的订单有可能显示“申请退款”
        if (MedEquipStatusEnum.valueOf(status) == MedEquipStatusEnum.PAID ||
                MedEquipStatusEnum.valueOf(status) == MedEquipStatusEnum.ALLOCATED ||
                MedEquipStatusEnum.valueOf(status) == MedEquipStatusEnum.RECYCLED) {
            // 只有开单的医生才显示
            if (isUserTheCreateOrderDoctor(userId, doctorId)) {
                refundFlag = 1;
            }
        }
        // 已支付的订单可能显示“确认回收”（不能点击）
        if (MedEquipStatusEnum.valueOf(status) == MedEquipStatusEnum.PAID) {
            // 开单医生、企业后台设置了设备管理员的医生、开单医生同医院的医生和设备管理员才显示
            if (isUserTheCreateOrderDoctor(userId, doctorId) || isUserAnAuthorizedDoctor(userId, hospitalId) || isUserEquipAdmin(userId, hospitalId)) {
                recycleFlag = -1;
            }
        } else {
            // 已领取但未回收的订单有可能显示“确认回收”
            if ((status & MedEquipStatusEnum.ALLOCATED.getValue()) == MedEquipStatusEnum.ALLOCATED.getValue() &&
                    (status & MedEquipStatusEnum.RECYCLED.getValue()) != MedEquipStatusEnum.RECYCLED.getValue()) {
                // 开单医生、企业后台设置了设备管理员的医生、开单医生同医院的医生和设备管理员才显示
                if (isUserTheCreateOrderDoctor(userId, doctorId) || isUserAnAuthorizedDoctor(userId, hospitalId) || isUserEquipAdmin(userId, hospitalId)) {
                    recycleFlag = 1;
                }
            }
        }
        int orderId = Checker.getIntegerValue(o.getId());
        // 判断能否修改设备码
        // “已领取”及以后的状态，有管理权限的用户，且该订单没有在医生端修改过设备码
        if ((status & MedEquipStatusEnum.ALLOCATED.getValue()) == MedEquipStatusEnum.ALLOCATED.getValue()) {
            if (checkIfUserHasAdminPermission(orderId, userId)) {
                ChecklistOrdersChangeEquipmentLog l = checklistOrdersChangeEquipmentLogRepository.findFirstByChecklistOrderIdAndSourceAndDeleted(orderId,
                        MedEquipSourceEnum.DOCTOR_MP_NFYYBYFY.getValue(), DeletedEnum.NORMAL.getValue());
                equipCodeEditable = l == null;
            }
        }

        List<ChecklistOrdersDetail> codList = checklistOrdersDetailRepository.findAllByChecklistOrdersIdAndDeleted(o.getId(), DeletedEnum.NORMAL.getValue());
        int doctorSignId = Checker.getIntegerValue(o.getDoctorSignId());
        return OrderInfo.newBuilder()
                .setOrderNo(Checker.getStringValue(o.getOrderNo()))
                .setDoctorName(Checker.getStringValue(o.getDoctorName()))
                .setDeptName(Checker.getStringValue(o.getDeptName()))
                .setPatientName(Checker.getStringValue(o.getPatientName()))
                .setPatientIdCard(Checker.getStringValue(o.getIdNo()))
                .setPatientAge(Checker.getIntegerValue(o.getAge()))
                .setPatientGender(Checker.getIntegerValue(o.getSex()))
                .setPatientMobile(Checker.getStringValue(o.getMobile()))
                .setPaymentTime(DateUtil.formatDate(o.getPaymentTime(), DateUtil.DADE_FROMAT_YMD))
                .setDiagnose(Checker.getStringValue(o.getDiagnose()))
                .addAllInspectionItem(codList.stream().map(this::convert).collect(Collectors.toList()))
                .setDoctorSignId(doctorSignId)
                .setRemark(Checker.getStringValue(o.getRemark()))
                .setStatus(status)
                .setStatusStr(MedEquipStatusEnum.valueOf(status).getDisplayName())
                .setPayUrl("")// TODO:
                .setEquipCode(Checker.getStringValue(o.getMedEquipCode()))
                .setSenderName(Checker.getStringValue(o.getSendEquipmentPersonnelName()))
                .setRecyclerName(Checker.getStringValue(o.getReceiveEquipmentPersonnelName()))
                .setRefundApplyDoctor(Checker.getStringValue(o.getRefundDoctorName()))
                .setRefundRejectReason(Checker.getStringValue(o.getAuditRemark()))
                .setEquipId(Checker.getIntegerValue(o.getMedicalEquipmentId()))
                .setOrderId(Checker.getIntegerValue(o.getOrderId()))
                .setCreateTime(DateUtil.formatDate(o.getCreateTime(), DateUtil.DADE_FROMAT_YMD))
                .setTotalPrice(Checker.getIntegerValue(o.getTotalPrice()))
                .setRefundFlag(refundFlag)
                .setAllocFlag(allocFlag)
                .setRecycleFlag(recycleFlag)
                .setEquipOrderId(o.getId())
                .setRecCode(Checker.getStringValue(o.getRecCode()))
                .setEquipCodeEditable(equipCodeEditable)
                .setHospitalId(Checker.getIntegerValue(o.getHospitalId()))
                .setChangedHospitalId(Checker.getIntegerValue(o.getChangeHospitalId()))
                ;
    }

    private InspectionItem convertCheckProject(CheckProject d) {
        if (d == null) {
            return InspectionItem.newBuilder().build();
        }
        return InspectionItem.newBuilder()
                .setId(Checker.getIntegerValue(d.getId()))
                .setName(Checker.getStringValue(d.getCheckName()))
                .setSpec(Checker.getStringValue(d.getSpec()))
                .setPrice(Checker.getIntegerValue(d.getPrice()))
                .setRemark(Checker.getStringValue(d.getRemark()))
                .build();
    }

    private PagedList<ChecklistOrders> getCheckOrders(int status, String startTime, String endTime, int hospitalId,
                                                      int pageSize, int pageIndex, int patientId, boolean isEquipAdminOnly,
                                                      String keyword) {
        List<Object> paramList = new ArrayList<>();
        String whereSql = " ( 1=1 )";
        String Format_Date = "yyyy-MM-dd";
        SimpleDateFormat format = new SimpleDateFormat(Format_Date);
        try {
            // 支付时间
            if (!Checker.isNone(endTime)) {
                whereSql += " and ( create_time < ?)";
                Date date = format.parse(endTime);
                //把日期往后增加一天.整数往后推,负数往前移动
                Calendar calendar = new GregorianCalendar();
                calendar.setTime(date);
                calendar.add(Calendar.DATE, 1);
                //这个时间就是日期往后推一天的结果
                date = calendar.getTime();
                paramList.add(date);
            }
            if (!Checker.isNone(startTime)) {
                whereSql += " and ( create_time >= ?)";
                Date date = format.parse(startTime);
                paramList.add(date);
            }
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#getCheckOrders(status={} , startTime={} , endTime={} , hospitalId={} , pageSize={} , pageIndex={} , patientId={} ):\n {}",
                    status, startTime, endTime, hospitalId, pageSize, pageIndex, patientId, e.getMessage(), e);
        }
        if (status >= 0) {
            List<Integer> statusList = statusCompat(status);
            if (!Checker.isNone(statusList) && statusList.size() > 1) {
                int startIncluded = statusList.get(0);
                int endExcluded = statusList.get(1);
                whereSql += String.format(" and (status >= %d and status < %d) ", startIncluded, endExcluded);
            }
        }
        if (isEquipAdminOnly) {
            // 只是设备管理员身份的过滤未支付的订单
            whereSql += " and status > 0 ";
        }
        if (patientId > 0) {
            whereSql += String.format(" and user_id = %d", patientId);
        }
        if (hospitalId > 0) {
            whereSql += String.format(" and ((change_hospital_id is null and hospital_id = %d) or change_hospital_id = %d) ", hospitalId, hospitalId);
        }
        if (!StringHelper.isNullOrEmpty(keyword)) {
            whereSql += String.format(" and patient_name like '%%%s%%' ", keyword);
        }
        whereSql += " and deleted = 0 ";

        String fields = "*";
        String from = "ywt_center.checklist_orders";
        String orderBy = "create_time desc";
        return sqlHelper.getPagedList(pageIndex, pageSize, fields, from, whereSql, orderBy, ChecklistOrders.class, paramList.toArray());
    }

    private List<Integer> statusCompat(int statusParam) {
        List<Integer> statusList = new ArrayList<>();
        switch (MedEquipStatusEnum.valueOf(statusParam)) {
            case UNPAID:
                statusList.add(MedEquipStatusEnum.UNPAID.getValue());
                statusList.add(MedEquipStatusEnum.PAID.getValue());
                break;
            case PAID:
                statusList.add(MedEquipStatusEnum.PAID.getValue());
                statusList.add(MedEquipStatusEnum.ALLOCATED.getValue());
                break;
            case ALLOCATED:
                statusList.add(MedEquipStatusEnum.ALLOCATED.getValue());
                statusList.add(MedEquipStatusEnum.RECYCLED.getValue());
                break;
            case RECYCLED:
                statusList.add(MedEquipStatusEnum.RECYCLED.getValue());
                statusList.add(MedEquipStatusEnum.REFUNDING.getValue());
                break;
            case REFUNDING:
                statusList.add(MedEquipStatusEnum.REFUNDING.getValue());
                statusList.add(MedEquipStatusEnum.REFUNDED.getValue());
                break;
            case REFUNDED:
                statusList.add(MedEquipStatusEnum.REFUNDED.getValue());
                statusList.add(MedEquipStatusEnum.EXPIRED.getValue());
                break;
            case EXPIRED:
                statusList.add(MedEquipStatusEnum.EXPIRED.getValue());
                statusList.add(64);
                break;
            default:
                break;
        }
        return statusList;
    }

    @Override
    public QueryInspectionItemsResponse queryInspectionItems(QueryInspectionItemsRequest request) {
        QueryInspectionItemsResponse.Builder builder = QueryInspectionItemsResponse.newBuilder();
        String keyword = request.getKeyword();
        int hospitalId = request.getHospitalId();
        int pageIndex = request.getPageIndex();
        int pageSize = request.getPageSize();
        if (pageIndex <= 0) {
            pageIndex = Constants.DEFAULT_PAGE_INDEX;
        }
        if (pageSize <= 0) {
            pageSize = Constants.PAGE_SIZE;
        }
        try {
            PagedList<CheckProject> list = queryInspectionItems(keyword, pageIndex, pageSize, hospitalId);
            builder.setCode(ResultCode.SUCCEED_VALUE)
                    .setCount(list.getTotal())
                    .setTotalPage(list.getTotalPage())
                    .addAllInspectionItem(Optional.of(list.getItems()).orElse(new ArrayList<>()).stream()
                            .map(this::convertCheckProject)
                            .collect(Collectors.toList())
                    );
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#queryInspectionItems(request={}):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        }
    }

    private PagedList<CheckProject> queryInspectionItems(String keyword, int pageIndex, int pageSize, int hospitalId) {
        String whereSql = " ( 1=1 )";
        if (!StringHelper.isNullOrEmpty(keyword)) {
            // 如果 keyword 是纯字母+数字，匹配拼音首字母
            boolean isLetterOrDigit = true;
            for (int i = 0; i < keyword.length(); i++) {
                if (!Character.isLetterOrDigit(keyword.charAt(i))) {
                    isLetterOrDigit = false;
                    break;
                }
            }
            if (isLetterOrDigit) {
                whereSql += String.format(" and (check_name_pinyin like '%s%%' or check_name like '%%%s%%' )", keyword.toLowerCase(Locale.ROOT), keyword);
            } else {
                whereSql += String.format(" and check_name like '%%%s%%' ", keyword);
            }
        }
        if (hospitalId > 0) {
            whereSql += " and hospital_id = " + hospitalId;
        }
        whereSql += " and deleted = 0 and status = 0 ";
        String fields = "*";
        String from = "ywt_center.check_project";
        String orderBy = "weight desc";
        logger.info("select * from {} where {} ;", from, whereSql);
        return sqlHelper.getPagedList(pageIndex, pageSize, fields, from, whereSql, orderBy, CheckProject.class, (new ArrayList<>()).toArray());
    }

    @Override
    public QueryEquipListResponse queryEquipList(QueryEquipListRequest request) {
        int hospitalId = request.getHospitalId();
        int deptId = request.getDeptId();
        int pageIndex = request.getPageIndex();
        int pageSize = request.getPageSize();
        if (pageIndex <= 0) {
            pageIndex = Constants.DEFAULT_PAGE_INDEX;
        }
        if (pageSize <= 0) {
            pageSize = Constants.PAGE_SIZE;
        }
        QueryEquipListResponse.Builder builder = QueryEquipListResponse.newBuilder();
        try {
            PagedList<MedicalEquipment> list = queryAvailableEquipList(hospitalId, deptId, pageIndex, pageSize);
            builder.setCode(ResultCode.SUCCEED_VALUE)
                    .setCount(list.getTotal())
                    .setTotalPage(list.getTotalPage())
                    .addAllEquip(Optional.of(list.getItems()).orElse(new ArrayList<>()).stream()
                            .map(i -> convertMedEquipment(i, hospitalService.getDepartmentList()))
                            .collect(Collectors.toList())
                    );
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#queryEquipList(request={}):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        }
    }

    private Equip convertMedEquipment(MedicalEquipment d, List<Department> depts) {
        if (d == null) {
            return Equip.newBuilder().build();
        }
        int deptId = Checker.getIntegerValue(d.getDeptId());
        String deptName = depts.stream().filter(dp -> dp.getId() == deptId).findFirst().map(Department::getName).orElse("");
        String equipCode = Checker.getStringValue(d.getEquipCode());
        return Equip.newBuilder()
                .setId(Checker.getIntegerValue(d.getId()))
                .setName(String.format("%s   %s", deptName, equipCode))
                .setDeptName(deptName)
                .setDeptId(Checker.getIntegerValue(d.getDeptId()))
                .setHospitalId(Checker.getIntegerValue(d.getHospitalId()))
                .setStatus(Checker.getIntegerValue(d.getStatus()))
                .setOccupationStatus(Checker.getIntegerValue(d.getUseStatus()))
//                .setPayCode(Checker.getStringValue(d.getPayCode()))
                .setEquipCode(equipCode)
                .build();
    }

    private PagedList<MedicalEquipment> queryAvailableEquipList(int hospitalId, int deptId, int pageIndex, int pageSize) {
        String whereSql = " ( 1=1 )";
        if (hospitalId > 0) {
            whereSql += " and hospital_id = " + hospitalId;
        }
        if (deptId > 0) {
            whereSql += " and dept_id = " + deptId;
        }
        whereSql += " and deleted = 0 and status = 0 and use_status = 0 ";
        String fields = "*";
        String from = "ywt_center.medical_equipment";
        String orderBy = "weight desc";
        return sqlHelper.getPagedList(pageIndex, pageSize, fields, from, whereSql, orderBy, MedicalEquipment.class, (new ArrayList<>()).toArray());
    }

    @Override
    public ApplyForRefundResponse applyForRefund(ApplyForRefundRequest request) {
        int orderId = request.getOrderId();
        int operatorId = request.getOperatorId();
        String reason = request.getReason();
        ApplyForRefundResponse.Builder builder = ApplyForRefundResponse.newBuilder();
        try {
            CheckUtil.ensureNotEmpty(reason, "申请原因不能为空");
            ChecklistOrders o = getOrderById(orderId);
            CheckUtil.ensureNotNull(o, String.format("订单信息无效(%d)", orderId));
            int doctorId = Checker.getIntegerValue(o.getDoctorId());
            if (doctorId != operatorId) {
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("非法操作，请联系管理员(%d-%d)", orderId, operatorId));
            }
            int status = Checker.getIntegerValue(o.getStatus());
            if ((status & MedEquipStatusEnum.PAID.getValue()) != MedEquipStatusEnum.PAID.getValue() ||
                    (status & MedEquipStatusEnum.REFUNDING.getValue()) == MedEquipStatusEnum.REFUNDING.getValue() ||
                    (status & MedEquipStatusEnum.REFUNDED.getValue()) == MedEquipStatusEnum.REFUNDED.getValue()) {
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, "订单状态不符合");
            }
            o.setRefundApply(reason);
            o.setRefundDoctorId(operatorId);
            DoctorInfo doctorInfo = doctorService.getDoctorInfoById(doctorId);
            String doctorName = doctorInfo == null ? "" : doctorInfo.getName();
            o.setRefundDoctorName(doctorName);
            o.setStatus(status | MedEquipStatusEnum.REFUNDING.getValue());
            checklistOrdersRepository.save(o);
            return builder.setCode(ResultCode.SUCCEED_VALUE).build();
        } catch (YwtCommonException e) {
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#applyForRefund(request={} ):\n {}", request, e.getMessage(), e);
            return builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage()).build();
        }
    }

    /**
     * 乐普推送检查报告入库
     */
    @Override
    public UpdateDiagnosisResponse updateDiagnosis(UpdateDiagnosisRequest request) {
        long wearId = request.getWearId();
        String content = request.getContent();
        String pdfUrl = request.getPdfUrl();
        String callbackTime = request.getCallbackTime();
        int orderId = request.getOrderId();
        String equipCode = request.getEquipCode();
        UpdateDiagnosisResponse.Builder builder = UpdateDiagnosisResponse.newBuilder();
        int hospitalId = 0;
        int userId = 0;
        try {
            Date cbt = DateUtil.parseDate(callbackTime, DateUtil.DADE_FROMAT_YMD);
            // 更新订单表
            ChecklistOrders o = getOrderById(orderId);
            if (o != null) {
//                orderId = o.getId();
                hospitalId = getHospitalId(o.getChangeHospitalId(), o.getHospitalId());
                userId = o.getUserId();
                // 由于修改设备码的需求，涉及隐私问题，报告需要和订单最新的设备码匹配，这里不再更新订单表的报告字段，需要查询的话直接查报告表
//                o.setDagnosisContent(content);
//                o.setDagnosisReport(pdfUrl);
//                o.setCallbackTime(cbt);
//                checklistOrdersRepository.save(o);
            } else {
                logger.error("MedEquipServiceImpl#updateDiagnosis: 根据 wearId {} 找不到订单", wearId);
            }
            // 插入记录到推送诊断历史表
            ChecklistOrdersDagnosisHistory h = new ChecklistOrdersDagnosisHistory();
            h.setChecklistOrdersId(orderId);
            h.setWearId(wearId);
            h.setDagnosisContent(content);
            h.setDagnosisReport(pdfUrl);
            h.setCallbackTime(cbt);
            h.setCreateTime(new Date());
            h.setDeleted(DeletedEnum.NORMAL.getValue());
            h.setHospitalId(hospitalId);
            h.setUserId(userId);
            h.setDeleted(DeletedEnum.NORMAL.getValue());
            h.setMedEquipCode(equipCode);
            ChecklistOrdersDagnosisHistory h2 = checklistOrdersDagnosisHistoryRepository.save(h);
            // 推送消息
            sendMsgAfterReportPushed(userId, h2.getId());
            builder.setCode(ResultCode.SUCCEED_VALUE);
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#updateDiagnosis(request={}):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        }

    }

    @Override
    public PayCallbackResponse payCallback(PayCallbackRequest request) {
        PayCallbackResponse.Builder builder = PayCallbackResponse.newBuilder();
        int bizId = request.getBizId();
        try {
            ChecklistOrders order = checklistOrdersRepository.findById(bizId).orElse(null);
            if (order == null) {
                builder.setCode(ResultCode.APP_ERROR_VALUE);
                builder.setInfo("找不到该订单的信息");
            } else {
                order.setStatus(MedEquipStatusEnum.PAID.getValue());
                order.setPaymentTime(new Date());
                order.setPaymentStatus(PaymentStatusEnum.Success.getValue());
                order.setPaymentNo(request.getPaymentNo());
                order.setPaymentChannel(request.getPaymentChannel());
                String recCode = BizUtil.formatIntWithLeadingZero(bizId, 4);
                order.setRecCode(recCode);
                checklistOrdersRepository.save(order);
                sendMsgAfterPaid(recCode, Checker.getStringValue(order.getOrderNo()), Checker.getIntegerValue(order.getTotalPrice()),
                        order.getCreateTime(), Checker.getIntegerValue(order.getUserId()), bizId);
                builder.setCode(ResultCode.SUCCEED_VALUE);
            }
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#payCallback(request={} ):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        }
    }

    @Override
    public RefundNotifyResponse orderRefundNotify(RefundNotifyRequest request) {
        logger.info("orderRefundNotify():{}", request);
        RefundNotifyResponse response;
        try {
            String sql = "update checklist_orders set " +
                    "refund_status = ?," +
                    "status = status | ?," +
                    "refund_time = now() " +
                    "where refund_no = ? and refund_status = ?;";
            Object[] args = new Object[]{
                    request.getRefundStatus(),
                    MedEquipStatusEnum.REFUNDED.getValue(),
                    request.getRefundNo(),
                    RefundStatusEnum.PENDING.getValue()
            };
            int[] types = new int[]{
                    Types.INTEGER,
                    Types.INTEGER,
                    Types.VARCHAR,
                    Types.INTEGER
            };
            int count = jdbcTemplate.update(sql, args, types);
            if (count > 0) {
                response = RefundNotifyResponse.newBuilder()
                        .setCode(ResultCode.SUCCEED_VALUE)
                        .setInfo("处理成功")
                        .build();
            } else {
                response = RefundNotifyResponse.newBuilder()
                        .setCode(ResultCode.APP_ERROR_VALUE)
                        .setInfo("流水号不存在或重复回调")
                        .build();
            }
            return response;
        } catch (Exception e) {
            logger.error("orderRefundNotify(): {}", e.getMessage(), e);
            return RefundNotifyResponse.newBuilder()
                    .setCode(ResultCode.APP_ERROR_VALUE)
                    .setInfo(e.getMessage())
                    .build();
        }
    }

    /**
     * 更新订单信息，患者端支付前提交姓名身份证手机号等信息
     */
    @Override
    public UpdateOrderInfoResponse updateOrderInfo(UpdateOrderInfoRequest request) {
        String patientName = request.getPatientName();
        String patientIdCard = request.getPatientIdCard();
        String patientMobile = request.getPatientMobile();
        int orderId = request.getOrderId();
        int userId = request.getUserId();
        UpdateOrderInfoResponse.Builder builder = UpdateOrderInfoResponse.newBuilder();
        try {
            CheckUtil.ensureNotEmpty(patientName, "请完善姓名");
            CheckUtil.ensureLargerThanZero(orderId, "订单 id 有误");
            if (!PhoneNumChecker.isPhoneNum(patientMobile)) {
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, "请完善手机号");
            }
            if (!IdCardUtil.verify(patientIdCard)) {
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, "请完善身份证");
            }
            ChecklistOrders o = getOrderById(orderId);
            CheckUtil.ensureNotNull(o, "订单信息不存在");
            int sex = IdCardUtil.getSexInt(patientIdCard);
            int age = IdCardUtil.getCurrentAge(patientIdCard);
            o.setPatientName(patientName);
            o.setMobile(patientMobile);
            o.setIdNo(patientIdCard);
            o.setSex(sex);
            o.setAge(age);
            o.setUserId(userId);
            checklistOrdersRepository.save(o);
            builder.setCode(ResultCode.SUCCEED_VALUE);
            return builder.build();
        } catch (YwtCommonException e) {
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#updateOrderInfo(request={} ):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        }

    }

    /**
     * 查询检查报告列表
     */
    @Override
    public QueryReportListResponse queryReportList(QueryReportListRequest request) {
        int userId = request.getUserId();
        int pageIndex = request.getPageIndex();
        int pageSize = request.getPageSize();
        int hospitalId = request.getHospitalId();
        QueryReportListResponse.Builder builder = QueryReportListResponse.newBuilder();
        if (pageIndex <= 0) {
            pageIndex = Constants.DEFAULT_PAGE_INDEX;
        }
        if (pageSize <= 0) {
            pageSize = Constants.PAGE_SIZE;
        }
        try {
            CheckUtil.ensureLargerThanZero(userId, "请登录后查询");
            PagedList<ChecklistOrdersDagnosisHistory> list = queryChecklistOrdersDagnosisHistoryList(hospitalId, userId, pageIndex, pageSize);
            builder.setCode(ResultCode.SUCCEED_VALUE)
                    .setCount(list.getTotal())
                    .setTotalPage(list.getTotalPage())
                    .addAllReportItem(Optional.of(list.getItems()).orElse(new ArrayList<>()).stream()
                            .map(this::convertDagnosisHistory)
                            .collect(Collectors.toList())
                    );
            return builder.build();
        } catch (YwtCommonException e) {
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#queryReportList(request={}):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        }
    }

    private PagedList<ChecklistOrdersDagnosisHistory> queryChecklistOrdersDagnosisHistoryList(int hospitalId, int userId, int pageIndex, int pageSize) {
        String whereSql = " ( 1=1 )";
        if (hospitalId > 0) {
            whereSql += " and h.hospital_id = " + hospitalId;
        }
        if (userId > 0) {
            whereSql += " and h.user_id = " + userId;
        }
        whereSql += " and h.deleted = 0 and o.id is not null and o.deleted = 0 ";
        String fields = "h.id as id, o.id as checklist_orders_id, h.med_equip_code as med_equip_code, " +
                "h.wear_id as wear_id, " +
                "h.hospital_id as hospital_id, " +
                "h.user_id as user_id, " +
                "h.dagnosis_content as dagnosis_content, " +
                "h.dagnosis_report as dagnosis_report, " +
                "h.callback_time as callback_time";
        String from = "ywt_center.checklist_orders_dagnosis_history h " +
                " left join ywt_center.checklist_orders o on h.checklist_orders_id = o.id and h.med_equip_code = o.med_equip_code ";
        String orderBy = "h.callback_time desc";
        logger.info("select * from {} where {} order by {}", from, whereSql, orderBy);
        return sqlHelper.getPagedList(pageIndex, pageSize, fields, from, whereSql, orderBy, ChecklistOrdersDagnosisHistory.class, (new ArrayList<>()).toArray());
    }

    private ReportItem convertDagnosisHistory(ChecklistOrdersDagnosisHistory d) {
        if (d == null) {
            return ReportItem.newBuilder().build();
        }
        return ReportItem.newBuilder()
                .setId(Checker.getIntegerValue(d.getId()))
                .setContent(Checker.getStringValue(d.getDagnosisContent()))
                .setPdfUrl(Checker.getStringValue(d.getDagnosisReport()))
                .setTimeStamp(DateUtil.convertToString(d.getCallbackTime(), DateUtil.DADE_FROMAT_YMD))
                .build();
    }

    /**
     * 查询检查报告详情
     */
    @Override
    public QueryReportDetailResponse queryReportDetail(QueryReportDetailRequest request) {
        int reportId = request.getReportId();
        int userId = request.getUserId();
        QueryReportDetailResponse.Builder builder = QueryReportDetailResponse.newBuilder();
        try {
            CheckUtil.ensureLargerThanZero(reportId, "报告 id 有误");
            CheckUtil.ensureLargerThanZero(userId, "请登录后操作");
            ChecklistOrdersDagnosisHistory d = checklistOrdersDagnosisHistoryRepository.findFirstByIdAndDeleted(reportId, DeletedEnum.NORMAL.getValue());
            CheckUtil.ensureNotNull(d, String.format("找不到该报告数据(%d)", reportId));
            int uid = Checker.getIntegerValue(d.getUserId());
            if (uid != userId)
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("非法操作(%d-%d)", reportId, userId));
            // 查看报告时，校验报告的设备码和关联订单的设备码是否一致
            int orderId = Checker.getIntegerValue(d.getChecklistOrdersId());
            ChecklistOrders o = getOrderById(orderId);
            CheckUtil.ensureNotNull(o, "该报告关联的订单信息找不到，无法查看");
            String orderEquipCode = Checker.getStringValue(o.getMedEquipCode());
            String reportEquipCode = Checker.getStringValue(d.getMedEquipCode());
            if (!orderEquipCode.equals(reportEquipCode)) {
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("该报告的设备码和关联订单的设备码不一致，无法查看(%s-%s)", orderEquipCode, reportEquipCode));
            }
            builder.setCode(ResultCode.SUCCEED_VALUE).setReportItem(convertDagnosisHistory(d));
            return builder.build();
        } catch (YwtCommonException e) {
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#queryReportDetail(request={}):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        }
    }

    /**
     * 判断用户是否有管理设备的权限(领取、回收)
     */
    @Override
    public CheckIfUserHasAdminPermissionResponse checkIfUserHasAdminPermission(CheckIfUserHasAdminPermissionRequest request) {
        int userId = request.getUserId();
        int orderId = request.getOrderId();
        CheckIfUserHasAdminPermissionResponse.Builder builder = CheckIfUserHasAdminPermissionResponse.newBuilder();
        try {
            boolean hasAdminPermission = checkIfUserHasAdminPermission(orderId, userId);
            builder.setCode(ResultCode.SUCCEED_VALUE)
                    .setIsAdmin(hasAdminPermission)
                    .setInfo(hasAdminPermission ? "" : String.format("该用户在该医院暂无设备管理权限，请联系管理员(%d-%d)", userId, orderId));
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#checkIfUserHasAdminPermission(request={}):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        }
    }

    /**
     * 判断用户是否有管理设备的权限(领取、回收)
     * 目前只有开单的医生、与开单医生同医院的医生及有管理员权限的用户才有权限
     */
    private boolean checkIfUserHasAdminPermission(int orderId, int userId) {
        boolean hasAdminPermission = false;
        try {
            ChecklistOrders o = getOrderById(orderId);
            if (o != null) {
                int doctorId = Checker.getIntegerValue(o.getDoctorId());
                int hospitalId = getHospitalId(o.getChangeHospitalId(), o.getHospitalId());
                hasAdminPermission = isUserTheCreateOrderDoctor(userId, doctorId);
                if (!hasAdminPermission) {
                    hasAdminPermission = isUserEquipAdmin(userId, hospitalId);
                    if (!hasAdminPermission) {
                        hasAdminPermission = isUserAnAuthorizedDoctor(userId, hospitalId);
                    }
                }
            }
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#checkIfUserHasAdminPermission(orderId={} , userId={} ):\n {}", orderId, userId, e.getMessage(), e);
        }
        return hasAdminPermission;
    }

    /**
     * 判断当前用户在某医院是否有设备管理权限
     *
     * @param userId     用户 id
     * @param hospitalId 医院 id
     */
    private boolean isUserEquipAdmin(int userId, int hospitalId) {
        EquipmentPersonnel ep = null;
        try {
            CheckUtil.ensureLargerThanZero(userId, "用户 id 参数有误");
            CheckUtil.ensureLargerThanZero(hospitalId, "医院 id 参数有误");
            User u = userService.findById(userId);
            CheckUtil.ensureNotNull(u, "用户不存在");
            String mobile = Checker.getStringValue(u.getMobile());
            CheckUtil.ensureNotEmpty(mobile, String.format("该用户没有绑定手机(%d)", userId));
            ep = equipmentPersonnelRepository.findFirstByMobileAndHospitalIdAndStatusAndDeleted(mobile, hospitalId, CommonStatusEnum.NORMAL.getValue(), DeletedEnum.NORMAL.getValue());
        } catch (YwtCommonException e) {
            logger.error("MedEquipServiceImpl#isUserEquipAdmin(userId={} , hospitalId={} ):\n {}", userId, hospitalId, e.getMessage());
        }
        return ep != null;
    }

    /**
     * 判断当前用户是否是和开单医生同一医院的医生用户
     *
     * @param userId     当前用户 id
     * @param hospitalId 开单医生所在医院 id
     */
    private boolean isUserAnAuthorizedDoctor(int userId, int hospitalId) {
        Doctor d = doctorService.getNormalDoctorById(userId);
        if (d == null) return false;
        int dhId = doctorService.getHospitalId(Checker.getStringValue(d.getHospitalIds()));
        return dhId == hospitalId;
    }

    /**
     * 判断当前用户是否是开单的医生
     *
     * @param userId           当前用户 id
     * @param createOrderDocId 开单医生 id
     */
    private boolean isUserTheCreateOrderDoctor(int userId, int createOrderDocId) {
        return userId == createOrderDocId;
    }

    @Override
    public GetEquipDeptListResponse getEquipDeptList(GetEquipDeptListRequest request) {
        int hospitalId = request.getHospitalId();
        GetEquipDeptListResponse.Builder builder = GetEquipDeptListResponse.newBuilder();
        try {
            List<Department> departmentList = hospitalService.getDepartmentList();
            List<MedicalEquipment> meList = medicalEquipmentRepository.findAllByHospitalIdAndUseStatusAndStatusAndDeleted(hospitalId, MedEquipOccupyStatusEnum.AVAILABLE.getValue(),
                    CommonStatusEnum.NORMAL.getValue(), DeletedEnum.NORMAL.getValue());
            List<EquipDept> edList = meList.stream()
                    .filter(d -> Checker.getIntegerValue(d.getDeptId()) > 0)
                    .map(MedicalEquipment::getDeptId)
                    .collect(Collectors.toSet())
                    .stream()
                    .map(d -> {
                        return EquipDept.newBuilder()
                                .setName(departmentList.stream().filter(dd -> d.equals(dd.getId())).findFirst().map(dd -> Checker.getStringValue(dd.getName())).orElse(""))
                                .setId(d)
                                .build();
                    })
                    .collect(Collectors.toList());
            builder.setCode(ResultCode.SUCCEED_VALUE).addAllEquipDept(edList);
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#getEquipDeptList(request={} ):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        }

    }

    /**
     * 修改设备码
     */
    @Override
    public ChangeEquipCodeResponse changeEquipCode(ChangeEquipCodeRequest request) {
        int orderId = request.getOrderId();
        String equipCode = request.getEquipCode();
        int operatorId = request.getOperatorId();
        int source = request.getSource();
        String operatorName = request.getOperatorName();
        ChangeEquipCodeResponse.Builder builder = ChangeEquipCodeResponse.newBuilder();
        ChecklistOrders o = getOrderById(orderId);
        try {
            CheckUtil.ensureNotNull(o, String.format("找不到订单信息(%d)", orderId));
            // 需求修改：领取过的都能修改设备码
            int status = Checker.getIntegerValue(o.getStatus());
            if ((status & MedEquipStatusEnum.ALLOCATED.getValue()) != MedEquipStatusEnum.ALLOCATED.getValue())
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, "订单状态不满足");
            CheckUtil.ensureNotEmpty(equipCode, "设备码不能为空");
            CheckUtil.ensureLargerThanZero(operatorId, "请登录后操作");
            if (equipCode.equals(o.getMedEquipCode()))
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, "当前修改的设备码和订单的设备码相同，无需修改");
            boolean fromMg = MedEquipSourceEnum.valueOf(source) == MedEquipSourceEnum.HOSPITAL_MG || MedEquipSourceEnum.valueOf(source) == MedEquipSourceEnum.DRUG_ENTERPRISE_MG;
            if (!fromMg) {
                boolean hasAdminPermission = checkIfUserHasAdminPermission(orderId, operatorId);
                if (!hasAdminPermission)
                    throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("当前用户暂无该权限(%d-%d)", orderId, operatorId));
            }
            MedEquipSourceEnum se = MedEquipSourceEnum.valueOf(source);
            if (se == null)
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("终端参数有误(%d)", source));
            int curEquipId = Checker.getIntegerValue(o.getMedicalEquipmentId());
            long curWearId = Checker.getLongValue(o.getWearId());
            String curEquipCode = Checker.getStringValue(o.getMedEquipCode());
            String curPatientName = Checker.getStringValue(o.getPatientName());
            int curAge = Checker.getIntegerValue(o.getAge());
            int curSex = Checker.getIntegerValue(o.getSex());
            String curMobile = Checker.getStringValue(o.getMobile());
            String curIdCard = Checker.getStringValue(o.getIdNo());
            if (StringHelper.isNullOrEmpty(operatorName)) {
                // 后台调用需要传 mg_admin 的名称。
                // 小程序调用不需要，会尝试自动获取相应的医生姓名
                DoctorInfo di = doctorService.getDoctorInfoById(operatorId);
                if (di != null) {
                    operatorName = Checker.getStringValue(di.getName());
                }
            }
            // 判断是否能修改
            // 医生端、医院后台只有一次修改机会；药企后台有多次
            ChecklistOrdersChangeEquipmentLog l = checklistOrdersChangeEquipmentLogRepository.findFirstByChecklistOrderIdAndSourceAndDeleted(orderId, source, DeletedEnum.NORMAL.getValue());
            if (l != null && (se == MedEquipSourceEnum.DOCTOR_MP_NFYYBYFY || se == MedEquipSourceEnum.HOSPITAL_MG)) {
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("订单 %d 已修改过", orderId));
            }
            // 如果未回收，先摘机
            if ((status & MedEquipStatusEnum.RECYCLED.getValue()) != MedEquipStatusEnum.RECYCLED.getValue()) {
                RecycleEquipResponse reResponse = lepuServiceBlockingStub.recycleEquip(RecycleEquipRequest.newBuilder()
                        .setOrderId(orderId)
                        .setOperatorId(operatorId)
                        .setOperatorName(operatorName)
                        .setFromMg(fromMg)
                        .build());
                if (reResponse.getCode() != ResultCode.SUCCEED_VALUE) {
                    throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("摘机失败:%s", reResponse.getInfo()));
                }
            }
            // 戴机
            AllocEquipResponse aeResponse = lepuServiceBlockingStub.allocEquip(AllocEquipRequest.newBuilder()
                    .setOrderId(orderId)
                    .setOperatorId(operatorId)
                    .setEqmtNo(equipCode)
                    .setPatientName(curPatientName)
                    .setAge(curAge)
                    .setSex(curSex)
                    .setMobile(curMobile)
                    .setIdCard(curIdCard)
                    .setOperatorName(operatorName)
                    .setFromMg(fromMg)
                    .build());
            if (aeResponse.getCode() != ResultCode.SUCCEED_VALUE) {
                throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("戴机失败:%s", aeResponse.getInfo()));
            }
            // 记录写入 checklist_orders_change_equipment_log
            ChecklistOrdersChangeEquipmentLog log = new ChecklistOrdersChangeEquipmentLog();
            log.setDeleted(DeletedEnum.NORMAL.getValue());
            log.setCreateTime(new Date());
            log.setMedEquipCode(curEquipCode);
            log.setChecklistOrderId(orderId);
            log.setMedicalEquipmentId(curEquipId);
            log.setSource(source);
            log.setObjId(operatorId);
            log.setWearId(curWearId);
            checklistOrdersChangeEquipmentLogRepository.save(log);
            builder.setCode(ResultCode.SUCCEED_VALUE);
            return builder.build();
        } catch (YwtCommonException e) {
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#changeEquipCode(request={} ):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        }
    }

    /**
     * 判断用户是否是设备管理员
     */
    @Override
    public CheckIfUserEquipAdminResponse checkIfUserEquipAdmin(CheckIfUserEquipAdminRequest request) {
        int userId = request.getUserId();
        int hospitalId = request.getHospitalId();
        CheckIfUserEquipAdminResponse.Builder builder = CheckIfUserEquipAdminResponse.newBuilder();
        try {
            boolean isEquipAdmin = isUserEquipAdmin(userId, hospitalId);
            Doctor d = doctorService.getNormalDoctorById(userId);
            boolean isEquipAdminOnly = isEquipAdmin && d == null;
            builder.setCode(ResultCode.SUCCEED_VALUE)
                    .setIsEquipAdmin(isEquipAdmin)
                    .setIsEquipAdminOnly(isEquipAdminOnly);
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#checkIfUserEquipAdmin(request={}):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        }
    }

    private void sendMsgAfterPaid(String recCode, String orderNo, int amount, Date createTime, int userId, int orderId) {
        Map<String, String> keywordMap = new HashMap<>();
        keywordMap.put("phrase1", "检查");
        keywordMap.put("character_string2", orderNo);
        keywordMap.put("amount3", String.format("%.2f元", amount / 100d));
        keywordMap.put("date4", DateUtil.convertToString(createTime));
        String templateId = "w4vveyDKFq55HbocWDb5AvNPCBzNAC8ggowtx4wpqVg";
        String page = String.format("pages/index/index?entry=medical_device%%2FcheckContent%%3ForderId%%3D%d", orderId);
        sendWxAppMsg(userId, templateId, page, keywordMap);
    }

    private void sendMsgAfterAllocated(int userId, int orderId) {
        Map<String, String> keywordMap = new HashMap<>();
        keywordMap.put("thing1", "检查");
        keywordMap.put("phrase2", "检查中");
        keywordMap.put("thing4", "已过24小时，可到原处归还设备");
        String templateId = "Rly9lVauVuC-6_9ucHmOgSK_42qEc7clrZwWca6TEIM";
        String page = String.format("pages/index/index?entry=medical_device%%2FcheckContent%%3ForderId%%3D%d", orderId);
        sendWxAppMsg(userId, templateId, page, keywordMap);
    }

    private void sendMsgAfterReportPushed(int userId, int reportId) {
        Map<String, String> keywordMap = new HashMap<>();
        keywordMap.put("thing1", "检查报告");
        keywordMap.put("phrase2", "已收到");
        String templateId = "Rly9lVauVuC-6_9ucHmOgTGP0zrBNGqcV_01EEzbzsA";
        String page = String.format("pages/index/index?entry=medical_device%%2FreportDetail%%3Fid%%3D%d", reportId);
        sendWxAppMsg(userId, templateId, page, keywordMap);
    }

    /**
     * 发送小程序服务消息
     * 目前只发送到南方医院白云分院患者端小程序
     */
    private void sendWxAppMsg(int userId, String templateId, String page, Map<String, String> keywords) {
        int terminal = TerminalEnum.NFYYBYFY_WXAPP.getValue();
        SnsUserInfo snsUserInfo = snsUserInfoRepository.getSnsUserInfoByUserId(userId, terminal);
        String openId = !Checker.isNone(snsUserInfo) ? Checker.getStringValue(snsUserInfo.getOpenId()) : "";
        weChatMsgProvider.sendMPSubscribeMessage(terminal, openId, templateId, page, keywords);
    }

    /**
     * 定时任务处理
     */
    @Override
    public TriggerScheduledJobResponse triggerScheduledJob(TriggerScheduledJobRequest request) {
        int event = request.getEvent();
        TriggerScheduledJobResponse.Builder builder = TriggerScheduledJobResponse.newBuilder();
        switch (TriggerScheduledJobEnum.valueOf(event)) {
            case NOTIFY_RETURN_EQUIP:
                // 找出所有已领取状态的订单
                List<ChecklistOrders> list = checklistOrdersRepository.findAllOvertimeAllocatedOrders();
                for (ChecklistOrders o : list) {
                    logger.info("定时任务处理：24小时归还设备提醒，id = {}", o.getId());
                    sendMsgAfterAllocated(Checker.getIntegerValue(o.getUserId()), Checker.getIntegerValue(o.getOrderId()));
                    o.setExtStatus(MedEquipOrderExtStatusEnum.RETURN_EQUIP_NOTIFIED.getValue());
                    checklistOrdersRepository.save(o);
                }
                break;
            default:
                break;
        }
        builder.setCode(ResultCode.SUCCEED_VALUE);
        return builder.build();
    }

    /**
     * 获取设备管理员信息
     */
    @Override
    public GetEquipAdminInfoResponse getEquipAdminInfo(GetEquipAdminInfoRequest request) {
        int userId = request.getUserId();
        int hospitalId = request.getHospitalId();
        GetEquipAdminInfoResponse.Builder builder = GetEquipAdminInfoResponse.newBuilder();
        try {
            User u = userService.findById(userId);
            CheckUtil.ensureNotNull(u, "用户不存在");
            String mobile = Checker.getStringValue(u.getMobile());
            CheckUtil.ensureNotEmpty(mobile, String.format("该用户没有绑定手机(%d)", userId));
            EquipmentPersonnel ep = equipmentPersonnelRepository.findFirstByMobileAndHospitalIdAndStatusAndDeleted(mobile, hospitalId, CommonStatusEnum.NORMAL.getValue(), DeletedEnum.NORMAL.getValue());
            CheckUtil.ensureNotNull(ep, String.format("该用户在当前医院非管理员(%d-%d)", userId, hospitalId));
            int deptId = Checker.getIntegerValue(ep.getDeptId());
            builder.setCode(ResultCode.SUCCEED_VALUE)
                    .setHospitalName(hospitalService.getHospitalNamesByHospitalId(hospitalId))
                    .setDeptName(Optional.of(hospitalService.getDepartmentBydepartemtId(deptId)).map(d -> Checker.getStringValue(d.getName())).orElse(""))
                    .setDeptId(deptId)
                    .setHospitalId(hospitalId)
                    .setName(Checker.getStringValue(ep.getName()))
                    .setMobile(Checker.getStringValue(ep.getMobile()))
                    .setRemark(Checker.getStringValue(ep.getRemark()))
                    .setStatus(Checker.getIntegerValue(ep.getStatus()));
            return builder.build();
        } catch (YwtCommonException e) {
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        } catch (Exception e) {
            logger.error("MedEquipServiceImpl#getEquipAdminInfo(request={}):\n {}", request, e.getMessage(), e);
            builder.setCode(ResultCode.APP_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        }
    }
}
