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

import math
import datetime

from collections import defaultdict
from cached_property import cached_property

from django.db import models
from django.db.models import Q
from django.conf import settings

from api.models import Person, OrderUserInfo, Merchant
from api.models.user import User
from api.models.order import Order, OrderCouponInfo
from api.models.doctor import Doctor
from gm_types.gaia import COUPON_STATUS, COUPON_THRESHOLD_VALIDATION_TYPE, DOCTOR_USE_COUPON_TYPE
from api.models.special import Special
from api.models.special import SpecialItem
from api.tool.datetime_tool import get_timestamp_or_none, get_timestamp
from api.models.service import Service
from api.models.service import ServiceItem
from gm_types.trade import COUPON_TIME_TYPE
from gm_types.doctor import COUPON_FILTER_STATUS
from api.models.business_channel import BusinessChannel
from rpc.tool.error_code import gen, CODES
from rpc.exceptions import GaiaRPCFaultException
from gm_types.gaia import ORDER_STATUS, SERVICE_ITEM_PRICE_TYPE, COUPON_INVALID, DOCTOR_USE_COUPON_TYPE
from gm_types.gaia import COUPON_TYPES, COUPON_GIFT_TYPES, COUPON_DISTRIBUTION_STATUS, BENEFIT_TYPE, COUPON_USE_TYPE
from rpc.tool.time_tool import get_current_time
from api.tool.datetime_tool import get_timestamp_epoch
from api.manager.coupon_manager import CouponManager, CouponInfoManager, fmt_discount_rate_show
from api.manager.coupon_manager import ChannelGiftUserRelationManager

from gm_upload import IMG_TYPE, ImgUrlField


HAS_BOUGHT = (ORDER_STATUS.PAID, ORDER_STATUS.USED, ORDER_STATUS.SETTLED)

_coupon_limited_doctor_id_set = set()
_coupon_limited_max_gengmei_percent = 0

try:
    _limited_doctor_ids = settings.COUPON_LIMITED_DOCTOR_IDS
    _coupon_limited_doctor_id_set = set(_limited_doctor_ids)
    _limited_max_gengmei_percent = settings.COUPON_LIMITED_MAX_GENGMEI_PERCENT
    if 0 <= _limited_max_gengmei_percent <= 100:
        _coupon_limited_max_gengmei_percent = _limited_max_gengmei_percent
except:
    pass


def get_coupon_limited_doctor_id_set():
    return set(_coupon_limited_doctor_id_set)


def get_limited_max_gengmei_percent():
    return _coupon_limited_max_gengmei_percent


