# coding=utf8
from __future__ import unicode_literals, absolute_import, print_function

import copy
import json
import datetime
import math
from django.conf import settings
from django.db import transaction
from django.db import models, IntegrityError
from django.contrib.auth.models import User
from gm_types.plutus import RM_STATUS
from gm_types.trade import SETTLEMENT_PAY_MODE
from gm_types.gaia import RESERVATION_STATUS, DOCTOR_REFUND_TYPE, USERCALL_STATUS,BWC_ORDER_NOTIFY_METHOD
from gm_types.doctor import HOSPITAL_MEDICAL_TYPE
from hippo.models.hospital import Hospital_Extra
from api.models.hospital import PeriodHospital
from .service import Service, ServiceItem
from .doctor import Doctor
from .person import Person
from .tickets import Tickets
from .types import (
    PAYMENT_CHANNEL,
    PAYMENT_TYPE,
    ORDER_STATUS,
    NEW_CASH_BACK_STATUS,
    CASH_BACK_STATUS,
    ORDER_SOURCE,
    SERVICE_MODE,
    STAGE_STATUS,
    ORDER_DURATION,
    ORDER_STATUS_DETAILS,
    REFUND_STATUS,
    ORDER_OPERATION_TYPE,
    ORDER_OPERATION_ROLE,
    TAG_TYPE,
    PROBLEM_REVIEW_STATUS_CHOICES,
)
from api.tool.image_utils import get_full_path
from api.tool.log_tool import logging_exception, info_logger
from api.tool.datetime_tool import get_timestamp, get_timestamp_or_none
from api.tool.user_tool import filter_user_nick_name
from api.business.order import order_operate_map

from rpc.tool import time_tool
from rpc.tool.random_tool import generate_refund_order_id, generate_cashback_order_id
from rpc.all import get_rpc_remote_invoker
from services.custom_phone_service import PhoneService

from gm_types.gaia import COUPON_TYPES, OPERATION_LOCATION_TYPE, ORDER_CATEGORY, GROUPBUY_STATUS
from gm_types.gaia import SETTLEMENT_STATUS, ACTIVITY_TYPE_ENUM, RESERVATION_TYPE, ORDER_BUTTON_TYPE, SERVICE_SELL_TYPE
from gm_types.trade import INSURANCE_TYPE
from gm_crypto.fields import EncryptedCharField

ORDER_STATUS_USED = (ORDER_STATUS.USED, ORDER_STATUS.SETTLED,
                     ORDER_STATUS.AUDITING, ORDER_STATUS.SETTLING)


