# coding=utf-8
from __future__ import unicode_literals, absolute_import, print_function

import json
import datetime
from collections import defaultdict

from django.db.models import Q
from django.conf import settings
from django.db import transaction
from gm_types.error import ERROR
from gm_types.gaia import (
    SERVICE_SELL_TYPE,
    MOMO_STAT_LOG_TYPE,
    GROUPBUY_STATUS,
    ACTIVITY_TYPE_ENUM,
    TAG_TYPE,
)
from gm_types.plutus.rm_type import RM_STATUS
from django.db.models import Count
from django.db.models import ObjectDoesNotExist
from helios.rpc import RPCFaultException

from api.manager import order_manager
from api.manager import refund_manager
from api.manager.groupbuy_manager import get_groupbuy_team_info_by_order_ids,cover_groupbuy_with_users
from api.models import Order, CashBackOrder, ServiceItemPrice
from api.models import Doctor, PeriodHospital, User
from api.models import Service, ServiceTag, ServiceComment, ServiceItem
from api.models import (
    ORDER_QUERY_TYPE,
    CASH_BACK_STATUS,
    Reservation,
    SERVICE_PAYMENT_TYPE
)
from api.models import ORDER_STATUS_USED
from api.models import ORDER_OPERATION_TYPE, ORDER_OPERATION_ROLE
from api.tasks import order_task
from api.tool.datetime_tool import (
    get_timestamp_epoch,
    get_timestamp,
)
from api.tool.image_utils import get_full_path
from api.tool.notification_tool import send_notification
from api.tool.user_tool import (
    get_user_from_context,
    filter_user_nick_name,
    get_user_by_username,
    get_user_by_id)
from api.tool.log_tool import logging_exception,info_logger
from api.tool.buryiny_point_tool import validate_order_log
from api.tool.datetime_tool import get_datetime
from api.tool.order_tool import get_momo_stat_log_info
from api.tasks.order_task import remind_user_write_diary_after_order_validate, set_order_is_read_by_order_ids
from api.tasks.period_task import momo_stat_log
from api.models.settlement import Settlement
from api.models.settlement import SettlementItem
from api.models.settlement import SETTLEMENT_STATUS
from api.models.types import ORDER_STATUS
from api.models.order import RefundOrder
from api.models.order import OrderOperation
from hippo.utils import merchant_doctors

from rpc.tool.error_code import CODES, gen
from rpc.tool import time_tool
from rpc.tool.dict_mixin import to_dict
from rpc.decorators import bind_context, bind
from rpc.decorators import list_interface
from rpc.all import get_rpc_remote_invoker

from pay.tool import new_order_tool
from pay.models import PaymentOrder, MomoStatLog
from pay.models import Installment
from pay.models import ServiceSnapshot
from pay.tool.new_order_tool import get_actual_refund_amount
from services.custom_phone_service import PhoneService
from services.doctor_notify_service import DoctorNotifyService
from sms.utils.smsfactory import send_sms
from gm_types.gaia import REFUND_STATUS, ORDER_SOURCE
from gm_types.gaia import XIAOYING_INSTALLMENT_STATUS
from gm_types.pay import SUFPAY_STATUS, SUFPAY_CHANNEL
from services.notify import notify
from statistic.models import Device


@bind_context('api/order/get_settlement', login_required=True)
def get_settlement(ctx, order_id):
    try:
        order = Order.objects.get(pk=order_id)
        settlement_id = order.settlementitem.settlement_id
        return {'settlement_id': settlement_id}
    except ObjectDoesNotExist:
        return gen(CODES.ORDER_NOT_FOUND)


@bind_context("api/order/detail", login_required=True)
@bind_context("api/order/get", login_required=True)
def order_detail(ctx, order_id):
    '''
        订单详情--
    '''
    try:
        order = Order.objects.prefetch_related(
            'service__doctor__hospital'
        ).get(pk=order_id)
    except Order.DoesNotExist:
        return gen(CODES.ORDER_NOT_FOUND)
    order.read = True
    order.save(update_fields=['read'])
    user = get_user_from_context(ctx)
    if order.user != user:
        return gen(CODES.NO_PERMISSION)

    info = order.order_data()
    #增加用户id
    info["user_id"]=user.id
    info['refund_fee'] = get_actual_refund_amount(order)
    if order.get_renmai_status(ctx, user.person.id.hex) in [RM_STATUS.GRANT, RM_STATUS.REPAY]:
        info['hospital_payment'] = '0'
    # 花呗分期
    if getattr(order, 'hospital_pay', None):
        if order.hospital_pay.status == SUFPAY_STATUS.PAID:
            info['hospital_payment'] = '0'

    # 拼团
    info['groupbuy_team_info'] = {}

    #info_logger.info("订单信息---")
    #info_logger.info(info)

    if info['is_groupbuy'] is True:
        groupbuy_info = get_groupbuy_team_info_by_order_ids(
            order_ids=[info['order_id']], need_user_info=True
        )[info['order_id']]

        info_logger.info("拿到的订单团购信息---")
        info_logger.info(groupbuy_info)

        info['groupbuy_team_info']=groupbuy_info
        if info['groupbuy_status'] != GROUPBUY_STATUS.GROUPBUY_STARTED or not groupbuy_info:
            return info
        service_item_id = groupbuy_info.get('service_item_id')

        groupbuy_info.update({"view_angle":"","order_id":order_id})


        price_list=ServiceItemPrice.objects.filter(service_item_id=service_item_id,is_enable=True).exclude(selling_rule__activity_type__in=[ACTIVITY_TYPE_ENUM.GROUPBUY,ACTIVITY_TYPE_ENUM.MOREBUY])
        result = min(price_list,key=lambda x:x.gengmei_price)

        # 获取当时美购的默认价， 用于分享
        try:
            service_item = ServiceItem.objects.get(id=service_item_id)

            price_info = service_item.get_current_groupbuy_price_info()
            if not price_info:
                price_info = service_item.get_current_price_info()
            info['service_item_default_price']=price_info.get('gengmei_price')
            info["gengmei_price"]= price_info.get('gengmei_price')
            info['original_price']=price_info.get('original_price')
            info["danmai_price"]=result.gengmei_price
        except:
            info['service_item_default_price'] = info['gengmei_price']
            logging_exception()

    return info


@bind_context("api/order/cancel", login_required=True)
def order_cancel(ctx, order_id):
    """
    取消订单
    :param ctx:
    :param order_id:
    :return:
    """
    # 应该是完全没有被使用的接口，这里先从代码级别禁止调用，之后再考虑删除
    return gen(CODES.ORDER_NOT_FOUND)


@bind_context("api/order/refund", login_required=True)
def order_refund(ctx, order_id, comment=None, device_id=None, ip=None):
    """
    申请退款
    老订单支付的可以直接退
    结算单有real_payment可以直接退
    """
    try:
        order = Order.objects.get(pk=order_id)
    except Order.DoesNotExist:
        return gen(CODES.ORDER_NOT_FOUND)

    is_groupbuy_fail_order = order.groupbuy_status == GROUPBUY_STATUS.GROUPBUY_FAIL

    if not is_groupbuy_fail_order and not order.refund_anytime:
        return gen(CODES.ORDER_CAN_NOT_REFUND)

    user = get_user_from_context(ctx)
    if order.user != user:
        return gen(CODES.NO_PERMISSION)

    # check if order can be apply for refund if its in settlement and use coupon for all
    the_settleitem = None
    try:
        the_settleitem = order.settlementitem
    except:
        pass

    if the_settleitem:
        if order.real_payment <= 0 and the_settleitem.get_actual_refund() <= 0:
            return gen(CODES.CAN_NOT_APPLY_REFUND_SINCE_COUPON_GT_PAYMENT)
    doctor_id = order.service.doctor_id

    success, error_code = order_manager.apply_refund_order_by_user(order.id, comment)
    # 记录用户的设备标识,ip
    order_task.record_user_information.delay([order], '', device_id, ip, ORDER_STATUS.WAIT_REFUNDED)

    if success:
        notify('refund/add', doctor_id=doctor_id)
        result = gen(CODES.SUCCESS, wait_refund=True)
    else:
        result = gen(error_code)

    return result


@bind_context("api/order/cansell", login_required=True, deprecated=True)
def can_sell(ctx, order_id):
    """check before pay, so the only valid order status is not paid."""
    order = order_manager.get_order_by_id(order_id)
    if order:
        if order.status != ORDER_STATUS.NOT_PAID and order.status != ORDER_STATUS.PAYING:
            can_be_sold = False
        else:
            can_be_sold = order.service.is_can_be_sold()
    else:
        can_be_sold = False

    return {'can_sell': can_be_sold}