class Coupon(models.Model):
    class Meta:
        verbose_name = '美券'
        app_label = 'api'

    name = models.CharField(max_length=15, verbose_name='美券名称')
    value = models.IntegerField(verbose_name='美券价值or抵扣金额')
    total = models.IntegerField(verbose_name='总张数', default=0)
    start_time = models.DateTimeField(verbose_name=u'起始时间', null=True)
    end_time = models.DateTimeField(verbose_name=u"结束时间", null=True)
    announcement = models.TextField(default='', verbose_name='特殊说明')
    is_recommand = models.BooleanField(default=False, help_text=u"是否首页推荐", verbose_name=u"是否首页推荐")
    recommand_ordering = models.IntegerField(default=100, verbose_name=u"推荐顺序", help_text=u"小的排在前，大的排在后")
    is_new_user = models.BooleanField(default=False, help_text=u"是否是新人美券", verbose_name=u"新人美券")
    # 新增
    is_doctor_new = models.BooleanField(default=False, verbose_name='是否限制医生新人')
    discount_limit = models.BooleanField(default=True, verbose_name='抽成限制')

    has_threshold = models.BooleanField(default=False, verbose_name='区分满减券直减券')
    prepay_threshold = models.IntegerField(default=0, verbose_name=u'门槛金额/美券可用区间下限')
    threshold_validation_type = models.IntegerField(choices=COUPON_THRESHOLD_VALIDATION_TYPE,
                                                    default=COUPON_THRESHOLD_VALIDATION_TYPE.BY_DEDUCTION_TYPE)

    seckill_avail = models.BooleanField(default=False, verbose_name='是否秒杀可用')
    groupbuy_avail = models.BooleanField(default=False, verbose_name='是否拼团可用')
    created_time = models.DateTimeField(verbose_name='创建时间', null=True, auto_now_add=True)
    updated_time = models.DateTimeField(verbose_name='更新时间', null=True, auto_now=True)
    activated_time = models.DateTimeField(verbose_name='生效时间', null=True)
    time_type = models.IntegerField(choices=COUPON_TIME_TYPE, default=COUPON_TIME_TYPE.START_END)
    countdown = models.IntegerField(verbose_name='倒计时', default=0)

    gengmei_percent = models.IntegerField(verbose_name=u'抵扣金额更美承担比例', help_text=u'抵扣金额更美承担比例', default=100)

    # DOCTOR 历史数据全部都是PLATFORM
    coupon_type = models.IntegerField(choices=COUPON_TYPES, default=COUPON_TYPES.PLATFORM)

    # OPEN, CLOSED （历史数据由activated_time决定）
    distribution_status = models.IntegerField(choices=COUPON_DISTRIBUTION_STATUS, default=COUPON_DISTRIBUTION_STATUS.DRAFT)

    # 只有当 医生自己去创建美券的时候去赋值, 同时CouponDoctorRestrict也会有关联记录
    doctor = models.ForeignKey(Doctor, null=True, blank=True, verbose_name='医生美券创建者')
    # 删除
    claim_end_time = models.DateTimeField(verbose_name=u'领取结束时间', null=True)
    campaign = models.ForeignKey('Campaign', null=True, related_name="coupons", verbose_name='关联活动')
    condition = models.CharField(max_length=300, null=True, verbose_name='使用条件')  # 先不要去掉

    objects = CouponManager()

    effect_person = models.ForeignKey(User, null=True, verbose_name='生效人')
    cost_bear = models.CharField('费用承担部门', max_length=300, null=True)

    # 7780 添加折扣券
    upper_limit = models.IntegerField(u'美券可用区间上限', null=False, default=9999999)
    discount_rate = models.IntegerField(u'折扣券折扣比例', null=True, default=100)
    benefit_type = models.IntegerField(u'美券类型', null=True, choices=BENEFIT_TYPE, default=BENEFIT_TYPE.ZHIJIAN)

    # http://wiki.wanmeizhensuo.com/pages/viewpage.action?pageId=21993531
    # 医生创建美券美券
    doctor_coupon_use_type = models.IntegerField(choices=DOCTOR_USE_COUPON_TYPE, default=DOCTOR_USE_COUPON_TYPE.NONE_FOR_DOCTOR)

    def data(self):
        if self.benefit_type == BENEFIT_TYPE.ZHIJIAN:
            value_term = '直' + u'减%s' % self.value
        elif self.benefit_type == BENEFIT_TYPE.MANJIAN:
            value_term = '满%s' % self.prepay_threshold + u'减%s' % self.value
        else: # self.benefit_type == BENEFIT_TYPE.DISCOUNT
            value_term = '全场%s折' % self.discount_rate if self.discount_rate % 10 else self.discount_rate // 10
        return {
            "id": self.id,
            "name": self.name,
            "has_threshold": self.has_threshold,
            "prepay_threshold": self.prepay_threshold,
            "value": self.value,
            "start_time": get_timestamp_epoch(self.start_time),
            "end_time": get_timestamp_epoch(self.end_time),
            'claim_end_time': get_timestamp_or_none(self.claim_end_time),
            "is_recommand": self.is_recommand,
            "recommand_ordering": self.recommand_ordering,
            "announcement": self.announcement,
            "use_term": self.use_term,
            "value_term": self.value_term,
            "time_type": self.time_type,
            "count_down": self.countdown,
            "groupbuy_avail": self.groupbuy_avail,

            # 7780 http://wiki.wanmeizhensuo.com/pages/viewpage.action?pageId=18589503
            # 7780 以后不再通过has_threshold来区分满减/直减，而是通过benefit_type
            "upper_limit": self.upper_limit,
            "discount_rate": self.discount_rate,
            "benefit_type": self.benefit_type,
        }

    @property
    def value_term(self):
        coupon_description = ''
        if self.benefit_type == BENEFIT_TYPE.MANJIAN:
            coupon_description += '满%s' % self.prepay_threshold + u'减%s' % self.value
        elif self.benefit_type == BENEFIT_TYPE.ZHIJIAN:
            coupon_description += '直' + u'减%s' % self.value
        elif self.benefit_type == BENEFIT_TYPE.DISCOUNT:
            if self.discount_rate == 0:
                coupon_description = '免单券({}-{}可用)'.format(self.prepay_threshold, self.upper_limit)
            else:
                discount_str = fmt_discount_rate_show(self.discount_rate)
                coupon_description += '{}折({}-{}可用)'.format(discount_str, self.prepay_threshold, self.upper_limit)

        return coupon_description

    @property
    def use_term(self):
        # 2016.07.12 产品经理 刘庆雪
        # 要求写死文案, 定制化的文案,由产品经理自己写self.announcement
        sentence = ''
        count = 1
        special_restrics = CouponSpecialRestrict.objects.filter(coupon=self)
        # if special_restrics:
        #     _specials = []
        #     for special_restric in special_restrics:
        #         _specials.append(special_restric.special.title)
        #     sentence += '%s' % count + '. ' + u'限' + u'、'.join(_specials) + u'专场美购使用\n'
        #     count += 1

        # if self.seckill_avail:
        #     sentence += '%s' % count + '. ' + u'秒杀美购也可使用\n'
        #     count += 1

        # if self.discount_limit:
        if self.coupon_type == COUPON_TYPES.DOCTOR:
            # 尾款券
            if self.doctor_coupon_use_type == DOCTOR_USE_COUPON_TYPE.PART_GENERAL:
                sentence += '%s' % count + '. ' + u'部分美购限制使用\n'
            elif self.doctor_coupon_use_type == DOCTOR_USE_COUPON_TYPE.DOCTOR_GENERAL:
                sentence += '%s' % count + '. ' + u'该医生全部美购适用\n'
            elif self.doctor_coupon_use_type == DOCTOR_USE_COUPON_TYPE.FULL_PLATFORM_GENERAL:
                sentence += '%s' % count + '. ' + u'该机构全部美购适用\n'
            count += 1
        #
        coupon_text = {
            COUPON_TYPES.PLATFORM: u'预付款', COUPON_TYPES.DOCTOR: u'尾款'
        }[self.coupon_type]
        # < 7780
        # if self.prepay_threshold:
        #     if self.threshold_validation_type == COUPON_THRESHOLD_VALIDATION_TYPE.GENGMEI_PRICE:
        #         coupon_text = u'更美价'
        #     sentence += '%s' % count + '. ' + u'订单%s满%s元可用\n' % (coupon_text, self.prepay_threshold)
        #     count += 1
        # else:
        #     sentence += '%s' % count + '. ' + u'订单%s直减%s元\n' % (coupon_text, self.value)
        #     count += 1

        # 7780
        if self.benefit_type == BENEFIT_TYPE.ZHIJIAN:
            sentence += '%s' % count + '. ' + u'订单%s直减%s元\n' % (coupon_text, self.value)
            count += 1
        elif self.benefit_type == BENEFIT_TYPE.MANJIAN:
            if self.threshold_validation_type == COUPON_THRESHOLD_VALIDATION_TYPE.GENGMEI_PRICE:
                coupon_text = u'更美价'
            sentence += '%s' % count + '. ' + u'订单%s满%s元可用\n' % (coupon_text, self.prepay_threshold)
            count += 1
        elif self.benefit_type == BENEFIT_TYPE.DISCOUNT:
            sentence += '%s' % count + '. ' + u'订单%s满%s-%s元可用\n' % (coupon_text, self.prepay_threshold, self.upper_limit)
            count += 1

        sentence += '%s' % count + '. ' + u'单笔订单限用1张，退款时不退美券\n'
        count += 1

        if self.coupon_type == COUPON_TYPES.DOCTOR:
            sentence += '%s' % count + '. ' + u'优惠金额从尾款扣除\n'
            count += 1

            if self.is_doctor_new:
                sentence += '%s' % count + '. ' + u'仅限未在该医生或机构账号下单的用户使用\n'
                count += 1

        if self.announcement:
            sentence += '%s' % count + '. ' + self.announcement

        # 目前在最后一个字符可能是\n，测试认为应该去掉...我希望"最后一个字符可能是\n"不是Feature
        sentence = sentence[:-1] if sentence[-1] == '\n' else sentence

        return sentence

    @property
    def use_new_term(self):
        # 719 添加 http://wiki.wanmeizhensuo.com/pages/viewpage.action?pageId=28905711
        if self.coupon_type == COUPON_TYPES.PLATFORM:
            sentence = self.announcement if self.announcement else "您的专属美券"
        elif self.coupon_type == COUPON_TYPES.DOCTOR:
            sentence = self.announcement if self.announcement else "机构发放给您的专属美券"
        else:
            sentence = ''
        return sentence

    def listitem_for_doctor(self):
        """
            仅适用于 医生美劵 相关调用
        """
        now = datetime.datetime.now()
        filter_status = None
        coupon_gift = self.doctor_coupon_gift()
        if self.distribution_status == COUPON_DISTRIBUTION_STATUS.DRAFT:
            filter_status = COUPON_FILTER_STATUS.DRAFT
        elif self.distribution_status == COUPON_DISTRIBUTION_STATUS.CLOSED:
            filter_status = COUPON_FILTER_STATUS.CLOSED
        elif self.distribution_status == COUPON_DISTRIBUTION_STATUS.OPEN:
            if coupon_gift.start_time > now:
                filter_status = COUPON_FILTER_STATUS.ACTIVATED
            elif coupon_gift.end_time > now:
                filter_status = COUPON_FILTER_STATUS.OPEN
            elif coupon_gift.end_time <= now:
                filter_status = COUPON_FILTER_STATUS.EXPIRED
        data = {
            'id': self.id,
            'name': self.name,
            'has_threshold': self.has_threshold,
            'prepay_threshold': self.prepay_threshold,
            'value': self.value,
            'activated_time': get_timestamp_epoch(self.activated_time),
            'doctor': {
                'id': self.doctor.id,
                'name': self.doctor.name,
            } if self.doctor else None,
            'filter_status': filter_status,
            'doctor_id': self.doctor_id,
            'doctor_name': self.doctor.name,
            'doctor_coupon_type': self.doctor_coupon_use_type
        }
        data.update({
            'get_start_time': get_timestamp_epoch(coupon_gift.start_time),
            'get_end_time': get_timestamp_epoch(coupon_gift.end_time),
        })
        # 医生券剩余数量 = 渠道礼包关联总数 - 用户在医生专有渠道领取数
        channelgift = coupon_gift.doctor_channelgift()
        user_get_num = ChannelGiftUserRelation.objects.filter(channel_gift=channelgift).count()
        data['residue'] = channelgift.total - user_get_num
        receive_num = CouponInfo.objects.filter(coupon=self.id).exclude(status=COUPON_STATUS.UNCLAIMED).count()
        data['receive_num'] = receive_num
        return data

    def detail_for_doctor(self):
        """
            仅适用于 医生美劵 相关调用
        """
        from api.tool.coupon_tool import skus_for_coupon
        data = {
            'id': self.id,
            'name': self.name,
            'has_threshold': self.has_threshold,
            'prepay_threshold': self.prepay_threshold,
            'value': self.value,
            'time_type': self.time_type,
            'countdown': self.countdown,
            'use_start_time': get_timestamp_epoch(self.start_time),
            'use_end_time': get_timestamp_epoch(self.end_time),
            'is_doctor_new': self.is_doctor_new,
            'distribution_status': self.distribution_status,
            'announcement': self.announcement,
            'doctor': {
                'id': self.doctor.id,
                'name': self.doctor.name,
            } if self.doctor else None,
            'get_start_time': 0,
            'get_end_time': 0,
            'total': 0,
            'limit': 0,
            'activated_time': get_timestamp_epoch(self.activated_time),
            'skus': skus_for_coupon(self.restric_skus.values_list('sku_id', flat=True)),
            'doctor_coupon_type': self.doctor_coupon_use_type,
        }
        coupon_gift = self.doctor_coupon_gift()
        data.update({
            'get_start_time': get_timestamp_epoch(coupon_gift.start_time),
            'get_end_time': get_timestamp_epoch(coupon_gift.end_time),
        })
        channelgift = coupon_gift.doctor_channelgift()
        data.update({
            'total': channelgift.total,
            'limit': channelgift.limit,
        })
        now = datetime.datetime.now()
        if self.distribution_status == COUPON_DISTRIBUTION_STATUS.DRAFT:
            filter_status = COUPON_FILTER_STATUS.DRAFT
        elif self.distribution_status == COUPON_DISTRIBUTION_STATUS.CLOSED:
            filter_status = COUPON_FILTER_STATUS.CLOSED
        elif self.distribution_status == COUPON_DISTRIBUTION_STATUS.OPEN:
            if coupon_gift.start_time > now:
                filter_status = COUPON_FILTER_STATUS.ACTIVATED
            elif coupon_gift.end_time > now:
                filter_status = COUPON_FILTER_STATUS.OPEN
            elif coupon_gift.end_time <= now:
                filter_status = COUPON_FILTER_STATUS.EXPIRED
        data.update({
            'filter_status': filter_status
        })
        return data

    def doctor_coupon_gift(self):
        """
            获取医生美券关联的大礼包
        """
        coupon_gift_set = self.coupon_gift.filter(gift_type=COUPON_GIFT_TYPES.DOCTOR)
        if coupon_gift_set.count() != 1:
            raise GaiaRPCFaultException(error=1, message=u'医生礼包数据数量不正确', data=None)
        return coupon_gift_set.first()