class Order(models.Model):
    class Meta:
        verbose_name = u'订单信息'
        verbose_name_plural = u'订单信息'
        db_table = 'api_order'
        app_label = 'api'

    id = models.CharField(max_length=12, verbose_name=u'订单号', primary_key=True)
    password = models.CharField(max_length=12, verbose_name=u'订单密码', null=True, blank=True, db_index=True)
    status = models.CharField(max_length=20, choices=ORDER_STATUS, default=ORDER_STATUS.NOT_PAID, null=False,
                              verbose_name=u'订单状态')

    payment = models.FloatField(verbose_name=u'付款额')
    points = models.IntegerField(default=0, verbose_name=u'使用积分数')
    expired_date = models.DateTimeField(verbose_name=u'过期时间', null=True, blank=True)
    service = models.ForeignKey(Service, verbose_name=u'美购')
    created_time = models.DateTimeField(auto_now_add=True, verbose_name=u"创建时间")
    last_modified = models.DateTimeField(auto_now=True, verbose_name=u"最后修改时间")
    payment_channel = models.CharField(max_length=1, choices=PAYMENT_CHANNEL, blank=True, verbose_name=u'支付渠道')

    service_item_key = models.CharField(max_length=64, blank=True, verbose_name=u'美购多属性')
    service_item_id = models.IntegerField(verbose_name=u'美购多属性Id', null=True, blank=True, )
    service_item_price_id = models.IntegerField(verbose_name=u'美购多属性价格Id', null=True, blank=True, )

    activity_type = models.IntegerField(choices=ACTIVITY_TYPE_ENUM, verbose_name=u'活动类型')
    activity_id = models.CharField(max_length=50, verbose_name=u'活动Id')

    groupbuy_status = models.IntegerField(verbose_name=u'拼团状态', choices=GROUPBUY_STATUS,
                                          default=GROUPBUY_STATUS.NOT_GROUPBUY)
    join_groupbuy_team_id = models.IntegerField(verbose_name=u"参加拼团的ID")

    service_price = models.FloatField(verbose_name=u'订单总价')

    # 用户相关信息
    user = models.ForeignKey(User, verbose_name=u'用户')
    name = models.CharField(max_length=20, null=True, blank=True, default='', verbose_name=u'姓名')
    phone = models.CharField(max_length=20, verbose_name=u"手机号", null=True, blank=True, default='')
    phone_crypt = EncryptedCharField(u'加密手机号', max_length=20, default='', db_index=True)

    # 其他
    postscript = models.TextField(verbose_name=u'附言', null=True, blank=True, default='')
    # 备注（管理员填写）
    comments = models.CharField(max_length=200, null=True, blank=True, default='', verbose_name=u'备注')

    # 验证相关
    operate_user = models.ForeignKey(User, related_name="operate_orders", null=True, verbose_name=u'操作用户',
                                     help_text=u'这里选择算是以哪个医生进行的验证')
    validate_time = models.DateTimeField(null=True, blank=True)
    validated = models.BooleanField(default=False)
    # 福利快照
    service_snapshot = models.TextField(default='', verbose_name='福利快照')
    service_mode = models.IntegerField(choices=SERVICE_MODE, default=SERVICE_MODE.NORMAL, help_text=u'是否是秒杀')

    # added at 4.6
    source = models.CharField(
        max_length=1, choices=ORDER_SOURCE, default=ORDER_SOURCE.UNKNOW,
        blank=True, verbose_name=u'订单来自哪个平台[iOS, Android, Web]'
    )

    # 商家结账相关
    is_settled = models.BooleanField(help_text='是否已结算', verbose_name='是否已结算', default=False)
    settled_time = models.DateTimeField(null=True, blank=True, verbose_name=u'结算时间')
    discount = models.IntegerField(verbose_name=u'抽成', default=0)

    # 转移到退款单或返现单
    refund_comment = models.CharField(max_length=200, verbose_name=u'退款原因', blank=True, default=u'')
    refund_time = models.DateTimeField(null=True, blank=True, verbose_name=u'退款时间')
    cash_back_time = models.DateTimeField(null=True, blank=True, verbose_name=u'返现时间')
    cash_back_status = models.CharField(max_length=1, choices=CASH_BACK_STATUS, default=CASH_BACK_STATUS.APPLY,
                                        verbose_name=u'返现状态', null=False)
    cash_back_fee = models.IntegerField(verbose_name=u'返现金额', blank=True, null=False, default=0, help_text=u'返现金额')
    cash_back_failure_time = models.DateTimeField(verbose_name=u'上次申请返现驳回的时间', blank=True, null=True,
                                                  help_text=u'上次提交返现申请被驳回的时间')

    # 转移到支付宝单, 第一阶段注释掉model上面的字段，之后确定没有问题之后rename表的对应字段，最终确认没有问题之后会从表里面删除这些字段
    # trade_no = models.CharField(max_length=64, null=True, blank=True, verbose_name=u'支付宝交易号')
    # trade_status = models.CharField(max_length=32, null=True, blank=True, verbose_name=u'支付宝交易状态')
    # origin_data = models.TextField(null=True, blank=True, verbose_name=u'原始支付信息')
    # buyer_id = models.CharField(max_length=30, null=True, blank=True, verbose_name=u'买家ID')
    # buyer_email = models.EmailField(null=True, blank=True, verbose_name=u'买家Email')
    # total_fee = models.FloatField(null=True, blank=True, verbose_name=u'付款总额')
    # gmt_create = models.DateTimeField(null=True, blank=True, verbose_name=u'订单生成时间')
    # gmt_payment = models.DateTimeField(null=True, blank=True, verbose_name=u'订单付款时间')

    # 跟validate_time 重复了, 废弃这个
    use_time = models.DateTimeField(verbose_name=u'使用时间', null=True, blank=True)

    # 重构新增字段
    points_deduction = models.FloatField(default=0, help_text=u'使用的积分数跟points不一样!!')
    real_payment = models.FloatField(default=0, verbose_name=u'实际付款额')
    coupon_deduction = models.FloatField(verbose_name=u'美劵抵扣', default=0)

    # 自营相关
    is_self_support = models.BooleanField(u'是否自营', default=True)

    # 广告系统相关 交易转服务
    is_ordertoservice = models.BooleanField(u'是否已经交易转服务', default=False)

    # !!!!!!!!不重要的字段请不要再往订单表里面加了， 订单表已经够大了， 文件末尾有OrderExtra表可使用
    # !!!!!!!!不重要的字段请不要再往订单表里面加了， 订单表已经够大了， 文件末尾有OrderExtra表可使用
    # !!!!!!!!不重要的字段请不要再往订单表里面加了， 订单表已经够大了， 文件末尾有OrderExtra表可使用

    # TODO: 可能要删除的字段
    duration = models.IntegerField(choices=ORDER_DURATION, default=ORDER_DURATION.NORMAL)
    address = models.CharField(max_length=100, verbose_name=u"地址", null=True, blank=True, default='')
    stage_app_id = models.CharField(max_length=20, null=True, default='', verbose_name=u'即科分期订单号')
    is_stage = models.BooleanField(help_text='是否支持分期付款', verbose_name='是否支持分期付款', default=False)
    stage_status = models.CharField(max_length=1, choices=STAGE_STATUS, default=STAGE_STATUS.SAVE, null=False,
                                    verbose_name='分期付款状态')

    is_vouchsafe = models.BooleanField(u'是否特惠', default=False)

    pay_time = models.DateTimeField(null=True, verbose_name=u'网关付款时间')
    read = models.BooleanField(u'订单当前状态是否已读', default=False)

    is_doctor_see = models.BooleanField(u'联系方式医生可见', default=False)

    operation_location_type = models.CharField(max_length=1, verbose_name='手术地点类型', choices=OPERATION_LOCATION_TYPE, default=OPERATION_LOCATION_TYPE.NORMAL)
    operation_hospital_id = models.CharField(max_length=100, default=None, null=True, blank=True, verbose_name='手术医院ID')

    order_has_service_comment = models.BooleanField(verbose_name=u'订单是否有评价', default=False)

    call_result = models.IntegerField('呼叫结果', default=USERCALL_STATUS.NOT_HANDLE, choices=USERCALL_STATUS, db_index=True)

    def save(self, *args, **kwargs):
        if self.phone is not None:
            self.phone_crypt = self.phone
        super(Order, self).save(*args, **kwargs)

    @property
    def payment_order(self):
        return self.payment_orders.first()

    @property
    def yinuo(self):
        from pay.models.yinuo import YinuoOrder
        try:
            return self.yinuo_order
        except YinuoOrder.DoesNotExist:
            return None

    @property
    def status_code(self):
        """抽象订单状态码
        将订单状态中已结算归入已使用
        :return:
        """
        if self.status in ORDER_STATUS_USED:
            return ORDER_STATUS.USED

        elif self.status in (ORDER_STATUS.REFUNDED, ORDER_STATUS.OTHER):
            return ORDER_STATUS.REFUNDED

        else:
            return self.status

    def is_refund(self):
        if self.status in (ORDER_STATUS.REFUNDED, ORDER_STATUS.WAIT_REFUNDED):
            return True
        return False

    @property
    def status_text(self):
        if self.status == ORDER_STATUS.NOT_PAID:
            return u'未付款'
        elif self.status == ORDER_STATUS.PAID:
            return u'未消费'
        elif self.status == ORDER_STATUS.USED or self.status == ORDER_STATUS.SETTLED:
            return u'已消费'
        elif self.status == ORDER_STATUS.REFUNDED:
            return u'已退款'
        elif self.status == ORDER_STATUS.WAIT_REFUNDED:
            return u'退款中'

    @property
    def status_code_plus(self):
        """抽象订单状态码
        增加返现状态
        :return:
        """
        if self.status == '2' or self.status == '4':
            if self.cash_back_status == '0':
                return '7'  # 待返现
            elif self.cash_back_status == '1':
                return '8'  # 返现审核中
            elif self.cash_back_status == '2':
                return '9'  # 已返现
        else:
            return self.status

    @property
    def status_text_v2(self):
        if self.status == ORDER_STATUS.NOT_PAID:
            return u'未付款'
        elif self.status == ORDER_STATUS.PAID:
            return u'已付款'
        elif self.status == ORDER_STATUS.USED:
            return u'待结算'
        elif self.status == ORDER_STATUS.REFUNDED:
            return u'已退款'
        elif self.status == ORDER_STATUS.SETTLED:
            return u'已结算'
        elif self.status == ORDER_STATUS.CANCEL:
            return u'已取消'
        elif self.status == ORDER_STATUS.OTHER:
            return u'其它'
        elif self.status == ORDER_STATUS.AUDITING:
            return u'审核中'
        elif self.status == ORDER_STATUS.SETTLING:
            return u'结算中'
        elif self.status == ORDER_STATUS.WAIT_REFUNDED:
            return u'等待退款'

    @property
    def payment_date_text(self):
        text = ''
        if self.status in [ORDER_STATUS.NOT_PAID, ORDER_STATUS.PAYING]:
            text = u'未付款'
        elif self.service.payment_type == PAYMENT_TYPE.FREE_PAYMENT:
            text = u'免费项目不需要付款'
        elif self.pay_time:
            text = self.pay_time.strftime('%Y-%m-%d %H:%M')

        return text

    @property
    def hospital_payment(self):
        """到院支付"""
        try:
            service_snapshot = json.loads(self.service_snapshot)
            payment_type = service_snapshot.get('payment_type', PAYMENT_TYPE.PREPAYMENT)
            is_multiattribute = service_snapshot.get('is_multiattribute', False)

            if payment_type == PAYMENT_TYPE.FREE_PAYMENT:
                price = u'0'

            elif is_multiattribute:
                price = self.service_price - service_snapshot.get('pre_payment_price', 0)
                assert price >= 0

            else:
                if service_snapshot.get('pre_payment_price', 0) == 0:
                    price = u'(秒杀价-预付款)' if self.is_seckill_order else u'(更美价-预付款)'
                else:
                    price = self.service_price - service_snapshot.get('pre_payment_price', 0)

            coupon_infos = self.get_order_coupon_infos()
            doctor_coupon = [x for x in coupon_infos if x['coupon_type'] == COUPON_TYPES.DOCTOR]
            if type(price) != unicode and doctor_coupon:
                # 历史问题会有unicode
                price -= doctor_coupon[0]['doctor_coupon_deduction']
            if service_snapshot.get('pre_payment_price', 0) == 0:
                order_extra = OrderExtra.objects.get(order=self)
                from gm_types.poseidon import AllPromotionType

                if order_extra.promotion_id and order_extra.promotion_type == AllPromotionType.DINE_AND_DASH:
                    price = 0

            return price
        except:
            logging_exception()
            return 0

    def hospital_name(self):
        # ??? 没人用了?
        if self.hospital:
            return self.hospital.name
        else:
            return None

    @property
    def password_for_display(self):
        pwd = ''

        if (self.status in ORDER_STATUS_USED or self.status == ORDER_STATUS.PAID
                or self.status == ORDER_STATUS.WAIT_REFUNDED) and \
                self.groupbuy_status in (GROUPBUY_STATUS.NOT_GROUPBUY, GROUPBUY_STATUS.GROUPBUY_SUCCESSED):
            pwd = self.password if self.password else ''

        return pwd

    @property
    def pay_mode(self):
        from api.models import SettlementItem
        try:
            return self.settlementitem.settlement.pay_mode
        except SettlementItem.DoesNotExist:
            pass
        except Exception:
            logging_exception()

        return SETTLEMENT_PAY_MODE.COMMON

    @property
    def settlement_id(self):
        from api.models import SettlementItem
        try:
            return self.settlementitem.settlement.id
        except SettlementItem.DoesNotExist:
            pass
        return 0

    def settle_data(self):

        ss = self.servicesnapshot

        coupon_info = {x['coupon_type']: x for x in self.get_order_coupon_infos()}
        coupon_doctor_value = coupon_info.get(COUPON_TYPES.PLATFORM, {}).get('coupon_doctor_value', 0)
        settle_payment = ss.pre_payment_price - self.discount - coupon_doctor_value

        return {
            'gengmei_price': ss.gengmei_price,  # TODO 此字段废弃，不要使用
            'service_price': self.service_price,
            'pre_payment_price': ss.pre_payment_price,
            'discount': self.discount,
            'settle_payment': settle_payment,
        }

    def data(self):
        # 不要再向这个方法提供新的输出列
        # 另外也不要再从 self.service_snapshot 这个JSON字符串里面加入新的数据
        # order.service_snapshot 将会被废除

        settle_data = self.settle_data()

        service_snapshot = json.loads(self.service_snapshot)
        data = {
            "service": {
                "id": self.service.id,
                "name": service_snapshot.get('name'),
                "doctor": {
                    "id": self.service.doctor.id,
                    "name": self.service.doctor.name,
                    "user_id": self.service.doctor.user.id,
                },
                "ceiling_price": service_snapshot.get('ceiling_price'),
                "gengmei_price": service_snapshot.get('gengmei_price'),
                "pre_payment_price": service_snapshot.get('pre_payment_price'),
                "multiattr_items_name": service_snapshot.get('items'),
                'image_header': service_snapshot.get('service_image') or service_snapshot.get('image_header'),
                'serviceitems': [i.service_item_data() for i in self.service.items.all()],
                "service_type": service_snapshot.get('service_type', 0),
            },
            "created_at": get_timestamp(self.created_time),
            "validate_at": get_timestamp(self.validate_time),
            "last_modified": get_timestamp(self.last_modified),
            'validated': self.validated,
            "status": self.status,
            "no": self.id,
            "discount": service_snapshot.get('discount'),
            "settle_price": settle_data['settle_payment'],
            'hospital_payment': self.hospital_payment,
            "user": {
                "name": self.user.last_name,
                "id": self.user.id,
            },
            "refund_comment": self.refund_comment,
            'payment': self.payment,
            'order_id': self.id,
            "pay_time": get_timestamp(self.pay_time),
            "refund_time": get_timestamp(self.refund_time),
            'installment': self.get_install_info(),
        }

        if self.service.doctor.hospital:
            data["service"]["hospital"] = {
                "id": self.service.doctor.hospital.id,
                "name": self.service.doctor.hospital.name
            }

        return data

    def simple_data(self):
        data = {
            'service': {
                'name': self.service.name,
                'image_header': self.service.image_header,
            },
            'order_id': self.id,
        }
        return data

    @classmethod
    def query(cls, q, offset, limit):
        orders = cls.objects.filter(q).order_by("-validate_time")
        count = orders.count()

        if offset is None:
            offset = 0
        if limit is None:
            limit = count

        orders = orders[offset:(offset + limit)]
        data = {}
        items = []
        for order in orders:
            items.append(order.data())
        data["orders"] = items
        data["count"] = count
        return data

    @property
    def order_multiattribute(self):
        if not self.service.is_multiattribute:
            return u''

        if len(self.service_snapshot) > 0:
            items = self.order_items_name_list()

            if items:

                if self.activity_type == ACTIVITY_TYPE_ENUM.MOREBUY:
                    serviceitem = ServiceItem.objects.get(pk=self.service_item_id)
                    more_buy_count = serviceitem.get_more_buy_count()
                    if more_buy_count:
                        return str(serviceitem.get_more_buy_count()) + '次' + ' '.join(items)
                else:
                    return ' '.join(items)
            else:
                return u''

    def order_items_name_list(self):
        service_snapshot = json.loads(self.service_snapshot)
        items = service_snapshot.get("items", [])
        return items

    @classmethod
    def stat(cls, q):
        data = {}
        orders = cls.objects.filter(q)
        total_settle_price = sum([order.settle_data()['settle_payment'] for order in orders])
        data["total_settle_price"] = total_settle_price
        return data

    # def validate(self, user):
    #     self.status = ORDER_STATUS.USED
    #     self.use_time = time_tool.get_current_time()
    #     self.operate_user = user
    #     self.validated = True
    #     self.validate_time = self.use_time
    #     if self.service.share_get_cashback:
    #         # 分享返现的订单需要把发现状态设置为等待返现
    #         self.cash_back_status = CASH_BACK_STATUS.WAIT
    #     self.save()

    def create_diary(self):
        tag_ids = []
        for tag in self.service.tags.filter(
                tag_type__in=(TAG_TYPE.BODY_PART_SUB_ITEM,
                              TAG_TYPE.ITEM_WIKI)):
            tag_ids.append(tag.id)
            break

        tags = list(filter(lambda item: item.get("type") in [TAG_TYPE.BODY_PART,
                                                             TAG_TYPE.BODY_PART_SUB_ITEM,
                                                             TAG_TYPE.ITEM_WIKI],
                           self.service.get_tags()))
        tags.sort(key=lambda x: x['tag_id'])

        try:
            city_tag = self.get_city_tag()
            if city_tag:
                tag_ids.append(city_tag.id)
        except:
            logging_exception()

        r = get_rpc_remote_invoker()
        result = r['diary/create_after_paid'](
            order_id=self.id,
            title=tags[0]["name"] + '日记' if tags else self.service.short_description,
            user_id=self.user_id,
            service_id=self.service_id,
            doctor_id=self.service.doctor_id,
            hospital_id=self.service.hospital_id or self.service.doctor.hospital_id,
            tag_ids=tag_ids
        ).unwrap_or(None)
        if not result:
            info_logger.info('create diary failed, order id: %s' % self.id)
            return {'diary_id': None}

        return {'diary_id': result['id']}

    def get_city_tag(self):

        tag = self.service.doctor.hospital.city.tag
        if tag:
            return tag

        if self.user.userextra.city:
            return self.user.userextra.city.tag

        return None

    @property
    def diary(self):
        from talos.models.diary import Diary
        return Diary.objects.filter(order_id=str(self.id)).first()

    def diary_data(self):
        _data = {}
        from talos.models.diary import Diary
        try:
            diary = Diary.objects.get(order_id=str(self.id))
            _data['id'] = diary.id
            _data['rating'] = diary.rating
            _data['comment'] = diary.comment
            _data['review_count'] = diary.topics.filter(
                review_status=PROBLEM_REVIEW_STATUS_CHOICES.OK, is_online=True
            ).count()
        except Diary.DoesNotExist:
            pass
        return _data

    def stage_data(self):
        _data = {}
        _data['is_stage'] = self.is_stage
        if self.is_stage:
            _data['stage_app_id'] = self.stage_app_id
            stage_status = self.stage_status
            _data['stage_status'] = self.stage_status
            if stage_status == STAGE_STATUS.SAVE:
                _data['stage_status_text'] = u'未申请'
            elif stage_status == STAGE_STATUS.SBMT:
                _data['stage_status_text'] = u'已提交'
            elif stage_status == STAGE_STATUS.DECL:
                _data['stage_status_text'] = u'已拒绝'
            elif stage_status == STAGE_STATUS.APRV:
                _data['stage_status_text'] = u'待上传合同'
            elif stage_status == STAGE_STATUS.CANL:
                _data['stage_status_text'] = u'已取消'
            elif stage_status == STAGE_STATUS.RESM:
                _data['stage_status_text'] = u'用户取消贷款'
            elif stage_status == STAGE_STATUS.POFF:
                _data['stage_status_text'] = u'还款已完成'
            elif stage_status == STAGE_STATUS.RETN:
                _data['stage_status_text'] = u'待补件'
            elif stage_status == STAGE_STATUS.DDWN:
                _data['stage_status_text'] = u'还款中'

        return _data

    @property
    def status_hint(self):
        hint = ORDER_STATUS_DETAILS.get(self.status) or ''
        if self.status == ORDER_STATUS.PAID:
            if self.service.need_address:
                # 礼品换购
                hint = u'换购成功，礼品即将快递到家哦！'
            else:
                # 非邮寄项目
                if self.payment > 0:
                    # 付费项目
                    hint = ORDER_STATUS_DETAILS[ORDER_STATUS.PAID]
                else:
                    # 免费项目
                    hint = u'不要着急，医生大大正在选择中！'
        return hint

    @property
    def refund_anytime(self):
        if hasattr(self, '_refund_anytime'):
            return self._refund_anytime

        refund_anytime = False

        from pay.models import ServiceSnapshot
        value_from_db = ServiceSnapshot.objects.filter(order_id=self.id) \
            .values_list('refund_anytime', flat=True) \
            .first()

        if value_from_db is not None:
            refund_anytime = value_from_db

        try:
            with transaction.atomic():
                order_extra = OrderExtra.objects.get(order=self)
                refund_anytime = refund_anytime and order_extra.can_refund
        except:
            pass

        setattr(self, '_refund_anytime', refund_anytime)
        return refund_anytime

    @property
    def is_seckill_order(self):
        is_seckill_order = (self.service_mode == SERVICE_MODE.SECKILL) or (
            self.service_mode == SERVICE_MODE.ACTIVITY and self.activity_type == ACTIVITY_TYPE_ENUM.SECKILL)

        return is_seckill_order

    @property
    def share_get_cashback(self):
        if hasattr(self, '_share_get_cashback'):
            return self._share_get_cashback

        share_get_cashback = False

        from pay.models import ServiceSnapshot

        value_from_db = ServiceSnapshot.objects.filter(order_id=self.id) \
            .values_list('share_get_cashback', flat=True) \
            .first()
        if value_from_db is not None:
            share_get_cashback = value_from_db

        setattr(self, '_share_get_cashback', share_get_cashback)
        return share_get_cashback

    def get_insurance_info(self):
        # 最低兼容到6.6.0后, 可以考虑去掉
        info = self.insurance_info()
        if info is None:
            return None

        return {
            'credential_id': info['policy_no'],
            'premium': info['premium'],
            'name': info['name'],
            'confirmed_at': info['confirmed_at'],
            'expiration': 210,
        }

    # 订单保险信息
    def insurance_info(self):
        try:
            insurance = get_rpc_remote_invoker()['plutus/insurance/status'](
                user_id=self.user_id,
                order_id=self.id
            ).unwrap()
        except:
            logging_exception()
            return None
        if insurance is None:
            return None
        return {
            'type': 'zhongan' if insurance['insurance_type'] == INSURANCE_TYPE.ZHONGAN else 'yinuo',
            'policy_no': insurance['policy_no'],
            'premium': insurance['premium'],
            'sum_insured': insurance['sum_insured'],  # 最高保额
            'name': insurance['name'],
            'confirmed_at': insurance['confirmed_at'],
            'begin_date': insurance['begin_date'],
            'end_date': insurance['end_date'],
            'insurance_type': insurance['insurance_type'],
            'expiration': 210,  # 一诺字段 210天过期
        }

    def get_install_info(self):
        installment = {
            'amount': '',
            'periods': '',
            'period_repay': '',
            'first_repay_time': ''
        }

        from api.models import SettlementItem
        from api.models import Settlement
        from pay.models.installment import Installment
        try:
            time = self.settlementitem.settlement.installment.first_repay_time
            if time:
                time = time.isoformat().replace('T', ' ')

            installment['periods'] = self.settlementitem.settlement.installment.periods
            installment['period_repay'] = self.settlementitem.settlement.installment.period_repay / 100.0
            installment['amount'] = self.settlementitem.settlement.installment.periods \
                                    * self.settlementitem.settlement.installment.period_repay / 100.0
            installment['first_repay_time'] = time
            return installment
        except SettlementItem.DoesNotExist:
            pass
        except Installment.DoesNotExist:
            pass
        except Settlement.DoesNotExist:
            pass
        except:
            logging_exception()
        return None

    def is_support_cash_back(self):
        service_snapshot = json.loads(self.service_snapshot)
        return service_snapshot.get('share_get_cashback', True)

    def _deprecated_coupon_info(self):
        """
        7630 一个订单可用两张券，为了维持数据结构
        在使用只有一张券的情况下, 按照原数据结构返回该券的信息
        如果使用了两张券则返回空
        """
        keys = 'coupon_name', 'coupon_type', 'coupon_value', 'doctor_coupon_deduction'
        result = {x: '' for x in keys}
        coupons = self.get_order_coupon_infos()
        if len(coupons) == 1:
            coupon = coupons[0]
            result = {x: coupon[x] for x in keys}
            # XXX 这里的coupon_value 值的是抵扣金额
            result['coupon_value'] = coupon['platform_coupon_deduction']
        return result

    def order_data(self):
        service_snapshot = json.loads(self.service_snapshot)

        call_url = ''
        doctor_phone_ext = self.service.doctor.phone_ext
        doctor_phone_ext = doctor_phone_ext.strip()
        if doctor_phone_ext:
            call_url = 'tel://' + PhoneService.get_phone_num(doctor_phone_ext)

        hospital_id = self.service.doctor.hospital_id
        #美购展示标帖
        service_tag_name = self.service.show_tags
        hospital_type = Hospital_Extra.objects.filter(hospital_id=hospital_id).values_list('hospital_type',
                                                                                           flat=True).first()
        is_beauty_of_live = hospital_type == HOSPITAL_MEDICAL_TYPE.BEAUTY_OF_LIVE

        # 资质显示
        show_lincence = self.service.doctor.get_doctor_lincence(self.service.doctor.title, 0)
        _order_data = {
            "accept_private_msg": self.service.doctor.accept_private_msg,
            "accept_reservation": self.service.doctor.accept_reserve,
            "address": self.service.doctor.hospital.location,
            "call_url": call_url,
            "doctor_accept_call": self.service.doctor.accept_call,
            'cash_back_status': self.cash_back_status,
            'city': self.service.doctor.hospital.city.name,
            'payment_without_coupon': self.servicesnapshot.payment_without_coupon,
            'detail_description': service_snapshot.get('detail_description'),
            "show_location": self.service.doctor.show_location,
            "doctor": self.service.doctor.name,
            "doctor_id": self.service.doctor.id,
            "doctor_type": self.service.doctor.doctor_type,
            "doctor_portrait": self.service.doctor.portrait,
            "doctor_title": self.service.doctor.doctor_title_display,
            "doctor_user_id": self.service.doctor.user.id,
            "hospital": self.service.doctor.hospital.name,
            "hospital_id": hospital_id,
            'hospital_lat': self.service.doctor.hospital.baidu_loc_lat,
            'hospital_lng': self.service.doctor.hospital.baidu_loc_lng,
            "hospital_payment": u'%s' % self.hospital_payment,
            "is_beauty_of_live": is_beauty_of_live,
            'image': get_full_path(service_snapshot['image_header']),
            'image_header': service_snapshot['image_header'],
            'is_seckill': self.is_seckill_order,
            'is_groupbuy': bool(self.groupbuy_status != GROUPBUY_STATUS.NOT_GROUPBUY),
            'groupbuy_status': self.groupbuy_status,
            "order_id": self.id,
            "order_multiattribute": self.order_multiattribute,
            "order_name": u'{}'.format(service_snapshot['name']),
            'password': self.password_for_display,
            'payment': self.payment,
            'payment_date': self.payment_date_text,
            'payment_type': service_snapshot['payment_type'],
            # 打电话，以后统一用call_url
            'phone': service_snapshot['phone'] if service_snapshot['phone'] else call_url,
            'points': u'{}'.format(self.points or 0),
            "reservation_id": 0,
            "reservation_status": 0,
            'service_id': service_snapshot['id'],
            'service_item_id': self.service_item_id,
            'service_tag_name': service_tag_name,
            'share_get_cashback': service_snapshot.get('share_get_cashback', True),
            'short_description': service_snapshot['short_description'],
            'status': ORDER_STATUS.getDesc(self.status),
            'status_code': self.status_code,
            "status_hint": self.status_hint,
            'status_raw': self.status,
            'total_price': self.total_price,
            'pre_payment_price': service_snapshot['pre_payment_price'],
            "user_phone": self.phone,
            'refund_anytime': self.refund_anytime,
            "compensation_in_advance": service_snapshot.get(
                'compensation_in_advance'
            ),
            'is_floor_price': service_snapshot.get('is_floor_price'),
            # price client should pay(prepay + hospital pay)
            'gengmei_price': self.service_price,
            'baidu_loc_lat': '',
            'baidu_loc_lng': '',
            'show_v': show_lincence['show_v'],
            'comment_count': self.comment_count(),
            'installment': self.get_install_info(),
            'insurance': self.get_insurance_info(),
            'pay_mode': self.pay_mode,
            'richtext': service_snapshot.get('richtext'),
            'special_remind': service_snapshot.get('special_remind'),
            'image_detail': service_snapshot.get('image_detail'),
            'is_doctor_see': self.is_doctor_see,
            'show_reserve': self.show_reserve(),
            'has_service_comment': self.has_service_comment(),
            'coupon_infos': [{
                "coupon_id": x['coupon_id'],
                "coupon_type": x['coupon_type'],
                "platform_coupon_deduction": x['platform_coupon_deduction'],
                "doctor_coupon_deduction": x['doctor_coupon_deduction']
            } for x in self.get_order_coupon_infos()],
            'hospital_rate': self.service.doctor.hospital.rate if not self.service.doctor.hospital.officer else self.service.doctor.hospital.officer.rate,
            'hospital_type': hospital_type,
            'hospital_diary_num': self.service.doctor.hospital.share_diary_num,  # 日记本数 7720版本可以删除
            'hospital_diary_topic_num': self.service.doctor.hospital.share_diary_topic_num,  # 日记帖数
            'hospital_portrait': self.service.doctor.hospital.get_hospital_portrait(),
            'activity_id': self.activity_id,
            'activity_name': "多买优惠" if self.activity_type == ACTIVITY_TYPE_ENUM.MOREBUY else '',
        }

        _order_data["cashback_fee"] = self.cash_back_fee
        _order_data["cashback_time"] = get_timestamp(self.cash_back_time)
        _order_data['cashback_limit'] = self.cashback_limit
        _order_data.update(self.stage_data())
        _order_data.update(self.reservation_data())
        _order_data.update(self.refund_data())
        _order_data.update(self._deprecated_coupon_info())
        _order_data['diary'] = self.diary_data()
        _order_data['comment_data'] = self.order_comment_diary()

        _order_data["expired_date"] = get_timestamp_or_none(self.expired_date)
        _order_data["payment_channel"] = self.payment_channel
        _order_data["user_id"] = self.user_id
        _order_data["pay_time"] = get_timestamp_or_none(self.pay_time)
        _order_data["real_payment"] = self.real_payment
        if self.service.service_type == SERVICE_SELL_TYPE.FLAGSHIP_STORE_SERVICE:
            from api.models import ServiceItem, Hospital
            sub_hos_info = ServiceItem.get_sub_hos_info(self.service_item_id)
            if sub_hos_info:
                hospital_id = sub_hos_info['hospital_id']
                hos_obj = Hospital.get_online_hospital_by_id(hospital_id)
                if hos_obj:
                    sub_hos_data = {}
                    sub_hos_data['hospital'] = hos_obj.name
                    sub_hos_data['hospital_lat'] = hos_obj.baidu_loc_lat
                    sub_hos_data['hospital_lng'] = hos_obj.baidu_loc_lng
                    sub_hos_data['address'] = hos_obj.location
                    # sub_hos_data['hospital_portrait'] = hos_obj.get_hospital_portrait()
                    _order_data.update(sub_hos_data)

        _order_data['promotion_type'] = self.promotion_type()

        return _order_data

    def promotion_type(self):
        obj = OrderExtra.objects.filter(order_id=self.id).first()
        from gm_types.poseidon import AllPromotionEnum, AllPromotionType

        return AllPromotionEnum.DINE_AND_DASH \
            if obj and obj.promotion_type == AllPromotionType.DINE_AND_DASH \
            else None

    def order_comment_diary(self):
        if self.servicecomment_set.exists():
            comment = self.servicecomment_set.first()
            return {"rating": comment.rating}
        else:
            return {"rating": 0}

    @staticmethod
    def get_order_id_to_coupon_infos(order_ids):
        ocis = list(OrderCouponInfo.objects.filter(order_id__in=order_ids))
        ocis.sort(key=lambda k: k.coupon_info_id)
        result = {}
        for oci in ocis:
            if oci.order_id not in result:
                result[oci.order_id] = []
            infos = {
                'order_id': oci.order_id,
                'coupon_id': oci.coupon_id,
                'coupon_info_id': oci.coupon_info_id,
                'coupon_name': oci.coupon_name,
                'coupon_value': oci.coupon_value,
                'coupon_type': oci.coupon_type,
                'platform_coupon_deduction': oci.platform_coupon_deduction,
                'coupon_gengmei_percent': oci.coupon_gengmei_percent,
                'coupon_gengmei_value': oci.coupon_gengmei_value,
                'coupon_doctor_value': oci.coupon_doctor_value,
                'doctor_coupon_deduction': oci.doctor_coupon_deduction,
            }
            result[oci.order_id].append(infos)
        return result

    def get_order_coupon_infos(self):
        """
            可能为 []
        :return:
        """
        key = '_order_coupon_infos'
        if hasattr(self, key):
            infos = getattr(self, key)
            return copy.deepcopy(infos)

        result = []
        d = Order.get_order_id_to_coupon_infos([self.id])
        if self.id in d:
            result = d[self.id]

        setattr(self, key, result)
        return copy.deepcopy(result)

    def order_coupon_data(self):
        servicesnapshot = self.servicesnapshot

        if servicesnapshot.coupon_info_id:

            # 以 coupon_info.id 是否存在判断是否有使用券，没有使用券就返回None
            coupon_info = json.loads(servicesnapshot.coupon_info or '{}')
            return {
                'coupon_id': servicesnapshot.coupon_id,  # coupon.id
                'coupon_info_id': servicesnapshot.coupon_info_id,  # coupon_info.id
                'coupon_name': servicesnapshot.coupon_name,
                'coupon_value': int(math.floor(servicesnapshot.coupon_value)),  # 美券分摊到订单的抵扣金额
                'coupon_type': coupon_info.get('coupon_type'),
                'coupon_gengmei_percent': servicesnapshot.coupon_gengmei_percent,
                'coupon_gengmei_value': servicesnapshot.coupon_gengmei_value,
                'coupon_doctor_value': servicesnapshot.coupon_doctor_value,
                'doctor_coupon_deduction': coupon_info.get('doctor_coupon_deduction', 0),  # 医生券抵扣
                'payment_without_coupon': coupon_info.get('payment_without_coupon', 0),  # 美券抵扣前的付款额
            }
        else:
            return None

    @property
    def cashback_limit(self):
        try:
            return self.cash_back_fee / 50 + 1
        except:
            return 1

    def latest_reservation(self):
        try:
            reservation = self.reservation.order_by("-created_time")[0]
            reservation.check_expired()
            return reservation
        except:
            return None

    def show_reserve(self):
        """
        付款之后，是否可进行医生预定
        :return:
        """
        if self.status != ORDER_STATUS.PAID or not self.service.doctor.accept_reserve:
            return False
        from api.models.reservation import Schedule

        reservation_types = [RESERVATION_TYPE.ALL, RESERVATION_TYPE.SURGERY, RESERVATION_TYPE.DIAGNOSE]
        return Schedule.objects.filter(date__gt=datetime.datetime.now(), doctor=self.service.doctor,
                                       reservation_type__in=reservation_types).exists()

    def has_reservation(self):
        if self.status == ORDER_STATUS.PAID:
            reservation = self.latest_reservation()
            if reservation and reservation.status in (RESERVATION_STATUS.ACCEPTED,
                                                      RESERVATION_STATUS.RESERVING):
                return True
        return False

    def reservation_data(self):
        if self.has_reservation():
            reservation = self.latest_reservation()
            _data = {
                "has_reservation": True,
                "reservation_id": reservation.id,
                "reservation_status": reservation.status,
            }
            return _data
        else:
            return {"has_reservation": False}

    def refund_data(self):
        try:
            refund = self.refund
            _data = {"has_refund": True}
            refund_data = {"refund_status": refund.status}
            refund_data["refund_order_id"] = refund.id
            if refund.status == REFUND_STATUS.DOCTOR_REJECT:
                try:
                    doctor_reject = OrderOperation.objects.filter(
                        optype=ORDER_OPERATION_TYPE.DOCTOR_REJECT).order_by(
                        "-operate_at")[0]
                    refund_data['doctor_reject_at'] = time_tool.get_timestamp_epoch(
                        doctor_reject.operate_at)
                    doctor_reject = \
                        OrderOperation.objects.filter(optype=ORDER_OPERATION_TYPE.DOCTOR_REJECT).order_by(
                            "-operate_at")[0]
                    refund_data['doctor_reject_at'] = time_tool.get_timestamp_epoch(doctor_reject.operate_at)
                except OrderOperation.DoesNotExist:
                    pass
            _data["refund"] = refund_data

            return _data
        except RefundOrder.DoesNotExist:
            return {"has_refund": False}
        except:
            logging_exception()
            return {"has_refund": False}

    def operate(self, operator, optype, role, source=ORDER_SOURCE.UNKNOW):
        func = order_operate_map.get(optype)
        if func:
            try:
                with transaction.atomic():
                    func(self)
                    self.save()
                    OrderOperation.objects.create(order=self, operator=operator, optype=optype, role=role,
                                                  source=source,
                                                  operate_at=datetime.datetime.now())
                    # TODO: 找出所有使用了is_success, 去掉
                    return True
            except Exception as e:
                raise e
        else:
            raise Exception("order operation not support")

    def topic_count(self):
        from talos.models.diary import Diary
        try:
            diary = Diary.objects.get(order_id=str(self.id))
            count = diary.topics.filter(is_online=True).count()
            return count
        except Diary.DoesNotExist:
            pass
        except:
            logging_exception()
        return 0

    def diary_id(self):
        try:
            return self.diary.id
        except:
            logging_exception(send_to_sentry=False)
            return None

    def has_diary_topic(self):
        return self.diary and self.diary.topic_num > 0

    def has_comment(self):
        count = self.topic_count()
        if count > 0:
            return True
        else:
            return False

    def get_renmai_status(self, ctx, person_id):
        from api.models import SettlementItem
        try:
            renmai_list = ctx.rpc_remote['plutus/installment/installment/status'](
                order_list=[self.id],
                person_id=person_id).unwrap()
            if renmai_list:
                return renmai_list[0]['status']
        except SettlementItem.DoesNotExist:
            pass
        except:
            logging_exception()

        return RM_STATUS.NONE

    def is_support_renmai_no_pay(self, min_price=500):
        # 宜人贷与任买的区别就是价格区间，目前宜人贷1000起
        try:
            if self.service.payment_type == PAYMENT_TYPE.PREPAYMENT \
                    and self.service.is_stage \
                    and PeriodHospital.check_hospital_in_list(self.service.doctor.hospital):
                price = self.hospital_payment
                if min_price <= price <= 60000:
                    return True
        except:
            pass
        return False

    def is_support_maidan_installment(self):
        try:
            price = self.hospital_payment
            if PeriodHospital.check_hospital_in_list(self.service.doctor.hospital) and 500 <= price <= 60000:
                return True
        except:
            False
        else:
            return False

    def is_support_renmai(self, min_price=500):
        # 宜人贷与任买的区别就是价格区间，目前宜人贷1000起
        try:
            if self.service.payment_type == PAYMENT_TYPE.PREPAYMENT \
                    and self.settlementitem.settlement.status == SETTLEMENT_STATUS.PAID \
                    and self.service.is_stage \
                    and self.status == ORDER_STATUS.PAID \
                    and PeriodHospital.check_hospital_in_list(self.service.doctor.hospital):
                price = self.hospital_payment
                if min_price <= price <= 60000:
                    return True
        except:
            pass
        return False

    def show_comment(self):
        if self.diary and self.diary.rate_count >= settings.SHOW_COMMENT_NUM:
            return False
        return True

    def comment_count(self):
        if self.diary:
            return self.diary.rate_count
        else:
            return 0

    def has_service_comment(self):
        return self.servicecomment_exists()

    @property
    def total_price(self):
        service_snapshot = json.loads(self.service_snapshot)
        return int(float(service_snapshot.get('pre_payment_price', 0))) + (self.yinuo.premium if self.yinuo else 0)

    def order_price(self, price_name):
        service_snapshot = json.loads(self.service_snapshot)
        return float(service_snapshot.get(price_name, 0))

    def deduction(self):
        return self.points_deduction + self.coupon_deduction

    def servicecomment_exists(self):
        return self.servicecomment_set.exists()

    def get_order_button(self):
        button_list = []

        # 代付款
        if self.status == ORDER_STATUS.NOT_PAID:
            button_list.append(ORDER_BUTTON_TYPE.PAY)
            button_list.append(ORDER_BUTTON_TYPE.MESSAGE_CHAT)

        # 已经使用
        elif self.status in ORDER_STATUS_USED:
            from api.models import ServiceComment
            comment = ServiceComment.objects.filter(order=self).first()
            if comment:
                button_list.append(ORDER_BUTTON_TYPE.VIEW_COMMENT)
            else:
                button_list.append(ORDER_BUTTON_TYPE.GOTO_COMMENT)

            if self.is_support_cash_back():
                button_list.append(ORDER_BUTTON_TYPE.CASH_BACK)

        # 未使用
        elif self.status == ORDER_STATUS.PAID:
            button_list.append(ORDER_BUTTON_TYPE.MESSAGE_CHAT)
            if self.latest_reservation():
                button_list.append(ORDER_BUTTON_TYPE.RESERVATION)
                button_list.append(ORDER_STATUS.VALIDATE)
            else:
                if self.service.doctor.accept_reserve:  # 这个字段已经废弃 线上医生都接受预定
                    button_list.append(ORDER_BUTTON_TYPE.VIEW_RESERVATION)

        # 退款
        elif self.status in [ORDER_STATUS.REFUNDED, ORDER_STATUS.WAIT_REFUNDED]:
            button_list.append(ORDER_BUTTON_TYPE.REFUND)

        return button_list

    def format_detail(self, with_fields):
        result = {}
        refund_type = None
        refund_id = None
        try:
            if self.refund.status in [REFUND_STATUS.ARBIT_APPROVE, REFUND_STATUS.DOCTOR_APPROVE, REFUND_STATUS.REFUNDED]:
                refund_type = DOCTOR_REFUND_TYPE.REFUNDED
                refund_id = self.refund.id
            if self.refund.status in [REFUND_STATUS.DOCTOR_REJECT, REFUND_STATUS.ARBITING, REFUND_STATUS.ARBIT_REJECT]:
                refund_type = DOCTOR_REFUND_TYPE.REJECT
                refund_id = self.refund.id
            if self.refund.status in [REFUND_STATUS.PROCESSING]:
                refund_type = DOCTOR_REFUND_TYPE.PROCESSING
                refund_id = self.refund.id
        except:
            pass
        if 'meta' in with_fields:
            result['meta'] = {
                'id': self.id,
                'status': self.status,
                'payment': self.payment,
                'created_time': get_timestamp_or_none(self.created_time),
                'refund_type': refund_type,
                'refund_id': refund_id
            }
        if 'service_related' in with_fields:
            if not self.servicesnapshot.items:
                service_items = ""
            else:
                try:
                    service_items = "".join(json.loads(self.servicesnapshot.items))
                except:
                    logging_exception()
                    service_items = ""
            result['service_related'] = {
                'service_name': self.servicesnapshot.name,
                'service_item_name': service_items,
                'service_type': self.servicesnapshot.service_type,
                'service_pic': self.servicesnapshot.image_header
            }
        if 'price_related' in with_fields:
            result['price_related'] = {
                'gengmei_price': self.servicesnapshot.gengmei_price,
                'pre_payment_price': self.servicesnapshot.pre_payment_price,
            }
        return result


    def get_cancel_timespan(self):
        """
        获取取消时间
        :return:
        """
        res = None
        if self.status==ORDER_STATUS.CANCEL:
            try:
                op = self.orderoperation_set.filter(optype=ORDER_OPERATION_TYPE.CANCEL).last()
                if op:
                    res =  get_timestamp(op.operate_at)
            except:
                pass
        return res