@bind_context("api/order/status/my", login_required=True)
def my_order_status(ctx):
    """
    我的订单状态信息
    包括未付款、未消费、已消费、退款单等各类订单数量
    :param ctx:
    :return:
    """
    user = get_user_from_context(ctx)

    orders = Order.objects.filter(Q(user=user) & ~Q(status=ORDER_STATUS.CANCEL))

    filter_not_paid = Q(status=ORDER_STATUS.NOT_PAID) & Q(settlementitem__isnull=True)
    filter_refunded = Q(status=ORDER_STATUS.REFUNDED) | Q(status=ORDER_STATUS.WAIT_REFUNDED)
    filter_paid = Q(status=ORDER_STATUS.PAID)
    filter_used = Q(status=ORDER_STATUS.USED) \
                  | Q(status=ORDER_STATUS.SETTLED) \
                  | Q(status=ORDER_STATUS.AUDITING) \
                  | Q(status=ORDER_STATUS.SETTLING)
    return {
        '0': orders.filter(filter_not_paid).count(),
        '1': orders.filter(filter_paid).count(),
        '2': orders.filter(filter_used).count(),
        '3': orders.filter(filter_refunded).count(),
    }


@bind_context("api/order/status/my/new", login_required=True)
def my_order_status_new(ctx, unpaid_order_count=None, filter_unread=False):
    """
    v5.2.0
    我的订单状态信息和结算单信息
    订单包括未消费、已消费、退款单等各类订单数量
    计算单未付款和正在支付都算作未付款
    :param ctx:
    :return:
    """
    user = get_user_from_context(ctx)

    orders = Order.objects.filter(
        Q(user=user) & ~Q(status=ORDER_STATUS.CANCEL))
    filter_not_paid = Q(status__in=(SETTLEMENT_STATUS.NOT_PAID,
                                    SETTLEMENT_STATUS.PAYING))
    filter_refunded = Q(status=ORDER_STATUS.REFUNDED)
    filter_paid = Q(status=ORDER_STATUS.PAID)
    filter_used = (Q(status=ORDER_STATUS.USED) |
                   Q(status=ORDER_STATUS.SETTLED) |
                   Q(status=ORDER_STATUS.AUDITING) |
                   Q(status=ORDER_STATUS.SETTLING))

    if filter_unread:
        filter_unread = Q(read=False)
        filter_paid = filter_paid & filter_unread
        filter_used = filter_used & filter_unread

    if not unpaid_order_count:
        unpaid_settlement = Settlement.objects.filter(
            person=user.person).filter(
            filter_not_paid).count()
    else:
        unpaid_settlement = Settlement.objects.filter(
            person=user.person).filter(filter_not_paid).annotate(
            count_items=Count('items')).filter(
            count_items=unpaid_order_count).count()

    used_orders = orders.filter(filter_used)

    return {
        '0': unpaid_settlement,
        '1': orders.filter(filter_paid).count(),
        '2': len([o for o in used_orders if o.has_service_comment() == False]),
        '3': orders.filter(filter_refunded & Q(read=False)).count(),
    }


@bind_context("api/orders/my", login_required=True)
@list_interface(offset_name='start_num',
                limit_name='count',
                element_model=Order)
def my_orders(ctx, count=10, start_num=0, status='', unpaid_with_settlement=False, update_read=False):
    """
    我的订单列表
    分为未付款、未消费、已消费、退款单等四种类型
    :param update_read:
    :param count:
    :param start_num:
    :param unpaid_with_settlement:
    :param status:
    :param ctx:
           count: 每次请求记录条数, 默认为10, str
           start_num: 起始条数, 默认为0, str
           status：订单状态（'0'：未付款；'1'：未消费；'2'：已消费；'3'：退款单）str
    :return:
    """
    #info_logger.info("我的订单状态---")
    #info_logger.info(status)
    user = get_user_from_context(ctx)
    #user=User.objects.get(id=20891740)

    status = str(status)
    orders = Order.objects.filter(Q(user=user) & ~Q(status=ORDER_STATUS.CANCEL))

    filter_not_paid = Q(status=ORDER_STATUS.NOT_PAID) & Q(settlementitem__isnull=True)
    filter_refunded = Q(status=ORDER_STATUS.REFUNDED) | Q(status=ORDER_STATUS.WAIT_REFUNDED)
    filter_paid = Q(status=ORDER_STATUS.PAID)
    filter_used = Q(status=ORDER_STATUS.USED) \
                  | Q(status=ORDER_STATUS.SETTLED) \
                  | Q(status=ORDER_STATUS.AUDITING) \
                  | Q(status=ORDER_STATUS.SETTLING)

    if status:
        if status == ORDER_STATUS.NOT_PAID:
            if unpaid_with_settlement:
                settlements = Settlement.objects.filter(
                    status=SETTLEMENT_STATUS.NOT_PAID,
                    person=user.person
                ).annotate(num_items=Count('items')).filter(
                    num_items=1).order_by('-created_at')
                orders = [settlement.items.all()[0].order
                          for settlement
                          in settlements]
            else:
                orders = orders.filter(filter_not_paid)
        elif status == ORDER_STATUS.PAID:
            orders = orders.filter(filter_paid)
        elif status == ORDER_STATUS.USED:
            # order_has_service_comment 为了解决订单小红点问题
            #info_logger.info("拿到的订单---")
            #info_logger.info(orders)
            orders = orders.filter(filter_used, order_has_service_comment=False)
        elif status == ORDER_STATUS.REFUNDED:
            orders = orders.filter(filter_refunded)
        orders = orders.order_by('-created_time')

        if update_read:
            to_update_order_ids = list(orders.filter(read=False).values_list('id', flat=True))
            if to_update_order_ids:
                set_order_is_read_by_order_ids.delay(to_update_order_ids)

    else:
        if unpaid_with_settlement:
            orders = [
                settlement.items.all()[0].order
                for settlement
                in Settlement.objects.filter(
                    status=SETTLEMENT_STATUS.NOT_PAID,
                    person=user.person
                ).annotate(num_items=Count('items')).filter(
                    num_items=1).order_by('-created_at')
            ]

            filter_other = (Q(user=user) & ~Q(status__in=(
                ORDER_STATUS.CANCEL,
                ORDER_STATUS.NOT_PAID))
                            )
            other_orders = Order.objects.filter(filter_other).order_by(
                '-created_time'
            )
            orders.extend(other_orders)

    orders_list = list(orders[start_num:start_num + count])
    order_ids = [order.id for order in orders_list]
    order_id_to_groupbuy_team_info = get_groupbuy_team_info_by_order_ids(order_ids)

    result = _get_order_list_items(orders_list, order_id_to_groupbuy_team_info)

    #info_logger.info("我的订单----")
    #info_logger.info(result)

    return result