class CouponSKURestrict(models.Model):
    class Meta:
        unique_together = ('coupon', 'sku')

    coupon = models.ForeignKey(Coupon, verbose_name='关联美券', related_name='restric_skus')
    sku = models.ForeignKey(ServiceItem, verbose_name='关联的SKU')


class CouponDoctorRestrict(models.Model):
    class Meta:
        unique_together = ('coupon', 'doctor')

    coupon = models.ForeignKey(Coupon, verbose_name='关联美券', related_name='restric_doctors')
    doctor = models.ForeignKey(Doctor, verbose_name='受限医生')


class CouponSpecialRestrict(models.Model):
    class Meta:
        unique_together = ('coupon', 'special')

    coupon = models.ForeignKey(Coupon, verbose_name='关联美券')
    special = models.ForeignKey(Special, verbose_name='受限专场')

class CouponMerchantRestrict(models.Model):
    class Meta:
        unique_together = ('coupon_id', 'merchant_id')

    coupon_id = models.IntegerField(u'美劵id', null=False)
    merchant_id = models.BigIntegerField(u'商户id', null=False)

class CouponGift(models.Model):
    class Meta:
        verbose_name = '美券大礼包'
        app_label = 'api'

    name = models.CharField(max_length=15, verbose_name='大礼包名字')
    coupon = models.ManyToManyField(Coupon, related_name='coupon_gift', through='GiftToCoupon')
    value = models.IntegerField(verbose_name='大礼包总价', default=0)

    # 新增
    created_time = models.DateTimeField(verbose_name='创建时间', null=True, auto_now_add=True)
    updated_time = models.DateTimeField(verbose_name='更新时间', null=True, auto_now=True)
    start_time = models.DateTimeField(verbose_name=u'领取开始时间', null=True)
    end_time = models.DateTimeField(verbose_name=u'领取结束时间', null=True)
    memo = models.TextField(verbose_name='备注', default='')

    # 删去
    channel = models.IntegerField(verbose_name='哪个渠道', null=True)
    total = models.IntegerField(verbose_name='大礼包总数', null=True)
    launch_time = models.DateTimeField(auto_now_add=True, verbose_name='投放时间', null=True)
    operator = models.ForeignKey(User, verbose_name='投放操作人', null=True)

    # DOCTOR 历史数据全部都是PLATFORM
    gift_type = models.IntegerField(choices=COUPON_GIFT_TYPES, default=COUPON_GIFT_TYPES.PLATFORM)

    def can_be_claimed(self):
        now = datetime.datetime.now()

        if self.start_time > now:
            raise gen(CODES.COUPON_NOT_START)
        if self.end_time < now:
            raise gen(CODES.COUPON_CLOSED)

    def get_coupon_and_number_list(self):
        gtcs = [gt for gt in GiftToCoupon.objects.filter(coupon_gift=self, coupon_enable=True).select_related('coupon')]
        coupons = [(gt.coupon, gt.number) for gt in gtcs]
        return coupons

    @property
    def info_data(self):
        """
            之后会将info_data废弃，因为目前使用这个的基本集中在view层，参考to_show_gift_dict
        :return:
        """
        coupon_and_number = self.get_coupon_and_number_list()
        all_coupons = [gt[0] for gt in coupon_and_number]
        number = sum([gt[1] for gt in coupon_and_number])
        # 这里的total_value，针对原有的预付款和尾款抵扣金额都直接相加
        # 如果需要增加不能理解为金额的类型的话...大量显示券和礼包的地方都要大改
        total_value = sum([gt[0].value * gt[1] for gt in coupon_and_number]) if number > 0 else 0
        return {
            'id': self.id,
            'name': self.name,
            'value': total_value,
            'coupon_number': number,
            'start_time': get_timestamp_or_none(self.start_time),
            'end_time': get_timestamp_or_none(self.end_time),
            'coupons': [coupon.data() for coupon in all_coupons],
            'gift_type': self.gift_type
        }

    def doctor_channelgift(self):
        """
            获取 美券大礼包对应的渠道关联
        """
        channelgift_set = self.channelgift_set.filter(business_channel_id=settings.DOCTOR_BUSINESS_CHANNAL_ID)
        if channelgift_set.count() != 1:
            raise GaiaRPCFaultException(error=1, message=u'医生渠道数据数量不正确', data=None)
        return channelgift_set.first()