class CashBackOrder(models.Model):
    class Meta:
        app_label = 'api'

    id = models.CharField(max_length=15, verbose_name=u'返现号', primary_key=True)
    order = models.OneToOneField(Order, related_name="cashback")
    fee = models.IntegerField(verbose_name=u'实际返现金额', null=True, help_text=u'实际返现金额')
    status = models.IntegerField(choices=NEW_CASH_BACK_STATUS, db_index=True, default=NEW_CASH_BACK_STATUS.WAIT,
                                 verbose_name=u'返现状态', null=False)
    created_at = models.DateTimeField(auto_now_add=True, verbose_name=u'创建时间')
    updated_at = models.DateTimeField(auto_now=True, verbose_name=u'最后一次修改时间')
    cashbacked_at = models.DateTimeField(null=True, verbose_name=u'支付回调完成时间')
    source = models.CharField(
        max_length=1, choices=ORDER_SOURCE, default=ORDER_SOURCE.UNKNOW,
        blank=True, verbose_name=u'订单来自哪个平台等'
    )
    stale = models.BooleanField(default=False, verbose_name=u'是否是超过三个月的过期订单', help_text=u'目前只针对于支付宝')
    low_balance = models.BooleanField(default=False, verbose_name=u'账户金额不足', help_text=u'目前只针对于applepay未结算金额')
    topic_cash_back_limit = models.IntegerField(verbose_name=u'返现贴数阀值', default=0)
    topic_cash_back_num = models.IntegerField(verbose_name=u'返现贴数', default=0)
    error = models.BooleanField(default=False, verbose_name=u'支付网关交互发生错误')

    @classmethod
    def create(cls, order, fee=None):
        # try to get cashback order if exist, return directly
        try:
            order.cashback
        except:
            pass
        else:
            return

        if not order.share_get_cashback:
            return

        fee = fee or order.cash_back_fee
        topic_cash_back_limit = int(fee / 50) + 1

        count = 0
        while True:
            try:
                with transaction.atomic():
                    id = generate_cashback_order_id()
                    obj = cls.objects.create(id=id, order=order, source=order.source, fee=fee,
                                             topic_cash_back_limit=topic_cash_back_limit)
                    order.cash_back_status = CASH_BACK_STATUS.WAIT
                    order.save()
                    return obj
            except IntegrityError:
                if count > 10:
                    logging_exception()
                    raise Exception("error order generate")
                else:
                    count += 1

    def set_topic_cash_back_num(self):
        if self.order_id:
            from talos.models.diary import Diary
            try:
                diary = Diary.objects.get(order_id=str(self.order_id))
                self.topic_cash_back_num = diary.topics.filter(
                    review_status=PROBLEM_REVIEW_STATUS_CHOICES.OK, is_online=True).count()
                self.save()
            except:
                pass

    def can_cash_back(self):
        if not self.order.diary:
            return {
                'can_cash_back': False,
                'message': u'日记本不存在',
            }

        if self.order.cash_back_status == CASH_BACK_STATUS.SUCCESS:
            return {
                'can_cash_back': False,
                'message': u"改返现单已完成",
            }

        if not (self.order.diary.comment or self.order.diary.rating > 0):
            return {
                'can_cash_back': False,
                'message': u"该返现单没有评价",
            }

        if not self.order.cashback.fee:
            return {
                'can_cash_back': False,
                'message': u'返现金额为0',
            }

        if self.order.cashback.fee > self.order.payment:
            return {
                'can_cash_back': False,
                'message': u'返现金额大于实际支付金额',
            }

        if self.topic_cash_back_num < self.topic_cash_back_limit:
            return {
                'can_cash_back': False,
                'message': u'审核通过贴子数不足',
            }

        else:
            return {
                'can_cash_back': True,
                'message': u'允许返现',
                'order_id': self.order.id,
            }