def _get_order_list_items(orders_list, order_id_to_groupbuy_team_info):
    result = []
    for order in orders_list:
        service_snapshot = json.loads(order.service_snapshot)
        order_data = {
            'service_id': order.service.id,
            'tag_name':order.service.show_tags,
            'service_item_id': order.service_item_id,
            'order_id': order.id,
            'create_time': get_timestamp(order.created_time),
            'service_image': (order.service.images.values_list('image_url', flat=True)[0]
                              if order.service.images.exists()
                              else u'http://www.wanmeizhensuo.com/zhengxing/media/images/2014/03/13/4969c3d163cc.jpg'
                              ),
            'service_name': service_snapshot.get('name', ''),
            'short_description': service_snapshot.get('short_description'),
            'hint': u'¥ {}'.format(order.payment),
            'purchased': False,
            'image': get_full_path(service_snapshot.get('image_header', '')),
            'image_header': service_snapshot.get('image_header', ''),
            'status_code': order.status_code,
            'order_status': ORDER_STATUS.getDesc(order.status_code),
            'total_price': order.total_price,
            'is_can_be_sold': order.service.is_can_be_sold(),
            'is_seckill': order.is_seckill_order,
            'accept_reservation': order.service.doctor.accept_reserve,
            'reservation_status': Reservation.order_latest_status(order.id),
            'reservation_id': '',
            'has_comment': order.has_comment(),
            'show_comment': order.show_comment(),
            'diary_id': order.diary_id(),
            'has_diary_topic': order.has_diary_topic(),
            'settlement_id': order.settlement_id,
            'installment': get_installment_from_order(order),
            'is_insurance': True if order.get_insurance_info() else False,
            'show_reserve': order.show_reserve(),
            'has_service_comment': order.has_service_comment(),
            'hospital_payment': order.hospital_payment,
            'options_desc': order.order_multiattribute,
            'hospital_pay_installment': get_installment_from_hospital_pay(order),
            'is_groupbuy': order.activity_type == ACTIVITY_TYPE_ENUM.GROUPBUY,  # 增加拼团信息
            'groupbuy_team_info': {},
            "display_groupbuy_team_info": False,
            'activity_id': order.activity_id,
            'groupbuy_status': order.groupbuy_status,
        }
        if (order.groupbuy_status == GROUPBUY_STATUS.GROUPBUY_STARTED and order.status == ORDER_STATUS.PAID):
            if order_data['order_id'] in order_id_to_groupbuy_team_info:
                order_data["display_groupbuy_team_info"] = True
                order_data['groupbuy_team_info'] = order_id_to_groupbuy_team_info[order_data['order_id']]
                try:
                    order_data['service_item_default_price'] = ServiceItemPrice.objects.get(
                        service_item_id=order.service_item_id, is_default_price=True, is_enable=True
                    ).gengmei_price
                    price_list=ServiceItemPrice.objects.filter(service_item_id=order.service_item_id,is_enable=True).exclude(selling_rule__activity_type__in=[ACTIVITY_TYPE_ENUM.GROUPBUY,ACTIVITY_TYPE_ENUM.MOREBUY])
                    danmai_price=min(price_list,key=lambda x:x.gengmei_price)
                    order_data['danmai_price']=danmai_price.gengmei_price
                except:
                    logging_exception()

                order_data['service_price'] = order.service_price
        # 尾款支付单
        try:
            order_data['hospital_pay_status'] = SUFPAY_STATUS.getDesc(order.hospital_pay.status)
            order_data['hospital_pay_status_code'] = order.hospital_pay.status
        except:
            pass

        order_data['user_id'] = order.user_id

        order_data['is_support_renmai'] = False  # 废弃字段
        order_data['is_support_yirendai'] = False  # 废弃字段
        order_data['renmai_status'] = RM_STATUS.NONE  # 废弃字段

        if order_data['reservation_status']:
            order_data['reservation_id'] = Reservation.objects.filter(order_id=order.id).order_by('-created_time')[0].id

        if order.status_code in (ORDER_STATUS.REFUNDED, ORDER_STATUS.WAIT_REFUNDED):
            try:
                order_data['refund_order_id'] = order.refund.id
            except Order.refund.RelatedObjectDoesNotExist:
                order_data['refund_order_id'] = ''

        result.append(order_data)

    return result


@bind_context("api/orders/filter", login_required=True)
@list_interface(offset_name='offset', limit_name='limit', element_model=Order)
def orders_filter(ctx, query, query_type, statuses,
                  offset=None,
                  limit=None,
                  start=None,
                  end=None,
                  payment_types=None,
                  payment_channels=None,
                  refund_reasons=None,
                  doctor_id=None):
    if query_type not in [ORDER_QUERY_TYPE.DOCTOR,
                          ORDER_QUERY_TYPE.HOSPITAL,
                          ORDER_QUERY_TYPE.SERVICE,
                          ORDER_QUERY_TYPE.AREA,
                          ORDER_QUERY_TYPE.ALL,
                          ORDER_QUERY_TYPE.ORDER_NO,
                          ORDER_QUERY_TYPE.USERNAME,
                          ORDER_QUERY_TYPE.PHONE]:
        raise gen(CODES.OPERATION_NOT_SUPPORTED)

    if len(query) > 0:
        if query_type == ORDER_QUERY_TYPE.DOCTOR:
            q = Q(service__doctor__name__contains=query)
        elif query_type == ORDER_QUERY_TYPE.HOSPITAL:
            q = Q(service__doctor__hospital__name__contains=query)
        elif query_type == ORDER_QUERY_TYPE.SERVICE:
            q = Q(service__name__contains=query)
        elif query_type == ORDER_QUERY_TYPE.AREA:
            q = Q(service__doctor__hospital__city__name__contains=query)
            q |= Q(service__doctor__hospital__city__province__name__contains=query)
        elif query_type == ORDER_QUERY_TYPE.ORDER_NO:
            q = Q(id__contains=query)
        elif query_type == ORDER_QUERY_TYPE.USERNAME:
            q = Q(user__last_name__contains=query)
        elif query_type == ORDER_QUERY_TYPE.PHONE:
            q = Q(phone__contains=query)
        elif query_type == ORDER_QUERY_TYPE.ALL:
            q = Q(service__doctor__name__contains=query)
            q |= Q(service__doctor__hospital__name__contains=query)
            q |= Q(service__name__contains=query)
            q |= Q(service__doctor__hospital__city__name__contains=query)
            q |= Q(service__doctor__hospital__city__province__name__contains=query)
            q |= Q(id__contains=query)
            q |= Q(user__last_name__contains=query)
            q |= Q(phone__contains=query)
        else:
            # unknown query_type
            q = Q()
    else:
        q = Q()

    if len(statuses) > 0:
        q &= Q(status__in=statuses)

    if payment_types:
        q &= Q(service__payment_type__in=payment_types)

    if payment_channels:
        q &= Q(payment_channel__in=payment_channels)

    if refund_reasons:
        for reason in refund_reasons:
            q |= Q(refund_comment__contains=reason)

    if doctor_id:
        q &= Q(service__doctor__id=doctor_id)

    if "business" in ctx.session.groups and "business_leader" in ctx.session.groups:
        pass
    elif "business" in ctx.session.groups:
        # 改为通过医生关联的商务过滤订单
        q &= Q(service__doctor__business_partener=ctx.session.user)

    if start is not None:
        start_date = get_datetime(start)
        q &= Q(validate_time__gte=start_date) | Q(validate_time__isnull=True)

    if end is not None:
        end_date = get_datetime(end)
        q &= Q(validate_time__lte=end_date) | Q(validate_time__isnull=True)

    data = Order.query(q, offset, limit)
    return data


@bind("api/orders")
def orders(nos):
    orders = Order.objects.filter(id__in=nos)
    order_datas = []
    for order in orders:
        order_datas.append(order.data())

    return {"orders": order_datas}


@bind_context('api/order/is_used', login_required=True)
def api_order_is_used(ctx, order_id):
    """添加refound 为了确定来源,退款和验证都调用这个接口,只有退款才需要减少退款单小数点"""
    user = get_user_from_context(ctx)

    orders = list(Order.objects.filter(id=order_id).values_list('id', 'user_id', 'status'))

    if len(orders) != 1:
        return gen(CODES.ORDER_NOT_FOUND)

    order_user_id = orders[0][1]
    order_status = orders[0][2]

    if order_user_id != user.id:
        return gen(CODES.NO_PERMISSION)

    order_is_used = order_status == ORDER_STATUS.USED

    return {"order_is_used": order_is_used}

@bind_context('api/order/code/validate', login_required=True)
def api_order_validate(ctx, order_id, order_password, is_refund=False):
    """
    验证订单号和验证码是否合理
    """
    user = get_user_from_context(ctx)
    doctor = Doctor.objects.get(user=user)

    password = order_password.replace(' ', '')

    if password and order_id:
        try:
            order = Order.objects.get(id=order_id)
        except Order.DoesNotExist:
            return gen(CODES.ORDER_NOT_FOUND)

        if order.password != password:
            return gen(CODES.ORDER_VALIDATE_FAILED)

    elif password and len(password) == 10:
        try:
            order = Order.objects.get(password=password)
        except Order.DoesNotExist:
            return gen(CODES.ORDER_VALIDATE_FAILED)
    else:
        return gen(CODES.ORDER_VALIDATE_FAILED)

    if order.service.doctor != doctor:
        # 判断是否是商户验证医生订单
        if merchant_doctors(doctor.id) is None or (order.service.doctor not in merchant_doctors(doctor.id)):
            # 不是商户验证医生订单，但可能是子门店验证旗舰店订单
            from hippo.models.chain_hospital import MasterMerchant, SlaveMerchant
            is_flagship = order.service.doctor.is_store_classification  # 订单是否是旗舰店商户订单
            if is_flagship:
                try:
                    mastermerchant = SlaveMerchant.objects.filter(slavemerchant__doctor_id=doctor.id) # 查找当前医生的主商户
                    if mastermerchant.exists():
                        mastermerchant = mastermerchant.first().mastermerchant
                        item_city_id = ServiceItem.objects.get(id=order.service_item_id).city_id
                        # 当前医生的主商户与订单商户相同且当前医生的城市与订单的sku城市相同时才可以验证订单
                        if mastermerchant.mastermerchant.doctor == order.service.doctor and doctor.hospital.city_id == item_city_id:
                            # 符合，将验证user置为旗舰店自身
                            user = mastermerchant.mastermerchant.doctor.user
                        # 不符合，抛错
                        else:
                            return gen(CODES.NO_PERMISSION)
                    else:
                        return gen(CODES.NO_PERMISSION)
                except:
                    return gen(CODES.NO_PERMISSION)
            else:
                return gen(CODES.NO_PERMISSION)

    if order.validated:
        return gen(CODES.ORDER_HAS_BEEN_VALIDATED)

    if order.status == ORDER_STATUS.REFUNDED:
        return gen(CODES.ORDER_HAS_BEEN_REFUNDED_CAN_NOT_VALIDATE)

    can_validate_groupbuy_status = (GROUPBUY_STATUS.NOT_GROUPBUY, GROUPBUY_STATUS.GROUPBUY_SUCCESSED)
    if order.groupbuy_status not in can_validate_groupbuy_status:
        return gen(CODES.ORDER_WRONG_STATUS_CODE)

    return gen(CODES.SUCCESS)