class GiftToCoupon(models.Model):
    class Meta:
        verbose_name = 'ManyToMany Relationship'
        app_label = 'api'

    coupon = models.ForeignKey(Coupon, verbose_name='关联美券')
    coupon_gift = models.ForeignKey(CouponGift, verbose_name='大礼包')
    number = models.IntegerField(verbose_name='每个大礼包中包含的美券个数', default=0)
    coupon_enable = models.BooleanField(default=True, verbose_name=u'券在礼包中生效')  # 只有美券下线的时候才为False


class ChannelGift(models.Model):
    class Meta:
        unique_together = ('gift', 'business_channel')

    gift = models.ForeignKey(CouponGift, verbose_name='大礼包')
    business_channel = models.ForeignKey(BusinessChannel, verbose_name='大礼包渠道')
    total = models.IntegerField(default=0, verbose_name='大礼包X渠道 个数')
    limit = models.IntegerField(verbose_name='限制每个用户可领取渠道大礼包个数', default=1)
    activated_time = models.DateTimeField(verbose_name='生效时间', null=True)

    gift_enable = models.BooleanField(default=True, verbose_name=u'礼包在渠道中生效')
    displayable = models.BooleanField(default=False, verbose_name=u'渠道中礼包显示可领取')

    @property
    def activated(self):
        return self.gift_enable

    def can_be_claimed(self):
        has_claimed_count = ChannelGiftUserRelation.objects.filter(channel_gift=self, user__isnull=False).count()
        if has_claimed_count > self.total:
            raise gen(CODES.COUPON_IS_EMPTY)

        self.gift.can_be_claimed()


class ChannelGiftStatistics(models.Model):
    class Meta:
        verbose_name = "渠道礼包统计相关数据"
        app_label = 'api'
        db_table = 'api_channelgiftstatistics'
        index_together = [
            ('channel_gift_id', 'claimed_count')
        ]

    channel_gift_id = models.IntegerField(verbose_name="渠道礼包ID", null=False, unique=True)
    claimed_count = models.IntegerField(verbose_name="已领数量", default=0, null=False)


class GiftChannelLaunch(models.Model):
    class Meta:
        verbose_name = '投放信息'
        app_label = 'api'

    total = models.IntegerField(verbose_name='不同渠道总数')  # 投放数量
    launch_time = models.DateTimeField(auto_now_add=True, verbose_name='投放时间')
    operator = models.ForeignKey(User, verbose_name='投放操作人')
    channel_gift = models.ForeignKey(ChannelGift, verbose_name='渠道X大礼包')

    @property
    def channel_name(self):
        return BusinessChannel.getDesc(self.channel_gift.business_channel_id)


class ChannelGiftUserRelation(models.Model):
    code = models.CharField(verbose_name='美券码', max_length=16, unique=True, null=True)
    claimed_time = models.DateTimeField(verbose_name='领取', null=True)
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建暨领取时间')
    user = models.ForeignKey(User, verbose_name='用户', null=True)
    channel_gift = models.ForeignKey(ChannelGift, verbose_name='绑定某渠道大礼包')
    launch = models.ForeignKey(GiftChannelLaunch, verbose_name='投放信息', null=True)

    objects = ChannelGiftUserRelationManager()

    def claim(self, user):
        if self.user:
            raise gen(CODES.COUPON_CLAIMED)
        self.claimed_time = datetime.datetime.now()
        self.user = user
        self.save()


# 删去此models
class CouponLaunch(models.Model):
    class Meta:
        verbose_name = '投放信息'
        app_label = 'api'

    coupon = models.ForeignKey(Coupon, verbose_name='关联美券')
    channel = models.IntegerField(verbose_name='哪个渠道')
    total = models.IntegerField(verbose_name='不同渠道总数')
    launch_time = models.DateTimeField(auto_now_add=True, verbose_name='投放时间')
    operator = models.ForeignKey(User, verbose_name='投放操作人')