class RefundOrder(models.Model):
    class Meta:
        app_label = 'api'

    id = models.CharField(max_length=15, verbose_name=u'退款单号', primary_key=True)
    order = models.OneToOneField(Order, related_name="refund")
    fee = models.IntegerField(null=True, verbose_name=u'实际退款金额')
    status = models.IntegerField(choices=REFUND_STATUS, db_index=True, default=REFUND_STATUS.PROCESSING,
                                 verbose_name=u'退款状态机')
    doctor = models.ForeignKey(Doctor, null=True)  # 处理医生
    doctor_reason = models.TextField()
    user_reason = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True, verbose_name=u'创建时间')
    updated_at = models.DateTimeField(auto_now=True, verbose_name=u'最后一次修改时间')
    refunded_at = models.DateTimeField(null=True, verbose_name=u'支付回调完成时间')
    source = models.CharField(
        max_length=1, choices=ORDER_SOURCE, default=ORDER_SOURCE.UNKNOW,
        blank=True, verbose_name=u'订单来自哪个平台'
    )
    stale = models.BooleanField(default=False, verbose_name=u'是否是超过三个月的过期订单', help_text=u'目前只针对于支付宝')
    lastest_apply_refund = models.DateTimeField(verbose_name=u'最后一次申请退款时间')
    low_balance = models.BooleanField(default=False, verbose_name=u'账户金额不足', help_text=u'目前只针对于applepay未结算金额')
    error = models.BooleanField(default=False, verbose_name=u'支付网关交互发生错误')
    # record = models.TextField(u'退款时状态记录', null=True)

    @classmethod
    def create_or_update(cls, order, user_reason):
        now = datetime.datetime.now()
        try:
            refund_order = order.refund
        except RefundOrder.DoesNotExist:
            pass
        else:
            if refund_order.status != REFUND_STATUS.CANCLED:
                return
            refund_order.lastest_apply_refund = now
            refund_order.status = REFUND_STATUS.PROCESSING
            refund_order.user_reason = user_reason
            refund_order.save()
            return refund_order

        count = 0
        while True:
            try:
                with transaction.atomic():
                    new_id = generate_refund_order_id()
                    obj = cls.objects.create(id=new_id, order=order, source=order.source, user_reason=user_reason,
                                             lastest_apply_refund=now)
                    return obj
            except IntegrityError:
                if count > 10:
                    logging_exception()
                    raise Exception("error order generate")
                else:
                    count += 1

    @property
    def estimated_fee(self):
        # 预计退款金额
        # 在支付宝or微信未完成退款前显示的金额
        return self.order.payment

    @property
    def real_fee(self):
        # 实际退款金额
        return self.fee

    @property
    def data(self):
        servicesnapshot = self.order.servicesnapshot

        data = {
            'id': self.id,
            'order_id': self.order.id,
            # 加入多次退款后, 退款时间为最后一次申请时间
            'created_at': get_timestamp(self.lastest_apply_refund),
            'updated_at': get_timestamp(self.updated_at),
            'refunded_time': get_timestamp(self.refunded_at),
            'estimated_fee': self.estimated_fee,
            'real_fee': self.real_fee,
            'status': self.status,
            'doctor': self.doctor.name if self.doctor else None,
            'doctor_name': self.order.service.doctor.name,
            'doctor_user_id': self.order.service.doctor.user.id,
            'doctor_reason': self.doctor_reason,
            'user_reason': self.user_reason,
            'service_name': servicesnapshot.name,
            'service_image': servicesnapshot.image_header,
            'is_multiattribute': servicesnapshot.is_multiattribute,
            'items_name': self.order.order_multiattribute,
            'payment': self.order.payment,  # 预付款
            'payment_date_text': self.order.payment_date_text,  # 付款时间
            'username': filter_user_nick_name(self.order.user),  # 付款人
            'user_id': self.order.user.id,  # 付款人
            'is_doctor_refund': None,
            'pay_mode': self.order.pay_mode,
            'order_create_time': get_timestamp(self.order.created_time),
            'service_id': self.order.service.id,

            # 价格信息
            'pre_payment_price': servicesnapshot.pre_payment_price,
            'hospital_payment': self.order.hospital_payment,
            'gengmei_price': servicesnapshot.gengmei_price,
            #美购类型
            'service_type': servicesnapshot.service_type
        }
        if servicesnapshot.service_type == 3:
            city_name = ServiceItem.objects.get(id=self.order.service_item_id).city.name
            data.update(city_name=city_name)
        if self.status in [REFUND_STATUS.ARBIT_APPROVE, REFUND_STATUS.DOCTOR_APPROVE, REFUND_STATUS.REFUNDED]:
            # 判断是 平台退款 还是 医生退款
            try:
                # 查找是医生同意退款的记录
                order_operation = OrderOperation.objects.filter(
                    order__id=self.order.id,
                    optype=ORDER_OPERATION_TYPE.DOCTOR_APPROVE).count()
                if order_operation:
                    data['is_doctor_refund'] = True
            except OrderOperation.DoesNotExist:
                pass
        return data

    @property
    def refund_timeline(self):
        refund_process_status = [ORDER_OPERATION_TYPE.APPLY_REFUND,
                                 ORDER_OPERATION_TYPE.DOCTOR_APPROVE,
                                 ORDER_OPERATION_TYPE.DOCTOR_REJECT,
                                 ORDER_OPERATION_TYPE.REFUND_TIMEOUT,
                                 ORDER_OPERATION_TYPE.TAG_STALE_REFUND,
                                 ORDER_OPERATION_TYPE.STALE_REFUND,
                                 ORDER_OPERATION_TYPE.REFUNDED,
                                 ]

        order_operation_qs = OrderOperation.objects.filter(order=self.order,
                                                           optype__in=refund_process_status).order_by('-operate_at')
        result = []
        for operation in order_operation_qs:
            text = ''
            merchant_name = getattr(self.doctor, 'name', None)
            if not merchant_name:
                merchant_name = self.order.service.doctor.name
            if operation.optype == ORDER_OPERATION_TYPE.APPLY_REFUND:
                reason_text = ''
                try:
                    reason_text = ' '.join(json.loads(self.user_reason))
                except:
                    pass
                text = '买家：{%s}，发起了退款申请，退款理由：{%s}' % (self.order.user.last_name, reason_text)
            elif operation.optype == ORDER_OPERATION_TYPE.DOCTOR_APPROVE:
                text = '商家：{%s}，同意了本次退款申请，退款申请已提交至{%s}' % (merchant_name, PAYMENT_CHANNEL.getDesc(self.order.payment_channel))
            elif operation.optype == ORDER_OPERATION_TYPE.DOCTOR_REJECT:
                text = '商家：{%s}，已拒绝退款，订单已验证' % (merchant_name)
            elif operation.optype == ORDER_OPERATION_TYPE.REFUND_TIMEOUT:
                text = '商家：{%s}，逾期未处理，系统自动同意，退款申请已提交至{%s}' % (merchant_name, PAYMENT_CHANNEL.getDesc(self.order.payment_channel))
            elif operation.optype == ORDER_OPERATION_TYPE.TAG_STALE_REFUND:
                text = '因此订单已超过{%s}退款时间，不可自动退款，退款金额将由更美平台线下返还给买家' % (PAYMENT_CHANNEL.getDesc(self.order.payment_channel))
            elif operation.optype == ORDER_OPERATION_TYPE.STALE_REFUND:
                text = '线下退款成功，已提醒买家查收'
            elif operation.optype == ORDER_OPERATION_TYPE.REFUNDED:
                text = '{%s}退款成功，已提醒买家查收' % (PAYMENT_CHANNEL.getDesc(self.order.payment_channel))

            result.append({
                "operator_person": operation.operator_id.hex,
                "optype": operation.optype,
                "role": operation.role,
                "source": operation.source,
                "operate_at": operation.operate_at.strftime('%Y-%m-%d %H:%M:%S'),
                "desc": text
            })
        return result