@bind_context('api/order/validate', login_required=True)
def api_order_validate(ctx, order_id, order_password, is_refund=False):
    """添加refound 为了确定来源,退款和验证都调用这个接口,只有退款才需要减少退款单小数点"""
    user = get_user_from_context(ctx)
    doctor = Doctor.objects.get(user=user)

    password = order_password.replace(' ', '')

    if password and order_id:
        try:
            order = Order.objects.get(id=order_id)
        except Order.DoesNotExist:
            return gen(CODES.ORDER_NOT_FOUND)

        if order.password != password:
            return gen(CODES.ORDER_VALIDATE_FAILED)

    elif password and len(password) == 10:
        try:
            order = Order.objects.get(password=password)
        except Order.DoesNotExist:
            return gen(CODES.ORDER_VALIDATE_FAILED)
    else:
        return gen(CODES.ORDER_VALIDATE_FAILED)

    if order.service.doctor != doctor:
        # 判断是否是商户验证医生订单
        if merchant_doctors(doctor.id) is None or (order.service.doctor not in merchant_doctors(doctor.id)):
            # 不是商户验证医生订单，但可能是子门店验证旗舰店订单
            from hippo.models.chain_hospital import MasterMerchant, SlaveMerchant
            is_flagship = order.service.doctor.is_store_classification  # 订单是否是旗舰店商户订单
            if is_flagship:
                try:
                    mastermerchant = SlaveMerchant.objects.filter(slavemerchant__doctor_id=doctor.id) # 查找当前医生的主商户
                    if mastermerchant.exists():
                        mastermerchant = mastermerchant.first().mastermerchant
                        item_city_id = ServiceItem.objects.get(id=order.service_item_id).city_id
                        # 当前医生的主商户与订单商户相同且当前医生的城市与订单的sku城市相同时才可以验证订单
                        if mastermerchant.mastermerchant.doctor == order.service.doctor and doctor.hospital.city_id == item_city_id:
                            # 符合，将验证user置为旗舰店自身
                            user = mastermerchant.mastermerchant.doctor.user
                        # 不符合，抛错
                        else:
                            return gen(CODES.NO_PERMISSION)
                    else:
                        return gen(CODES.NO_PERMISSION)
                except:
                    return gen(CODES.NO_PERMISSION)
            else:
                return gen(CODES.NO_PERMISSION)

    if order.validated:
        return gen(CODES.ORDER_HAS_BEEN_VALIDATED)

    if order.status == ORDER_STATUS.REFUNDED:
        return gen(CODES.ORDER_HAS_BEEN_REFUNDED_CAN_NOT_VALIDATE)

    can_validate_groupbuy_status = (GROUPBUY_STATUS.NOT_GROUPBUY, GROUPBUY_STATUS.GROUPBUY_SUCCESSED)
    if order.groupbuy_status not in can_validate_groupbuy_status:
        return gen(CODES.ORDER_WRONG_STATUS_CODE)

    validate_order(order, user, ctx)
    _send_notification_message(order, doctor)

    ss = DoctorNotifyService.get_service(order.service.doctor_id)
    ss.notify_verify_order(order)

    # 数据埋点, http://wiki.wanmeizhensuo.com/pages/viewpage.action?pageId=23892080
    ctx.logger.app(**validate_order_log(order_id=order.id))

    # 更改订单是否已读状态 用于订单列表红点的展示
    try:
        order.read = False
        order.save(update_fields=['read'])
    except:
        pass

    # add 医生版实时小红点数量
    if is_refund:  # 如果来自于退款验证则通知
        notify("refund/delete", doctor_id=doctor.id)
    return order.data()


def validate_order(order, operator, ctx=None):
    with transaction.atomic():
        try:
            RefundOrder.objects.get(order=order, status=REFUND_STATUS.PROCESSING)
            is_success = order.operate(operator.person, ORDER_OPERATION_TYPE.DOCTOR_REJECT, ORDER_OPERATION_ROLE.DOCTOR)
        except RefundOrder.DoesNotExist:
            is_success = order.operate(operator.person, ORDER_OPERATION_TYPE.VALIDATE, ORDER_OPERATION_ROLE.DOCTOR)

        if is_success:

            business_partener_id = None
            try:
                business_partener_id = order.service.doctor.business_partener_id
            except:
                logging_exception()

            order.servicesnapshot.business_partener_at_validate_id = business_partener_id
            order.servicesnapshot.save()

            order.operate_user = operator
            order.use_time = time_tool.get_current_time()
            order.validated = True
            order.validate_time = order.use_time
            order.save()

        try:
            if (
                            order.settlementitem and
                            order.settlementitem.settlement.installment and
                            order.settlementitem.settlement.installment.status == XIAOYING_INSTALLMENT_STATUS.AUDIT_SUCCESS
            ):
                from pay.tool.installment import confirm_loan
                confirm_loan(order.settlementitem.settlement.id)

        except Installment.DoesNotExist:
            pass

        except:
            logging_exception()
        try:
            ctx.rpc_remote['plutus/insurance/correct'](
                order_id=order.id,
                doctor_title=order.service.doctor.title,
                doctor_name=order.service.doctor.name,
                operation_name=order.service.name,
                office_name=order.service.doctor.department,
            ).unwrap()
        except RPCFaultException as e:
            if e.error == ERROR.INSURANCE_ORDER_NOT_EXIST:
                pass
            else:
                logging_exception()
        except:
            logging_exception()

        try:
            ctx.rpc_remote['plutus/installment/user/confirm'](person_id=None,
                                                              order_id=order.id).unwrap()
        except RPCFaultException as e:
            if e.error == ERROR.NO_INSTALLMENT:
                pass
            else:
                logging_exception()
        except:
            logging_exception()

        momo_param_info = get_momo_stat_log_info(order)
        if momo_param_info:

            json_text = json.dumps(momo_param_info)

            MomoStatLog(
                message_type=1,
                json_text=json_text
            ).save()

    remind_user_write_diary_after_order_validate.delay(order.id)

    order_manager.send_order_used_event(order)
    # add point,growth
    # send_event_task.delay(user_id=order.user_id, event_type=EventType.BUYSERVICE, related_item=order.service_id)



def _send_notification_message(order, doctor=None):
    # 订单验证，推送：有； 通知：有； 短信：有
    validate_time = order.validate_time.strftime('%Y/%m/%d %H:%M')

    city_and_doctor_name = order.service.doctor.name
    if order.service.doctor.hospital and order.service.doctor.hospital.city:
        city_and_doctor_name = u'{}@{}'.format(order.service.doctor.hospital.city.name,
                                               order.service.doctor.name)

    notification_content = (
        u'您购买的【{}】（订单号：{}）已于{}消费成功！'
        u'现在登录更美APP记录你的变美历程，还能获得返现奖励，快来分享吧~如有任何疑问，请拨打更美客服：{}转6666'
    )
    notification_content = notification_content.format(
        city_and_doctor_name, order.id, validate_time, PhoneService.get_phone_prefix('6666')
    )
    send_notification(
        order.user.id,
        u'订单已消费',
        notification_content,
        settings.PUSH_URL_PROTOCOL['ORDER_DETAIL'].format(id=order.id)
    )

    try:
        installments = get_rpc_remote_invoker()['plutus/installment/installment/status'](
            person_id='',
            order_list=[order.id]
        ).unwrap()
        if len(installments) and installments[0]['status'] in [RM_STATUS.GRANT, RM_STATUS.REPAY]:
            send_sms(
                doctor.phone,
                25,
                [
                    {'hospital': doctor.name},
                    {'time': datetime.datetime.now().strftime('%Y-%m-%d')},
                    {'name': installments[0].get('username')},
                    {'order': order.id},
                    {'price': order.hospital_payment},
                ]
            )
    except:
        logging_exception()