class CouponInfo(models.Model):
    class Meta:
        verbose_name = '美券具体信息'
        app_label = 'api'

    coupon = models.ForeignKey(Coupon)
    user = models.ForeignKey(User, null=True)
    status = models.IntegerField(verbose_name='状态', default=COUPON_STATUS.UNCLAIMED)
    claimed_time = models.DateTimeField(verbose_name='认领时间', null=True)
    frozen_time = models.DateTimeField(verbose_name='冻结时间', null=True)
    consumed_time = models.DateTimeField(verbose_name='消费时间', null=True)
    returned_time = models.DateTimeField(verbose_name='退还时间', null=True)
    order = models.ForeignKey(Order, verbose_name='关联订单', null=True)
    # 新增
    start_time = models.DateTimeField(verbose_name='开始时间', null=True)
    end_time = models.DateTimeField(verbose_name='结束时间', null=True)
    relation = models.ForeignKey(ChannelGiftUserRelation, null=True)
    # 删去
    value = models.IntegerField(verbose_name='美券价值', null=True)
    coupon_launch = models.ForeignKey(CouponLaunch, verbose_name='投放信息', null=True)
    coupon_gift = models.ForeignKey(CouponGift, verbose_name='关联的大礼包', null=True)
    channel = models.IntegerField(verbose_name='哪个渠道', null=True)
    code = models.CharField(verbose_name='美券码', max_length=12, unique=True, null=True)

    objects = CouponInfoManager()

    def discount_limit(self):
        """判断是否设置了抽成限制条件
        """
        # if getattr(self, '_discount_limit', None):
        #     return self._discount_limit
        # condition = self.coupon.get_condition()
        # discount_limit = condition.get('discount_limit', False)
        # self._discount_limit = discount_limit
        # return self._discount_limit
        return self.coupon.discount_limit

    #
    def _condition_prepay_check(self, service_item):
        pre_payment_price = service_item.pre_payment_price
        gengmei_price = service_item.gengmei_price
        prepay_threshold = self.coupon.prepay_threshold
        if int(gengmei_price) >= prepay_threshold \
                and int(pre_payment_price) > self.coupon.value:
            # 满减根据更美价确定，同时优惠券金额不能大于预付款
            return True
        return False

    #
    def _condition_discount_check(self, service_item):
        """抽成限制条件: 美券金额小于抽成金额才可以使用
        """
        discount_limit = self.coupon.discount_limit
        if not discount_limit:
            # 没有勾选限制，直接通过
            return True

        discount = service_item.discount

        if self.coupon.value <= discount:
            return True
        return False

    def _condition_special_check(self, service):
        if not self.coupon.couponspecialrestrict_set.exists():
            return True
        # 专场和秒杀的item存放位置已经分开，需要分别判断
        flag_normal = Service.objects.filter(
            specialitem__special__couponspecialrestrict__coupon_id=self.coupon_id,
            id=service.id,
        ).exists()
        flag_seckill = Service.objects.filter(
            specialseckillservice__special__couponspecialrestrict__coupon_id=self.coupon_id,
            id=service.id,
        ).exists()
        return flag_normal | flag_seckill

    def _condition_seckill_check(self, service, service_item):
        if not service_item:
            service_item = ServiceItem.objects.get(service=service, is_delete=False)

        price_info = service_item.get_current_price_info()

        if price_info and price_info.get('price_type') == SERVICE_ITEM_PRICE_TYPE.SECKILL:
            return self.coupon.seckill_avail

        return True

    def _condition_scenario_check(self, service, service_item):
        if not self._condition_seckill_check(service, service_item):
            return False

        if self._condition_special_check(service):
            return True

    def _condition_new_user_check(self):
        if not self.coupon.is_new_user:
            return True

        return not Order.objects.filter(user=self.user).filter(status__in=HAS_BOUGHT).exists()

    def _condition_doctor_check(self, service):
        # 没有医生限制 true
        # 有医生限制
        #    当前美购是可用医生列表里的 true
        #    当前美购不是可用医生列表的 false
        if not self.coupon.restric_doctors.exists():
            return True

        exists = Service.objects.filter(doctor__coupondoctorrestrict__coupon_id=self.coupon_id, id=service.id).exists()
        if exists:
            if self.coupon.is_doctor_new:
                return not Order.objects.filter(user=self.user, service__doctor=service.doctor,
                                                status__in=HAS_BOUGHT).exists()
            return True
        else:
            return False

    def _datetime_check(self):
        return self.start_time <= get_current_time() < self.end_time

    def _status_check(self):
        return self.status == COUPON_STATUS.CLAIMED

    def coupon_info_data(self):
        return {
            'id': self.id,
            'value': self.coupon.value,
            'name': self.coupon.name,
            'coupon_type': self.coupon.coupon_type,
            'announcement': self.coupon.announcement,
            'start_time': get_timestamp_or_none(self.start_time),
            'end_time': get_timestamp_or_none(self.end_time),
            'claimed_time': get_timestamp_or_none(self.claimed_time),
            'frozen_time': get_timestamp_or_none(self.frozen_time),
            'consumed_time': get_timestamp_or_none(self.consumed_time),
            'returned_time': get_timestamp_or_none(self.returned_time),
            'status': self.status,
            'use_term': self.coupon.use_term,
            'use_new_term': self.coupon.use_new_term,
            'prepay_threshold': self.coupon.prepay_threshold,
            'has_threshold': self.coupon.has_threshold,
            'gengmei_percent': self.coupon.gengmei_percent,
            'coupon_id': self.coupon_id,
            'groupbuy_avail': self.coupon.groupbuy_avail,
            # 'condition': self.coupon.get_condition(),
            # 7780 折扣券添加 http://wiki.wanmeizhensuo.com/pages/viewpage.action?pageId=18589503
            'discount_rate': self.coupon.discount_rate,
            'upper_limit': self.coupon.upper_limit,
            'benefit_type': self.coupon.benefit_type
        }

    def can_be_frozon(self, service, service_item, for_settlement=False):
        # 新人券
        # 医生[新人]券
        # 起始日期
        # 状态
        # 预付款
        # 抽成
        # 使用场景, 专场
        if not self._condition_new_user_check():
            return False, CODES.COUPON_SCENARIOS_FALSE
        if not self._condition_doctor_check(service):
            return False, CODES.COUPON_SCENARIOS_FALSE

        if not self._datetime_check():
            return False, CODES.COUPON_UNAVAILABLE

        if not self._status_check():
            return False, CODES.COUPON_UNAVAILABLE
        if not for_settlement:
            if not self._condition_prepay_check(service_item):
                return False, CODES.COUPON_PRE_PAYMENT_FALSE
        if not self._condition_discount_check(service_item):
            return False, CODES.COUPON_SCENARIOS_FALSE

        if not self._condition_scenario_check(service, service_item):
            return False, CODES.COUPON_SCENARIOS_FALSE
        return True, None

    def can_be_frozon_for_settlement(self, services, parm=None):
        services_pre_price = []
        services_gengmei_price = []
        for k, v in services.iteritems():
            service_obj, service_item_obj = v[0], v[1]
            if self.can_be_frozon(service_obj, service_item_obj, for_settlement=True)[0]:
                services_pre_price.append(service_item_obj.pre_payment_price)
                services_gengmei_price.append(service_item_obj.gengmei_price)
        sum_prepay_price = sum(services_pre_price)
        sum_gengmei_price = sum(services_gengmei_price)
        if int(sum_gengmei_price) >= self.coupon.prepay_threshold \
                and int(sum_prepay_price) > self.coupon.value:
            # 满减根据更美价确定，同时优惠券金额不能大于预付款
            return True, None
        else:
            return False, CODES.COUPON_PRE_PAYMENT_FALSE

    def can_be_consumed(self):
        if self.status == COUPON_STATUS.FROZEN and self._datetime_check():
            return True
        else:
            return False

    def can_be_returned(self):
        if self.status == COUPON_STATUS.FROZEN:
            return True
        else:
            return False

    def freeze_coupon(self, order_id=None):
        if self.status != COUPON_STATUS.CLAIMED:
            raise Exception('can not freeze')
        self.status = COUPON_STATUS.FROZEN
        self.order_id = order_id
        self.frozen_time = get_current_time()
        self.save()

    def consume_coupon(self):
        self.status = COUPON_STATUS.CONSUMED
        self.consumed_time = get_current_time()
        self.save()

    def return_coupon(self):
        self.status = COUPON_STATUS.CLAIMED
        self.order = None
        self.returned_time = get_current_time()
        self.frozen_time = None
        self.save()

    @property
    def value(self):
        return self.coupon.value

    @classmethod
    def valid_coupons(
            cls, user, services, only_show_platform_coupon=True,
            coupon_type=0, device_id=None, coupon_use_type=COUPON_USE_TYPE.VALID_COUPON,
            promotion_ids=[]):
        now = datetime.datetime.now()
        q_coupon_type = Q()
        if only_show_platform_coupon or coupon_type == COUPON_TYPES.PLATFORM:
            q_coupon_type = Q(coupon__coupon_type=COUPON_TYPES.PLATFORM)
        elif coupon_type == COUPON_TYPES.DOCTOR:
            q_coupon_type = Q(coupon__coupon_type=COUPON_TYPES.DOCTOR)

        cps = list(cls.objects.filter(user=user)
                   .filter(status=COUPON_STATUS.CLAIMED)
                   .filter(q_coupon_type)
                   .filter(end_time__gt=now, start_time__lt=now)
                   .select_related('user')
                   .select_related('coupon')
                   .order_by('-claimed_time')) if user else []

        # 注意，在这里之前应该已经过滤掉所有单个不符合只针对CouponInfo规则的的CouponInfo
        # 比如上面是通过SQL过滤掉status和start_time、end_time，因此下面才可以针对相同Coupon的CouponInfo只挑选一个来验证

        return cls.get_vaild_coupons(services, cps, device_id=device_id, coupon_use_type=coupon_use_type, promotion_ids=promotion_ids)

    @classmethod
    def get_vaild_coupons(
            cls, services, cps, device_id=None,
            coupon_use_type=COUPON_USE_TYPE.VALID_COUPON, promotion_ids=[]):
        sku_nums = 0
        for k, v in services.items():
            sku_nums += v.number
        valid_dict, unvalid_dict = {}, {}
        for cp in cps:
            _c_coupon = cp.coupon
            # 免单券只能用于单个sku支付, 因为在后续的计算券金额时, 产品逻辑存在问题    2019-10-29 ---pm: 吕国玮
            if _c_coupon.benefit_type == BENEFIT_TYPE.DISCOUNT and _c_coupon.discount_rate == 0 and sku_nums > 1:
                continue
            if cp.coupon_id not in valid_dict:
                if coupon_use_type == COUPON_USE_TYPE.VALID_COUPON:
                    valid_dict[cp.coupon_id] = cp.valid_for(services, device_id=device_id, promotion_ids=promotion_ids)[0]
                else:
                    unvalid_dict[cp.coupon_id] = (cp, cp.valid_for(services, device_id=device_id, promotion_ids=promotion_ids)[1])

        if coupon_use_type == COUPON_USE_TYPE.VALID_COUPON:
            return [x for x in cps if valid_dict.get(x.coupon_id)]
        else:
            return [unvalid_dict.get(x.coupon_id) for x in cps if unvalid_dict.get(x.coupon_id)]

    @classmethod
    def sort_coupons(cls, coupons):
        # 最优惠券排序规则
        # http://wiki.gengmei.cc/pages/viewpage.action?pageId=4147298
        # coupons: [CouponInfo.coupon_info_data, ...]
        coupons.sort(key=lambda x: x['id'], reverse=True)
        coupons.sort(key=lambda x: not x['has_threshold'])
        coupons.sort(key=lambda x: x['gengmei_percent'])
        coupons.sort(key=lambda x: x['coupon_type'] == COUPON_TYPES.PLATFORM)
        coupons.sort(key=lambda x: x['value'], reverse=True)
        return coupons

    @staticmethod
    def get_shared(service_items, coupon_info, promotion_ids=[]):
        if not isinstance(coupon_info, CouponInfo):
            return {}
        shared, reason = coupon_info.valid_for(service_items, promotion_ids=promotion_ids)
        return shared

    @cached_property
    def coupon_restrict_promotion_ids(self):
        promotion_ids = frozenset(PromotionCoupon.objects.filter(
            coupon_id=self.coupon_id
        ).values_list('promotion_id', flat=True))
        return promotion_ids

    @cached_property
    def coupon_restrict_special_ids(self):
        special_ids = frozenset(CouponSpecialRestrict.objects.filter(
            coupon_id=self.coupon_id
        ).values_list('special_id', flat=True))
        return special_ids

    @cached_property
    def coupon_restrict_doctor_ids(self):
        doctor_ids = frozenset(CouponDoctorRestrict.objects.filter(
            coupon_id=self.coupon_id
        ).values_list('doctor_id', flat=True))
        return doctor_ids

    @cached_property
    def coupon_restrict_sku_ids(self):
        sku_ids = frozenset(CouponSKURestrict.objects.filter(
            coupon_id=self.coupon_id
        ).values_list('sku_id', flat=True))
        return sku_ids

    @staticmethod
    def _sku_restrict_coupon(sku_ids):
        objs = CouponSKURestrict.objects.filter(sku_id__in=sku_ids)
        res = {}
        for obj in objs:
            item = res.get(obj.sku_id)
            if item:
                item.append(obj.coupon_id)
            else:
                item = [obj.coupon_id]
        return res

    @classmethod
    def get_best_coupon_by_sku_ids(cls, sku_ids):
        """
        return  {
                    sku_id(int): {
                        "platform": max_couponinfo_obj(CouponInfo) },
                        "doctor": max_couponinfo_obj(CouponInfo) }
                    }
                }
        """
        relation_dict = cls._sku_restrict_coupon(sku_ids)
        coupon_ids = []
        for _, ids in relation_dict.items():
            coupon_ids.extend(ids)

        platform_ids = Coupon.objects.filter(id__in=coupon_ids, coupon_type=COUPON_TYPES.PLATFORM).values_list('id', flat=True)
        doctor_ids = Coupon.objects.filter(id__in=coupon_ids, coupon_type=COUPON_TYPES.DOCTOR).values_list('id', flat=True)

        coupon_objs = CouponInfo.objects.filter(coupon_id__in=coupon_ids)
        coupon_dict = {obj.coupon_id: obj for obj in coupon_objs}
        relation_res = {}
        for sku_id, ids in relation_dict.items():
            for obj_id in ids:
                item = relation_res.get(sku_id)
                platform_item = item.get('platform')
                doctor_item = item.get('doctor')
                if obj_id in platform_ids:
                    if platform_item:
                        platform_item.append(coupon_dict.get(obj_id))
                    else:
                        platform_item = [coupon_dict.get(obj_id)]
                if obj_id in doctor_ids:
                    if doctor_item:
                        doctor_item.append(coupon_dict.get(obj_id))
                    else:
                        doctor_item = [coupon_dict.get(obj_id)]

        res = {}
        for sku_id in relation_res:
            item = relation_res.get(sku_id)
            platform_objs = item.get('platform')
            doctor_objs = item.get('doctor')
            res[sku_id]['platform'] = max(platform_objs, key=lambda x:x.value)
            res[sku_id]['doctor'] = max(doctor_objs, key=lambda x:x.value)

        return res

    def valid_for(self, service_items, device_id=None, promotion_ids=None):
        """
        可用 return {(service_id, service_item_key): shared, ...}, None
        不可用 return {}, COUPON_INVALID.NEW_USER
        """
        # 重要说明：
        # 1. rule 顺序很重要，一些验证代价低的rule优先排在前面。
        # 2. valid_coupons方法中，对于属于同一个Coupon的CouponInfo，为了避免重复计算，只会挑选一个CouponInfo进行验证，
        #    如果选中的CouponInfo满足规则，就认为属于同一个Coupon的CouponInfo都可用，因此在valid_coupons中会预先过滤掉
        #    不符合单个CouponInfo的验证规则。
        #    因此，如果新增加了任何只是针对单个CouponInfo的验证规则（现针对单个CouponInfo的规则有 _valid_status、 _valid_time）
        #    必须在valid_coupons进行对应的处理。

        filter_rules = [
            (True, self._valid_status, COUPON_INVALID.STATUS),
            (True, self._valid_time, COUPON_INVALID.TIME),
            (self.coupon.is_new_user, self._valid_new_user, COUPON_INVALID.NEW_USER),
            (len(self.coupon_restrict_doctor_ids) > 0, self._valid_rdoctor, COUPON_INVALID.DOCTOR),
            (len(_coupon_limited_doctor_id_set) > 0, self._valid_rdoctor_and_gengmei_percent, COUPON_INVALID.DOCTOR),
            (len(self.coupon_restrict_doctor_ids) > 0 and self.coupon.is_doctor_new, self._valid_doctor_new,
             COUPON_INVALID.DOCTOR_NEW),
            (len(self.coupon_restrict_sku_ids) > 0, self._valid_sku, COUPON_INVALID.SKU),
            (not self.coupon.seckill_avail, self._valid_seckill, COUPON_INVALID.SECKILL),
            (not self.coupon.groupbuy_avail, self._valid_groupbuy, COUPON_INVALID.GROUPBUY),
            (len(self.coupon_restrict_special_ids) > 0, self._valid_special_restrict, COUPON_INVALID.SPECIAL),
            (self.coupon.has_threshold, self._valid_threshold, COUPON_INVALID.THRESHOLD),
            (self.coupon.coupon_type == COUPON_TYPES.PLATFORM and self.coupon.benefit_type != BENEFIT_TYPE.DISCOUNT, self._valid_prepay, COUPON_INVALID.REPAY),
            (self.coupon.coupon_type == COUPON_TYPES.DOCTOR, self._valid_hospital_payment, COUPON_INVALID.HOSPITAL_PAYMENT),
            (self.coupon.benefit_type == BENEFIT_TYPE.DISCOUNT, self._valid_price_range, COUPON_INVALID.PRICE_RANGE),
            (len(self.coupon_restrict_special_ids) > 0, self._valid_price_in_special, COUPON_INVALID.PRECE_NOT_IN_SPECIAL),
            (True, self._valid_discount_and_share, COUPON_INVALID.DISCOUNT),
            (len(self.coupon_restrict_promotion_ids) > 0, self._valid_promotion, COUPON_INVALID.PROMOTION),
        ]
        ordering_infos = service_items.values()
        for _, filter_rule, reason in filter(lambda x: x[0], filter_rules):
            if filter_rule is self._valid_new_user:
                ordering_infos = filter_rule(ordering_infos, device_id)
            elif filter_rule == self._valid_promotion:
                ordering_infos = filter_rule(ordering_infos, promotion_ids)
            else:
                ordering_infos = filter_rule(ordering_infos)
            if not ordering_infos:
                return {}, reason
        return ordering_infos, None

    def _valid_seckill(self, items):
        # XXX 秒杀不可用
        def valid(ordering_info):
            price = ordering_info.ordering_price_info
            return price.get('price_type') != SERVICE_ITEM_PRICE_TYPE.SECKILL
        return filter(lambda x: valid(x), items)

    def _valid_groupbuy(self, items):
        # XXX 拼团不可用
        def valid(ordering_info):
            price = ordering_info.ordering_price_info
            return price.get('price_type') != SERVICE_ITEM_PRICE_TYPE.GROUPBUY
        return filter(lambda x: valid(x), items)

    def _valid_special_restrict(self, items):
        special_ids = self.coupon_restrict_special_ids

        def item_in_special(sp_ids, item):
            valid = False

            if item.service_online_special_id_by_tags_set:
                if len(item.service_online_special_id_by_tags_set & sp_ids) > 0:
                    return True

            if item.online_special_id_set:
                match_special_ids = item.online_special_id_set & sp_ids
                valid = len(match_special_ids) > 0
            return valid

        return filter(lambda x: item_in_special(special_ids, x), items)

    def _valid_price_in_special(self, items):
        special_ids = self.coupon_restrict_special_ids
        special_ids = map(int, special_ids)
        def price_rule_in_special(sp_ids, item):
            ordering_price_info = getattr(item, 'ordering_price_info', None)
            if ordering_price_info:
                o_sp_id = (ordering_price_info.get('selling_rule') or {}).get('activity_id') or None
                if o_sp_id and int(o_sp_id) in sp_ids:
                    return True
            return False

        return filter(lambda x: price_rule_in_special(special_ids, x), items)

    def _valid_promotion(self, items, promotion_ids=[]):
        restrict_promotion_ids = self.coupon_restrict_promotion_ids
        restrict_promotion_ids = map(int, restrict_promotion_ids)

        if not promotion_ids:
            if not restrict_promotion_ids:
                return items
            else:
                return []

        for promotion_id in promotion_ids:
            if promotion_id in restrict_promotion_ids:
                return items
        return []

    def _valid_rdoctor(self, items):
        # XXX 限制表内医生可用
        doctor_ids = self.coupon_restrict_doctor_ids

        def item_in_doctor(dt_ids, item):
            valid = item.service_obj.doctor_id in dt_ids
            return valid

        return filter(lambda x: item_in_doctor(doctor_ids, x), items)

    def _valid_rdoctor_and_gengmei_percent(self, items):
        # XXX 限制部分医生如果要用平台券，gengmei_percent必须小于等于配置值
        doctor_ids = _coupon_limited_doctor_id_set

        def item_in_doctor(dt_ids, item):
            doctor_is_limited = item.service_obj.doctor_id in dt_ids

            valid = True
            if doctor_is_limited and self.coupon.coupon_type == COUPON_TYPES.PLATFORM \
                and self.coupon.gengmei_percent > _coupon_limited_max_gengmei_percent:
                valid = False

            return valid

        return filter(lambda x: item_in_doctor(doctor_ids, x), items)

    def _valid_doctor_new(self, items):
        # XXX 限制表内医生且医生新用户可用
        valid = lambda oi: not Order.objects.filter(
            user=self.user, status__in=HAS_BOUGHT,
            service__doctor=oi.service_obj.doctor,
        ).exists()
        return filter(lambda x: valid(x), items)

    @classmethod
    def _is_new_user_to_coupon(cls, user):
        '''
        是否为新用户
        :param user:
        :return:
        '''
        s_timeout = datetime.timedelta(seconds=settings.SETTLEMENT_SOFT_TIMEOUT)  # 未支付的超时时间
        h_timeout = datetime.timedelta(seconds=settings.SETTLEMENT_HARD_TIMEOUT)  # 付款中的超时时间
        s_expire_end_time = datetime.datetime.now() - s_timeout
        h_expire_end_time = datetime.datetime.now() - h_timeout
        order_objs = Order.objects.filter(
            Q(user=user),
            Q(status__in=HAS_BOUGHT) |
            Q(status=ORDER_STATUS.NOT_PAID, created_time__gte=s_expire_end_time) |
            Q(status=ORDER_STATUS.PAYING, created_time__gte=h_expire_end_time)
        )
        if order_objs.exists():
            return False
        return True

    def _is_new_user_to_coupon_by_device_id(self, device_id):
        '''
        是否为新用户(通过device_id判断)
        :param user:
        :return:
        '''
        order_ids = list(OrderUserInfo.objects.filter(device_id=device_id).values_list('order_id', flat=True))

        s_timeout = datetime.timedelta(seconds=settings.SETTLEMENT_SOFT_TIMEOUT)  # 未支付的超时时间
        h_timeout = datetime.timedelta(seconds=settings.SETTLEMENT_HARD_TIMEOUT)  # 付款中的超时时间
        s_expire_end_time = datetime.datetime.now() - s_timeout
        h_expire_end_time = datetime.datetime.now() - h_timeout
        order_objs = Order.objects.filter(
            Q(id__in=order_ids),
            Q(status__in=HAS_BOUGHT) |
            Q(status=ORDER_STATUS.NOT_PAID, created_time__gte=s_expire_end_time) |
            Q(status=ORDER_STATUS.PAYING, created_time__gte=h_expire_end_time)
        )
        if order_objs.exists():
            return False
        return True

    def _valid_new_user(self, items, device_id=None):
        # XXX 只有新用户可用
        is_new_user = self._is_new_user_to_coupon(self.user)
        if not is_new_user:
            return []

        if device_id:
            if not self._is_new_user_to_coupon_by_device_id(device_id):
                return []
        # else:
        #     return []

        return items

    def _valid_status(self, items):
        # XXX 只有已领取券可用
        return items if self.status == COUPON_STATUS.CLAIMED else []

    def _valid_time(self, items):
        now = datetime.datetime.now()
        if self.start_time < now < self.end_time:
            return items
        return []

    def _valid_sku(self, items):
        # XXX 限制表内sku可用
        sku_ids = self.coupon_restrict_sku_ids

        def item_in_sku(ids, item):
            sku_id = item.service_item_ordering_dict["id"]
            valid = sku_id in ids
            return valid

        return filter(lambda x: item_in_sku(sku_ids, x), items)

    def _valid_threshold(self, items):
        # XXX 更美价(或 预付款，尾款）大于满减限制可用
        # XXX coupon.threshold_validation_type = GENTMEIPRICE 用更美价计算
        # XXX coupon.threshold_validation_type = BY_DEDUCTION_TYPE 用预付款或者尾款计算
        sum_price = 0
        if self.coupon.threshold_validation_type == COUPON_THRESHOLD_VALIDATION_TYPE.GENGMEI_PRICE:
            sum_price = self._sum_price(items, 'gengmei_price')
        elif self.coupon.threshold_validation_type == COUPON_THRESHOLD_VALIDATION_TYPE.BY_DEDUCTION_TYPE:
            if self.coupon.coupon_type == COUPON_TYPES.PLATFORM:
                sum_price = self._sum_price(items, 'pre_payment_price')
            elif self.coupon.coupon_type == COUPON_TYPES.DOCTOR:
                sum_price = self._sum_price(items, 'gengmei_price') - self._sum_price(items, 'pre_payment_price')
        if sum_price >= self.coupon.prepay_threshold:
            return items
        return []

    def _valid_prepay(self, items):
        # XXX 预付款大于券额可用
        if self._sum_price(items, 'pre_payment_price') > self.coupon.value:
            return items
        return []

    def _valid_hospital_payment(self, items):
        # XXX 尾款大于券额可用
        sum_gengmei_price = self._sum_price(items, 'gengmei_price')
        sum_pre_payment_price = self._sum_price(items, 'pre_payment_price')
        if sum_gengmei_price - sum_pre_payment_price > self.coupon.value:
            return items
        return []

    def _valid_discount_and_share(self, items):
        """ return {(service_id, service_item_key): shared, ...}
        """
        # XXX 对于每个商品,平台券抽成大于分摊可用, 医生券尾款大于0可用
        is_coupon_platform = self.coupon.coupon_type == COUPON_TYPES.PLATFORM
        is_coupon_doctor = self.coupon.coupon_type == COUPON_TYPES.DOCTOR
        is_threshold_gengmei_price = self.coupon.threshold_validation_type == COUPON_THRESHOLD_VALIDATION_TYPE.GENGMEI_PRICE
        sum_gengmei_price = self._sum_price(items, 'gengmei_price')
        sum_pre_payment_price = self._sum_price(items, 'pre_payment_price')
        if is_threshold_gengmei_price:
            total = self._sum_price(items, 'gengmei_price')
        else:
            if is_coupon_platform:
                total = self._sum_price(items, 'pre_payment_price')
            else:
                total = sum_gengmei_price - sum_pre_payment_price

        left = value = self.coupon.value

        # 折扣券的价值根据商品价值进行计算
        if self.coupon.benefit_type == BENEFIT_TYPE.DISCOUNT:
            left = value = round(sum_pre_payment_price - (sum_pre_payment_price * self.coupon.discount_rate / 100))

        result = defaultdict(list)
        for index, ordering_info in enumerate(items):
            for count in range(ordering_info.number):
                sku_id = ordering_info.service_item_ordering_dict["id"]

                price_info = ordering_info.ordering_price_info
                gengmei_price = price_info['gengmei_price']
                pre_payment_price = price_info['pre_payment_price']
                discount = price_info['discount']

                hospital_payment = gengmei_price - pre_payment_price
                if is_threshold_gengmei_price:
                    price = gengmei_price
                else:
                    if is_coupon_platform:
                        price = pre_payment_price
                    else:
                        price = hospital_payment
                shared = round(float(price) / float(total) * float(value))
                if index == len(items) - 1 and (ordering_info.number - 1) == count:
                    # 最后一个直接用剩余的left
                    shared = left
                left -= shared
                result[sku_id].append(shared)
                if is_coupon_platform and (pre_payment_price < shared or (self.coupon.discount_limit and discount < shared)):
                    return {}
                elif is_coupon_doctor and hospital_payment < shared:
                    return {}
        return result

    def _valid_price_range(self, items):
        """预付款价值位于可用价格区间"""
        price = self._sum_price(items, 'pre_payment_price')
        if price >= self.coupon.prepay_threshold and price <= self.coupon.upper_limit:
            return items
        return []

    @staticmethod
    def _sum_price(items, threshold_type):
        item_price = lambda item: item.ordering_price_info
        return int(sum([item_price(x)[threshold_type]*x.number for x in items if x.ordering_price_info]))