class CashBackLock(models.Model):
    class Meta:
        app_label = 'api'

    order = models.OneToOneField(Order, related_name="cashback_lock")
    lock_at = models.DateTimeField(null=True, blank=True, auto_now_add=True, verbose_name=u'返现锁定时间')


class RefundLock(models.Model):
    class Meta:
        app_label = 'api'

    order = models.OneToOneField(Order, related_name="refund_lock")
    lock_at = models.DateTimeField(null=True, blank=True, auto_now_add=True, verbose_name=u'退款锁定时间')


class OrderOperation(models.Model):
    class Meta:
        app_label = 'api'

    order = models.ForeignKey(Order)
    operator = models.ForeignKey(Person)
    optype = models.IntegerField(verbose_name=u'目标类型', choices=ORDER_OPERATION_TYPE,
                                 default=ORDER_OPERATION_TYPE.CREATE)
    role = models.IntegerField(verbose_name=u'操作角色', choices=ORDER_OPERATION_ROLE, default=ORDER_OPERATION_ROLE.USER)
    source = models.CharField(
        max_length=1, choices=ORDER_SOURCE, default=ORDER_SOURCE.UNKNOW,
        blank=True, verbose_name=u'操作来自哪个平台[iOS, Android, Web]'
    )
    operate_at = models.DateTimeField(null=True, blank=True, verbose_name=u'操作时间')