@bind_context("api/order/cash_back", login_required=True)
def order_cash_back(ctx, order_id):
    """
    申请返现
    :param ctx:
    :param order_id:
    :return:

    NOTE: 申请返现，推送：无； 通知：无； 短信：无
    """
    try:
        order = Order.objects.get(pk=order_id)
    except Order.DoesNotExist:
        return gen(CODES.ORDER_NOT_FOUND)

    user = get_user_from_context(ctx)
    if order.user != user:
        return gen(CODES.NO_PERMISSION)
    if order.status in ORDER_STATUS_USED:
        if order.payment == 0:
            return gen(CODES.FREE_SERVICE_NOT_SUPPORT_CASHBACK)

        if order.cash_back_status == CASH_BACK_STATUS.WAIT:
            return gen(CODES.ORDER_CASH_BACK_STATUS_WAIT)

        if order.cash_back_status == CASH_BACK_STATUS.APPLY:
            if order.cash_back_failure_time:
                df_time = datetime.datetime.now() - order.cash_back_failure_time
                if df_time.days < 3:
                    return gen(CODES.ORDER_CASH_BACK_FAILURE_TIME)

            diary = order.diary

            if order.cash_back_fee:
                need_diary_topics_num = order.cash_back_fee // 50
            else:
                need_diary_topics_num = order.service_price * 0.05 // 50

            num = 0
            if diary:
                num = 0
                for topic in diary.topics.all():
                    if len(topic.answer) >= 20:
                        num += 1

            if num < need_diary_topics_num:
                return gen(CODES.ORDER_NEED_DIARY_TOPICS_NUM)

            order.cash_back_status = CASH_BACK_STATUS.WAIT
            order.save(update_fields=['cash_back_status'])

    else:
        return gen(CODES.OPERATION_NOT_SUPPORTED)

    return None


@bind_context('api/order/list/all', login_required=True)
@list_interface(offset_name='start_num', limit_name='count')
def order_list(ctx, start_num=0, count=10):
    user = get_user_from_context(ctx)

    unpaid_settlement_status = (SETTLEMENT_STATUS.NOT_PAID, SETTLEMENT_STATUS.PAYING)

    unpaid_settlement_count = Settlement.objects.filter(
        person=user.person, status__in=unpaid_settlement_status
    ).count()

    unpaid_settlement_data = list(Settlement.objects.filter(
        person=user.person, status__in=unpaid_settlement_status
    ).order_by('-created_at')[start_num:start_num + count])

    now = datetime.datetime.now()
    result = _get_settlement_list_items(unpaid_settlement_data, True, now)

    if len(unpaid_settlement_data) < count:
        order_start = start_num - unpaid_settlement_count if start_num - unpaid_settlement_count >= 0 else 0
        order_count = count - len(unpaid_settlement_data)

        orders = Order.objects.filter(Q(user=user) & ~Q(status=ORDER_STATUS.CANCEL))

        filter_refunded = Q(status=ORDER_STATUS.REFUNDED) | Q(status=ORDER_STATUS.WAIT_REFUNDED)
        filter_paid = Q(status=ORDER_STATUS.PAID)
        filter_used = Q(status=ORDER_STATUS.USED) \
                      | Q(status=ORDER_STATUS.SETTLED) \
                      | Q(status=ORDER_STATUS.AUDITING) \
                      | Q(status=ORDER_STATUS.SETTLING)

        full_filter = filter_paid | filter_used | filter_refunded
        orders = orders.filter(full_filter).select_related('service__doctor').order_by('-created_time')

        orders_list = list(orders[order_start:order_start + order_count])
        order_ids = [order.id for order in orders_list]
        order_id_to_groupbuy_team_info = get_groupbuy_team_info_by_order_ids(order_ids)

        order_result = _get_order_list_items(orders_list, order_id_to_groupbuy_team_info)

        for od in order_result:
            order_status_text = od.pop('order_status', '')
            od['order_status'] = {
                'text': order_status_text,
                'status': od['status_code'],
            }
            hospital_pay_status = od.pop('hospital_pay_status', None)
            od['hospital_pay_status'] = {
                'text': hospital_pay_status,
                'status': od.get('hospital_pay_status_code')
            }

            installment = od.pop('installment', None)
            create_time = od['create_time']

            result.append({'type': 0, 'order': od,
                           'create_time': create_time,
                           'installment': installment})

    return result


@bind_context('api/order/list', login_required=True)
@list_interface(offset_name='start_num', limit_name='count',
                element_model=Order)
def order_list(ctx, status='', start_num=0, count=10, unpaid_order_count=None,
               merge_same_sku_orders=False, hidden_cancel_settlement=False):
    """ 全部订单 未付款订单
    """
    user = get_user_from_context(ctx)

    #  hidden_cancel_settlement 这个只是为了兼容旧版本行为

    # 查询未付款和支付中的订单
    if status != ORDER_STATUS.NOT_PAID or hidden_cancel_settlement:
        settlements = Settlement.objects.filter(person=user.person).exclude(
            status__in=(
                SETTLEMENT_STATUS.CANCEL,
                SETTLEMENT_STATUS.PAID)).order_by('-created_at')
    else:
        settlements = Settlement.objects.filter(person=user.person).exclude(
            status__in=(
                SETTLEMENT_STATUS.PAID)).order_by('status', '-created_at')

    if unpaid_order_count:
        settlements = settlements.annotate(
            num_items=Count('items')).filter(
            num_items=unpaid_order_count)

    now = datetime.datetime.now()

    settlements_list = list(settlements[start_num:start_num + count])
    result = _get_settlement_list_items(settlements_list, merge_same_sku_orders, now)

    if len(result) >= count:
        return result

    if status != ORDER_STATUS.NOT_PAID:
        all_settlement_count = settlements.count()

        order_start_num = start_num - all_settlement_count if start_num >= all_settlement_count else 0
        order_count = count - len(result)

        exclude_status = (ORDER_STATUS.NOT_PAID, ORDER_STATUS.CANCEL, ORDER_STATUS.PAYING)  # 付款中的订单被排除了

        #  之前的直接order join settlementitem然后orderby的写法在生产环境数据会被少量用户触发超过5秒以上的慢查询
        #  主要是因为这些用户的订单数量很多以及针对order表的orderby导致的
        #  经过排查生产环境数据中一个用户的没有settlement的订单数量最大不超过50（而且已经不会增长了）
        #  所以这里通过二次查询先过滤出order id，在内存中进行必要处理之后再获取对应的数据

        order_not_settlementitem_data = Order.objects.filter(user=user, settlementitem__isnull=True).exclude(
            status__in=exclude_status
        ).values_list('id', 'created_time')

        ordered_order_ids = [oid for oid, ct in sorted(
            order_not_settlementitem_data, key=lambda (oid, ct): ct, reverse=True
        )][order_start_num:order_start_num + order_count]

        order_id_to_order = {o.id: o for o in Order.objects.filter(id__in=ordered_order_ids)}

        orders = [order_id_to_order[oid] for oid in ordered_order_ids if oid in order_id_to_order]

        if orders:
            for order in orders:
                service_snapshot = json.loads(order.service_snapshot)

                order_data = {
                    'service_id': order.service.id,
                    'order_id': order.id,
                    'create_time': get_timestamp(order.created_time),
                    'service_image': (order.service.images.values_list('image_url', flat=True)[0]
                                      if order.service.images.exists()
                                      else u'http://www.wanmeizhensuo.com/zhengxing/media/images/2014/03/13/4969c3d163cc.jpg'
                                      ),
                    'service_name': service_snapshot.get('name', ''),
                    'hint': u'¥ {}'.format(order.payment),
                    'purchased': False,
                    'image': get_full_path(service_snapshot.get('image_header', '')),
                    'order_status': {
                        'text': ORDER_STATUS.getDesc(order.status_code),
                        'status': order.status_code,
                    },
                    'total_price': order.total_price,
                    'is_can_be_sold': order.service.is_can_be_sold(),
                    'is_seckill': order.is_seckill_order,
                    'accept_reservation': order.service.doctor.accept_reserve,
                    'reservation_status': Reservation.order_latest_status(order.id),
                    'reservation_id': '',
                    'options_desc': order.order_multiattribute,
                    'has_comment': order.has_comment(),
                    'show_comment': order.show_comment(),
                    'diary_id': order.diary_id(),
                    'settlement_id': order.settlement_id,
                    'is_insurance': True if order.get_insurance_info() else False,
                    'is_support_renmai': order.is_support_renmai(),
                    'is_support_yirendai': order.is_support_renmai(1000),
                    'renmai_status': order.get_renmai_status(ctx, user.person.id.hex),
                    'show_reserve': order.show_reserve(),
                    'has_service_comment': order.has_service_comment(),
                    'hospital_payment': order.hospital_payment,
                    'groupbuy_status': order.groupbuy_status,
                }
                if order_data['renmai_status'] != RM_STATUS.NONE and not order.is_refund():
                    order_data['is_support_renmai'] = True
                    order_data['is_support_yirendai'] = True

                if order_data['reservation_status']:
                    order_data['reservation_id'] = Reservation.objects.filter(order_id=order.id).order_by('-created_time')[
                        0].id
                if order.status_code in (ORDER_STATUS.REFUNDED, ORDER_STATUS.WAIT_REFUNDED):
                    try:
                        order_data['refund_order_id'] = order.refund.id
                    except Order.refund.RelatedObjectDoesNotExist:
                        order_data['refund_order_id'] = ''

                result.append({'type': 0, 'order': order_data,
                               'create_time': get_timestamp(order.created_time),
                               'installment': get_installment_from_order(order)})

    return result