class QQCouponRecord(models.Model):
    class Meta:
        verbose_name = 'QQ卡券兑换记录'
        app_label = 'api'

    card_id = models.CharField(max_length=128, null=False, verbose_name=u'QQ卡券card_id')
    card_code = models.CharField(max_length=128, null=False, verbose_name=u'QQ卡券Code卡券唯一标示')
    authorization_code = models.CharField(max_length=128, null=False, verbose_name=u'QQ卡券用户鉴权Code(Authorization Code)')
    openid = models.CharField(max_length=128, null=False, verbose_name=u'QQ卡券用户openid')
    access_token = models.CharField(max_length=128, null=False, verbose_name=u'QQ卡券用户access-token')
    attach = models.CharField(max_length=128, null=True, verbose_name=u'QQ卡券跳转参数', default='')
    phone = models.CharField(max_length=12, null=False, verbose_name=u'领取卡券对应礼包手机号')
    gift_channel = models.CharField(max_length=8, null=False, verbose_name=u'领取卡券对应礼包渠道')
    gift = models.CharField(max_length=64, null=False, verbose_name=u'领取卡券对应礼包')
    gain_time = models.DateTimeField(auto_now_add=True, verbose_name=u'领取时间')
    consume_notify = models.BooleanField(default=False, verbose_name=u'是否通知QQ卡券已消费')