class OrderRel(models.Model):
    """临时字段is_hidden, 用于过滤需要隐藏不显示在订单列表中的订单 - 20160604
    """

    class Meta:
        app_label = 'api'

    order = models.ForeignKey(Order, related_name='rel')
    is_hidden = models.BooleanField(u'是否隐藏', default=True)


class SingleRecord(models.Model):
    """飞单记录
    """

    class Meta:
        app_label = 'api'

    user = models.ForeignKey(User, verbose_name=u'用户')
    doctor = models.ForeignKey(Doctor)  # 医生
    service = models.ForeignKey(Service, verbose_name=u'美购', null=True, blank=True)
    single_date = models.DateTimeField(verbose_name=u'飞单发生时间')
    record_date = models.DateTimeField(auto_now_add=True, verbose_name=u'记录时间')
    remark = models.TextField()
    create_user = models.ForeignKey(User, verbose_name='创建人', related_name='create_singlerecord', null=True, blank=True)
    order = models.ForeignKey(Order, verbose_name=u'订单号', null=True, blank=True)
    is_revoke = models.BooleanField(verbose_name=u'是否撤销', default=False)
    tickets_time = models.DateTimeField(verbose_name=u'开罚单时间', null=True, blank=True)
    tickets = models.ForeignKey(Tickets, verbose_name=u'关联罚单', related_name='singlerecord', null=True, blank=True)