def _get_settlement_list_items(settlements_list, merge_same_sku_orders, now):
    result = []
    for settlement in settlements_list:
        gift_exchange = (
            True
            if settlement.service_payment_type == SERVICE_PAYMENT_TYPE.EXCHANGE_GIFT
            else False)
        items = settlement.items.all()
        settlement_info = []

        service_item_id_to_order_count = {}
        total_pre_payment_price = 0
        total_hospital_payment = 0

        for item in items:
            order = item.order
            siid = order.service_item_id

            total_pre_payment_price += order.payment
            if type(order.hospital_payment) != unicode:
                total_hospital_payment += order.hospital_payment

            if siid and siid in service_item_id_to_order_count and merge_same_sku_orders:
                service_item_id_to_order_count[siid] += 1
            else:
                if siid:
                    service_item_id_to_order_count[siid] = 1

                order_info = {
                    'id': order.service_id,
                    'image': order.servicesnapshot.image_header,
                    'name': order.servicesnapshot.name,
                    'order_id': order.id,
                    'options_desc': order.order_multiattribute,
                    'pre_payment_price': int(order.servicesnapshot.pre_payment_price),
                    'settlement_id': settlement.id,
                    'is_insurance': True if order.get_insurance_info() else False,
                    'service_item_id': siid or None,  # 这个字段暂时没有计划输出
                    'is_groupbuy': order.groupbuy_status != GROUPBUY_STATUS.NOT_GROUPBUY,
                }
                settlement_info.append(order_info)

        for order in settlement_info:
            siid = order.get('service_item_id', None)
            number = 1
            if siid in service_item_id_to_order_count:
                number = service_item_id_to_order_count[siid]
            order['number'] = number
            order.pop('service_item_id', None)

        timeout = settings.SETTLEMENT_HARD_TIMEOUT if settlement.status == SETTLEMENT_STATUS.PAYING else settings.SETTLEMENT_SOFT_TIMEOUT
        pay_end_time = settlement.created_at + datetime.timedelta(seconds=timeout)
        pay_countdown = int((pay_end_time - now).total_seconds()) if pay_end_time > now else 0

        info = {
            'type': 1,
            'settlement_price': settlement.payment,
            'total_pre_payment_price': total_pre_payment_price,
            'total_hospital_payment': total_hospital_payment,
            'gift_exchange': gift_exchange,
            'settlement': settlement_info,
            'settlement_id': settlement.id,
            'settlement_name': settlement.name,
            'settlement_status': settlement.status,
            'settlement_create_time': get_timestamp(settlement.created_at),
            'settlement_pay_countdown': pay_countdown,
            'installment': get_installment_from_settlement(settlement),
        }
        result.append(info)
    return result


def get_installment_from_order(order):
    installment = None
    try:
        installment = get_installment_from_settlement(order.settlementitem.settlement)
    except SettlementItem.DoesNotExist:
        installment = None
    except:
        logging_exception()
    return installment


def get_installment_from_settlement(settlement):
    installment = None
    try:
        if settlement.installment.status == XIAOYING_INSTALLMENT_STATUS.REFUND:
            return None
        installment = {
            'status': str(settlement.installment.status),
            'status_desc': settings.INSTALLMENT_STATUS.get(str(settlement.installment.status), u'分期审核中'),
        }
    except Installment.DoesNotExist:
        installment = None
    except:
        logging_exception()
    return installment


def get_installment_from_hospital_pay(order):
    installment = False
    try:
        hospital = order.servicesnapshot.doctor.hospital
        period_hospital = PeriodHospital.objects.get(hospital=hospital)
        if period_hospital.is_online and order.service.is_stage:
            installment = True
    except:
        pass
    return installment


@bind_context("api/order/bind_diary", login_required=True)
def order_bind_diary(ctx, status):
    user = get_user_from_context(ctx)
    result = []
    orders = Order.objects.filter(user=user, status__in=status, diary__isnull=True).order_by('-use_time')
    if not orders:
        return {'result': result}
    result = [order.simple_data() for order in orders]
    return {'result': result}


@bind_context("api/order/get_diary")
def get_order_diary(ctx, order_id):
    """
    获取订单关联的日记本。
    update in v 7.7.25 若订单关联的日记本未创建，则自动创建一个日记本
    :param ctx:
    :param order_id:
    :return:
    """
    try:
        order = Order.objects.get(pk=order_id)
        if not getattr(order, 'diary', None):
            order.create_diary()

        data = order.diary.simple_data() if getattr(order, 'diary', None) else {}
        topics = order.diary.topics_data() if getattr(order, 'diary', None) else []
        return {"diary": data, "topics": topics}
    except Order.DoesNotExist:
        return gen(CODES.ORDER_NOT_FOUND)


@bind_context("api/order/cancle_refund", login_required=True)
def order_cancel_refund(ctx, order_id):
    try:
        order = Order.objects.get(pk=order_id)
    except Order.DoesNotExist:
        return gen(CODES.ORDER_NOT_FOUND)

    user = get_user_from_context(ctx)
    if order.user != user:
        return gen(CODES.NO_PERMISSION)

    order.operate(user.person, ORDER_OPERATION_TYPE.CANCEL_REFUND, ORDER_OPERATION_ROLE.USER)
    doctor_id = order.service.doctor_id
    notify('refund/delete', doctor_id=doctor_id)

    ss = DoctorNotifyService.get_service(order.service.doctor_id)
    ss.notify_user_cancel_refund(order.refund)


@bind_context("api/refund_order/detail", login_required=True)
def refund_order_detail(ctx, refund_order_id):
    try:
        refund_order = RefundOrder.objects.get(pk=refund_order_id)
    except RefundOrder.DoesNotExist:
        return gen(CODES.ORDER_REFUDN_NOT_FOUND)
    user = get_user_from_context(ctx)
    if refund_order.order.user != user:
        return gen(CODES.NO_PERMISSION)

    payment_order = PaymentOrder.objects.get(orders=refund_order.order_id)
    third_party_trade_no = payment_order.transaction_id

    payment_channel = new_order_tool.get_channel_to_payment_channel(payment_order.channel)

    # 只显示最后一次申请退款时间, 认为申请退款时间肯定排在第一个
    operations = []
    record = {
        'operate_at': refund_order.lastest_apply_refund.strftime('%Y-%m-%d %H:%M:%S'),
        'optype': ORDER_OPERATION_TYPE.APPLY_REFUND
    }
    operations.append(record)
    for operation in OrderOperation.objects.filter(order=refund_order.order).exclude(
            optype=ORDER_OPERATION_TYPE.APPLY_REFUND).order_by('operate_at'):
        record = {
            'operate_at': operation.operate_at.strftime('%Y-%m-%d %H:%M:%S'),
            'optype': operation.optype
        }
        operations.append(record)

    refund_value_info = refund_manager.refund_value_info(refund_order)
    info = {
        'payment_channel': payment_channel,
        'refund_value_info': refund_value_info,
        'operations': operations,
        'third_party_order_no': third_party_trade_no,
        'pay_mode': refund_order.data['pay_mode']
    }
    return info