class CouponDistribute(models.Model):
    class Meta:
        verbose_name = '美券分发'
        app_label = 'api'

    name = models.CharField('分发页标题', max_length=32, default='')
    gift = models.ForeignKey(ChannelGift, related_name='distribution')
    gift_img = ImgUrlField('礼包图片', max_length=255, img_type=IMG_TYPE.NOWATERMARK)
    background = ImgUrlField(
        '背景图', img_type=IMG_TYPE.NOWATERMARK, max_length=256)
    enable = models.BooleanField(default=True)
    title = models.CharField('分享标题', max_length=32, default='')
    wechat = models.CharField('朋友圈', max_length=256, default='')
    qq = models.CharField('微信/qq', max_length=256, default='')
    weibo = models.CharField('微博', max_length=256, default='')
    share_image = ImgUrlField(
        '分享图片', img_type=IMG_TYPE.NOWATERMARK, max_length=255)
    create_time = models.DateTimeField(auto_now_add=True)
    update_time = models.DateTimeField(auto_now=True)


class DoctorCouponInfoRecord(models.Model):
    class Meta:
        verbose_name = '医生美券信息记录表'
        app_label = 'api'

    doctor_id = models.CharField(max_length=100, default='', verbose_name="医生Id")
    coupon_data_batch = models.CharField(max_length=20, default='', verbose_name="美券数据批次")
    coupon_id = models.IntegerField(verbose_name="美券Id")
    threshold = models.IntegerField(default=0, verbose_name="美券门槛金额")
    value = models.IntegerField(verbose_name='美券抵扣金额')
    left_number = models.IntegerField(verbose_name='美券剩余可领取数量')

    business_partener_id = models.IntegerField(verbose_name=u'负责此医生的商务同事的UserId', null=True)
    business_partener_user_name = models.CharField(verbose_name=u'负责此医生的商务同事的UserName', max_length=30, null=True)

    business_group_id = models.IntegerField('组织架构的组织', null=True)
    business_group_name = models.CharField('组名', max_length=128, null=True)

    created_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)


class PromotionCoupon(models.Model):
    class Meta:
        verbose_name = "促销活动劵关联"
        db_table = "promotion_coupon"
        app_label = "api"

    id = models.AutoField(verbose_name="主键", primary_key=True, db_column="id")
    create_time = models.DateTimeField(
        verbose_name="创建时间", auto_now_add=True, db_column="create_time"
    )
    update_time = models.DateTimeField(
        verbose_name="更新时间", auto_now=True, db_column="update_time"
    )
    promotion_id = models.IntegerField(
        verbose_name="促销活动id", null=False, default=0
    )
    coupon_id = models.IntegerField(verbose_name="劵", null=False, default=0)
    deleted = models.IntegerField(verbose_name="是否删除", null=False, default=0)