class OrderExtra(models.Model):
    """订单额外字段
    """

    class Meta:
        app_label = 'api'

    order = models.OneToOneField(Order)

    # 双十一大促增加字段
    get_points = models.IntegerField(default=0, help_text=u'支付送美分')
    can_refund = models.BooleanField('是否可退款', default=True)
    promotion_id = models.IntegerField(default=0, help_text=u'活动ID')
    promotion_type = models.CharField(
        verbose_name="促销活动类型名称", null=False, max_length=128, default=""
    )


class OrderCost(models.Model):
    """订单成本
    """
    order = models.OneToOneField(Order)
    cost_price = models.IntegerField(verbose_name=u'成本价')


class OrderInfo(models.Model):
    """
        只用来保存所有不！可！修！改！复杂的数据信息，不能被任何运营/医生/后台/客户端直接读取。
        如果有任何需要修改的字段，或者是需要直接显示出去的复杂数据结构请不要放在这个表（比如 coupon_info）
        如果事后发现某些信息确实要读取，冗余字段到其他表。
        为了便于日后扩展，表中已经预留了多个以col_unused_开头的longtext字段
        所有这些字段的格式一般来说应该是
        用JSON表示的dict，其中key为不同的信息名称（注意避免重复）,value为字符串，一般最好使用JSON格式的字符串

        {}
    """
    class Meta:
        verbose_name = u'订单复杂不可变不需要直接读取信息'
        verbose_name_plural = u'订单复杂不可变不需要直接读取信息'
        app_label = 'api'

    order_id = models.CharField(max_length=30, verbose_name=u'订单号', unique=True)

    create_order_info = models.TextField(blank=True, verbose_name=u"下单时候记录的信息")