@bind_context('api/order/hide_badge', login_required=True)
def hide_badge(ctx, status):
    user = get_user_from_context(ctx)
    if status == ORDER_STATUS.PAID:
        order_filter = Q(status=ORDER_STATUS.PAID)
    elif status == ORDER_STATUS.USED:
        order_filter = Q(status=ORDER_STATUS.USED)
    else:
        return gen(CODES.ORDER_WRONG_STATUS_CODE)
    orders = user.order_set.filter(order_filter)

    to_update_order_ids = list(orders.filter(read=False).values_list('id', flat=True))
    if to_update_order_ids:
        set_order_is_read_by_order_ids.delay(to_update_order_ids)

    return {
        'updated': True,
    }


@bind_context('api/order/insurance_info', login_required=True)
def insurance_info(ctx, order_id):
    user = get_user_from_context(ctx)
    order = Order.objects.get(id=order_id, user=user)
    return order.insurance_info()


@bind_context('api/order/user_info')
def order_user_info(ctx, order_id):
    # 脚本使用一次 6.7.5 上线完成可删除
    order = Order.objects.get(id=order_id)
    return {
        'user_id': order.user_id,
        'service_name': order.servicesnapshot.name,
    }


@bind('api/order/by_id')
def order_by_id(id):
    # NOTE: @guojiahua
    model = Order
    try:
        m = model.objects.get(id=id)
        if hasattr(m, 'to_dict'):
            r = m.to_dict()
        else:
            r = to_dict(m)
        r['service_snapshot'] = m.service_snapshot  # TODO CR: 临时 hack  防止被 escape
        r["service_is_operation"] = m.service.is_operation  # 订单关联美购是否是手术类
        if hasattr(m, 'cashback'):
            r['cashback_id'] = m.cashback.id
            r['cashback_time'] = str(m.cashback.created_at)
        else:
            r['cashback_id'] = None
            r['cashback_time'] = None
        return r
    except model.DoesNotExist:
        return gen(CODES.ORDER_NOT_FOUND)


@bind('api/order/service_snapshot')
def order_service_snapshot(id):
    # NOTE: @guojiahua
    model = Order
    try:
        m = model.objects.get(id=id)
    except model.DoesNotExist:
        return gen(CODES.ORDER_NOT_FOUND)
    else:
        return m.service_snapshot


@bind_context('api/order/get_data_for_update_diary', login_required=True)
def get_data_for_update_diary(ctx, order_id):
    """get doctor, organization and price info for update diary.

    @:param
        order_id

    @:return
        {
            "order_id":,
            "doctor_id":,
            "doctor_name":,
            "hospital_id":,
            "hospital_name":,
            "price":,
            "service_id":,
            "service_name",
            "is_operation",
            "city_tag_id",
        }

    NOTE: @guojiahua
    """
    user = get_user_from_context(ctx)

    try:
        order = Order.objects.get(id=order_id, user_id=user.id)
        order_comment = ServiceComment.objects.filter(order_id=order.id).first()
    except Order.DoesNotExist:
        return gen(CODES.ORDER_NOT_FOUND)

    ss = json.loads(order.service_snapshot)
    data = {
        "order_id": order.id,
        "doctor_name": order.service.doctor.name,
        "doctor_id": order.service.doctor.id,
        "hospital_name": order.service.doctor.hospital.name,
        "hospital_id": order.service.doctor.hospital.id,
        "service_id": ss.get("id"),
        "service_name": ss.get("name"),
        "price": int(ss.get('total_price')),
        "is_operation": order.service.is_operation,
        "city_tag_id": order.service.doctor.hospital.city.tag.id,
        'operation_effect': order_comment.operation_effect if order_comment else 0,
        'doctor_attitude': order_comment.doctor_attitude if order_comment else 0,
        'hospital_env': order_comment.hospital_env if order_comment else 0,
        'rating': order_comment.rating if order_comment else 0,
    }
    return data


@bind_context('api/order/create_cashback_order', login_required=True)
def create_cashback_order(ctx, order_id):
    # NOTE: @guojiahua
    user = get_user_from_context(ctx)

    order = Order.objects.get(id=order_id)
    if order.user_id != user.id:
        return gen(CODES.NO_PERMISSION)

    CashBackOrder.create(order)


@bind("api/order/apollo_get_order_info")
def get_order_info_for_apollo(ids):
    orders = Order.objects.filter(id__in=ids)
    result = {}
    for order in orders:
        phone = order.phone_crypt
        nick_name = filter_user_nick_name(order.user)
        if len(nick_name) == 2:
            nick_name = '*' + nick_name[-1:]
        if len(nick_name) > 2:
            nick_name = nick_name[:1] + '*' + nick_name[-1:]
        result[str(order.id)] = {
            "username": nick_name,
            "phone": phone[:2] + '*' * 7 + phone[9:]
        }
    return result


@bind("api/order/apollo_get_order_detail")
def get_order_detail_for_apollo(ids):
    orders = Order.objects.filter(id__in=ids)
    result = {}
    for order in orders:
        phone = order.phone_crypt
        nick_name = filter_user_nick_name(order.user)
        if len(nick_name) == 2:
            nick_name = '*' + nick_name[-1:]
        if len(nick_name) > 2:
            nick_name = nick_name[:1] + '*' + nick_name[-1:]

        result[str(order.id)] = {
            "username": nick_name,
            "service_name": order.service.name,
            "image_url": order.service.image_header,
            "phone": phone[:3] + '*' * 4 + phone[7:],
            'is_special_sales': True if order.service.service_type == SERVICE_SELL_TYPE.FENGXIANGGOU else False,
        }
    return result


@bind("api/order/get_order_info")
def get_order_object_info(order_id):
    """
    仅为 hera后台 提供订单信息,其他方法不要调用此接口!!!
    :param order_id: 订单id
    :return:
    """
    try:
        order = Order.objects.get(id=order_id)
        try:
            order_city_tag_id = order.service.doctor.hospital.city.tag.id
        except Exception:
            order_city_tag_id = None
        data = {
            "city_tag_id": order_city_tag_id,
        }
        return data
    except Order.DoesNotExist:
        return gen(CODES.ORDER_NOT_FOUND)


@bind("api/order/list_for_momo")
@list_interface(offset_name='offset', limit_name='count', element_model=Order)
def get_order_list_for_momo(user_name, start_num=0, count=20):
    """
    外部合作，陌陌订单数据接口
    :param user_name:
    :param start_num:
    :param count:
    :return:
    """
    result = {
        "orders": []
    }

    user = get_user_by_username(user_name)
    if not user:
        return result

    orders = Order.objects.filter(user=user, source=ORDER_SOURCE.MOMO).order_by("-created_time")[start_num: start_num + count]
    order_ids = list(orders.values_list("id", flat=True))

    services = ServiceSnapshot.objects.filter(order_id__in=order_ids).values(
        "order_id", "service_id", "name", "image_header", "items", "pre_payment_price", "is_multiattribute")
    order_service_dict = {info["order_id"]: info for info in services}

    settlement_info = SettlementItem.objects.filter(order_id__in=order_ids).values_list("order_id", "settlement_id")
    settlement_dict = dict()
    for order_id, settlement_id in settlement_info:
        if order_id not in settlement_dict:
            settlement_dict[order_id] = list()
        settlement_dict[order_id].append(settlement_id)

    for order in orders:
        service = order_service_dict.get(order.id, {})
        settlement = settlement_dict.get(order.id, [])
        _data = {
            "service_id": service.get("service_id", 0),
            "service_name": service.get("name", ""),
            "service_image": service.get("image_header", ""),
            "status_code": order.status,  # 订单状态，归类交于m站处理
            "order_id": order.id,
            "settlement_id": settlement and settlement[0] or 0,
            "service_items": service.get("items", ""),
            "options_desc": order.order_multiattribute,  # 增加字段，项目
            "service_is_multiattribute": service.get("is_multiattribute", False),
            "hint": order.payment,
            "total_price": order.total_price,  # 合计总价
            "hospital_payment": order.hospital_payment,  # 到医院支付的尾款
            "service_price": service.get("pre_payment_price", 0),
            "payment_time": get_timestamp_epoch(order.pay_time),  # 付款成功的时间
            "created_time": get_timestamp(order.created_time),  # 订单创建时间
        }
        result["orders"].append(_data)

    return result


@bind_context("api/order/get_user_all_order_ids", login_required=True)
def get_user_all_order_ids(ctx):
    """
    获取用户的 某种状态下的订单,目前主要是用于 日记本订单列表数据的过滤！！！
    :param ctx:
    :return:
    """
    user = get_user_from_context(ctx)
    order_filter_status = [
        ORDER_STATUS.PAID,
        ORDER_STATUS.USED,
        ORDER_STATUS.SETTLED,
        ORDER_STATUS.AUDITING,
        ORDER_STATUS.SETTLING
    ]

    return  {
        "all_order_ids": list(Order.objects.filter(user_id=user.id, status__in=order_filter_status).values_list("id", flat=True)),
    }


@bind_context('api/order/list_for_create_diary', login_required=True)
@list_interface(offset_name='offset', limit_name='count', element_model=Order)
def get_user_order_list_for_create_diary(ctx, offset=0, count=10, exclude_ids=None, only_need_order_count=False,
                                         order_id=None, display_single_data_only=False):
    """
    获取日记本创建选择的订单列表页
    :param ctx:
    :param offset: start num
    :param count:  count
    :param order_id: 订单id
    :param exclude_ids: 要排除的订单id,list
    :param only_need_order_count: 仅需要订单数, bool
    :param display_single_data_only: 仅展示单条数据
    :return:
    """
    result = {
        "order_count": 0,
        "order_list": [],
    }
    user = get_user_from_context(ctx)
    can_create_diary_status = [
        ORDER_STATUS.PAID,
        ORDER_STATUS.USED,
        ORDER_STATUS.SETTLED,
        ORDER_STATUS.AUDITING,
        ORDER_STATUS.SETTLING
    ]
    exclude_order_ids = exclude_ids if exclude_ids else []
    query = Q(user_id=user.id, status__in=can_create_diary_status)

    if order_id:  # 关于单个订单数据获取规则
        if order_id in exclude_order_ids:
            exclude_order_ids.remove(order_id)

        if display_single_data_only:
            query &= Q(id=order_id)

    orders = Order.objects.filter(query).exclude(id__in=exclude_order_ids).order_by("-created_time", "-id").values(
        "id", "status", "created_time", "pay_time", "validate_time", "service_id")

    if only_need_order_count:
        result["order_count"] = orders.count()
        return result
    else:
        orders = orders[offset: offset + count]

    service_ids = set(order_item["service_id"] for order_item in orders)
    # 评价部分
    order_ids = set(order_item["id"] for order_item in orders)
    order_comments_objs = ServiceComment.objects.filter(
        order_id__in=order_ids).only("order_id", "operation_effect", "doctor_attitude", "hospital_env", "rating")

    order_comments_dic = {order_comment.order_id: {
        "operation_effect_rating": order_comment.operation_effect,
        "doctor_attitude_rating": order_comment.doctor_attitude,
        "hospital_env_rating": order_comment.hospital_env,
        "rating": order_comment.rating,
    } for order_comment in order_comments_objs}

    # 处理标签
    service_tags = ServiceTag.objects.filter(service_id__in=service_ids).values("id", "service_id", "tag_id", "tag__name", "tag__tag_type")
    service_tag_dict = defaultdict(list)
    for item in service_tags:
        service_tag_dict[item["service_id"]].append({
            "id":item["id"], "tag_id": item["tag_id"], "tag_name": item["tag__name"], "tag_type": item["tag__tag_type"]})

    # 处理美购数据
    service_list = Service.objects.filter(id__in=service_ids).values("id", "image_header", "name", "doctor_id")
    service_dict = defaultdict(dict)  # 美购数据
    service_doctor_ids = set()  # 医生id

    for service_item in service_list:
        service_item["service_id"] = service_item.pop("id", 0)
        service_item["service_name"] = service_item.pop("name", "")
        _tags = sorted(service_tag_dict.get(service_item["service_id"], []), key=lambda st: st.pop("id", 0))
        service_item["tags"] = list(filter(lambda t: t["tag_type"] in [TAG_TYPE.BODY_PART_SUB_ITEM, TAG_TYPE.ITEM_WIKI], _tags))[:1]
        service_dict[service_item["service_id"]].update(service_item)

        service_doctor_ids.add(service_item["doctor_id"])

    # 医生、医院数据
    doctor_list = Doctor.objects.filter(id__in=list(service_doctor_ids)).values("id", "name", "hospital_id", "hospital__name")
    doctor_info_dict = defaultdict(dict)

    for doctor_item in doctor_list:
        doctor_id = doctor_item.pop("id", "")
        doctor_item["doctor_name"] = doctor_item.pop("name", "")
        doctor_item["hospital_name"] = doctor_item.pop("hospital__name", "")

        doctor_info_dict[doctor_id] = doctor_item

    # 订单列表数据整合
    for order_item in orders:
        order_comment = order_comments_dic.get(order_item.get("id", ''), {})  # 用评分 去判断 是否展示 去评价，目前先不返回评分的数据
        _data = {
            "order_id": order_item.get("id", ''),
            "show_comment": False if order_comment else True,
            "status": order_item.get("status", None),
            "created_time": get_timestamp_epoch(order_item.get("created_time", None)),  # 创建时间
            "pay_time": get_timestamp_epoch(order_item.get("pay_time", None)),  # 支付时间
            "validate_time": get_timestamp_epoch(order_item.get("validate_time", None)),  # 验证时间
            "service_id": 0,
            "service_name": "",
            "image_header": "",
            "tags": [],
            "doctor_id": "",
            "doctor_name": "",
            "hospital_id": "",
            "hospital_name": "",
            # "rating": 0,
            # "operation_effect_rating": 0,
            # "doctor_attitude_rating": 0,
            # "hospital_env_rating": 0,
        }
        _service_info_dict = service_dict.get(order_item.get("service_id", 0), {})
        _data.update(_service_info_dict)

        _doctor_dic = doctor_info_dict.get(_service_info_dict.get("doctor_id", ""), {})
        _data.update(_doctor_dic)
        # _data.update(order_comment)  #

        result["order_list"].append(_data)

    return result


@bind('api/get_user_order_ids')
@list_interface(
    offset_name='offset', limit_name='limit',
    element_model=Order
)
def get_user_order_ids(user_id, status, offset=0, limit=5, hospital_id=None, doctor_id=None):
    user = get_user_by_id(user_id)
    if not user:
        gen(ERROR.USER_NOT_FOUND)
    query_q = Q(user=user, status__in=status)
    if hospital_id is not None:
        from hippo.models import Doctor
        doctor_ids = list(Doctor.objects.filter(hospital_id=hospital_id).values_list('id', flat=True))
        query_q &= Q(servicesnapshot__doctor_id__in=doctor_ids)
    if doctor_id is not None:
        query_q &= Q(servicesnapshot__doctor_id=doctor_id)
    order_ids = Order.objects.using(settings.SLAVE_DB_NAME).\
                    filter(query_q).values_list('id', flat=True)
    return list(order_ids)


@bind('api/get_order_detail_by_ids')
def get_order_detail_by_ids(ids, with_fields=None, force_primary=False):
    NOT_VIEW_TYPE = [REFUND_STATUS.CANCLED, REFUND_STATUS.REFUNDING, REFUND_STATUS.REFUND_APPLY_SELLER_TIMEOUT]
    if with_fields is None:
        with_fields = ['meta']
    orders = Order.objects.filter(id__in=ids).order_by('-created_time').prefetch_related('servicesnapshot')
    if not force_primary:
        orders = orders.using(settings.SLAVE_DB_NAME)
    result = list()
    for order in orders:
        # 不返回订单在退款中且退款单状态在医生后台不展示的订单
        if order.status == ORDER_STATUS.WAIT_REFUNDED and order.refund.status in NOT_VIEW_TYPE:
            pass
        else:
            result.append(order.format_detail(with_fields=with_fields))
    return result


@bind_context('api/order/is_exist',login_required=True)
def user_order_exist(ctx):
    '''
        查看用户是否有订单
    '''
    user =get_user_from_context(ctx)

    can_create_topic_status = [
        ORDER_STATUS.PAID,
        ORDER_STATUS.USED,
        ORDER_STATUS.SETTLED
    ]
    result=Order.objects.filter(user=user,status__in=can_create_topic_status,order_has_service_comment=False).exists()
    return result


@bind_context('api/order/is_exist_by_device')
def user_order_exist_by_device(ctx, device_id):
    '''
        查看用户是否有订单 通过设备判断
    '''
    user_ids = Device.get_user_ids_by_device(device_id)
    if not user_ids:
        return False
    can_create_topic_status = [
        ORDER_STATUS.PAID,
        ORDER_STATUS.USED,
        ORDER_STATUS.SETTLED
    ]
    return Order.objects.filter(user_id__in=user_ids, status__in=can_create_topic_status, order_has_service_comment=False).exists()