class OrderCouponInfo(models.Model):
    class Meta:
        verbose_name = u'订单关联美券信息'
        verbose_name_plural = u'订单关联美券信息'
        db_table = 'api_ordercouponinfo'
        app_label = 'api'

    order_id = models.CharField(max_length=30, verbose_name=u'订单号', db_index=True)

    coupon_id = models.IntegerField(verbose_name=u'美劵Id', db_index=True)
    coupon_info_id = models.IntegerField(verbose_name=u'美劵信息Id', db_index=True)
    coupon_name = models.CharField(max_length=15, verbose_name='美券名称')
    coupon_value = models.IntegerField(verbose_name='美券抵扣金额')
    coupon_type = models.IntegerField(choices=COUPON_TYPES, default=COUPON_TYPES.PLATFORM, db_index=True)

    # 下面字段是平台券有意义
    platform_coupon_deduction = models.IntegerField(verbose_name=u'美劵在该订单上的预付款抵扣金额，元', default=0)
    coupon_gengmei_percent = models.IntegerField(verbose_name=u'预付款抵扣金额更美承担比例', default=100)
    coupon_gengmei_value = models.IntegerField(verbose_name=u'更美承担金额', default=0)
    coupon_doctor_value = models.IntegerField(verbose_name=u'医生承担金额', default=0)

    # 下面字段是医生券有意义
    doctor_coupon_deduction = models.IntegerField(verbose_name=u'美劵在该订单上的尾款抵扣金额，元', default=0)


class OrderCategory(models.Model):
    class Meta:
        verbose_name = u'订单类型'
        db_table = 'api_ordercategory'
        app_label = 'api'

    order = models.OneToOneField(Order)
    category = models.IntegerField(
        u'订单类型', choices=ORDER_CATEGORY, default=ORDER_CATEGORY.NORMAL,
    )

class OrderUserInfo(models.Model):
    class Meta:
        verbose_name = u'订单关联用户信息'
        db_table = 'api_orderuserinfo'
        app_label = 'api'

    order_id = models.CharField(max_length=30, verbose_name=u'订单号', null=True, blank=True)
    hospitalpay_id = models.CharField(max_length=30, verbose_name=u'尾款支付单信息id', null=True, blank=True)
    status = models.CharField(max_length=20, choices=ORDER_STATUS, default=ORDER_STATUS.NOT_PAID, null=False,
                              verbose_name=u'订单状态')
    device_id = models.CharField(db_index=True, max_length=100, verbose_name=u'设备ID', null=True, blank=True)
    ip = models.CharField(db_index=True, max_length=255, verbose_name="ip地址")


class BWCOrderVerifyNotice(models.Model):
    class Meta:
        verbose_name = u'霸王餐订单未验证用户推送记录表'
        db_table = 'api_bwc_orderverifynotice'
        app_label = 'api'

    order = models.ForeignKey(Order, verbose_name=u'关联订单')
    user = models.ForeignKey(User, verbose_name=u'关联用户')
    msg_id = models.CharField(max_length=255, verbose_name=u'调用push返回的msg_id')
    send_method = models.IntegerField(verbose_name=u'发送策略', choices=BWC_ORDER_NOTIFY_METHOD, default=BWC_ORDER_NOTIFY_METHOD.PAID_AFTER_THREE_DAY)
    title = models.TextField(verbose_name=u'主标题', default='')
    alert = models.TextField(verbose_name=u'副标题', default='')
    push_param = models.TextField(verbose_name=u'调用push参数', default='')
    create_at = models.DateTimeField(verbose_name=u'创建时间', auto_now_add=True)
    update_at = models.DateTimeField(verbose_name=u'更新时间', auto_now=True)


